Initital version
This commit is contained in:
commit
274de7d1d5
6 changed files with 251 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
twitch-my-emotes
|
5
README.md
Normal file
5
README.md
Normal 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
26
go.mod
Normal 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
58
go.sum
Normal 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
139
main.go
Normal 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
22
struct.go
Normal 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"`
|
||||||
|
}
|
||||||
|
)
|
Loading…
Reference in a new issue