Initital version

This commit is contained in:
Knut Ahlers 2024-03-09 11:35:02 +01:00
commit 274de7d1d5
Signed by: luzifer
SSH key fingerprint: SHA256:/xtE5lCgiRDQr8SLxHMS92ZBlACmATUmF1crK16Ks4E
6 changed files with 251 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
twitch-my-emotes

5
README.md Normal file
View file

@ -0,0 +1,5 @@
# Luzifer / twitch-my-emotes
`twitch-my-emotes` fetches the emotes accessible to the given user in order to cache them locally for other scripts to use.
As the fetch for me takes 32s this should not be used synchronously but used to cache accessible emotes periodically.

26
go.mod Normal file
View file

@ -0,0 +1,26 @@
module git.luzifer.io/luzifer/twitch-my-emotes
go 1.22.0
require (
github.com/Luzifer/go_helpers/v2 v2.22.0
github.com/Luzifer/rconfig/v2 v2.5.0
github.com/Luzifer/twitch-bot/v3 v3.26.1
github.com/pkg/errors v0.9.1
github.com/sirupsen/logrus v1.9.3
)
require (
github.com/gorilla/websocket v1.5.1 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
gopkg.in/irc.v4 v4.0.0 // indirect
gopkg.in/validator.v2 v2.0.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gorm.io/gorm v1.25.7 // indirect
)

58
go.sum Normal file
View file

@ -0,0 +1,58 @@
github.com/Luzifer/go_helpers/v2 v2.22.0 h1:rJrZkJDzAiq4J0RUbwPI7kQ5rUy7BYQ/GUpo3fSM0y0=
github.com/Luzifer/go_helpers/v2 v2.22.0/go.mod h1:cIIqMPu3NT8/6kHke+03hVznNDLLKVGA74Lz47CWJyA=
github.com/Luzifer/rconfig/v2 v2.5.0 h1:zx5lfQbNX3za4VegID97IeY+M+BmfgHxWJTYA94sxok=
github.com/Luzifer/rconfig/v2 v2.5.0/go.mod h1:eGWUPQeCPv/Pr/p0hjmwFgI20uqvwi/Szen69hUzGzU=
github.com/Luzifer/twitch-bot/v3 v3.26.1 h1:TdNNVyMe45GW4//dY83NcVyBiokurbxFydaLFX+zxcU=
github.com/Luzifer/twitch-bot/v3 v3.26.1/go.mod h1:lq0bQcdIYdxR/BCxHkSLlii+VtTgwiHrX/VI+WW+FxQ=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4=
github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/irc.v4 v4.0.0 h1:5jsLkU2Tg+R2nGNqmkGCrciasyi4kNkDXhyZD+C31yY=
gopkg.in/irc.v4 v4.0.0/go.mod h1:BfjDz9MmuWW6OZY7iq4naOhudO8+QQCdO4Ko18jcsRE=
gopkg.in/validator.v2 v2.0.1 h1:xF0KWyGWXm/LM2G1TrEjqOu4pa6coO9AlWSf3msVfDY=
gopkg.in/validator.v2 v2.0.1/go.mod h1:lIUZBlB3Im4s/eYp39Ry/wkR02yOPhZ9IwIRBjuPuG8=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A=
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=

139
main.go Normal file
View file

