From 33d8a54cd8788a46a52f6168efead0df43ffc03e Mon Sep 17 00:00:00 2001 From: Luxferre Date: Mon, 23 Sep 2024 21:46:21 +0300 Subject: [PATCH] Implemented channel point rewards --- README.md | 2 +- src/go.mod | 10 ++++++++++ src/go.sum | 26 ++++++++++++++++++++++++++ src/twitch.go | 15 +++++++++++---- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b593c76..eb7dfff 100644 --- a/README.md +++ b/README.md @@ -195,7 +195,7 @@ Example `config.json` file: The following procedure needs to be only done once per ~2 months: -1. Go to the [Twitch Chat OAuth Password Generator](https://twitchapps.com/tmi/) website. +1. Go to the Twitch Chat OAuth Password Generator website using [this link](https://id.twitch.tv/oauth2/authorize?response_type=token&client_id=q6batx0epp608isickayubi39itsckt&redirect_uri=https://twitchapps.com/tmi/&scope=chat:read+chat:edit+channel:read:redemptions+channel:moderate+whispers:read+whispers:edit+channel_editor). 2. Login with your own Twitch account and give it the access. 3. Copy the token string that starts with `oauth:` and paste it into the `twitch-token.txt` file in the `auth` subdirectory in the application directory (note that it is different from `twitch-cid.txt` that already is present in this subdirectory). 4. Save the file. The Twitch connection should now work. diff --git a/src/go.mod b/src/go.mod index f24f5eb..66f263c 100644 --- a/src/go.mod +++ b/src/go.mod @@ -2,15 +2,25 @@ module streamgoose go 1.23.1 +replace github.com/pajlada/go-twitch-pubsub => github.com/plugnburn/go-twitch-pubsub v0.0.0-20240922200010-4bf88de12fae + require ( centrifuge.hectabit.org/HectaBit/webview_go v0.0.0-20240502074857-17ae24882d16 github.com/DaRealFreak/cloudflare-bp-go v1.0.4 + github.com/plugnburn/go-twitch-pubsub v0.0.0-20240922200010-4bf88de12fae ) require ( + github.com/BurntSushi/toml v1.4.0 // indirect github.com/EDDYCJY/fake-useragent v0.2.0 // indirect github.com/PuerkitoBio/goquery v1.7.1 // indirect github.com/andybalholm/cascadia v1.3.1 // indirect + github.com/gorilla/websocket v1.5.3 // indirect github.com/stretchr/testify v1.9.0 // indirect + golang.org/x/exp/typeparams v0.0.0-20240613232115-7f521ea00fb8 // indirect + golang.org/x/mod v0.18.0 // indirect golang.org/x/net v0.29.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/tools v0.22.0 // indirect + honnef.co/go/tools v0.4.7 // indirect ) diff --git a/src/go.sum b/src/go.sum index 5a1f2d4..4f95d89 100644 --- a/src/go.sum +++ b/src/go.sum @@ -1,5 +1,7 @@ centrifuge.hectabit.org/HectaBit/webview_go v0.0.0-20240502074857-17ae24882d16 h1:gOFnYZ+G9R7Prc3Mn7z/v+LkPf/kQ2oUh5NCQd6n39k= centrifuge.hectabit.org/HectaBit/webview_go v0.0.0-20240502074857-17ae24882d16/go.mod h1:cAr/pT090G1kcy9uuCEUbZyGhUQRGOo4KVnkocx8BMA= +github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= +github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DaRealFreak/cloudflare-bp-go v1.0.4 h1:33X8Z0YMV1DEVvL/kYLku+rjb4wF712+VIh3xBoifQ0= github.com/DaRealFreak/cloudflare-bp-go v1.0.4/go.mod h1:oBI9KAKb9FqdoB42uUqHU6pdP+YDWlKjpZRSk8JTuwk= github.com/EDDYCJY/fake-useragent v0.2.0 h1:Jcnkk2bgXmDpX0z+ELlUErTkoLb/mxFBNd2YdcpvJBs= @@ -11,19 +13,43 @@ github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x0 github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/plugnburn/go-twitch-pubsub v0.0.0-20240922200010-4bf88de12fae h1:SAOjVB7aO1Ez9YRsGaMzH2NVwBTA09kyHFGnLNUYUvU= +github.com/plugnburn/go-twitch-pubsub v0.0.0-20240922200010-4bf88de12fae/go.mod h1:jdM6kjYNIz29klHhXERCjFHGwdTLVtk2RqB8W1cdhmw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/exp/typeparams v0.0.0-20240613232115-7f521ea00fb8 h1:+ZJmEdDFzH5H0CnzOrwgbH3elHctfTecW9X0k2tkn5M= +golang.org/x/exp/typeparams v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= +golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= +golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= +golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.4.7 h1:9MDAWxMoSnB6QoSqiVr7P5mtkT9pOc1kSxchzPCnqJs= +honnef.co/go/tools v0.4.7/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= diff --git a/src/twitch.go b/src/twitch.go index f32278d..c30421c 100644 --- a/src/twitch.go +++ b/src/twitch.go @@ -26,6 +26,7 @@ var TW_BADGE_CACHE map[string]string /* Twitch OAuth global cache */ var TW_OAUTH_TOKEN string var TW_CLIENT_ID string +var TW_BROADCASTER_ID string /* helper function to write a string to the socket */ func tcpwritestr(conn net.Conn, str string) int { @@ -55,6 +56,9 @@ func tw_api_req(url string, data string) []byte { req, err := http.NewRequest(method, "https://api.twitch.tv/helix" + url, bytes.NewReader([]byte(data))) req.Header.Set("Authorization", "Bearer " + TW_OAUTH_TOKEN) req.Header.Set("Client-Id", TW_CLIENT_ID) + if len(data) > 0 { + req.Header.Set("Content-Type", "application/json") + } client := &http.Client{} resp, err := client.Do(req) @@ -69,7 +73,6 @@ func tw_api_req(url string, data string) []byte { /* badge cache population function */ func tw_fetch_badge_cache(channelname string) map[string]string { readycache := make(map[string]string) - broadcaster_id := "" var ustruct map[string]interface{} /* get broadcaster ID from the channel name */ body := tw_api_req("/users?login=" + channelname, "") @@ -78,7 +81,7 @@ func tw_fetch_badge_cache(channelname string) map[string]string { if err == nil { if _, ok := ustruct["data"]; ok && len(ustruct["data"].([]interface{})) > 0 { bdata := ustruct["data"].([]interface{})[0].(map[string]interface{}) - broadcaster_id = bdata["id"].(string) + TW_BROADCASTER_ID = bdata["id"].(string) } } } @@ -99,8 +102,8 @@ func tw_fetch_badge_cache(channelname string) map[string]string { } } } - if broadcaster_id != "" { /* also get the channel-specific badges */ - body = tw_api_req(fmt.Sprintf("/chat/badges?broadcaster_id=%s", broadcaster_id), "") + if TW_BROADCASTER_ID != "" { /* also get the channel-specific badges */ + body = tw_api_req(fmt.Sprintf("/chat/badges?broadcaster_id=%s", TW_BROADCASTER_ID), "") if len(body) > 0 { err := json.Unmarshal(body, &ustruct) if err == nil { @@ -284,6 +287,7 @@ func twitch_run(config map[string]interface{}) { chname, _ := strings.CutPrefix(config["tw_channel"].(string), "#") TW_BADGE_CACHE = tw_fetch_badge_cache(chname) + /* Connect to the Twitch IRC server */ conn, err := net.Dial("tcp", "irc.chat.twitch.tv:6667") if err != nil { @@ -296,6 +300,9 @@ func twitch_run(config map[string]interface{}) { /* request advanced chat message capabilities */ tcpwritestr(conn, "CAP REQ :twitch.tv/membership twitch.tv/tags twitch.tv/commands\n") + /* set up subscriptions and start the WebSocket event loop goroutine */ + tw_ws_pubsub_readloop() + log.Println("StreamGoose Twitch Go backend connected") var rawmsg string