Add Discord Invite Connector

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2023-09-13 23:39:29 +02:00
parent b3cc7e8d9a
commit 8d438c6c01
Signed by: luzifer
GPG key ID: D91C3E91E4CAD6F5
3 changed files with 203 additions and 0 deletions

View file

@ -16,6 +16,35 @@ Pretty-print issues Twitch AutoMod had with the message
match_message: '!autodebug (.+)' 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 ## dice
Throw `N` `M`-sided dices and print the result into the chat Throw `N` `M`-sided dices and print the result into the chat

81
dc-invite/invite.go Normal file
View file

@ -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
}

93
dc-invite/main.go Normal file
View file

@ -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")
}
}