@ -0,0 +1,139 @@
package main
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"sort"
"strings"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/Luzifer/go_helpers/v2/str"
"github.com/Luzifer/rconfig/v2"
"github.com/Luzifer/twitch-bot/v3/pkg/twitch"
)
var (
cfg = struct {
ExcludeEmoteType []string `flag:"exclude-emote-type,e" default:"" description:"Filter out these emote types"`
IncludeEmoteType []string `flag:"include-emote-type,i" default:"" description:"Only return these emote types"`
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
Output string `flag:"output,o" default:"emote-code" description:"Output format: emote-code, json"`
TwitchClientID string `flag:"twitch-client-id" default:"" description:"ClientID to token belongs to"`
TwitchClientSecret string `flag:"twitch-client-secret" default:"" description:"ClientSecret for the ClientID"`
TwitchToken string `flag:"twitch-token" default:"" description:"Token to identify the user"`
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
}{}
version = "dev"
)
func initApp() error {
rconfig.AutoEnv(true)
if err := rconfig.ParseAndValidate(&cfg); err != nil {
return errors.Wrap(err, "parsing cli options")
}
l, err := logrus.ParseLevel(cfg.LogLevel)
if err != nil {
return errors.Wrap(err, "parsing log-level")
}
logrus.SetLevel(l)
return nil
}
func main() {
var err error
if err = initApp(); err != nil {
logrus.WithError(err).Fatal("initializing app")
}
if cfg.VersionAndExit {
logrus.WithField("version", version).Info("twitch-my-emotes")
os.Exit(0)
}
if len(cfg.ExcludeEmoteType) == 1 && cfg.ExcludeEmoteType[0] == "" {
cfg.ExcludeEmoteType = nil
}
if len(cfg.IncludeEmoteType) == 1 && cfg.IncludeEmoteType[0] == "" {
cfg.IncludeEmoteType = nil
}
emotes, err := collectEmotes()
if err != nil {
logrus.WithError(err).Fatal("fetching emotes")
}
switch cfg.Output {
case "emote-code":
var codes []string
for _, e := range emotes {
codes = append(codes, e.Name)
}
sort.Strings(codes)
fmt.Println(strings.Join(codes, "\n")) //nolint:forbidigo // Okay for this case
case "json":
if err = json.NewEncoder(os.Stdout).Encode(emotes); err != nil {
logrus.WithError(err).Fatal("encoding emotes to JSON")
}
default:
logrus.Fatal("unknown output format")
}
}
func collectEmotes() ([]emote, error) {
client := twitch.New(cfg.TwitchClientID, cfg.TwitchClientSecret, cfg.TwitchToken, "")
userID, _, err := client.GetAuthorizedUser(context.Background())
if err != nil {
return nil, fmt.Errorf("fetching logged-in user: %w", err)
}
var (
emotes []emote
params = make(url.Values)
)
params.Set("user_id", userID)
for {
var payload emoteResponse
if err = client.Request(context.Background(), twitch.ClientRequestOpts{
AuthType: twitch.AuthTypeBearerToken,
Method: http.MethodGet,
OKStatus: http.StatusOK,
Out: &payload,
URL: fmt.Sprintf("https://api.twitch.tv/helix/chat/emotes/user?%s", params.Encode()),
}); err != nil {
return nil, fmt.Errorf("fetching emotes: %w", err)
}
logrus.Debugf("fetched %d emotes", len(payload.Data))
for _, e := range payload.Data {
if str.StringInSlice(e.EmoteType, cfg.ExcludeEmoteType) || len(cfg.IncludeEmoteType) > 0 && !str.StringInSlice(e.EmoteType, cfg.IncludeEmoteType) {
continue
}
emotes = append(emotes, e)
}
if payload.Pagination == nil || payload.Pagination.Cursor == "" {
break
}
params.Set("after", payload.Pagination.Cursor)
}
return emotes, nil
}

22
struct.go Normal file
View file

@ -0,0 +1,22 @@
package main
type (
emote struct {
EmoteSetID string `json:"emote_set_id"`
EmoteType string `json:"emote_type"`
Format []string `json:"format"`
ID string `json:"id"`
Name string `json:"name"`
OwnerID string `json:"owner_id"`
Scale []string `json:"scale"`
ThemeMode []string `json:"theme_mode"`
}
emoteResponse struct {
Data []emote `json:"data"`
Template string `json:"template"`
Pagination *struct {
Cursor string `json:"cursor"`
} `json:"pagination"`
}
)