mirror of
https://github.com/Luzifer/lounge-control.git
synced 2024-12-22 14:31:17 +00:00
Add sync-twitch-follows command
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
8702cb5d8a
commit
20565e033b
4 changed files with 168 additions and 0 deletions
159
cmd_syncTwitchFollows.go
Normal file
159
cmd_syncTwitchFollows.go
Normal 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
3
constants.go
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
const twitchClientID = "53govsefmz3c7pd5ev8slxlphtfo1j"
|
1
go.mod
1
go.mod
|
@ -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
5
go.sum
|
@ -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=
|
||||||
|
|
Loading…
Reference in a new issue