diff --git a/discord/delete.go b/discord/delete.go index a60548e..bb2b986 100644 --- a/discord/delete.go +++ b/discord/delete.go @@ -96,13 +96,8 @@ func DeleteSmart( var messageIDsToBulkDelete []string // nolint: prealloc for _, message := range messages { - createdAt, err := message.Timestamp.Parse() - if err != nil { - return err - } - // delete one by one, if older than 14 Days - if time.Since(createdAt) > 24*time.Hour*14 { + if time.Since(message.Timestamp) > 24*time.Hour*14 { err = Delete( redis, session, diff --git a/discord/permissions.go b/discord/permissions.go index 4b17fdc..3f3ec15 100644 --- a/discord/permissions.go +++ b/discord/permissions.go @@ -7,7 +7,7 @@ import ( // UserHasPermission returns true if the User has all of th egiven permissions in the given channel func UserHasPermission( - state interfaces.State, userID, channelID string, firstPermission int, permissions ...int, + state interfaces.State, userID, channelID string, firstPermission int64, permissions ...int64, ) bool { if userID == "" || channelID == "" { return false @@ -23,7 +23,6 @@ func UserHasPermission( } for _, permission := range append(permissions, firstPermission) { - if userChannelPermissions&permission != permission { return false } @@ -34,7 +33,7 @@ func UserHasPermission( // UserHasPermissionOr returns true if the User has any of the given permissions in the given channel func UserHasPermissionOr( - state *state.State, userID, channelID string, firstPermission int, permissions ...int, + state *state.State, userID, channelID string, firstPermission int64, permissions ...int64, ) bool { if userID == "" || channelID == "" { return false @@ -50,7 +49,6 @@ func UserHasPermissionOr( } for _, permission := range append(permissions, firstPermission) { - if userChannelPermissions&permission == permission { return true } diff --git a/events/generating.go b/events/generating.go index 1ea2826..1593fb5 100644 --- a/events/generating.go +++ b/events/generating.go @@ -351,6 +351,9 @@ func GenerateEventFromDiscordgoEvent( event.GuildID = t.GuildID event.ChannelID = t.ChannelID event.CacheKey, err = hash(string(event.Type) + t.GuildID + t.ChannelID + t.Token) + if err != nil { + return nil, expiration, err + } case *discordgo.TypingStart, *discordgo.Ready, *discordgo.Event, *discordgo.Connect: // ignored events return nil, expiration, nil diff --git a/events/keys.go b/events/keys.go index c51fd2c..abf2bf4 100644 --- a/events/keys.go +++ b/events/keys.go @@ -30,17 +30,10 @@ func presenceUpdateKey(event *discordgo.PresenceUpdate) string { key += event.User.Avatar } - key += event.Nick key += string(event.Status) - key += strings.Join(event.Roles, "") - - if event.Game != nil { - key += event.Game.State - key += strconv.Itoa(int(event.Game.Type)) - key += event.Game.Name - key += event.Game.URL - key += event.Game.ApplicationID - key += event.Game.Details + + if event.Since != nil { + key += strconv.Itoa(*event.Since) } return key @@ -59,8 +52,6 @@ func guildKey(event *discordgo.Guild) string { event.AfkChannelID + strconv.Itoa(event.AfkTimeout) + strconv.Itoa(int(event.DefaultMessageNotifications)) + - event.EmbedChannelID + - strconv.FormatBool(event.EmbedEnabled) + strconv.Itoa(int(event.ExplicitContentFilter)) + strings.Join(event.Features, "") + strconv.Itoa(int(event.MfaLevel)) + @@ -75,8 +66,8 @@ func memberKey(event *discordgo.Member) string { key := event.GuildID + strings.Join(event.Roles, "") + event.Nick + - string(event.PremiumSince) + - string(event.JoinedAt) + + event.PremiumSince.String() + + event.JoinedAt.String() + strconv.FormatBool(event.Deaf) + strconv.FormatBool(event.Mute) @@ -104,12 +95,11 @@ func roleKey(event *discordgo.Role) string { return event.ID + strconv.Itoa(event.Color) + event.Name + - strconv.Itoa(event.Permissions) + + strconv.FormatInt(event.Permissions, 10) + strconv.FormatBool(event.Hoist) + strconv.FormatBool(event.Managed) + strconv.FormatBool(event.Mentionable) + strconv.Itoa(event.Position) - } func emojisUpdateKey(event *discordgo.GuildEmojisUpdate) string { @@ -164,15 +154,12 @@ func channelKey(event *discordgo.Channel) string { } func permissionOverwriteKey(event *discordgo.PermissionOverwrite) string { - return event.ID + - event.Type + - strconv.Itoa(event.Allow) + - strconv.Itoa(event.Deny) + return event.ID + strconv.Itoa(int(event.Type)) + + strconv.FormatInt(event.Allow, 10) + strconv.FormatInt(event.Deny, 10) } func messageUpdateKey(event *discordgo.MessageUpdate) string { - return event.ID + - string(event.EditedTimestamp) + return event.ID + event.EditedTimestamp.String() } func messageReactionKey(event *discordgo.MessageReaction) string { diff --git a/external/iexcloud/stocks.go b/external/iexcloud/stocks.go index 47ed495..59d302e 100644 --- a/external/iexcloud/stocks.go +++ b/external/iexcloud/stocks.go @@ -61,7 +61,7 @@ func (iex *IEX) StocksPrice(ctx context.Context, symbol string) (float64, error) return 0, err } - return strconv.ParseFloat(string(raw), 10) + return strconv.ParseFloat(string(raw), 64) } // OHLC models the open, high, low, close for a stock. diff --git a/go.mod b/go.mod index c193204..5bb1889 100644 --- a/go.mod +++ b/go.mod @@ -20,6 +20,7 @@ require ( go.uber.org/zap v1.10.0 gocloud.dev v0.20.0 gocloud.dev/pubsub/rabbitpubsub v0.20.0 + golang.org/x/text v0.3.6 mvdan.cc/xurls/v2 v2.0.0 ) @@ -59,7 +60,6 @@ require ( golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect golang.org/x/sync v0.0.0-20201008141435-b3e1573b7520 // indirect golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d // indirect - golang.org/x/text v0.3.6 // indirect golang.org/x/tools v0.0.0-20200608174601-1b747fd94509 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/api v0.26.0 // indirect diff --git a/interfaces/state.go b/interfaces/state.go index 2f0b17b..a1d0a64 100644 --- a/interfaces/state.go +++ b/interfaces/state.go @@ -10,6 +10,6 @@ type State interface { Member(guildID, userID string) (member *discordgo.Member, err error) Role(guildID, roleID string) (role *discordgo.Role, err error) - UserPermissions(userID, guildID string) (apermissions int, err error) - UserChannelPermissions(userID, channelID string) (apermissions int, err error) + UserPermissions(userID, guildID string) (apermissions int64, err error) + UserChannelPermissions(userID, channelID string) (apermissions int64, err error) } diff --git a/localization/funcs.go b/localization/funcs.go index 82f4ad0..2041b89 100644 --- a/localization/funcs.go +++ b/localization/funcs.go @@ -14,10 +14,11 @@ import ( "github.com/Masterminds/sprig" polr "github.com/Seklfreak/polr-go" "github.com/bwmarrin/discordgo" + humanize "github.com/dustin/go-humanize" "github.com/pkg/errors" + "golang.org/x/text/cases" + "golang.org/x/text/language" "mvdan.cc/xurls/v2" - - humanize "github.com/dustin/go-humanize" ) const nonBreakingSpace = "\u00A0" @@ -30,7 +31,6 @@ var ( ) func init() { - if os.Getenv("POLR_BASE_URL") != "" && os.Getenv("POLR_API_KEY") != "" { @@ -76,184 +76,181 @@ func getTranslationFuncs() map[string]interface{} { return methods } -var ( - // the additional functions to use in the template engine - translationFuncs = template.FuncMap{ - - // ToUpper returns a uppercase version of a string - // example: {{ToUpper foobar}} => FOOBAR - "ToUpper": strings.ToUpper, - - // ToLower returns a lowercase version of a string - // example: {{ToLower FOOBAR}} => foobar - "ToLower": strings.ToLower, - - // Title returns a titleised version of a string - // example: {{Title foo bar}} => Foo Bar - "Title": strings.Title, - - // Replace replaces - "Replace": strings.Replace, - - // Escape returns a text to be escaped to be used in Discord Embeds - // example: {{Escape "`"}} => \` - "Escape": func(text string) string { - // TODO: same as discord.EscapeDiscordStrict, cannot import here due to import cycle, fix this - text = strings.Replace(text, "`", "\\`", -1) - text = strings.Replace(text, "*", "\\*", -1) - text = strings.Replace(text, "_", "\\_", -1) - text = strings.Replace(text, "~", "\\~", -1) - text = strings.Replace(text, "#", "\\#", -1) - text = strings.Replace(text, "@", "\\@", -1) - - return text - }, - - // EscapeLink returns a link to be escaped to be used in Discord Embeds - // example: {{EscapeLink "https://example.org/A+(B)"}} => https://example.org/A+%28B%29 - "EscapeLink": func(text string) string { - text = strings.Replace(text, ")", "%29", -1) - text = strings.Replace(text, "(", "%28", -1) - return text - }, - - // HumanizeNumber adds commas after every three orders of magnitude - "HumanizeNumber": func(number int) string { - return humanize.Comma(int64(number)) - }, - - // HumanizeNumber64 adds commas after every three orders of magnitude - "HumanizeNumber64": humanize.Comma, - - // HumanizeTime formats a time into a relative string, eg 3 days ago - "HumanizeTime": func(then time.Time) string { - if then.IsZero() { - return "Never" - } - - return humanize.Time(then) - }, - - "HumanizeBytes": func(size int64) string { - return humanize.Bytes(uint64(size)) - }, - - "ToString": func(value interface{}) (string, error) { - switch v := value.(type) { - case string: - return v, nil - case int: - return strconv.Itoa(v), nil - default: - return "", errors.New("unable to convert into string") - } - }, - - "Shorten": func(value string) string { - shortenedLinkCacheLock.Lock() - defer shortenedLinkCacheLock.Unlock() - - if shortenedLinkCache[value] != "" { - return shortenedLinkCache[value] - } - - if polrClient == nil { - return value - } - - shortened, err := polrClient.Shorten(value, "", false) - if err != nil { - return value - } - - shortenedLinkCache[value] = shortened - - return shortened - }, - - "Contains": strings.Contains, - - "Join": strings.Join, - - "MessageLink": func(message *discordgo.Message) string { - if message == nil { - return "" - } - - guildID := message.GuildID - if message.GuildID == "" { - guildID = "@me" - } - - return fmt.Sprintf( - "https://discordapp.com/channels/%s/%s/%s", - guildID, - message.ChannelID, - message.ID, - ) - }, - - "TimeIsZero": func(t time.Time) bool { - return t.IsZero() - }, - - "TimeFormat": func(t time.Time, zone *time.Location) string { - if t.IsZero() { - return "`Never`" - } - - return fmt.Sprintf( - "`%s` (%s)", - t.In(zone).Format(time.RFC1123), - humanize.Time(t), - ) - }, - - "TimeFormatShort": func(t time.Time, zone *time.Location) string { - if t.IsZero() { - return "`Never`" - } - - return fmt.Sprintf( - "`%s`", - t.In(zone).Format(time.RFC822), - ) - }, - - "RandIntn": func(n int) int { - return rand.Intn(n) - }, - - "HideEmbeds": func(text string) string { - indexes := xurlsStrict.FindAllStringIndex(text, -1) - - var index []int - for i := len(indexes) - 1; i >= 0; i-- { - index = indexes[i] - text = text[:index[0]] + "<" + text[index[0]:index[1]] + ">" + text[index[1]:] - } - - return text - }, - - "ReplaceWithNonBreakingSpace": func(input string) string { - return strings.Replace(input, " ", nonBreakingSpace, -1) - }, - - "NonBreakingSpace": func() string { - return nonBreakingSpace - }, - - "ParseTimestamp": func(i int64) time.Time { - return time.Unix(i, 0) - }, - - "QuoteText": func(input string) string { - return "> " + strings.Replace(input, "\n", "\n> ", -1) - }, - - "Pad": func(text string, length int) string { - return fmt.Sprintf("%-"+strconv.Itoa(length)+"v", text) - }, - } -) +// the additional functions to use in the template engine +var translationFuncs = template.FuncMap{ + // ToUpper returns a uppercase version of a string + // example: {{ToUpper foobar}} => FOOBAR + "ToUpper": strings.ToUpper, + + // ToLower returns a lowercase version of a string + // example: {{ToLower FOOBAR}} => foobar + "ToLower": strings.ToLower, + + // Title returns a titleised version of a string + // example: {{Title foo bar}} => Foo Bar + "Title": cases.Title(language.English).String, + + // Replace replaces + "Replace": strings.Replace, + + // Escape returns a text to be escaped to be used in Discord Embeds + // example: {{Escape "`"}} => \` + "Escape": func(text string) string { + // TODO: same as discord.EscapeDiscordStrict, cannot import here due to import cycle, fix this + text = strings.Replace(text, "`", "\\`", -1) + text = strings.Replace(text, "*", "\\*", -1) + text = strings.Replace(text, "_", "\\_", -1) + text = strings.Replace(text, "~", "\\~", -1) + text = strings.Replace(text, "#", "\\#", -1) + text = strings.Replace(text, "@", "\\@", -1) + + return text + }, + + // EscapeLink returns a link to be escaped to be used in Discord Embeds + // example: {{EscapeLink "https://example.org/A+(B)"}} => https://example.org/A+%28B%29 + "EscapeLink": func(text string) string { + text = strings.Replace(text, ")", "%29", -1) + text = strings.Replace(text, "(", "%28", -1) + return text + }, + + // HumanizeNumber adds commas after every three orders of magnitude + "HumanizeNumber": func(number int) string { + return humanize.Comma(int64(number)) + }, + + // HumanizeNumber64 adds commas after every three orders of magnitude + "HumanizeNumber64": humanize.Comma, + + // HumanizeTime formats a time into a relative string, eg 3 days ago + "HumanizeTime": func(then time.Time) string { + if then.IsZero() { + return "Never" + } + + return humanize.Time(then) + }, + + "HumanizeBytes": func(size int64) string { + return humanize.Bytes(uint64(size)) + }, + + "ToString": func(value interface{}) (string, error) { + switch v := value.(type) { + case string: + return v, nil + case int: + return strconv.Itoa(v), nil + default: + return "", errors.New("unable to convert into string") + } + }, + + "Shorten": func(value string) string { + shortenedLinkCacheLock.Lock() + defer shortenedLinkCacheLock.Unlock() + + if shortenedLinkCache[value] != "" { + return shortenedLinkCache[value] + } + + if polrClient == nil { + return value + } + + shortened, err := polrClient.Shorten(value, "", false) + if err != nil { + return value + } + + shortenedLinkCache[value] = shortened + + return shortened + }, + + "Contains": strings.Contains, + + "Join": strings.Join, + + "MessageLink": func(message *discordgo.Message) string { + if message == nil { + return "" + } + + guildID := message.GuildID + if message.GuildID == "" { + guildID = "@me" + } + + return fmt.Sprintf( + "https://discordapp.com/channels/%s/%s/%s", + guildID, + message.ChannelID, + message.ID, + ) + }, + + "TimeIsZero": func(t time.Time) bool { + return t.IsZero() + }, + + "TimeFormat": func(t time.Time, zone *time.Location) string { + if t.IsZero() { + return "`Never`" + } + + return fmt.Sprintf( + "`%s` (%s)", + t.In(zone).Format(time.RFC1123), + humanize.Time(t), + ) + }, + + "TimeFormatShort": func(t time.Time, zone *time.Location) string { + if t.IsZero() { + return "`Never`" + } + + return fmt.Sprintf( + "`%s`", + t.In(zone).Format(time.RFC822), + ) + }, + + "RandIntn": func(n int) int { + return rand.Intn(n) + }, + + "HideEmbeds": func(text string) string { + indexes := xurlsStrict.FindAllStringIndex(text, -1) + + var index []int + for i := len(indexes) - 1; i >= 0; i-- { + index = indexes[i] + text = text[:index[0]] + "<" + text[index[0]:index[1]] + ">" + text[index[1]:] + } + + return text + }, + + "ReplaceWithNonBreakingSpace": func(input string) string { + return strings.Replace(input, " ", nonBreakingSpace, -1) + }, + + "NonBreakingSpace": func() string { + return nonBreakingSpace + }, + + "ParseTimestamp": func(i int64) time.Time { + return time.Unix(i, 0) + }, + + "QuoteText": func(input string) string { + return "> " + strings.Replace(input, "\n", "\n> ", -1) + }, + + "Pad": func(text string, length int) string { + return fmt.Sprintf("%-"+strconv.Itoa(length)+"v", text) + }, +} diff --git a/logging/zap_hook_discord.go b/logging/zap_hook_discord.go index f9ae784..c67944d 100644 --- a/logging/zap_hook_discord.go +++ b/logging/zap_hook_discord.go @@ -9,6 +9,8 @@ import ( "github.com/bwmarrin/discordgo" "go.uber.org/zap/zapcore" + "golang.org/x/text/cases" + "golang.org/x/text/language" ) // NewZapHookDiscord sends Zap log messages to a Discord Webhook @@ -27,7 +29,7 @@ func NewZapHookDiscord(serviceName, webhookURL string, client *http.Client) func } body, err := json.Marshal(discordgo.WebhookParams{ - Username: strings.Title(serviceName), + Username: cases.Title(language.English).String(serviceName), Embeds: []*discordgo.MessageEmbed{ { Title: "Logging message: " + strings.ToUpper(entry.Level.String()), @@ -55,5 +57,4 @@ func NewZapHookDiscord(serviceName, webhookURL string, client *http.Client) func ) return err } - } diff --git a/permissions/discord_perms.go b/permissions/discord_perms.go index 78d30ed..1539771 100644 --- a/permissions/discord_perms.go +++ b/permissions/discord_perms.go @@ -9,10 +9,10 @@ import ( type Discord struct { name string - id int + id int64 } -func NewDiscordPermission(name string, id int) *Discord { +func NewDiscordPermission(name string, id int64) *Discord { return &Discord{ name: name, id: id, diff --git a/state/bot.go b/state/bot.go index 78ed493..6e38cd8 100644 --- a/state/bot.go +++ b/state/bot.go @@ -9,7 +9,7 @@ import ( // if possible one should use BotForChannel with the specific permissions func (s *State) BotForGuild( guildID string, - permissions ...int, + permissions ...int64, ) ( botID string, err error, @@ -20,7 +20,7 @@ func (s *State) BotForGuild( } var permissionsMatch bool - var botPermissions int + var botPermissions int64 for _, botID := range botIDs { botPermissions, err = s.UserPermissions( diff --git a/state/getters.go b/state/getters.go index 97f39d5..97abc19 100644 --- a/state/getters.go +++ b/state/getters.go @@ -319,7 +319,7 @@ func (s *State) GuildInvitesWithRefresh(guildID string, session *discordgo.Sessi } // UserChannelPermissions returns the permission of a user in a channel -func (s *State) UserChannelPermissions(userID, channelID string) (apermissions int, err error) { +func (s *State) UserChannelPermissions(userID, channelID string) (apermissions int64, err error) { var channel *discordgo.Channel channel, err = s.Channel(channelID) if err != nil { @@ -353,7 +353,7 @@ func (s *State) UserChannelPermissions(userID, channelID string) (apermissions i } // UserPermissions returns the permissions of a user in a guild -func (s *State) UserPermissions(userID, guildID string) (apermissions int, err error) { +func (s *State) UserPermissions(userID, guildID string) (apermissions int64, err error) { var guild *discordgo.Guild guild, err = s.guildLight(guildID) if err != nil { @@ -412,7 +412,7 @@ func memberChannelPermissions( guildRoles []*discordgo.Role, channel *discordgo.Channel, member *discordgo.Member, -) (apermissions int) { +) (apermissions int64) { userID := member.User.ID if userID == guildOwnerID { @@ -449,13 +449,12 @@ func memberChannelPermissions( } } - denies := 0 - allows := 0 + var denies, allows int64 // Member overwrites can override role overrides, so do two passes for _, overwrite := range channel.PermissionOverwrites { for _, roleID := range member.Roles { - if overwrite.Type == "role" && roleID == overwrite.ID { + if overwrite.Type == discordgo.PermissionOverwriteTypeRole && roleID == overwrite.ID { denies |= overwrite.Deny allows |= overwrite.Allow break @@ -467,7 +466,7 @@ func memberChannelPermissions( apermissions |= allows for _, overwrite := range channel.PermissionOverwrites { - if overwrite.Type == "member" && overwrite.ID == userID { + if overwrite.Type == discordgo.PermissionOverwriteTypeMember && overwrite.ID == userID { apermissions &= ^overwrite.Deny apermissions |= overwrite.Allow break @@ -483,7 +482,7 @@ func memberChannelPermissions( // memberPermissions calculates the permissions for a member in a guild // Source: https://github.com/bwmarrin/discordgo/blob/develop/restapi.go#L503 -func memberPermissions(guildID, guildOwnerID string, guildRoles []*discordgo.Role, member *discordgo.Member) (apermissions int) { +func memberPermissions(guildID, guildOwnerID string, guildRoles []*discordgo.Role, member *discordgo.Member) (apermissions int64) { userID := member.User.ID if userID == guildOwnerID { diff --git a/state/handlers.go b/state/handlers.go index a3f7596..cf44870 100644 --- a/state/handlers.go +++ b/state/handlers.go @@ -7,9 +7,7 @@ import ( "go.uber.org/zap" ) -var ( - messagesLimit = 10 -) +var messagesLimit = 10 func (s *State) onReady(_ *discordgo.Session, ready *discordgo.Ready) (err error) { // fmt.Println("running onReady", ready.User.ID) @@ -181,7 +179,7 @@ func (s *State) memberAdd(session *discordgo.Session, member *discordgo.Member, previousMember, err := s.Member(member.GuildID, member.User.ID) if err == nil { // carry over previous member fields if set - if member.JoinedAt == "" { + if member.JoinedAt.IsZero() { member.JoinedAt = previousMember.JoinedAt } } @@ -730,25 +728,16 @@ func (s *State) SharedStateEventHandler(session *discordgo.Session, i interface{ // Member not found; this is a user coming online previousMember = &discordgo.Member{ GuildID: t.GuildID, - Nick: t.Nick, User: t.User, - Roles: t.Roles, } - } else { - if (t.Nick == "" || t.Nick == previousMember.Nick) && - (t.User.Username == "" || t.User.Username == previousMember.User.Username) && + if (t.User.Username == "" || t.User.Username == previousMember.User.Username) && (t.User.Discriminator == "" || t.User.Discriminator == previousMember.User.Discriminator) && - (t.User.Avatar == "" || t.User.Avatar == previousMember.User.Avatar) && - sliceMatches(t.Roles, previousMember.Roles) { + (t.User.Avatar == "" || t.User.Avatar == previousMember.User.Avatar) { // fmt.Println("skipped presenceUpdate, no changes") return nil } - if t.Nick != "" { - previousMember.Nick = t.Nick - } - if t.User.Username != "" { previousMember.User.Username = t.User.Username } @@ -760,9 +749,6 @@ func (s *State) SharedStateEventHandler(session *discordgo.Session, i interface{ if t.User.Avatar != "" { previousMember.User.Avatar = t.User.Avatar } - - // PresenceUpdates always contain a list of roles, so there's no need to check for an empty list here - previousMember.Roles = t.Roles } err = s.memberAdd(session, previousMember, false) @@ -802,20 +788,6 @@ func (s *State) SharedStateEventHandler(session *discordgo.Session, i interface{ return nil } -func sliceMatches(a, b []string) bool { - if len(a) != len(b) { - return false - } - - for _, itemA := range a { - if !sliceContains(itemA, b) { - return false - } - } - - return true -} - func sliceContains(needle string, haystack []string) bool { for _, item := range haystack { if item == needle { diff --git a/state/manual_sync.go b/state/manual_sync.go index b86ddfe..e97e213 100644 --- a/state/manual_sync.go +++ b/state/manual_sync.go @@ -43,7 +43,7 @@ func (s *State) initGuildBans(session *discordgo.Session, guildID string) (err e } // cache new guild bans - bans, err := session.GuildBans(guildID) + bans, err := session.GuildBans(guildID, 1000, "", "") if err != nil { return err }