catbase/plugins/twitch/twitch.go

302 lines
7.7 KiB
Go
Raw Normal View History

2016-08-09 00:44:28 +00:00
package twitch
import (
"bytes"
2016-08-09 00:44:28 +00:00
"encoding/json"
2017-09-27 20:29:04 +00:00
"fmt"
2022-05-20 21:09:39 +00:00
"github.com/go-chi/chi/v5"
2016-08-09 00:44:28 +00:00
"io/ioutil"
"net/http"
"net/url"
2016-08-09 00:44:28 +00:00
"strings"
"text/template"
2016-08-09 00:44:28 +00:00
"time"
2019-03-07 16:35:42 +00:00
"github.com/rs/zerolog/log"
2016-08-09 00:44:28 +00:00
"github.com/velour/catbase/bot"
"github.com/velour/catbase/bot/msg"
"github.com/velour/catbase/config"
)
const (
isStreamingTplFallback = "{{.Name}} is streaming {{.Game}} at {{.URL}}"
notStreamingTplFallback = "{{.Name}} is not streaming"
stoppedStreamingTplFallback = "{{.Name}} just stopped streaming"
)
2016-08-09 00:44:28 +00:00
type TwitchPlugin struct {
2019-05-27 23:21:53 +00:00
bot bot.Bot
2016-08-09 00:44:28 +00:00
config *config.Config
twitchList map[string]*Twitcher
}
type Twitcher struct {
2019-02-06 02:13:35 +00:00
name string
gameID string
2016-08-09 00:44:28 +00:00
}
func (t Twitcher) URL() string {
u, _ := url.Parse("https://twitch.tv/")
u2, _ := url.Parse(t.name)
return u.ResolveReference(u2).String()
}
type stream struct {
Data []struct {
ID string `json:"id"`
UserID string `json:"user_id"`
GameID string `json:"game_id"`
CommunityIds []string `json:"community_ids"`
Type string `json:"type"`
Title string `json:"title"`
ViewerCount int `json:"viewer_count"`
StartedAt time.Time `json:"started_at"`
Language string `json:"language"`
ThumbnailURL string `json:"thumbnail_url"`
} `json:"data"`
Pagination struct {
Cursor string `json:"cursor"`
} `json:"pagination"`
2016-08-09 00:44:28 +00:00
}
func New(b bot.Bot) *TwitchPlugin {
2016-08-09 00:44:28 +00:00
p := &TwitchPlugin{
2019-05-27 23:21:53 +00:00
bot: b,
config: b.Config(),
2016-08-09 00:44:28 +00:00
twitchList: map[string]*Twitcher{},
}
2019-01-22 00:16:57 +00:00
for _, ch := range p.config.GetArray("Twitch.Channels", []string{}) {
for _, twitcherName := range p.config.GetArray("Twitch."+ch+".Users", []string{}) {
2016-08-09 00:44:28 +00:00
if _, ok := p.twitchList[twitcherName]; !ok {
p.twitchList[twitcherName] = &Twitcher{
2019-02-06 02:13:35 +00:00
name: twitcherName,
gameID: "",
2016-08-09 00:44:28 +00:00
}
}
}
2019-05-27 23:21:53 +00:00
go p.twitchLoop(b.DefaultConnector(), ch)
2016-08-09 00:44:28 +00:00
}
b.Register(p, bot.Message, p.message)
b.Register(p, bot.Help, p.help)
2019-02-07 16:30:42 +00:00
p.registerWeb()
2016-08-09 00:44:28 +00:00
return p
}
2019-02-07 16:30:42 +00:00
func (p *TwitchPlugin) registerWeb() {
r := chi.NewRouter()
2022-05-20 21:09:39 +00:00
r.HandleFunc("/{user}", p.serveStreaming)
p.bot.RegisterWeb(r, "/isstreaming")
2017-09-27 20:29:04 +00:00
}
func (p *TwitchPlugin) serveStreaming(w http.ResponseWriter, r *http.Request) {
2022-05-20 21:09:39 +00:00
userName := chi.URLParam(r, "user")
if userName == "" {
2017-09-27 20:29:04 +00:00
fmt.Fprint(w, "User not found.")
return
}
2022-05-20 21:09:39 +00:00
twitcher := p.twitchList[userName]
2017-09-27 20:29:04 +00:00
if twitcher == nil {
fmt.Fprint(w, "User not found.")
return
}
status := "NO."
2019-02-06 02:13:35 +00:00
if twitcher.gameID != "" {
2017-09-27 20:29:04 +00:00
status = "YES."
}
context := map[string]any{"Name": twitcher.name, "Status": status}
2017-09-27 20:29:04 +00:00
t, err := template.New("streaming").Parse(page)
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Err(err).Msg("Could not parse template!")
2017-09-27 20:29:04 +00:00
return
}
err = t.Execute(w, context)
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Err(err).Msg("Could not execute template!")
2017-09-27 20:29:04 +00:00
}
2016-08-09 00:44:28 +00:00
}
func (p *TwitchPlugin) message(c bot.Connector, kind bot.Kind, message msg.Message, args ...any) bool {
body := strings.ToLower(message.Body)
if body == "twitch status" {
2016-08-09 00:44:28 +00:00
channel := message.Channel
2019-01-22 00:16:57 +00:00
if users := p.config.GetArray("Twitch."+channel+".Users", []string{}); len(users) > 0 {
for _, twitcherName := range users {
if _, ok := p.twitchList[twitcherName]; ok {
2019-05-27 23:21:53 +00:00
p.checkTwitch(c, channel, p.twitchList[twitcherName], true)
2016-08-09 00:44:28 +00:00
}
}
}
return true
} else if body == "reset twitch" {
p.config.Set("twitch.istpl", isStreamingTplFallback)
p.config.Set("twitch.nottpl", notStreamingTplFallback)
p.config.Set("twitch.stoppedtpl", stoppedStreamingTplFallback)
2016-08-09 00:44:28 +00:00
}
return false
}
func (p *TwitchPlugin) help(c bot.Connector, kind bot.Kind, message msg.Message, args ...any) bool {
msg := "You can set the templates for streams with\n"
msg += fmt.Sprintf("twitch.istpl (default: %s)\n", isStreamingTplFallback)
msg += fmt.Sprintf("twitch.nottpl (default: %s)\n", notStreamingTplFallback)
msg += fmt.Sprintf("twitch.stoppedtpl (default: %s)\n", stoppedStreamingTplFallback)
msg += "You can reset all messages with `!reset twitch`"
msg += "And you can ask who is streaming with `!twitch status`"
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, message.Channel, msg)
return true
2016-08-09 00:44:28 +00:00
}
2019-05-27 23:21:53 +00:00
func (p *TwitchPlugin) twitchLoop(c bot.Connector, channel string) {
2019-01-22 00:16:57 +00:00
frequency := p.config.GetInt("Twitch.Freq", 60)
if p.config.Get("twitch.clientid", "") == "" || p.config.Get("twitch.token", "") == "" {
2019-03-07 16:35:42 +00:00
log.Info().Msgf("Disabling twitch autochecking.")
return
}
2016-08-09 00:44:28 +00:00
2019-03-07 16:35:42 +00:00
log.Info().Msgf("Checking every %d seconds", frequency)
2016-08-09 00:44:28 +00:00
for {
time.Sleep(time.Duration(frequency) * time.Second)
2019-01-22 00:16:57 +00:00
for _, twitcherName := range p.config.GetArray("Twitch."+channel+".Users", []string{}) {
2019-05-27 23:21:53 +00:00
p.checkTwitch(c, channel, p.twitchList[twitcherName], false)
2016-08-09 00:44:28 +00:00
}
}
}
func getRequest(url, clientID, token string) ([]byte, bool) {
bearer := fmt.Sprintf("Bearer %s", token)
var body []byte
var resp *http.Response
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
2016-08-09 00:44:28 +00:00
if err != nil {
goto errCase
2016-08-09 00:44:28 +00:00
}
req.Header.Add("Client-ID", clientID)
req.Header.Add("Authorization", bearer)
resp, err = client.Do(req)
2016-08-09 00:44:28 +00:00
if err != nil {
goto errCase
}
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
goto errCase
2016-08-09 00:44:28 +00:00
}
return body, true
errCase:
2019-03-07 16:35:42 +00:00
log.Error().Err(err)
return []byte{}, false
2016-08-09 00:44:28 +00:00
}
2019-05-27 23:21:53 +00:00
func (p *TwitchPlugin) checkTwitch(c bot.Connector, channel string, twitcher *Twitcher, alwaysPrintStatus bool) {
baseURL, err := url.Parse("https://api.twitch.tv/helix/streams")
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Msg("Error parsing twitch stream URL")
return
}
query := baseURL.Query()
query.Add("user_login", twitcher.name)
baseURL.RawQuery = query.Encode()
2016-08-09 00:44:28 +00:00
cid := p.config.Get("twitch.clientid", "")
token := p.config.Get("twitch.token", "")
if cid == token && cid == "" {
2019-03-07 16:35:42 +00:00
log.Info().Msgf("Twitch plugin not enabled.")
2019-01-22 00:16:57 +00:00
return
}
body, ok := getRequest(baseURL.String(), cid, token)
2016-08-09 00:44:28 +00:00
if !ok {
return
}
var s stream
err = json.Unmarshal(body, &s)
2016-08-09 00:44:28 +00:00
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Err(err)
2016-08-09 00:44:28 +00:00
return
}
games := s.Data
2019-02-06 02:13:35 +00:00
gameID, title := "", ""
if len(games) > 0 {
2019-02-06 02:13:35 +00:00
gameID = games[0].GameID
title = games[0].Title
}
notStreamingTpl := p.config.Get("Twitch.NotTpl", notStreamingTplFallback)
isStreamingTpl := p.config.Get("Twitch.IsTpl", isStreamingTplFallback)
stoppedStreamingTpl := p.config.Get("Twitch.StoppedTpl", stoppedStreamingTplFallback)
buf := bytes.Buffer{}
info := struct {
Name string
Game string
URL string
}{
twitcher.name,
title,
twitcher.URL(),
}
2016-08-09 00:44:28 +00:00
if alwaysPrintStatus {
2019-02-06 02:13:35 +00:00
if gameID == "" {
t, err := template.New("notStreaming").Parse(notStreamingTpl)
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Err(err)
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, err)
t = template.Must(template.New("notStreaming").Parse(notStreamingTplFallback))
}
t.Execute(&buf, info)
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, buf.String())
2016-08-09 00:44:28 +00:00
} else {
t, err := template.New("isStreaming").Parse(isStreamingTpl)
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Err(err)
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, err)
t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback))
}
t.Execute(&buf, info)
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, buf.String())
2016-08-09 00:44:28 +00:00
}
2019-02-06 02:13:35 +00:00
} else if gameID == "" {
if twitcher.gameID != "" {
t, err := template.New("stoppedStreaming").Parse(stoppedStreamingTpl)
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Err(err)
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, err)
t = template.Must(template.New("stoppedStreaming").Parse(stoppedStreamingTplFallback))
}
t.Execute(&buf, info)
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, buf.String())
2016-08-09 00:44:28 +00:00
}
2019-02-06 02:13:35 +00:00
twitcher.gameID = ""
2016-08-09 00:44:28 +00:00
} else {
2019-02-06 02:13:35 +00:00
if twitcher.gameID != gameID {
t, err := template.New("isStreaming").Parse(isStreamingTpl)
if err != nil {
2019-03-07 16:35:42 +00:00
log.Error().Err(err)
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, err)
t = template.Must(template.New("isStreaming").Parse(isStreamingTplFallback))
}
t.Execute(&buf, info)
2019-05-27 23:21:53 +00:00
p.bot.Send(c, bot.Message, channel, buf.String())
2016-08-09 00:44:28 +00:00
}
2019-02-06 02:13:35 +00:00
twitcher.gameID = gameID
2016-08-09 00:44:28 +00:00
}
}