diff --git a/bot/bot.go b/bot/bot.go index 98a5824..d8a3b51 100644 --- a/bot/bot.go +++ b/bot/bot.go @@ -147,6 +147,8 @@ func (b *bot) setupHTTP() { b.router.HandleFunc("/", b.serveRoot) b.router.HandleFunc("/nav", b.serveNav) + b.router.HandleFunc("/navHTML", b.serveNavHTML) + b.router.HandleFunc("/navHTML/{currentPage}", b.serveNavHTML) } func (b *bot) ListenAndServe() { diff --git a/bot/interfaces.go b/bot/interfaces.go index c574212..c238409 100644 --- a/bot/interfaces.go +++ b/bot/interfaces.go @@ -26,6 +26,8 @@ const ( Reply // Action any /me action Action + // Spoiler is for commented out messages + Spoiler // Reaction Icon reaction if service supports it Reaction // Edit message ref'd new message to replace diff --git a/bot/mock.go b/bot/mock.go index b1d7ae2..cab54c9 100644 --- a/bot/mock.go +++ b/bot/mock.go @@ -37,6 +37,9 @@ func (mb *MockBot) GetPassword() string { return "12345" } func (mb *MockBot) SetQuiet(bool) {} func (mb *MockBot) Send(c Connector, kind Kind, args ...any) (string, error) { switch kind { + case Spoiler: + mb.Messages = append(mb.Messages, "||"+args[1].(string)+"||") + return fmt.Sprintf("m-%d", len(mb.Actions)-1), nil case Message: mb.Messages = append(mb.Messages, args[1].(string)) return fmt.Sprintf("m-%d", len(mb.Actions)-1), nil diff --git a/bot/nav.html b/bot/nav.html new file mode 100644 index 0000000..c3d9068 --- /dev/null +++ b/bot/nav.html @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/bot/web.go b/bot/web.go index e32aa23..4d901a6 100644 --- a/bot/web.go +++ b/bot/web.go @@ -3,8 +3,12 @@ package bot import ( "embed" "encoding/json" + "fmt" + "github.com/go-chi/chi/v5" + "github.com/rs/zerolog/log" "net/http" "strings" + "text/template" ) //go:embed *.html @@ -15,6 +19,19 @@ func (b *bot) serveRoot(w http.ResponseWriter, r *http.Request) { w.Write(index) } +func (b *bot) serveNavHTML(w http.ResponseWriter, r *http.Request) { + currentPage := chi.URLParam(r, "currentPage") + tpl := template.Must(template.ParseFS(embeddedFS, "nav.html")) + if err := tpl.Execute(w, struct { + CurrentPage string + Items []EndPoint + }{currentPage, b.GetWebNavigation()}); err != nil { + log.Error().Err(err).Msg("template error") + w.WriteHeader(500) + fmt.Fprint(w, "Error parsing nav template") + } +} + func (b *bot) serveNav(w http.ResponseWriter, r *http.Request) { enc := json.NewEncoder(w) err := enc.Encode(b.GetWebNavigation()) diff --git a/connectors/discord/discord.go b/connectors/discord/discord.go index 6e5109a..66834a6 100644 --- a/connectors/discord/discord.go +++ b/connectors/discord/discord.go @@ -74,6 +74,9 @@ func (d Discord) Send(kind bot.Kind, args ...any) (string, error) { return d.sendMessage(args[0].(string), args[2].(string), false, args...) case bot.Message: return d.sendMessage(args[0].(string), args[1].(string), false, args...) + case bot.Spoiler: + outgoing := "||" + args[1].(string) + "||" + return d.sendMessage(args[0].(string), outgoing, false, args...) case bot.Action: return d.sendMessage(args[0].(string), args[1].(string), true, args...) case bot.Edit: @@ -151,7 +154,27 @@ func (d *Discord) sendMessage(channel, message string, meMessage bool, args ...a Interface("data", data). Msg("sending message") - st, err := d.client.ChannelMessageSendComplex(channel, data) + maxLen := 2000 + chunkSize := maxLen - 100 + var st *discordgo.Message + var err error + if len(data.Content) > maxLen { + tmp := data.Content + data.Content = tmp[:chunkSize] + st, err = d.client.ChannelMessageSendComplex(channel, data) + if err != nil { + return "", err + } + for i := chunkSize; i < len(data.Content); i += chunkSize { + data := &discordgo.MessageSend{Content: tmp[i : i+chunkSize]} + st, err = d.client.ChannelMessageSendComplex(channel, data) + if err != nil { + break + } + } + } else { + st, err = d.client.ChannelMessageSendComplex(channel, data) + } //st, err := d.client.ChannelMessageSend(channel, message) if err != nil { diff --git a/main.go b/main.go index b134a0e..296ce88 100644 --- a/main.go +++ b/main.go @@ -135,7 +135,6 @@ func main() { b.AddPlugin(roles.New(b)) b.AddPlugin(twitch.New(b)) b.AddPlugin(pagecomment.New(b)) - b.AddPlugin(gpt.New(b)) b.AddPlugin(secrets.New(b)) b.AddPlugin(mayi.New(b)) b.AddPlugin(giphy.New(b)) @@ -179,8 +178,9 @@ func main() { b.AddPlugin(cowboy.New(b)) b.AddPlugin(topic.New(b)) b.AddPlugin(talker.New(b)) - // catches anything left, will always return true b.AddPlugin(fact.New(b)) + // catches anything left, will always return true + b.AddPlugin(gpt.New(b)) if err := client.Serve(); err != nil { log.Fatal().Err(err) diff --git a/plugins/admin/vars.html b/plugins/admin/vars.html index eded1ce..74b0bf5 100644 --- a/plugins/admin/vars.html +++ b/plugins/admin/vars.html @@ -1,77 +1,36 @@ - + - - - - - - - - - - - - - Vars + + + vars + + +
-
- - Variables - - {{ item.name }} - - - - - {{ err }} - - - - -
- - +
+ + + + + + + + + {{range .Items}} + + + + {{else}} + + + + {{end}} + +
KeyValue
{{.Key}}{{.Value}}
No data
+
+ \ No newline at end of file diff --git a/plugins/admin/web.go b/plugins/admin/web.go index b20f143..706edd9 100644 --- a/plugins/admin/web.go +++ b/plugins/admin/web.go @@ -6,6 +6,7 @@ import ( "embed" "encoding/json" "fmt" + "html/template" "io/ioutil" "net/http" "strings" @@ -166,9 +167,32 @@ func writeErr(w http.ResponseWriter, err error) { fmt.Fprint(w, string(j)) } +type configEntry struct { + Key string `json:"key"` + Value string `json:"value"` +} + func (p *AdminPlugin) handleVars(w http.ResponseWriter, r *http.Request) { - index, _ := embeddedFS.ReadFile("vars.html") - w.Write(index) + tpl := template.Must(template.ParseFS(embeddedFS, "vars.html")) + var configEntries []configEntry + q := `select key, value from config` + err := p.db.Select(&configEntries, q) + if err != nil { + log.Error(). + Err(err). + Msg("Error getting config entries.") + w.WriteHeader(500) + fmt.Fprint(w, err) + return + } + + if err := tpl.Execute(w, struct { + Items []configEntry + }{configEntries}); err != nil { + log.Error().Err(err).Msg("template error") + w.WriteHeader(500) + fmt.Fprint(w, "Error parsing template") + } } func (p *AdminPlugin) handleVarsAPI(w http.ResponseWriter, r *http.Request) { diff --git a/plugins/counter/api.go b/plugins/counter/api.go index 9d0ba18..35eb4df 100644 --- a/plugins/counter/api.go +++ b/plugins/counter/api.go @@ -5,8 +5,9 @@ import ( "encoding/json" "fmt" "github.com/velour/catbase/bot/user" - "io/ioutil" + "io" "net/http" + "net/url" "strconv" "time" @@ -29,8 +30,8 @@ func (p *CounterPlugin) registerWeb() { subrouter.Use(httprate.LimitByIP(requests, dur)) subrouter.HandleFunc("/api/users/{user}/items/{item}/increment/{delta}", p.mkIncrementByNAPI(1)) subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement/{delta}", p.mkIncrementByNAPI(-1)) - subrouter.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementAPI(1)) - subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementAPI(-1)) + subrouter.HandleFunc("/api/users/{user}/items/{item}/increment", p.mkIncrementByNAPI(1)) + subrouter.HandleFunc("/api/users/{user}/items/{item}/decrement", p.mkIncrementByNAPI(-1)) r.Mount("/", subrouter) r.HandleFunc("/api", p.handleCounterAPI) r.HandleFunc("/", p.handleCounter) @@ -39,9 +40,14 @@ func (p *CounterPlugin) registerWeb() { func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) { - userName := chi.URLParam(r, "user") - itemName := chi.URLParam(r, "item") - delta, _ := strconv.Atoi(chi.URLParam(r, "delta")) + userName, _ := url.QueryUnescape(chi.URLParam(r, "user")) + itemName, _ := url.QueryUnescape(chi.URLParam(r, "item")) + delta, err := strconv.Atoi(chi.URLParam(r, "delta")) + if err != nil || delta == 0 { + delta = direction + } else { + delta = delta * direction + } secret, pass, ok := r.BasicAuth() if !ok || !p.b.CheckPassword(secret, pass) { @@ -77,7 +83,7 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri return } - body, _ := ioutil.ReadAll(r.Body) + body, _ := io.ReadAll(r.Body) postData := map[string]string{} err = json.Unmarshal(body, &postData) personalMsg := "" @@ -85,7 +91,11 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri personalMsg = fmt.Sprintf("\nMessage: %s", inputMsg) } - chs := p.cfg.GetArray("channels", []string{p.cfg.Get("channels", "none")}) + chs := p.cfg.GetMap("counter.channelItems", map[string]string{}) + ch, ok := chs[itemName] + if len(chs) == 0 || !ok { + return + } req := &bot.Request{ Conn: p.b.DefaultConnector(), Kind: bot.Message, @@ -93,7 +103,7 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri User: &u, // Noting here that we're only going to do goals in a "default" // channel even if it should send updates to others. - Channel: chs[0], + Channel: ch, Body: fmt.Sprintf("%s += %d", itemName, delta), Time: time.Now(), }, @@ -102,7 +112,13 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri } msg := fmt.Sprintf("%s changed their %s counter by %d for a total of %d via the amazing %s API. %s", userName, itemName, delta, item.Count+delta*direction, p.cfg.Get("nick", "catbase"), personalMsg) - for _, ch := range chs { + if !ok { + chs := p.cfg.GetArray("counter.channels", []string{}) + for _, ch := range chs { + p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg) + req.Msg.Channel = ch + } + } else { p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg) req.Msg.Channel = ch } @@ -112,80 +128,6 @@ func (p *CounterPlugin) mkIncrementByNAPI(direction int) func(w http.ResponseWri } } -func (p *CounterPlugin) mkIncrementAPI(delta int) func(w http.ResponseWriter, r *http.Request) { - return func(w http.ResponseWriter, r *http.Request) { - userName := chi.URLParam(r, "user") - itemName := chi.URLParam(r, "item") - - secret, pass, ok := r.BasicAuth() - if !ok || !p.b.CheckPassword(secret, pass) { - err := fmt.Errorf("unauthorized access") - log.Error(). - Err(err). - Msg("error authenticating user") - w.WriteHeader(401) - j, _ := json.Marshal(struct { - Status bool - Error string - }{false, err.Error()}) - fmt.Fprint(w, string(j)) - return - } - - // Try to find an ID if possible - id := "" - u, err := p.b.DefaultConnector().Profile(userName) - if err == nil { - id = u.ID - } - - item, err := GetUserItem(p.db, userName, id, itemName) - if err != nil { - log.Error().Err(err).Msg("error finding item") - w.WriteHeader(400) - j, _ := json.Marshal(struct { - Status bool - Error error - }{false, err}) - fmt.Fprint(w, string(j)) - return - } - - body, _ := ioutil.ReadAll(r.Body) - postData := map[string]string{} - err = json.Unmarshal(body, &postData) - personalMsg := "" - if inputMsg, ok := postData["message"]; ok { - personalMsg = fmt.Sprintf("\nMessage: %s", inputMsg) - } - - chs := p.cfg.GetArray("channels", []string{p.cfg.Get("channels", "none")}) - req := &bot.Request{ - Conn: p.b.DefaultConnector(), - Kind: bot.Message, - Msg: msg.Message{ - User: &u, - // Noting here that we're only going to do goals in a "default" - // channel even if it should send updates to others. - Channel: chs[0], - Body: fmt.Sprintf("%s += %d", itemName, delta), - Time: time.Now(), - }, - Values: nil, - Args: nil, - } - msg := fmt.Sprintf("%s changed their %s counter by %d for a total of %d via the amazing %s API. %s", - userName, itemName, delta, item.Count+delta, p.cfg.Get("nick", "catbase"), personalMsg) - for _, ch := range chs { - p.b.Send(p.b.DefaultConnector(), bot.Message, ch, msg) - req.Msg.Channel = ch - } - item.UpdateDelta(req, delta) - j, _ := json.Marshal(struct{ Status bool }{true}) - fmt.Fprint(w, string(j)) - } -} - func (p *CounterPlugin) handleCounter(w http.ResponseWriter, r *http.Request) { index, _ := embeddedFS.ReadFile("index.html") w.Write(index) diff --git a/plugins/fact/factoid.go b/plugins/fact/factoid.go index d4098e8..ca1bddf 100644 --- a/plugins/fact/factoid.go +++ b/plugins/fact/factoid.go @@ -90,21 +90,8 @@ func New(botInst bot.Bot) *FactoidPlugin { // findAction simply regexes a string for the action verb func findAction(message string) string { - r, err := regexp.Compile("<.+?>") - if err != nil { - panic(err) - } - action := r.FindString(message) - - if action == "" { - if strings.Contains(message, " is ") { - return "is" - } else if strings.Contains(message, " are ") { - return "are" - } - } - - return action + r := regexp.MustCompile("<.+?>") + return r.FindString(message) } // learnFact assumes we have a learning situation and inserts a new fact @@ -157,7 +144,6 @@ func (p *FactoidPlugin) findTrigger(fact string) (bool, *Factoid) { f, err := GetSingleFact(p.db, fact) if err != nil { - log.Error().Err(err).Msg("GetSingleFact") return findAlias(p.db, fact) } return true, f @@ -485,18 +471,7 @@ func (p *FactoidPlugin) register() { return true } - notFound := p.c.GetArray("fact.notfound", []string{ - "I don't know.", - "NONONONO", - "((", - "*pukes*", - "NOPE! NOPE! NOPE!", - "One time, I learned how to jump rope.", - }) - - // We didn't find anything, panic! - p.b.Send(c, bot.Message, message.Channel, notFound[rand.Intn(len(notFound))]) - return true + return false }}, } p.b.RegisterTable(p, p.handlers) diff --git a/plugins/gpt/chatgpt.go b/plugins/gpt/chatgpt.go index f7ea523..9b0f5f5 100644 --- a/plugins/gpt/chatgpt.go +++ b/plugins/gpt/chatgpt.go @@ -6,7 +6,7 @@ import ( ) import "github.com/andrewstuart/openai" -var session *openai.ChatSession +var session openai.ChatSession var client *openai.Client func (p *GPTPlugin) getClient() (*openai.Client, error) { @@ -14,31 +14,37 @@ func (p *GPTPlugin) getClient() (*openai.Client, error) { if token == "" { return nil, fmt.Errorf("no GPT token given") } - if client == nil { - return openai.NewClient(token) - } - return client, nil + return openai.NewClient(token) } func (p *GPTPlugin) chatGPT(request string) (string, error) { - if session == nil { - if err := p.setDefaultPrompt(); err != nil { + if client == nil { + if err := p.setPrompt(p.getDefaultPrompt()); err != nil { return "", err } } + if p.chatCount > p.c.GetInt("gpt.maxchats", 10) { + p.setPrompt(p.c.Get("gpt3.lastprompt", p.getDefaultPrompt())) + p.chatCount = 0 + } + p.chatCount++ return session.Complete(context.Background(), request) } -func (p *GPTPlugin) setDefaultPrompt() error { - return p.setPrompt(p.c.Get("gpt.prompt", "")) +func (p *GPTPlugin) getDefaultPrompt() string { + return p.c.Get("gpt.prompt", "") } func (p *GPTPlugin) setPrompt(prompt string) error { - client, err := p.getClient() + var err error + client, err = p.getClient() + if err != nil { + return err + } + session = client.NewChatSession(prompt) + err = p.c.Set("gpt3.lastprompt", prompt) if err != nil { return err } - sess := client.NewChatSession(prompt) - session = &sess return nil } diff --git a/plugins/gpt/gpt3.go b/plugins/gpt/gpt3.go index 10d37c8..764ca53 100644 --- a/plugins/gpt/gpt3.go +++ b/plugins/gpt/gpt3.go @@ -23,6 +23,8 @@ type GPTPlugin struct { b bot.Bot c *config.Config h bot.HandlerTable + + chatCount int } func New(b bot.Bot) *GPTPlugin { @@ -54,6 +56,11 @@ func (p *GPTPlugin) register() { HelpText: "set the ChatGPT prompt", Handler: p.setPromptMessage, }, + { + Kind: bot.Message, IsCmd: true, + Regex: regexp.MustCompile(`(?P.*)`), + Handler: p.chatMessage, + }, } log.Debug().Msg("Registering GPT3 handlers") p.b.RegisterTable(p, p.h) @@ -96,7 +103,7 @@ func (p *GPTPlugin) gpt3(stem string) string { TopP: p.c.GetFloat64("gpt3.top_p", 1), N: p.c.GetInt("gpt3.n", 1), Stop: p.c.GetArray("gpt3.stop", []string{"\n"}), - Echo: true, + Echo: p.c.GetBool("gpt3.echo", false), } val, err := p.mkRequest(gpt3URL, postStruct) if err != nil { diff --git a/plugins/meme/meme.go b/plugins/meme/meme.go index 178c385..319eabf 100644 --- a/plugins/meme/meme.go +++ b/plugins/meme/meme.go @@ -329,7 +329,7 @@ func FindFontSize(c *config.Config, config []string, fontLocation string, w, h i longestStr, longestW := "", 0.0 for _, s := range config { - err := m.LoadFontFace(getFont(c, fontLocation), 12) + err := m.LoadFontFace(GetFont(c, fontLocation), 12) if err != nil { log.Error().Err(err).Msg("could not load font") return fontSize @@ -343,7 +343,7 @@ func FindFontSize(c *config.Config, config []string, fontLocation string, w, h i } for _, sz := range sizes { - err := m.LoadFontFace(getFont(c, fontLocation), sz) // problem + err := m.LoadFontFace(GetFont(c, fontLocation), sz) // problem if err != nil { log.Error().Err(err).Msg("could not load font") return fontSize @@ -476,7 +476,7 @@ func (p *MemePlugin) genMeme(spec specification) ([]byte, error) { if fontLocation == "" { fontLocation = defaultFont } - m.LoadFontFace(getFont(p.c, fontLocation), fontSize) + m.LoadFontFace(GetFont(p.c, fontLocation), fontSize) x := float64(w)*c.XPerc + float64(dx) y := float64(h)*c.YPerc + float64(dy) m.DrawStringAnchored(c.Text, x, y, 0.5, 0.5) @@ -491,7 +491,7 @@ func (p *MemePlugin) genMeme(spec specification) ([]byte, error) { if fontLocation == "" { fontLocation = defaultFont } - m.LoadFontFace(getFont(p.c, fontLocation), fontSize) + m.LoadFontFace(GetFont(p.c, fontLocation), fontSize) x := float64(w) * c.XPerc y := float64(h) * c.YPerc m.DrawStringAnchored(c.Text, x, y, 0.5, 0.5) @@ -506,7 +506,7 @@ func (p *MemePlugin) genMeme(spec specification) ([]byte, error) { return p.images[jsonSpec].repr, nil } -func getFont(c *config.Config, name string) string { +func GetFont(c *config.Config, name string) string { location := c.Get("meme.fontLocation", "") fontShortcuts := c.GetMap("meme.fontShortcuts", map[string]string{"impact": "impact.ttf"}) if file, ok := fontShortcuts[name]; ok { diff --git a/plugins/reminder/reminder.go b/plugins/reminder/reminder.go index f37f37a..31f5523 100644 --- a/plugins/reminder/reminder.go +++ b/plugins/reminder/reminder.go @@ -120,7 +120,6 @@ func (p *ReminderPlugin) message(c bot.Connector, kind bot.Kind, message msg.Mes channel := message.Channel from := message.User.Name - message.Body = replaceDuration(p.when, message.Body) parts := strings.Fields(message.Body) if len(parts) >= 5 { diff --git a/plugins/talker/talker.go b/plugins/talker/talker.go index 5cba8a1..618e078 100644 --- a/plugins/talker/talker.go +++ b/plugins/talker/talker.go @@ -104,7 +104,7 @@ func (p *TalkerPlugin) message(c bot.Connector, kind bot.Kind, message msg.Messa line = strings.Replace(line, "{nick}", nick, 1) output += line + "\n" } - p.bot.Send(c, bot.Message, channel, output) + p.bot.Send(c, bot.Spoiler, channel, output) return true } diff --git a/plugins/tappd/image.go b/plugins/tappd/image.go index 87b1eb1..893ad0f 100644 --- a/plugins/tappd/image.go +++ b/plugins/tappd/image.go @@ -67,7 +67,7 @@ func defaultSpec() textSpec { } func (p *Tappd) overlay(img image.Image, texts []textSpec) ([]byte, error) { - font := p.c.Get("meme.font", "impact.ttf") + font := meme.GetFont(p.c, p.c.Get("meme.font", "impact.ttf")) fontSizes := []float64{48, 36, 24, 16, 12} r := img.Bounds() w := r.Dx() diff --git a/plugins/twitch/demo/main.go b/plugins/twitch/demo/main.go new file mode 100644 index 0000000..e77fdb8 --- /dev/null +++ b/plugins/twitch/demo/main.go @@ -0,0 +1,146 @@ +package main + +import ( + "encoding/json" + "fmt" + "github.com/nicklaw5/helix" + "io" + "log" + "net/http" +) + +func main() { + client, err := helix.NewClient(&helix.Options{ + ClientID: "ptwtiuzl9tcrekpf3d26ey3hb7qsge", + ClientSecret: "rpa0w6qemjqp7sgrmidwi4k0kcah82", + }) + if err != nil { + log.Printf("Login error: %v", err) + return + } + + access, err := client.RequestAppAccessToken([]string{"user:read:email"}) + if err != nil { + log.Printf("Login error: %v", err) + return + } + + fmt.Printf("%+v\n", access) + + // Set the access token on the client + client.SetAppAccessToken(access.Data.AccessToken) + + users, err := client.GetUsers(&helix.UsersParams{ + Logins: []string{"drseabass"}, + }) + if err != nil { + log.Printf("Error getting users: %v", err) + return + } + + if users.Error != "" { + log.Printf("Users error: %s", users.Error) + return + } + + log.Printf("drseabass: %+v", users.Data.Users[0]) + return + + resp, err := client.CreateEventSubSubscription(&helix.EventSubSubscription{ + Type: helix.EventSubTypeStreamOnline, + Version: "1", + Condition: helix.EventSubCondition{ + BroadcasterUserID: users.Data.Users[0].ID, + }, + Transport: helix.EventSubTransport{ + Method: "webhook", + Callback: "https://rathaus.chrissexton.org/live", + Secret: "s3cre7w0rd", + }, + }) + if err != nil { + log.Printf("Eventsub error: %v", err) + return + } + + fmt.Printf("%+v\n", resp) + + resp, err = client.CreateEventSubSubscription(&helix.EventSubSubscription{ + Type: helix.EventSubTypeStreamOffline, + Version: "1", + Condition: helix.EventSubCondition{ + BroadcasterUserID: users.Data.Users[0].ID, + }, + Transport: helix.EventSubTransport{ + Method: "webhook", + Callback: "https://rathaus.chrissexton.org/offline", + Secret: "s3cre7w0rd", + }, + }) + if err != nil { + log.Printf("Eventsub error: %v", err) + return + } + + fmt.Printf("%+v\n", resp) + + http.HandleFunc("/offline", func(w http.ResponseWriter, r *http.Request) { + body, err := io.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("s3cre7w0rd", r.Header, string(body)) { + log.Println("no valid signature on subscription") + return + } else { + log.Println("verified signature for subscription") + } + var vals map[string]any + if err = json.Unmarshal(body, &vals); err != nil { + log.Println(err) + return + } + + if challenge, ok := vals["challenge"]; ok { + w.Write([]byte(challenge.(string))) + return + } + + log.Printf("got offline webhook: %v\n", vals) + w.WriteHeader(200) + w.Write([]byte("ok")) + }) + http.HandleFunc("/live", func(w http.ResponseWriter, r *http.Request) { + body, err := io.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("s3cre7w0rd", r.Header, string(body)) { + log.Println("no valid signature on subscription") + return + } else { + log.Println("verified signature for subscription") + } + var vals map[string]any + if err = json.Unmarshal(body, &vals); err != nil { + log.Println(err) + return + } + + if challenge, ok := vals["challenge"]; ok { + w.Write([]byte(challenge.(string))) + return + } + + log.Printf("got live webhook: %v\n", vals) + w.WriteHeader(200) + w.Write([]byte("ok")) + }) + http.ListenAndServe("0.0.0.0:1337", nil) +} diff --git a/util/stats/main.go b/util/stats/main.go new file mode 100644 index 0000000..43b4fd5 --- /dev/null +++ b/util/stats/main.go @@ -0,0 +1 @@ +package stats