From e09ec92a3bdf9175635d99440c3699897cae3a04 Mon Sep 17 00:00:00 2001 From: lyx0 <66651385+lyx0@users.noreply.github.com> Date: Tue, 13 Feb 2024 19:13:44 +0100 Subject: [PATCH] add eventsub notifications for channels going live --- cmd/nourybot/commands.go | 12 +++++++++++ cmd/nourybot/eventsub.go | 44 ++++++++++++++++++++++++++++++++++++++ cmd/nourybot/main.go | 2 ++ cmd/nourybot/router.go | 46 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 cmd/nourybot/eventsub.go diff --git a/cmd/nourybot/commands.go b/cmd/nourybot/commands.go index 0163cb0..adc7d16 100644 --- a/cmd/nourybot/commands.go +++ b/cmd/nourybot/commands.go @@ -122,6 +122,18 @@ func (app *application) handleCommand(message twitch.PrivateMessage) { case "frankerfacez": reply = commands.Ffz(cmdParams[1]) + case "notify": + switch cmdParams[1] { + case "live": + if userLevel >= 100 { + reply = app.createLiveSubscription(target, cmdParams[2]) + } + case "offline": + if userLevel >= 100 { + //reply = app.createFollowSubscription(target, cmdParams[2]) + } + } + case "ddg": reply = commands.DuckDuckGo(message.Message[6:len(message.Message)]) diff --git a/cmd/nourybot/eventsub.go b/cmd/nourybot/eventsub.go new file mode 100644 index 0000000..4099945 --- /dev/null +++ b/cmd/nourybot/eventsub.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + + "github.com/lyx0/nourybot/pkg/ivr" + "github.com/nicklaw5/helix/v2" +) + +func (app *application) createLiveSubscription(target, channel string) string { + client, err := helix.NewClient(&helix.Options{ + ClientID: app.Config.twitchClientId, + AppAccessToken: app.HelixClient.GetAppAccessToken(), + }) + if err != nil { + app.Log.Errorw("Error creating new helix client", + "err", err, + ) + } + + uid := ivr.IDByUsername(channel) + + resp, err := client.CreateEventSubSubscription(&helix.EventSubSubscription{ + Type: helix.EventSubTypeStreamOnline, + Version: "1", + Condition: helix.EventSubCondition{ + BroadcasterUserID: uid, + }, + Transport: helix.EventSubTransport{ + Method: "webhook", + Callback: "https://bot.noury.li/eventsub", + Secret: app.Config.eventSubSecret, + }, + }) + if err != nil { + app.Log.Errorw("Error creating EvenSub subscription", + "resp", resp, + "err", err, + ) + } + + app.Log.Infof("%+v\n", resp) + return fmt.Sprintf("Created subscription for channel %v; uid=%v", channel, uid) +} diff --git a/cmd/nourybot/main.go b/cmd/nourybot/main.go index 0efe710..d5feef1 100644 --- a/cmd/nourybot/main.go +++ b/cmd/nourybot/main.go @@ -25,6 +25,7 @@ type config struct { twitchOauth string twitchClientId string twitchClientSecret string + eventSubSecret string twitchID string wolframAlphaAppID string commandPrefix string @@ -88,6 +89,7 @@ func run(ctx context.Context, w io.Writer, args []string) error { cfg.twitchOauth = os.Getenv("TWITCH_OAUTH") cfg.twitchClientId = os.Getenv("TWITCH_CLIENT_ID") cfg.twitchClientSecret = os.Getenv("TWITCH_CLIENT_SECRET") + cfg.eventSubSecret = os.Getenv("EVENTSUB_SECRET") cfg.wolframAlphaAppID = os.Getenv("WOLFRAMALPHA_APP_ID") cfg.twitchID = os.Getenv("TWITCH_ID") cfg.env = os.Getenv("ENV") diff --git a/cmd/nourybot/router.go b/cmd/nourybot/router.go index 5c96094..e051ca0 100644 --- a/cmd/nourybot/router.go +++ b/cmd/nourybot/router.go @@ -1,8 +1,12 @@ package main import ( + "bytes" + "encoding/json" "fmt" "html/template" + "io/ioutil" + "log" "net/http" "os" "sort" @@ -10,12 +14,14 @@ import ( "github.com/julienschmidt/httprouter" "github.com/lyx0/nourybot/internal/common" "github.com/lyx0/nourybot/internal/data" + "github.com/nicklaw5/helix/v2" ) func (app *application) startRouter() { router := httprouter.New() router.GET("/", app.homeRoute) router.GET("/status", app.statusPageRoute) + router.POST("/eventsub", app.eventsubFollow) router.GET("/commands", app.commandsRoute) router.GET("/commands/:channel", app.channelCommandsRoute) router.GET("/timer", app.timersRoute) @@ -29,6 +35,46 @@ func (app *application) startRouter() { app.Log.Fatal(http.ListenAndServe(":8080", router)) } +type eventSubNotification struct { + Subscription helix.EventSubSubscription `json:"subscription"` + Challenge string `json:"challenge"` + Event json.RawMessage `json:"event"` +} + +func (app *application) eventsubFollow(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { + body, err := ioutil.ReadAll(r.Body) + if err != nil { + log.Println(err) + return + } + defer r.Body.Close() + // verify that the notification came from twitch using the secret. + if !helix.VerifyEventSubNotification(app.Config.eventSubSecret, r.Header, string(body)) { + log.Println("no valid signature on subscription") + return + } else { + log.Println("verified signature for subscription") + } + var vals eventSubNotification + err = json.NewDecoder(bytes.NewReader(body)).Decode(&vals) + if err != nil { + log.Println(err) + return + } + // if there's a challenge in the request, respond with only the challenge to verify your eventsub. + if vals.Challenge != "" { + w.Write([]byte(vals.Challenge)) + return + } + var liveEvent helix.EventSubStreamOnlineEvent + err = json.NewDecoder(bytes.NewReader(vals.Event)).Decode(&liveEvent) + + log.Printf("got stream online event webhook: %s is live\n", liveEvent.BroadcasterUserName) + w.WriteHeader(200) + w.Write([]byte("ok")) + app.SendNoContext("nouryxd", fmt.Sprintf("%s is now live!", liveEvent.BroadcasterUserName)) +} + type timersRouteData struct { Timers []data.Timer }