From 8d438c6c0142fe4592f7ef12ce5ebf23ea894857 Mon Sep 17 00:00:00 2001 From: Knut Ahlers Date: Wed, 13 Sep 2023 23:39:29 +0200 Subject: [PATCH] Add Discord Invite Connector Signed-off-by: Knut Ahlers --- README.md | 29 ++++++++++++++ dc-invite/invite.go | 81 +++++++++++++++++++++++++++++++++++++++ dc-invite/main.go | 93 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 dc-invite/invite.go create mode 100644 dc-invite/main.go diff --git a/README.md b/README.md index 50c2cda..59fc61f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,35 @@ Pretty-print issues Twitch AutoMod had with the message match_message: '!autodebug (.+)' ``` +## dc-invite + +Create a Discord invite and send the invite link through a whisper + +```yaml +- description: Discord-Invite + actions: + - type: script + attributes: + command: + - /data/bin/dc-invite + - --channel-id=1234... # Right-click on channel, copy ID + #- --discord-bot-token=... # Can provide through env: DISCORD_BOT_TOKEN + - --message-template + - 'Hey, hier ist der Invite für meinen Discord: https://discord.gg/%s' + - --send-to={{ fixUsername .user }} + - type: discordhook + attributes: + content: | + Ein Discord-Invite (`{{ .invite_code }}`) wurde erstellt: + `{{ fixUsername .user }}` ({{ .user_id }}) + hook_url: https://discord.com/api/webhooks/... + username: Invite-Bot + match_channels: + - '#luziferus' + match_event: channelpoint_redeem + disable_on_template: '{{ ne .reward_id "45d441e9-6a00-4c3e-8bd8-b2cd8b913753" }}' +``` + ## dice Throw `N` `M`-sided dices and print the result into the chat diff --git a/dc-invite/invite.go b/dc-invite/invite.go new file mode 100644 index 0000000..3ed882a --- /dev/null +++ b/dc-invite/invite.go @@ -0,0 +1,81 @@ +package main + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +const discordRequestTimeout = 2 * time.Second + +type ( + createChannelInviteReq struct { + MaxAge int64 `json:"max_age"` + MaxUses int64 `json:"max_uses"` + Temporary bool `json:"temporary"` + Unique bool `json:"unique"` + // more unsupported params + } + + createChannelInviteResp struct { + Code string `json:"code"` + // more unsupported params + } +) + +func createInvite() (string, error) { + ctx, cancel := context.WithTimeout(context.Background(), discordRequestTimeout) + defer cancel() + + var ( + body = new(bytes.Buffer) + err error + reqURL = fmt.Sprintf("https://discord.com/api/channels/%s/invites", cfg.ChannelID) + ) + + if err = json.NewEncoder(body).Encode(createChannelInviteReq{ + MaxAge: int64(cfg.ExpireIn / time.Second), + MaxUses: cfg.Uses, + Temporary: false, + Unique: true, + }); err != nil { + return "", errors.Wrap(err, "encoding request body") + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, reqURL, body) + if err != nil { + return "", errors.Wrap(err, "creating request") + } + + req.Header.Set("Authorization", strings.Join([]string{"Bot", cfg.DiscordBotToken}, " ")) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Audit-Log-Reason", fmt.Sprintf("twitch-bot-tools/dc-invite requested for %s", cfg.SendTo)) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", errors.Wrap(err, "executing request") + } + defer func() { + if err := resp.Body.Close(); err != nil { + logrus.WithError(err).Error("closing response body (leaked fd)") + } + }() + + if resp.StatusCode != http.StatusOK { + return "", errors.Errorf("unexpected status %d", resp.StatusCode) + } + + var invite createChannelInviteResp + if err = json.NewDecoder(resp.Body).Decode(&invite); err != nil { + return "", errors.Wrap(err, "decoding invite response") + } + + return invite.Code, nil +} diff --git a/dc-invite/main.go b/dc-invite/main.go new file mode 100644 index 0000000..7eac0b5 --- /dev/null +++ b/dc-invite/main.go @@ -0,0 +1,93 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "os" + "time" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + + "github.com/Luzifer/rconfig/v2" + "github.com/Luzifer/twitch-bot/v3/plugins" +) + +var ( + cfg = struct { + ChannelID string `flag:"channel-id,c" default:"" description:"Snowflake ID of the channel to invite to" validate:"nonzero"` + DiscordBotToken string `flag:"discord-bot-token,t" default:"" description:"Token for a Bot with CREATE_INSTANT_INVITE permission" validate:"nonzero"` + ExpireIn time.Duration `flag:"expire-in,e" default:"24h" description:"How long should the invite be valid"` + LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"` + MessageTemplate string `flag:"message-template,m" default:"Your discord invite: https://discord.gg/%s" description:"Message to send in the whisper (use %s at the position of the invite code)"` + SendTo string `flag:"send-to,s" default:"" description:"Twitch user to whisper the code to"` + VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"` + Uses int64 `flag:"uses,u" default:"1" description:"Expire after N uses"` + }{} + + 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-bot-tools/dc-invite") + os.Exit(0) + } + + invite, err := createInvite() + if err != nil { + logrus.WithError(err).Fatal("creating invite") + } + + fieldsPayload := new(bytes.Buffer) + if err = json.NewEncoder(fieldsPayload).Encode(map[string]any{ + "invite_code": invite, + }); err != nil { + logrus.WithError(err).Fatal("encoding fields payload") + } + + if err = json.NewEncoder(os.Stdout).Encode([]plugins.RuleAction{ + { + Type: "eventmod", + Attributes: plugins.FieldCollectionFromData(map[string]any{ + "fields": fieldsPayload.String(), + }), + }, + { + Type: "log", + Attributes: plugins.FieldCollectionFromData(map[string]any{ + "message": fmt.Sprintf("twitch-bot-tools/dc-invite: created invite %s for user %s", invite, cfg.SendTo), + }), + }, + { + Type: "whisper", + Attributes: plugins.FieldCollectionFromData(map[string]any{ + "message": fmt.Sprintf(cfg.MessageTemplate, invite), + "to": cfg.SendTo, + }), + }, + }); err != nil { + logrus.WithError(err).Fatal("encoding bot actions") + } +}