From ae53b7777f6c1f28fcbda6e2e6c148f5349d06c7 Mon Sep 17 00:00:00 2001 From: jul Date: Sun, 18 Apr 2021 16:45:45 +0200 Subject: [PATCH 1/7] Renamed Message.delete to Message.Delete wow --- src/peach_discord_client/channel.go | 20 +++++++++++++++++++- src/peach_discord_client/ext_about.go | 2 +- src/peach_discord_client/ext_clear.go | 4 ++-- src/peach_discord_client/extensions.go | 21 +++++++++++++++++++++ src/peach_discord_client/permissions.go | 20 -------------------- src/peach_discord_client/websocket.go | 5 ----- 6 files changed, 43 insertions(+), 29 deletions(-) diff --git a/src/peach_discord_client/channel.go b/src/peach_discord_client/channel.go index 3c2b414..8e00575 100644 --- a/src/peach_discord_client/channel.go +++ b/src/peach_discord_client/channel.go @@ -59,9 +59,12 @@ type Message struct { Flags int `json:"flags,omitempty"` } -func (m Message) delete(c *Client) error { +func (m *Message) Delete(c *Client) error { return c.DeleteMessage(m.ChannelID, m.ID) +} +func (m *Message) Edit(c *Client, args EditMessageArgs) (*Message, error) { + return c.EditMessage(m.ChannelID, m.ID, args) } // Attachment represents a Discord message's attachment @@ -173,3 +176,18 @@ type EmbedField struct { Value string `json:"value"` Inline bool `json:"inline,omitempty"` } + +type AllowedMentions struct { + MentionTypes []MentionType `json:"parse"` + RoleIDs []string `json:"roles"` + UserIDs []string `json:"users"` + MentionRepliedUser bool `json:"replied_user"` +} + +type MentionType string + +const ( + MentionRoles MentionType = "roles" + MentionUsers = "users" + MentionEveryone = "everyone" +) diff --git a/src/peach_discord_client/ext_about.go b/src/peach_discord_client/ext_about.go index 1646506..874c151 100644 --- a/src/peach_discord_client/ext_about.go +++ b/src/peach_discord_client/ext_about.go @@ -34,7 +34,7 @@ func (c *Client) extAboutOnMessage(ctx *EventMessageCreate) error { return err } - err = ctx.delete(c) + err = ctx.Delete(c) if err != nil { return err } diff --git a/src/peach_discord_client/ext_clear.go b/src/peach_discord_client/ext_clear.go index 91f1c23..ddadba5 100644 --- a/src/peach_discord_client/ext_clear.go +++ b/src/peach_discord_client/ext_clear.go @@ -30,7 +30,7 @@ func (c *Client) extClearOnMessage(ctx *EventMessageCreate, args []string) error return nil } - err = ctx.delete(c) + err = ctx.Delete(c) if err != nil { return err } @@ -61,7 +61,7 @@ func (c *Client) extClearOnMessage(ctx *EventMessageCreate, args []string) error time.Sleep(5 * time.Second) - err = success.delete(c) + err = success.Delete(c) if err != nil { return err } diff --git a/src/peach_discord_client/extensions.go b/src/peach_discord_client/extensions.go index b07f3f3..c4501ca 100644 --- a/src/peach_discord_client/extensions.go +++ b/src/peach_discord_client/extensions.go @@ -1,5 +1,7 @@ package main +import "time" + var aliasMap = map[string]string{ "clear": "clear", "c": "clear", @@ -24,3 +26,22 @@ func (c *Client) runOnMessage(invoke string, args []string, ctx *EventMessageCre } return err } + +func (c *Client) handleNoPermission(m *Message) error { + sorry, err := c.SendMessage(m.ChannelID, NewMessage{":no_entry: It seems like you do not have the permissions to use this command.", false, nil}) + if err != nil { + return err + } + + time.Sleep(5 * time.Second) + + err = sorry.Delete(c) + if err != nil { + return err + } + err = m.Delete(c) + if err != nil { + return err + } + return nil +} diff --git a/src/peach_discord_client/permissions.go b/src/peach_discord_client/permissions.go index d041cf8..09a1627 100644 --- a/src/peach_discord_client/permissions.go +++ b/src/peach_discord_client/permissions.go @@ -2,7 +2,6 @@ package main import ( "strconv" - "time" ) // Role represents a discord guild role @@ -132,22 +131,3 @@ func (c *Client) hasPermission(channelID string, author User, member GuildMember } return false, nil } - -func (c *Client) handleNoPermission(m *Message) error { - sorry, err := c.SendMessage(m.ChannelID, NewMessage{":no_entry: It seems like you do not have the permissions to use this command.", false, nil}) - if err != nil { - return err - } - - time.Sleep(5 * time.Second) - - err = sorry.delete(c) - if err != nil { - return err - } - err = m.delete(c) - if err != nil { - return err - } - return nil -} diff --git a/src/peach_discord_client/websocket.go b/src/peach_discord_client/websocket.go index 1161dce..2078b7d 100644 --- a/src/peach_discord_client/websocket.go +++ b/src/peach_discord_client/websocket.go @@ -210,11 +210,6 @@ func (c *Client) Heartbeat() { } } -// Dispatch runs events through the plugin system -func (c *Client) Dispatch(event *Event) { - -} - // Hello handles the initial Hello event func (c *Client) Hello() error { From b450539125ca3dea482c0f88847bde0cf830371a Mon Sep 17 00:00:00 2001 From: jul Date: Sun, 18 Apr 2021 16:46:02 +0200 Subject: [PATCH 2/7] Added all channel methods --- src/peach_discord_client/endpoints.go | 12 +- .../http_channel_methods.go | 396 +++++++++++++++++- 2 files changed, 389 insertions(+), 19 deletions(-) diff --git a/src/peach_discord_client/endpoints.go b/src/peach_discord_client/endpoints.go index f44f3f8..d9eade7 100644 --- a/src/peach_discord_client/endpoints.go +++ b/src/peach_discord_client/endpoints.go @@ -110,12 +110,16 @@ var ( EndpointChannelPermission = func(channelID, overwriteID string) string { return EndpointChannels + channelID + "/permissions/" + overwriteID } - EndpointChannelInvites = func(channelID string) string { return EndpointChannels + channelID + "/invites" } - EndpointChannelTyping = func(channelID string) string { return EndpointChannels + channelID + "/typing" } - EndpointChannelMessages = func(channelID string) string { return EndpointChannels + channelID + "/messages" } - EndpointChannelMessage = func(channelID, messageID string) string { + EndpointChannelInvites = func(channelID string) string { return EndpointChannels + channelID + "/invites" } + EndpointChannelTyping = func(channelID string) string { return EndpointChannels + channelID + "/typing" } + EndpointChannelFollowers = func(channelID string) string { return EndpointChannels + channelID + "/followers" } + EndpointChannelMessages = func(channelID string) string { return EndpointChannels + channelID + "/messages" } + EndpointChannelMessage = func(channelID, messageID string) string { return EndpointChannels + channelID + "/messages/" + messageID } + EndpointChannelCrosspostMessage = func(channelID, messageID string) string { + return EndpointChannels + channelID + "/messages/" + messageID + "/crosspost" + } EndpointChannelMessageAck = func(channelID, messageID string) string { return EndpointChannels + channelID + "/messages/" + messageID + "/ack" } diff --git a/src/peach_discord_client/http_channel_methods.go b/src/peach_discord_client/http_channel_methods.go index 7eaa5c4..9b07fb0 100644 --- a/src/peach_discord_client/http_channel_methods.go +++ b/src/peach_discord_client/http_channel_methods.go @@ -5,6 +5,7 @@ import ( "fmt" "io" "net/http" + "net/url" "strconv" "github.com/patrickmn/go-cache" @@ -31,7 +32,7 @@ func (c *Client) GetChannel(ID string) (ch *Channel, err error) { func (c *Client) getChannel(channelID string) (*Channel, error) { // Send Request - resp, body, err := c.Request("GET", EndpointChannel(channelID), nil) + resp, body, err := c.Request(http.MethodGet, EndpointChannel(channelID), nil) if err != nil { return nil, err } @@ -119,7 +120,7 @@ func (c *Client) DeleteChannel(channelID string, withCounts bool) (*Channel, err } // GetChannelMessages returns an array containing a channels messages -func (c *Client) GetChannelMessages(channelID string, around string, before string, after string, limit int) (*[]Message, error) { +func (c *Client) GetChannelMessages(channelID, around, before, after string, limit int) (*[]Message, error) { var args string if around != "" { @@ -136,7 +137,7 @@ func (c *Client) GetChannelMessages(channelID string, around string, before stri } // Send Request - resp, body, err := c.Request("GET", EndpointChannelMessages(channelID)+args, nil) + resp, body, err := c.Request(http.MethodGet, EndpointChannelMessages(channelID)+args, nil) if err != nil { return nil, err } @@ -157,10 +158,10 @@ func (c *Client) GetChannelMessages(channelID string, around string, before stri return data, nil } -func (c *Client) GetChannelMessage(channelID string, messageID string) (*Message, error) { +func (c *Client) GetChannelMessage(channelID, messageID string) (*Message, error) { // Send Request - resp, body, err := c.Request("GET", EndpointChannelMessage(channelID, messageID), nil) + resp, body, err := c.Request(http.MethodGet, EndpointChannelMessage(channelID, messageID), nil) if err != nil { return nil, err } @@ -181,33 +182,195 @@ func (c *Client) GetChannelMessage(channelID string, messageID string) (*Message return message, nil } -// DeleteMessage deletes a specific message with a given ID from a specific channel -func (c *Client) DeleteMessage(channelID, messageID string) error { +// SendMessage posts a message to a guild text or DM channel. +func (c *Client) SendMessage(channelID string, message NewMessage) (*Message, error) { - // Send Request - resp, _, err := c.Request("DELETE", EndpointChannelMessage(channelID, messageID), *new(io.Reader)) + resp, body, err := c.Request(http.MethodPost, EndpointChannelMessages(channelID), message) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) + c.Log.Debugf("SendMessage: %s", err.Error()) + return nil, err + } + + sentMessage := new(Message) + err = json.Unmarshal(body, sentMessage) + if err != nil { + return nil, err + } + + return sentMessage, nil +} + +func (c *Client) CrosspostMessage(channelID, messageID string) (*Message, error) { + + resp, body, err := c.Request(http.MethodPost, EndpointChannelCrosspostMessage(channelID, messageID), nil) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) + c.Log.Debugf("CrosspostMessage: %s", err.Error()) + return nil, err + } + + message := new(Message) + err = json.Unmarshal(body, message) + if err != nil { + return nil, err + } + + return message, nil +} + +func (c *Client) CreateReaction(channelID, messageID, emoji string, customEmoji Emoji) error { + + if emoji == "" { + emoji = customEmoji.Name + ":" + customEmoji.ID + } + + resp, _, err := c.Request(http.MethodPost, EndpointMessageReaction(channelID, messageID, url.QueryEscape(emoji), "@me"), nil) if err != nil { return err } if resp.StatusCode != http.StatusNoContent { - return fmt.Errorf("DeleteMessage: unexpected response code. Want: 204 No Content, Got: %s instead", resp.Status) + err = ErrUnexpectedStatus(http.StatusNoContent, resp.StatusCode) + c.Log.Debugf("CreateReaction: %s", err.Error()) + return err + } + + return nil +} + +func (c *Client) DeleteBotReaction(channelID, messageID, emoji string, customEmoji Emoji) error { + + if emoji == "" { + emoji = customEmoji.Name + ":" + customEmoji.ID + } + + resp, _, err := c.Request(http.MethodDelete, EndpointMessageReaction(channelID, messageID, url.QueryEscape(emoji), "@me"), nil) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusNoContent { + err = ErrUnexpectedStatus(http.StatusNoContent, resp.StatusCode) + c.Log.Debugf("DeleteBotReaction: %s", err.Error()) + return err } return nil } +func (c *Client) DeleteUserReaction(channelID, messageID, emoji string, customEmoji Emoji, userID string) error { + + if emoji == "" { + emoji = customEmoji.Name + ":" + customEmoji.ID + } + + resp, _, err := c.Request(http.MethodDelete, EndpointMessageReaction(channelID, messageID, url.QueryEscape(emoji), userID), nil) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusNoContent { + err = ErrUnexpectedStatus(http.StatusNoContent, resp.StatusCode) + c.Log.Debugf("DeleteUserReaction: %s", err.Error()) + return err + } + + return nil +} + +func (c *Client) GetReactions(channelID, messageID, emoji string, customEmoji Emoji) (*[]User, error) { + + if emoji == "" { + emoji = customEmoji.Name + ":" + customEmoji.ID + } + + // Send Request + resp, body, err := c.Request(http.MethodGet, EndpointMessageReactions(channelID, messageID, url.QueryEscape(emoji)), nil) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) + c.Log.Debugf("GetReactions: %s", err.Error()) + return nil, err + } + + users := new([]User) + + err = json.Unmarshal(body, users) + if err != nil { + return nil, err + } + + return users, nil +} + +func (c *Client) DeleteAllReactions(channelID, messageID string) error { + + // Send Request + resp, _, err := c.Request(http.MethodDelete, EndpointMessageReactionsAll(channelID, messageID), nil) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) + c.Log.Debugf("DeleteAllReactions: %s", err.Error()) + return err + } + + return nil +} + +func (c *Client) DeleteEmojiReactions(channelID, messageID, emoji string, customEmoji Emoji) error { + + if emoji == "" { + emoji = customEmoji.Name + ":" + customEmoji.ID + } + + // Send Request + resp, _, err := c.Request(http.MethodDelete, EndpointMessageReactions(channelID, messageID, url.QueryEscape(emoji)), nil) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) + c.Log.Debugf("DeleteEmojiReactions: %s", err.Error()) + return err + } + + return nil +} + +type EditMessageArgs struct { + Content string `json:"content,omitempty"` + Embed Embed `json:"embed,omitempty"` + Flags int `json:"flags,omitempty"` + AllowedMentions AllowedMentions `json:"allowed_mentions,omitempty"` +} + // SendMessage posts a message to a guild text or DM channel. -func (c *Client) SendMessage(channelID string, message NewMessage) (*Message, error) { +func (c *Client) EditMessage(channelID, messageID string, args EditMessageArgs) (*Message, error) { - resp, body, err := c.Request("POST", EndpointChannelMessages(channelID), message) + resp, body, err := c.Request(http.MethodPatch, EndpointChannelMessage(channelID, messageID), args) if err != nil { return nil, err } if resp.StatusCode != http.StatusOK { err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) - c.Log.Debugf("SendMessage: %s", err.Error()) + c.Log.Debugf("EditMessage: %s", err.Error()) return nil, err } @@ -220,7 +383,22 @@ func (c *Client) SendMessage(channelID string, message NewMessage) (*Message, er return sentMessage, nil } -// BulkDeleteMessages deletes a lot of messages in a single request, duh +// DeleteMessage deletes a specific message with a given ID from a specific channel +func (c *Client) DeleteMessage(channelID, messageID string) error { + + // Send Request + resp, _, err := c.Request("DELETE", EndpointChannelMessage(channelID, messageID), *new(io.Reader)) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("DeleteMessage: unexpected response code. Want: 204 No Content, Got: %s instead", resp.Status) + } + + return nil +} + func (c *Client) BulkDeleteMessages(channelID string, messages []string) error { if len(messages) == 0 { @@ -237,7 +415,7 @@ func (c *Client) BulkDeleteMessages(channelID string, messages []string) error { c.Log.Debug(body) - resp, _, err := c.Request("POST", EndpointChannelMessagesBulkDelete(channelID), body) + resp, _, err := c.Request(http.MethodPost, EndpointChannelMessagesBulkDelete(channelID), body) if err != nil { return err } @@ -250,3 +428,191 @@ func (c *Client) BulkDeleteMessages(channelID string, messages []string) error { return nil } + +type EditChannelPermissionsArgs struct { + Allow string `json:"allow"` + Deny string `json:"deny"` + Type string `json:"type"` +} + +func (c *Client) EditChannelPermissions(channelID, overwriteID string, args EditChannelPermissionsArgs) error { + + resp, _, err := c.Request(http.MethodPut, EndpointChannelPermission(channelID, overwriteID), args) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusNoContent { + err = ErrUnexpectedStatus(http.StatusNoContent, resp.StatusCode) + c.Log.Debugf("EditChannelPermissions: %s", err.Error()) + return err + } + + return nil +} + +func (c *Client) GetChannelInvites(channelID string) (*[]Invite, error) { + + resp, body, err := c.Request(http.MethodGet, EndpointChannelInvites(channelID), nil) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) + c.Log.Debugf("GetChannelInvites: %s", err.Error()) + return nil, err + } + + invites := new([]Invite) + + err = json.Unmarshal(body, invites) + if err != nil { + return nil, err + } + + return invites, nil +} + +type CreateChannelInviteArgs struct { + MaxAge int `json:"max_age,omitempty"` + MaxUses int `json:"max_uses,omitempty"` + Temporary bool `json:"temporary,omitempty"` + Unique bool `json:"unique,omitempty"` + TargetType int `json:"target_type,omitempty"` + TargetUserID string `json:"target_user_id,omitempty"` + TargetApplicationID string `json:"target_application_id,omitempty"` +} + +func (c *Client) CreateChannelInvite(channelID string, args CreateChannelInviteArgs) (*Invite, error) { + + resp, body, err := c.Request(http.MethodPost, EndpointChannelInvites(channelID), args) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) + c.Log.Debugf("CreateChannelInvite: %s", err.Error()) + return nil, err + } + + invite := new(Invite) + + err = json.Unmarshal(body, invite) + if err != nil { + return nil, err + } + + return invite, nil +} + +func (c *Client) DeleteChannelPermission(channelID, overwriteID string) error { + + resp, _, err := c.Request(http.MethodDelete, EndpointChannelPermission(channelID, overwriteID), nil) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusNoContent { + err = ErrUnexpectedStatus(http.StatusNoContent, resp.StatusCode) + c.Log.Debugf("DeleteChannelPermission: %s", err.Error()) + return err + } + + return nil +} + +func (c *Client) FollowNewsChannel(channelID, targetChannelID string) error { + + args := struct { + WebhookChannelID string `json:"webhook_channel_id"` + }{ + WebhookChannelID: targetChannelID, + } + + resp, _, err := c.Request(http.MethodPost, EndpointChannelFollowers(channelID), args) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) + c.Log.Debugf("FollowNewsChannel: %s", err.Error()) + return err + } + + return nil +} + +func (c *Client) TriggerTypingIndicator(channelID string) error { + + resp, _, err := c.Request(http.MethodPost, EndpointChannelTyping(channelID), nil) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusNoContent { + err = ErrUnexpectedStatus(http.StatusNoContent, resp.StatusCode) + c.Log.Debugf("TriggerTypingIndicator: %s", err.Error()) + return err + } + + return nil +} + +func (c *Client) GetPinnedMessages(channelID string) (*[]Message, error) { + + // Send Request + resp, body, err := c.Request(http.MethodGet, EndpointChannelMessagesPins(channelID), nil) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + err = ErrUnexpectedStatus(http.StatusOK, resp.StatusCode) + c.Log.Debugf("GetPinnedMessages: %s", err.Error()) + return nil, err + } + + messages := new([]Message) + + err = json.Unmarshal(body, messages) + if err != nil { + return nil, err + } + + return messages, nil +} + +func (c *Client) PinMessage(channelID, messageID string) error { + + resp, _, err := c.Request(http.MethodPut, EndpointChannelMessagePin(channelID, messageID), nil) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusNoContent { + err = ErrUnexpectedStatus(http.StatusNoContent, resp.StatusCode) + c.Log.Debugf("PinMessage: %s", err.Error()) + return err + } + + return nil +} + +func (c *Client) DeletePinnedMessage(channelID, messageID string) error { + + resp, _, err := c.Request(http.MethodDelete, EndpointChannelMessagePin(channelID, messageID), nil) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusNoContent { + err = ErrUnexpectedStatus(http.StatusNoContent, resp.StatusCode) + c.Log.Debugf("DeletePinnedMessage: %s", err.Error()) + return err + } + + return nil +} From 65685a0844055ebe630b6670cc3e64d7650ebb2c Mon Sep 17 00:00:00 2001 From: jul Date: Sun, 18 Apr 2021 23:30:28 +0200 Subject: [PATCH 3/7] Reworked extension system --- launchcfg_example.json | 9 ++- src/peach_discord_client/client.go | 12 ++- src/peach_discord_client/eventhandlers.go | 25 +++--- src/peach_discord_client/ext_clear.go | 70 ----------------- .../{ext_about.go => ext_internal.go} | 16 +++- src/peach_discord_client/ext_moderation.go | 78 +++++++++++++++++++ src/peach_discord_client/extensions.go | 45 +++++++---- src/peach_discord_client/http.go | 7 +- src/peach_discord_client/main.go | 5 ++ src/peach_launcher/main.go | 16 ++-- 10 files changed, 167 insertions(+), 116 deletions(-) delete mode 100644 src/peach_discord_client/ext_clear.go rename src/peach_discord_client/{ext_about.go => ext_internal.go} (66%) create mode 100644 src/peach_discord_client/ext_moderation.go diff --git a/launchcfg_example.json b/launchcfg_example.json index a47f533..e8bdb9a 100644 --- a/launchcfg_example.json +++ b/launchcfg_example.json @@ -3,15 +3,16 @@ "sharded": true, "shards": 2, "token": "", - "loglevel": "info", - "coordinator": "http://localhost:8080/api/" + "log_level": "info", + "coordinator": "http://localhost:8080/api/", }, "clientcoordinator": { "launch": true, "port": "5000", "dbc": "database, user, password, host, port", - "certtype": "letsencrypt", + "cert_type": "letsencrypt", "domain": "example.domain" }, - "secret": "" + "secret": "somethingsomething", + "redact_sensitive": true } \ No newline at end of file diff --git a/src/peach_discord_client/client.go b/src/peach_discord_client/client.go index 1529a64..4b8d889 100644 --- a/src/peach_discord_client/client.go +++ b/src/peach_discord_client/client.go @@ -52,7 +52,7 @@ type Client struct { Sequence *int64 // User - User User + User *User // Heartbeat HeartbeatInterval time.Duration // Interval in which client should sent heartbeats @@ -76,6 +76,9 @@ type Client struct { ChannelCache *cache.Cache Settings map[string]cfgSettings // Map guildIDs to settings and cache that shit + // Extensions + Extensions Extensions + // Starttime Starttime time.Time } @@ -121,11 +124,16 @@ func CCLogin(c *Client) error { if err != nil { return err } - c.TOKEN = ClientCoordinator.Token c.ShardCount = ClientCoordinator.TotalShards c.ShardID = ClientCoordinator.ShardID c.GatewayURL = ClientCoordinator.GatewayURL c.CCHeartbeatInterval = ClientCoordinator.HeartbeatInterval + c.TOKEN = ClientCoordinator.Token + + if redactSensitive { + ClientCoordinator.Token = "[REDACTED]" + } + c.Log.Debugf("Websocket: Received from client coordinator: %v", ClientCoordinator) return nil } diff --git a/src/peach_discord_client/eventhandlers.go b/src/peach_discord_client/eventhandlers.go index 1d9576a..cf5fed7 100644 --- a/src/peach_discord_client/eventhandlers.go +++ b/src/peach_discord_client/eventhandlers.go @@ -125,29 +125,34 @@ func (c *Client) onInviteDelete(ctx *EventInviteDelete) error { func (c *Client) onMessageCreate(ctx *EventMessageCreate) error { - if ctx.Author.ID != c.User.ID { - c.Log.WithFields(logrus.Fields{ - "author": ctx.Author.Username, - "message": ctx.Content, - "serverid": ctx.GuildID, - }).Debug("Websocket: received message") - } + c.Log.WithFields(logrus.Fields{ + "author": ctx.Author.Username, + "message": ctx.Content, + "serverid": ctx.GuildID, + }).Debug("Websocket: received message") + // Do nothing for messages sent by applications if ctx.WebhookID != "" || ctx.Author.Bot { return nil } + // Search for commands and execute if found prefix := c.getSetting(ctx.GuildID, "bot", "prefix") if strings.HasPrefix(ctx.Content, prefix) && len(ctx.Content) > 1 { noPrefix := ctx.Content[1:] invoke := strings.Fields(noPrefix)[0] args := strings.Fields(noPrefix)[1:] - err := c.runOnMessage(invoke, args, ctx) + err := c.Extensions.runCommand(invoke, args, ctx) if err != nil { return fmt.Errorf("Couldn't execute command: %s", err) } } + err := c.Extensions.Spotify.OnMessage(ctx.Message) + if err != nil { + c.Log.Error(err) + } + return nil } @@ -186,7 +191,7 @@ func (c *Client) onPresenceUpdate(ctx *EventPresenceUpdate) error { func (c *Client) onReady(ctx *EventReady) error { //Cache User object - c.User = ctx.User + c.User = &ctx.User //Store session ID c.SessionID = ctx.SessionID @@ -221,7 +226,7 @@ func (c *Client) onTypingStart(ctx *EventTypingStart) error { func (c *Client) onUserUpdate(ctx *EventUserUpdate) error { - c.User = ctx.User + c.User = &ctx.User return nil } diff --git a/src/peach_discord_client/ext_clear.go b/src/peach_discord_client/ext_clear.go deleted file mode 100644 index ddadba5..0000000 --- a/src/peach_discord_client/ext_clear.go +++ /dev/null @@ -1,70 +0,0 @@ -package main - -import ( - "fmt" - "strconv" - "time" -) - -func (c *Client) extClearOnMessage(ctx *EventMessageCreate, args []string) error { - - // Check if user is allowed to delete messages - hasPerm, err := c.hasPermission(ctx.ChannelID, ctx.Author, ctx.Member, permissionManageMessages) - if err != nil { - return err - } - - if !hasPerm { - err = c.handleNoPermission(ctx.Message) - return err - } - - if len(args) > 1 || len(args) == 0 { - c.SendMessage(ctx.ChannelID, NewMessage{"Please provide an amount of messages to clear. Example: `!clear 10`", false, nil}) - return nil - } - - amount, err := strconv.Atoi(args[0]) - if err != nil || amount > 100 || amount < 1 { - c.SendMessage(ctx.ChannelID, NewMessage{"Please provide an amount of messages to clear (Max 100). Example: `!clear 10`", false, nil}) - return nil - } - - err = ctx.Delete(c) - if err != nil { - return err - } - - messages, err := c.GetChannelMessages(ctx.ChannelID, "", "", "", amount) - if err != nil { - return err - } - var messageIDs []string - for _, message := range *messages { - messageIDs = append(messageIDs, message.ID) - } - - pluralS := "" - if amount > 1 { - pluralS = "s" - } - - err = c.BulkDeleteMessages(ctx.ChannelID, messageIDs) - if err != nil { - return err - } - - success, err := c.SendMessage(ctx.ChannelID, NewMessage{fmt.Sprintf("Deleted %s message%s for you :slight_smile:", args[0], pluralS), false, nil}) - if err != nil { - return err - } - - time.Sleep(5 * time.Second) - - err = success.Delete(c) - if err != nil { - return err - } - - return nil -} diff --git a/src/peach_discord_client/ext_about.go b/src/peach_discord_client/ext_internal.go similarity index 66% rename from src/peach_discord_client/ext_about.go rename to src/peach_discord_client/ext_internal.go index 874c151..536723b 100644 --- a/src/peach_discord_client/ext_about.go +++ b/src/peach_discord_client/ext_internal.go @@ -6,7 +6,15 @@ import ( "github.com/hako/durafmt" ) -func (c *Client) extAboutOnMessage(ctx *EventMessageCreate) error { +type extInternal struct { + Bot *Client +} + +func (e *extInternal) Setup(bot *Client) { + e.Bot = bot +} + +func (e *extInternal) About(ctx *EventMessageCreate) error { m := NewMessage{ Embed: Embed{ Description: "Need help with something? Join the [support server](https://discord.gg/HfrjV3ybEs)!", @@ -18,7 +26,7 @@ func (c *Client) extAboutOnMessage(ctx *EventMessageCreate) error { }, { Name: "Uptime", - Value: durafmt.Parse(time.Now().Sub(c.Starttime)).LimitFirstN(2).String(), + Value: durafmt.Parse(time.Now().Sub(e.Bot.Starttime)).LimitFirstN(2).String(), Inline: true, }, }, @@ -29,12 +37,12 @@ func (c *Client) extAboutOnMessage(ctx *EventMessageCreate) error { }, }, } - _, err := c.SendMessage(ctx.ChannelID, m) + _, err := e.Bot.SendMessage(ctx.ChannelID, m) if err != nil { return err } - err = ctx.Delete(c) + err = ctx.Delete(e.Bot) if err != nil { return err } diff --git a/src/peach_discord_client/ext_moderation.go b/src/peach_discord_client/ext_moderation.go new file mode 100644 index 0000000..c678779 --- /dev/null +++ b/src/peach_discord_client/ext_moderation.go @@ -0,0 +1,78 @@ +package main + +import ( + "fmt" + "strconv" + "time" +) + +type extModeration struct { + Bot *Client +} + +func (e *extModeration) Setup(bot *Client) { + e.Bot = bot +} + +func (e *extModeration) Clear(ctx *EventMessageCreate, args []string) error { + + // Check if user is allowed to delete messages + hasPerm, err := e.Bot.hasPermission(ctx.ChannelID, ctx.Author, ctx.Member, permissionManageMessages) + if err != nil { + return err + } + + if !hasPerm { + err = e.Bot.Extensions.handleNoPermission(ctx.Message) + return err + } + + if len(args) > 1 || len(args) == 0 { + e.Bot.SendMessage(ctx.ChannelID, NewMessage{"Please provide an amount of messages to clear. Example: `!clear 10`", false, nil}) + return nil + } + + amount, err := strconv.Atoi(args[0]) + if err != nil || amount > 100 || amount < 1 { + e.Bot.SendMessage(ctx.ChannelID, NewMessage{"Please provide an amount of messages to clear (Max 100). Example: `!clear 10`", false, nil}) + return nil + } + + err = ctx.Delete(e.Bot) + if err != nil { + return err + } + + messages, err := e.Bot.GetChannelMessages(ctx.ChannelID, "", "", "", amount) + if err != nil { + return err + } + var messageIDs []string + for _, message := range *messages { + messageIDs = append(messageIDs, message.ID) + } + + pluralS := "" + if amount > 1 { + pluralS = "s" + } + + err = e.Bot.BulkDeleteMessages(ctx.ChannelID, messageIDs) + if err != nil { + return err + } + + success, err := e.Bot.SendMessage(ctx.ChannelID, NewMessage{fmt.Sprintf("Deleted %s message%s for you :slight_smile:", args[0], pluralS), false, nil}) + if err != nil { + return err + } + + time.Sleep(5 * time.Second) + + err = success.Delete(e.Bot) + if err != nil { + return err + } + + return nil +} diff --git a/src/peach_discord_client/extensions.go b/src/peach_discord_client/extensions.go index c4501ca..754a286 100644 --- a/src/peach_discord_client/extensions.go +++ b/src/peach_discord_client/extensions.go @@ -2,44 +2,63 @@ package main import "time" -var aliasMap = map[string]string{ - "clear": "clear", +type Extensions struct { + Bot *Client "c": "clear", - "about": "about", - "version": "about", - "help": "about", + Moderation extModeration + Internal extInternal + AliasMap map[string]string } -func (c *Client) runOnMessage(invoke string, args []string, ctx *EventMessageCreate) error { +func (x *Extensions) setup(bot *Client, spotifyid, spotifysecret string) error { + + x.Bot = bot + // configure command aliases + x.AliasMap = map[string]string{ + "clear": "clear", + "c": "clear", + "about": "about", + "version": "about", + "help": "about", + } + + // run extension setups + x.Moderation.Setup(x.Bot) + x.Internal.Setup(x.Bot) + + return nil +} + +func (x *Extensions) runCommand(invoke string, args []string, ctx *EventMessageCreate) error { var err error - command := aliasMap[invoke] + command := x.AliasMap[invoke] switch command { case "clear": - err = c.extClearOnMessage(ctx, args) + err = x.Moderation.Clear(ctx, args) case "about": - err = c.extAboutOnMessage(ctx) + err = x.Internal.About(ctx) default: err = nil } return err } -func (c *Client) handleNoPermission(m *Message) error { - sorry, err := c.SendMessage(m.ChannelID, NewMessage{":no_entry: It seems like you do not have the permissions to use this command.", false, nil}) +func (x *Extensions) handleNoPermission(m *Message) error { + sorry, err := x.Bot.SendMessage(m.ChannelID, NewMessage{":no_entry: It seems like you do not have the permissions to use this command.", false, nil}) if err != nil { return err } time.Sleep(5 * time.Second) - err = sorry.Delete(c) + err = sorry.Delete(x.Bot) if err != nil { return err } - err = m.Delete(c) + err = m.Delete(x.Bot) if err != nil { return err } diff --git a/src/peach_discord_client/http.go b/src/peach_discord_client/http.go index 195661e..536f566 100644 --- a/src/peach_discord_client/http.go +++ b/src/peach_discord_client/http.go @@ -15,12 +15,6 @@ var ( } ) -// SetDefaultRequestHeaders adds authorization and content type to request header -func (c *Client) SetDefaultRequestHeaders(req *http.Request) *http.Request { - c.Log.Debugf("Sending %s request to %s", req.Method, req.URL.String()) - return req -} - func addURLArg(query string, key string, value string) string { if query == "" { return fmt.Sprintf("?%s=%s", key, value) @@ -29,6 +23,7 @@ func addURLArg(query string, key string, value string) string { } func (c *Client) request(method string, endpointURL string, routeid string, body []byte, attempt int) (*http.Response, []byte, error) { + c.Log.Debugf("Sending %s request to %s. Ratelimiter routeid: %s, Attempt #%d", method, endpointURL, routeid, attempt) route := c.Ratelimiter.PrepareRoute(routeid) req, err := http.NewRequest(method, endpointURL, bytes.NewBuffer(body)) diff --git a/src/peach_discord_client/main.go b/src/peach_discord_client/main.go index a4661ca..a6435e1 100644 --- a/src/peach_discord_client/main.go +++ b/src/peach_discord_client/main.go @@ -13,6 +13,8 @@ import ( "github.com/sirupsen/logrus" ) +var redactSensitive bool + func createLog() *logrus.Logger { // Set log format, output and level l := logrus.New() @@ -41,9 +43,12 @@ func main() { loglevel := flag.String("log", "info", "declares how verbose the logging should be ('debug', 'info', 'error')") ccURL := flag.String("ccurl", "", "url of the client coordinator") secret := flag.String("secret", "", "secret for communicating with the client coordinator") + redactSensitiveFlag := flag.Bool("redactsensitive", true, "Set to true to sensitive tokens and secrets from logs") flag.Parse() log.Infof("Sharded: %t, LogLevel: %s, coordinator URL: %s", *sharded, *loglevel, *ccURL) + redactSensitive = *redactSensitiveFlag + switch *loglevel { case "debug": log.SetLevel(logrus.DebugLevel) diff --git a/src/peach_launcher/main.go b/src/peach_launcher/main.go index 18ce2bc..14a6736 100644 --- a/src/peach_launcher/main.go +++ b/src/peach_launcher/main.go @@ -35,20 +35,21 @@ type Coordinator struct { type Config struct { Clients struct { - Sharded bool `json:"sharded"` - Shards int `json:"shards"` - Token string `json:"token"` - LogLevel string `json:"loglevel"` - CoordinatorURL string `json:"coordinator"` + Sharded bool `json:"sharded"` + Shards int `json:"shards"` + Token string `json:"token"` + LogLevel string `json:"log_level"` + CoordinatorURL string `json:"coordinator"` } `json:"clients"` Clientcoordinator struct { Launch bool `json:"launch"` Port string `json:"port"` DBCredentials string `json:"dbc"` - CertType string `json:"certtype"` + CertType string `json:"cert_type"` Domain string `json:"domain"` } `json:"clientcoordinator"` - Secret string `json:"secret"` + Secret string `json:"secret"` + RedactSensitive bool `json:"redact_sensitive"` } func (l *Launcher) runClient() { @@ -62,6 +63,7 @@ func (l *Launcher) runClient() { fmt.Sprintf("--token=%s", l.Config.Clients.Token), fmt.Sprintf("--ccurl=%s", shellescape.Quote(l.Config.Clients.CoordinatorURL)), fmt.Sprintf("--secret=%s", l.Config.Secret), + fmt.Sprintf("--redactsensitive=%t", l.Config.RedactSensitive), }, Stdout: os.Stdout, Stderr: os.Stderr, From 1cfec9572655939b998d58f95766885f266305e9 Mon Sep 17 00:00:00 2001 From: jul Date: Sun, 18 Apr 2021 23:30:46 +0200 Subject: [PATCH 4/7] Added Spotify embeds --- go.mod | 4 +- go.sum | 11 +++ launchcfg_example.json | 2 + src/peach_discord_client/ext_spotify.go | 100 ++++++++++++++++++++++++ src/peach_discord_client/extensions.go | 3 +- src/peach_discord_client/main.go | 3 + src/peach_launcher/main.go | 4 + 7 files changed, 125 insertions(+), 2 deletions(-) create mode 100644 src/peach_discord_client/ext_spotify.go diff --git a/go.mod b/go.mod index 024323b..4a0fa08 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,9 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible github.com/pkg/errors v0.9.1 github.com/sirupsen/logrus v1.8.1 - github.com/stretchr/testify v1.5.1 + github.com/stretchr/testify v1.7.0 + github.com/zmb3/spotify v1.1.2 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/text v0.3.5 // indirect gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 ) diff --git a/go.sum b/go.sum index 0174847..9f7d48b 100644 --- a/go.sum +++ b/go.sum @@ -81,6 +81,7 @@ github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4er github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= @@ -324,11 +325,15 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +github.com/zmb3/spotify v1.1.2 h1:X/t7NUhhPuMqga4C2ZfoM3ZSaRanEInSroVst5Ztg2M= +github.com/zmb3/spotify v1.1.2/go.mod h1:GD7AAEMUJVYc2Z7p2a2S0E3/5f/KxM/vOnErNr4j+Tw= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= @@ -381,9 +386,12 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -445,6 +453,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -478,6 +487,8 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/launchcfg_example.json b/launchcfg_example.json index e8bdb9a..f2bbc7a 100644 --- a/launchcfg_example.json +++ b/launchcfg_example.json @@ -5,6 +5,8 @@ "token": "", "log_level": "info", "coordinator": "http://localhost:8080/api/", + "spotify_client_id": "2ih14ihojk21hjko312kjh", + "spotify_client_secret": "3iuhj4i2huuih4oj32uhio423" }, "clientcoordinator": { "launch": true, diff --git a/src/peach_discord_client/ext_spotify.go b/src/peach_discord_client/ext_spotify.go new file mode 100644 index 0000000..cb64214 --- /dev/null +++ b/src/peach_discord_client/ext_spotify.go @@ -0,0 +1,100 @@ +package main + +import ( + "context" + "fmt" + "regexp" + "time" + + "github.com/hako/durafmt" + "github.com/zmb3/spotify" + "golang.org/x/oauth2/clientcredentials" +) + +type extSpotify struct { + Bot *Client + URLRegex *regexp.Regexp + ClientID string + ClientSecret string + SpotifyClient spotify.Client +} + +func (e *extSpotify) Setup(clientid, clientsecret string, bot *Client) error { + e.ClientID = clientid + e.ClientSecret = clientsecret + e.Bot = bot + e.URLRegex = regexp.MustCompile(`https:\/\/open\.spotify\.com\/(album|track|artist)\/(\w{22})`) + + config := &clientcredentials.Config{ + ClientID: clientid, + ClientSecret: clientsecret, + TokenURL: spotify.TokenURL, + } + token, err := config.Token(context.Background()) + if err != nil { + return err + } + e.SpotifyClient = spotify.Authenticator{}.NewClient(token) + + return nil +} + +func (e *extSpotify) OnMessage(ctx *Message) error { + + s := e.URLRegex.FindStringSubmatch(ctx.Content) + if len(s) == 0 { + return nil + } + + spotifytype := s[1] + spotifyid := spotify.ID(s[2]) + + switch spotifytype { + case "album": + _, err := e.SpotifyClient.GetAlbum(spotifyid) + if err != nil { + return err + } + case "track": + track, err := e.SpotifyClient.GetTrack(spotifyid) + if err != nil { + return err + } + + _, err = e.Bot.SendMessage(ctx.ChannelID, NewMessage{ + Embed: Embed{ + Author: EmbedAuthor{ + Name: "Spotify", + IconURL: "https://assets.ifttt.com/images/channels/51464135/icons/large.png", + }, + Thumbnail: EmbedThumbnail{URL: track.Album.Images[0].URL}, + Color: 1947988, + Title: track.Name, + URL: track.ExternalURLs["spotify"], + Description: "by " + track.Artists[0].Name, + Fields: []*EmbedField{ + { + Name: "Release Date", + Value: track.Album.ReleaseDate, + Inline: true, + }, + { + Name: "Length", + Value: durafmt.Parse(time.Duration(track.Duration * int(time.Millisecond))).LimitFirstN(2).String(), + Inline: true, + }, + { + Name: "Album", + Value: fmt.Sprintf("[%s](%s)", track.Album.Name, track.Album.ExternalURLs["spotify"]), + Inline: false, + }, + }, + }, + }) + if err != nil { + return err + } + } + + return nil +} diff --git a/src/peach_discord_client/extensions.go b/src/peach_discord_client/extensions.go index 754a286..b020d6f 100644 --- a/src/peach_discord_client/extensions.go +++ b/src/peach_discord_client/extensions.go @@ -4,7 +4,7 @@ import "time" type Extensions struct { Bot *Client - "c": "clear", + Spotify extSpotify Moderation extModeration Internal extInternal AliasMap map[string]string @@ -23,6 +23,7 @@ func (x *Extensions) setup(bot *Client, spotifyid, spotifysecret string) error { } // run extension setups + x.Spotify.Setup(spotifyid, spotifysecret, x.Bot) x.Moderation.Setup(x.Bot) x.Internal.Setup(x.Bot) diff --git a/src/peach_discord_client/main.go b/src/peach_discord_client/main.go index a6435e1..87db83c 100644 --- a/src/peach_discord_client/main.go +++ b/src/peach_discord_client/main.go @@ -43,6 +43,8 @@ func main() { loglevel := flag.String("log", "info", "declares how verbose the logging should be ('debug', 'info', 'error')") ccURL := flag.String("ccurl", "", "url of the client coordinator") secret := flag.String("secret", "", "secret for communicating with the client coordinator") + spotifyid := flag.String("spotifyid", "", "Spotify client id for spotify extension") + spotifysecret := flag.String("spotifysecret", "", "Spotify client secret for spotify extension") redactSensitiveFlag := flag.Bool("redactsensitive", true, "Set to true to sensitive tokens and secrets from logs") flag.Parse() log.Infof("Sharded: %t, LogLevel: %s, coordinator URL: %s", *sharded, *loglevel, *ccURL) @@ -89,6 +91,7 @@ func main() { c.httpClient = &http.Client{} c.GuildCache = cache.New(120*time.Minute, 5*time.Minute) c.ChannelCache = cache.New(120*time.Minute, 5*time.Minute) + c.Extensions.setup(c, *spotifyid, *spotifysecret) err = c.Run() if err != nil { diff --git a/src/peach_launcher/main.go b/src/peach_launcher/main.go index 14a6736..8663c1a 100644 --- a/src/peach_launcher/main.go +++ b/src/peach_launcher/main.go @@ -40,6 +40,8 @@ type Config struct { Token string `json:"token"` LogLevel string `json:"log_level"` CoordinatorURL string `json:"coordinator"` + SpotifyClientID string `json:"spotify_client_id"` + SpotifyClientSecret string `json:"spotify_client_secret"` } `json:"clients"` Clientcoordinator struct { Launch bool `json:"launch"` @@ -63,6 +65,8 @@ func (l *Launcher) runClient() { fmt.Sprintf("--token=%s", l.Config.Clients.Token), fmt.Sprintf("--ccurl=%s", shellescape.Quote(l.Config.Clients.CoordinatorURL)), fmt.Sprintf("--secret=%s", l.Config.Secret), + fmt.Sprintf("--spotifyid=%s", l.Config.Clients.SpotifyClientID), + fmt.Sprintf("--spotifysecret=%s", l.Config.Clients.SpotifyClientSecret), fmt.Sprintf("--redactsensitive=%t", l.Config.RedactSensitive), }, Stdout: os.Stdout, From 48b894dcb1557ed4175ca84bed67f154efc29cb4 Mon Sep 17 00:00:00 2001 From: jul Date: Mon, 19 Apr 2021 10:55:19 +0200 Subject: [PATCH 5/7] Added some helpers to deal with slices --- src/peach_discord_client/helpers.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/peach_discord_client/helpers.go diff --git a/src/peach_discord_client/helpers.go b/src/peach_discord_client/helpers.go new file mode 100644 index 0000000..7bd5733 --- /dev/null +++ b/src/peach_discord_client/helpers.go @@ -0,0 +1,17 @@ +package main + +import "sort" + +func sliceContains(s []string, searchterm string) bool { + sort.Strings(s) + i := sort.SearchStrings(s, searchterm) + return i < len(s) && s[i] == searchterm +} + +func sliceRemove(s []string, str string) []string { + if sliceContains(s, str) { + i := sort.SearchStrings(s, str) + s = append(s[:i], s[i+1:]...) + } + return s +} From 67027ff4f9e4ab7d6ebaec6eca7a382553e4038d Mon Sep 17 00:00:00 2001 From: jul Date: Mon, 19 Apr 2021 11:40:07 +0200 Subject: [PATCH 6/7] Fix wrong method in CreateReaction --- src/peach_discord_client/http_channel_methods.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/peach_discord_client/http_channel_methods.go b/src/peach_discord_client/http_channel_methods.go index 9b07fb0..e0fbb3e 100644 --- a/src/peach_discord_client/http_channel_methods.go +++ b/src/peach_discord_client/http_channel_methods.go @@ -227,13 +227,13 @@ func (c *Client) CrosspostMessage(channelID, messageID string) (*Message, error) return message, nil } -func (c *Client) CreateReaction(channelID, messageID, emoji string, customEmoji Emoji) error { +func (c *Client) CreateReaction(channelID, messageID, emoji string, customEmoji *Emoji) error { if emoji == "" { emoji = customEmoji.Name + ":" + customEmoji.ID } - resp, _, err := c.Request(http.MethodPost, EndpointMessageReaction(channelID, messageID, url.QueryEscape(emoji), "@me"), nil) + resp, _, err := c.Request(http.MethodPut, EndpointMessageReaction(channelID, messageID, url.QueryEscape(emoji), "@me"), nil) if err != nil { return err } @@ -247,7 +247,7 @@ func (c *Client) CreateReaction(channelID, messageID, emoji string, customEmoji return nil } -func (c *Client) DeleteBotReaction(channelID, messageID, emoji string, customEmoji Emoji) error { +func (c *Client) DeleteBotReaction(channelID, messageID, emoji string, customEmoji *Emoji) error { if emoji == "" { emoji = customEmoji.Name + ":" + customEmoji.ID @@ -267,7 +267,7 @@ func (c *Client) DeleteBotReaction(channelID, messageID, emoji string, customEmo return nil } -func (c *Client) DeleteUserReaction(channelID, messageID, emoji string, customEmoji Emoji, userID string) error { +func (c *Client) DeleteUserReaction(channelID, messageID, emoji string, customEmoji *Emoji, userID string) error { if emoji == "" { emoji = customEmoji.Name + ":" + customEmoji.ID @@ -287,7 +287,7 @@ func (c *Client) DeleteUserReaction(channelID, messageID, emoji string, customEm return nil } -func (c *Client) GetReactions(channelID, messageID, emoji string, customEmoji Emoji) (*[]User, error) { +func (c *Client) GetReactions(channelID, messageID, emoji string, customEmoji *Emoji) (*[]User, error) { if emoji == "" { emoji = customEmoji.Name + ":" + customEmoji.ID @@ -332,7 +332,7 @@ func (c *Client) DeleteAllReactions(channelID, messageID string) error { return nil } -func (c *Client) DeleteEmojiReactions(channelID, messageID, emoji string, customEmoji Emoji) error { +func (c *Client) DeleteEmojiReactions(channelID, messageID, emoji string, customEmoji *Emoji) error { if emoji == "" { emoji = customEmoji.Name + ":" + customEmoji.ID From e89a6f495565afa1858da81948c734dbd869b54e Mon Sep 17 00:00:00 2001 From: jul Date: Mon, 19 Apr 2021 11:40:44 +0200 Subject: [PATCH 7/7] Spotify: Added Albums and Artists + Deletion of Message --- go.mod | 2 +- src/peach_discord_client/eventhandlers.go | 5 + src/peach_discord_client/ext_spotify.go | 130 +++++++++++++++++++++- 3 files changed, 134 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4a0fa08..d302717 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,6 @@ require ( github.com/stretchr/testify v1.7.0 github.com/zmb3/spotify v1.1.2 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d - golang.org/x/text v0.3.5 // indirect + golang.org/x/text v0.3.5 gopkg.in/alessio/shellescape.v1 v1.0.0-20170105083845-52074bc9df61 ) diff --git a/src/peach_discord_client/eventhandlers.go b/src/peach_discord_client/eventhandlers.go index cf5fed7..7713a78 100644 --- a/src/peach_discord_client/eventhandlers.go +++ b/src/peach_discord_client/eventhandlers.go @@ -165,6 +165,11 @@ func (c *Client) onMessageDeleteBulk(ctx *EventMessageDeleteBulk) error { } func (c *Client) onMessageReactionAdd(ctx *EventMessageReactionAdd) error { + if ctx.UserID == c.User.ID { + return nil + } + + c.Extensions.Spotify.OnReact(ctx) return nil } diff --git a/src/peach_discord_client/ext_spotify.go b/src/peach_discord_client/ext_spotify.go index cb64214..ce5688e 100644 --- a/src/peach_discord_client/ext_spotify.go +++ b/src/peach_discord_client/ext_spotify.go @@ -4,11 +4,15 @@ import ( "context" "fmt" "regexp" + "strings" + "sync" "time" "github.com/hako/durafmt" "github.com/zmb3/spotify" "golang.org/x/oauth2/clientcredentials" + "golang.org/x/text/language" + "golang.org/x/text/message" ) type extSpotify struct { @@ -17,6 +21,8 @@ type extSpotify struct { ClientID string ClientSecret string SpotifyClient spotify.Client + Responses []string + sync.Mutex } func (e *extSpotify) Setup(clientid, clientsecret string, bot *Client) error { @@ -49,19 +55,61 @@ func (e *extSpotify) OnMessage(ctx *Message) error { spotifytype := s[1] spotifyid := spotify.ID(s[2]) + var msg *Message = nil + switch spotifytype { case "album": - _, err := e.SpotifyClient.GetAlbum(spotifyid) + album, err := e.SpotifyClient.GetAlbum(spotifyid) + if err != nil { + return err + } + + var playtime int + for _, track := range album.Tracks.Tracks { + playtime += track.Duration + } + + msg, err = e.Bot.SendMessage(ctx.ChannelID, NewMessage{ + Embed: Embed{ + Author: EmbedAuthor{ + Name: "Spotify", + IconURL: "https://assets.ifttt.com/images/channels/51464135/icons/large.png", + }, + Thumbnail: EmbedThumbnail{URL: album.Images[0].URL}, + Color: 1947988, + Title: album.Name, + URL: album.ExternalURLs["spotify"], + Description: "by " + album.Artists[0].Name, + Fields: []*EmbedField{ + { + Name: "Release Date", + Value: album.ReleaseDate, + Inline: true, + }, + { + Name: "Tracks", + Value: fmt.Sprint(album.Tracks.Total), + Inline: true, + }, + { + Name: "Length", + Value: durafmt.Parse(time.Duration(playtime * int(time.Millisecond))).LimitFirstN(2).String(), + Inline: false, + }, + }, + }, + }) if err != nil { return err } + case "track": track, err := e.SpotifyClient.GetTrack(spotifyid) if err != nil { return err } - _, err = e.Bot.SendMessage(ctx.ChannelID, NewMessage{ + msg, err = e.Bot.SendMessage(ctx.ChannelID, NewMessage{ Embed: Embed{ Author: EmbedAuthor{ Name: "Spotify", @@ -94,7 +142,85 @@ func (e *extSpotify) OnMessage(ctx *Message) error { if err != nil { return err } + + case "artist": + artist, err := e.SpotifyClient.GetArtist(spotifyid) + if err != nil { + return err + } + + p := message.NewPrinter(language.English) + + var genreLabel string = "Genre" + var genre string = "None" + if len(artist.Genres) > 0 { + if len(artist.Genres) >= 3 { + genreLabel = "Genres" + genre = fmt.Sprintf("%s, %s, %s", artist.Genres[0], artist.Genres[1], artist.Genres[2]) + } else { + genre = fmt.Sprintf("%s", artist.Genres[0]) + } + } + + popularity := artist.Popularity / 20 + + msg, err = e.Bot.SendMessage(ctx.ChannelID, NewMessage{ + Embed: Embed{ + Author: EmbedAuthor{ + Name: "Spotify", + IconURL: "https://assets.ifttt.com/images/channels/51464135/icons/large.png", + }, + Thumbnail: EmbedThumbnail{URL: artist.Images[0].URL}, + Color: 1947988, + Title: artist.Name, + URL: artist.ExternalURLs["spotify"], + Fields: []*EmbedField{ + { + Name: "Followers", + Value: p.Sprint(artist.Followers.Count), + Inline: true, + }, + { + Name: "Popularity", + Value: strings.Repeat("★", popularity) + strings.Repeat("☆", 5-popularity), + Inline: true, + }, + { + Name: genreLabel, + Value: genre, + Inline: false, + }, + }, + }, + }) + if err != nil { + return err + } + + } + + if msg == nil { + return nil } + err := e.Bot.CreateReaction(msg.ChannelID, msg.ID, "🗑", nil) + if err != nil { + return err + } + + e.Lock() + e.Responses = append(e.Responses, msg.ID) + e.Unlock() + return nil } + +func (e *extSpotify) OnReact(ctx *EventMessageReactionAdd) { + e.Bot.Log.Debug(ctx.Emoji.Name) + if sliceContains(e.Responses, ctx.MessageID) && ctx.Emoji.Name == "🗑" { + e.Bot.DeleteMessage(ctx.ChannelID, ctx.MessageID) + e.Lock() + e.Responses = sliceRemove(e.Responses, ctx.MessageID) + e.Unlock() + } +}