164 lines
4.3 KiB
Go
164 lines
4.3 KiB
Go
package website
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
|
|
"git.handmade.network/hmn/hmn/src/config"
|
|
"git.handmade.network/hmn/hmn/src/db"
|
|
"git.handmade.network/hmn/hmn/src/hmndata"
|
|
"git.handmade.network/hmn/hmn/src/models"
|
|
"git.handmade.network/hmn/hmn/src/oops"
|
|
"git.handmade.network/hmn/hmn/src/templates"
|
|
"git.handmade.network/hmn/hmn/src/twitch"
|
|
)
|
|
|
|
func TwitchEventSubCallback(c *RequestContext) ResponseData {
|
|
secret := config.Config.Twitch.EventSubSecret
|
|
messageId := c.Req.Header.Get("Twitch-Eventsub-Message-Id")
|
|
timestamp := c.Req.Header.Get("Twitch-Eventsub-Message-Timestamp")
|
|
signature := c.Req.Header.Get("Twitch-Eventsub-Message-Signature")
|
|
messageType := c.Req.Header.Get("Twitch-Eventsub-Message-Type")
|
|
|
|
body, err := io.ReadAll(c.Req.Body)
|
|
if err != nil {
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to read request body"))
|
|
}
|
|
|
|
hmacMessage := fmt.Sprintf("%s%s%s", messageId, timestamp, string(body[:]))
|
|
hmac := hmac.New(sha256.New, []byte(secret))
|
|
hmac.Write([]byte(hmacMessage))
|
|
hash := hmac.Sum(nil)
|
|
hmacStr := "sha256=" + hex.EncodeToString(hash)
|
|
|
|
if hmacStr != signature {
|
|
var res ResponseData
|
|
res.StatusCode = 403
|
|
return res
|
|
}
|
|
|
|
c.Logger.Debug().Str("Body", string(body[:])).Str("Type", messageType).Msg("Got twitch webhook")
|
|
|
|
if messageType == "webhook_callback_verification" {
|
|
type challengeReq struct {
|
|
Challenge string `json:"challenge"`
|
|
}
|
|
var data challengeReq
|
|
err = json.Unmarshal(body, &data)
|
|
if err != nil {
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to unmarshal twitch verification"))
|
|
}
|
|
var res ResponseData
|
|
res.StatusCode = 200
|
|
|
|
res.Header().Set("Content-Type", "text/plain") // NOTE(asaf): No idea why, but the twitch-cli fails when we don't set this.
|
|
res.Write([]byte(data.Challenge))
|
|
return res
|
|
} else {
|
|
err := twitch.QueueTwitchNotification(c, c.Conn, messageType, body)
|
|
if err != nil {
|
|
c.Logger.Error().Err(err).Msg("Failed to process twitch callback")
|
|
// NOTE(asaf): Returning 200 either way here
|
|
}
|
|
var res ResponseData
|
|
res.StatusCode = 200
|
|
return res
|
|
}
|
|
}
|
|
|
|
type TwitchDebugData struct {
|
|
templates.BaseData
|
|
DataJson string
|
|
}
|
|
|
|
func TwitchDebugPage(c *RequestContext) ResponseData {
|
|
type dataUser struct {
|
|
Login string `json:"login"`
|
|
Live bool `json:"live"`
|
|
}
|
|
type dataLog struct {
|
|
ID int `json:"id"`
|
|
LoggedAt int64 `json:"loggedAt"`
|
|
Type string `json:"type"`
|
|
Login string `json:"login"`
|
|
Message string `json:"message"`
|
|
Payload string `json:"payload"`
|
|
}
|
|
type dataJson struct {
|
|
Users []dataUser `json:"users"`
|
|
Logs []dataLog `json:"logs"`
|
|
}
|
|
streamers, err := hmndata.FetchTwitchStreamers(c, c.Conn)
|
|
if err != nil {
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch twitch streamers"))
|
|
}
|
|
live, err := db.Query[models.TwitchLatestStatus](c, c.Conn,
|
|
`
|
|
SELECT $columns
|
|
FROM
|
|
twitch_latest_status
|
|
WHERE
|
|
live = TRUE
|
|
`,
|
|
)
|
|
if err != nil {
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch live twitch streamers"))
|
|
}
|
|
logs, err := db.Query[models.TwitchLog](c, c.Conn,
|
|
`
|
|
SELECT $columns
|
|
FROM twitch_log
|
|
ORDER BY logged_at DESC, id DESC
|
|
`,
|
|
)
|
|
if err != nil {
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch twitch logs"))
|
|
}
|
|
|
|
var data dataJson
|
|
for _, u := range streamers {
|
|
var user dataUser
|
|
user.Login = u.TwitchLogin
|
|
user.Live = false
|
|
for _, l := range live {
|
|
if l.TwitchLogin == u.TwitchLogin {
|
|
user.Live = true
|
|
break
|
|
}
|
|
}
|
|
data.Users = append(data.Users, user)
|
|
}
|
|
messageTypes := []string{
|
|
"",
|
|
"Other",
|
|
"Hook",
|
|
"REST",
|
|
}
|
|
data.Logs = make([]dataLog, 0, 0)
|
|
for _, l := range logs {
|
|
var log dataLog
|
|
log.ID = l.ID
|
|
log.LoggedAt = l.LoggedAt.UnixMilli()
|
|
log.Login = l.Login
|
|
log.Type = messageTypes[l.Type]
|
|
log.Message = l.Message
|
|
log.Payload = l.Payload
|
|
data.Logs = append(data.Logs, log)
|
|
}
|
|
jsonStr, err := json.Marshal(data)
|
|
if err != nil {
|
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to stringify twitch logs"))
|
|
}
|
|
var res ResponseData
|
|
res.MustWriteTemplate("twitch_debug.html", TwitchDebugData{
|
|
BaseData: getBaseDataAutocrumb(c, "Twitch Debug"),
|
|
DataJson: string(jsonStr),
|
|
}, c.Perf)
|
|
return res
|
|
}
|