1
0
Fork 0
mirror of https://github.com/Luzifer/lounge-control.git synced 2024-11-09 19:50:00 +00:00

Add sync-twitch-follows command

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2020-05-10 15:58:10 +02:00
parent 8702cb5d8a
commit 20565e033b
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
4 changed files with 168 additions and 0 deletions

159
cmd_syncTwitchFollows.go Normal file
View file

@ -0,0 +1,159 @@
package main
import (
"encoding/json"
"fmt"
"net/http"
"os"
"strings"
"time"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/lounge-control/sioclient"
)
func init() {
registerCommand("sync-twitch-follows", commandSyncTwitchFollows)
}
func commandSyncTwitchFollows(args []string) handlerFunc {
channelAct := func(lobbyID int, action, twitchName string) error {
msg, err := sioclient.NewMessage(sioclient.MessageTypeEvent, 0, "input", map[string]interface{}{
"text": fmt.Sprintf("/%s #%s", action, twitchName),
"target": lobbyID,
})
if err != nil {
return errors.Wrap(err, "Unable to compose join message")
}
if err = msg.Send(client); err != nil {
return errors.Wrap(err, "Unable to send join message")
}
// Twitch limits the number of actions, so we need an arbitrary delay
time.Sleep(750 * time.Millisecond)
return nil
}
return addGenericHandler(func(pType string, msg *sioclient.Message) error {
if pType != "init" {
return nil
}
network := initData.NetworkByNameOrUUID(cfg.Network)
if network == nil {
return errors.New("Network not found")
}
// Find lobby to send commands to
var lobby *channel
for _, c := range network.Channels {
if c.Type == "lobby" {
lobby = &c
break
}
}
if lobby == nil {
return errors.New("Unable to find lobby for network")
}
// Get configured nickname (must match Twitch nick)
var user = network.Nick
log.WithField("username", user).Info("Synchronizing with twitch user")
// Convert username into user ID
req, _ := http.NewRequest("GET", fmt.Sprintf("https://api.twitch.tv/kraken/users?login=%s", user), nil)
req.Header.Set("Accept", "application/vnd.twitchtv.v5+json")
req.Header.Set("Client-ID", twitchClientID)
resp, err := http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "Unable to get user ID for Twitch user")
}
defer resp.Body.Close()
var respObjUsers struct {
Users []struct {
ID string `json:"_id"`
} `json:"users"`
}
if err = json.NewDecoder(resp.Body).Decode(&respObjUsers); err != nil {
return errors.Wrap(err, "Unable to read Twitch response")
}
if l := len(respObjUsers.Users); l != 1 {
return errors.Errorf("Received invalid number of user IDs: %d", l)
}
var userID = respObjUsers.Users[0].ID
// Retrieve follows
req, _ = http.NewRequest("GET", fmt.Sprintf("https://api.twitch.tv/kraken/users/%s/follows/channels?limit=100", userID), nil)
req.Header.Set("Accept", "application/vnd.twitchtv.v5+json")
req.Header.Set("Client-ID", twitchClientID)
resp, err = http.DefaultClient.Do(req)
if err != nil {
return errors.Wrap(err, "Unable to get follows for Twitch user")
}
defer resp.Body.Close()
var respObjFollows struct {
Follows []struct {
Channel struct {
Name string `json:"name"`
} `json:"channel"`
} `json:"follows"`
}
if err = json.NewDecoder(resp.Body).Decode(&respObjFollows); err != nil {
return errors.Wrap(err, "Unable to read Twitch response")
}
// Compare channel list and act on them
var (
expectedChannels = []string{user}
presentChannels []string
)
for _, c := range network.Channels {
if c.Type != "channel" {
continue
}
presentChannels = append(presentChannels, strings.TrimPrefix(c.Name, "#"))
}
for _, f := range respObjFollows.Follows {
expectedChannels = append(expectedChannels, f.Channel.Name)
}
// Join new channels
for _, cn := range expectedChannels {
if str.StringInSlice(cn, presentChannels) {
continue
}
log.WithField("channel", cn).Info("Joining new channel")
if err = channelAct(lobby.ID, "join", cn); err != nil {
return errors.Wrap(err, "Unable to execute channel action")
}
}
// Leave unexpected channels
for _, cn := range presentChannels {
if str.StringInSlice(cn, expectedChannels) {
log.WithField("channel", cn).Debug("Retaining channel")
continue
}
log.WithField("channel", cn).Info("Leaving channel")
if err = channelAct(lobby.ID, "part", cn); err != nil {
return errors.Wrap(err, "Unable to execute channel action")
}
}
interrupt <- os.Interrupt
return nil
})
}

3
constants.go Normal file
View file

@ -0,0 +1,3 @@
package main
const twitchClientID = "53govsefmz3c7pd5ev8slxlphtfo1j"

1
go.mod
View file

@ -5,6 +5,7 @@ go 1.14
replace github.com/Luzifer/lounge-control/sioclient => ./sioclient replace github.com/Luzifer/lounge-control/sioclient => ./sioclient
require ( require (
github.com/Luzifer/go_helpers/v2 v2.10.0
github.com/Luzifer/lounge-control/sioclient v0.0.0-00010101000000-000000000000 github.com/Luzifer/lounge-control/sioclient v0.0.0-00010101000000-000000000000
github.com/Luzifer/rconfig/v2 v2.2.1 github.com/Luzifer/rconfig/v2 v2.2.1
github.com/gorilla/websocket v1.4.2 // indirect github.com/gorilla/websocket v1.4.2 // indirect

5
go.sum
View file

@ -1,11 +1,16 @@
github.com/Luzifer/go_helpers v1.4.0 h1:Pmm058SbYewfnpP1CHda/zERoAqYoZFiBHF4l8k03Ko=
github.com/Luzifer/go_helpers/v2 v2.10.0 h1:rA3945P6tH1PKRdcVD+nAdAWojfgwX8wQm/jjUNPmfg=
github.com/Luzifer/go_helpers/v2 v2.10.0/go.mod h1:ZnWxPjyCdQ4rZP3kNiMSUW/7FigU1X9Rz8XopdJ5ZCU=
github.com/Luzifer/rconfig v1.2.0 h1:waD1sqasGVSQSrExpLrQ9Q1JmMaltrS391VdOjWXP/I= github.com/Luzifer/rconfig v1.2.0 h1:waD1sqasGVSQSrExpLrQ9Q1JmMaltrS391VdOjWXP/I=
github.com/Luzifer/rconfig/v2 v2.2.1 h1:zcDdLQlnlzwcBJ8E0WFzOkQE1pCMn3EbX0dFYkeTczg= github.com/Luzifer/rconfig/v2 v2.2.1 h1:zcDdLQlnlzwcBJ8E0WFzOkQE1pCMn3EbX0dFYkeTczg=
github.com/Luzifer/rconfig/v2 v2.2.1/go.mod h1:OKIX0/JRZrPJ/ZXXWklQEFXA6tBfWaljZbW37w+sqBw= github.com/Luzifer/rconfig/v2 v2.2.1/go.mod h1:OKIX0/JRZrPJ/ZXXWklQEFXA6tBfWaljZbW37w+sqBw=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8= github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/leekchan/gtf v0.0.0-20190214083521-5fba33c5b00b/go.mod h1:thNruaSwydMhkQ8dXzapABF9Sc1Tz08ZBcDdgott9RA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=