From 7d5c6f6ea9d4c0dc6f21d269d1b7dd1c32f0964b Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Fri, 24 Nov 2023 22:28:13 +0100 Subject: [PATCH 01/10] improved logging --- config.yaml | 4 +++- config/config.go | 4 +++- data/lang/lang.go | 3 ++- database/connection.go | 3 ++- event/commands.go | 1 - event/event.go | 3 +++ event/scheduledTriggers.go | 23 +++++++++++++++++------ event/voiceUpdates.go | 1 - main.go | 4 +++- webserver/logger.go | 1 - webserver/main.go | 4 +++- webserver/resonse404.go | 1 - webserver/youtube/api.go | 1 - webserver/youtube/discord.go | 13 ++++++------- webserver/youtube/youtube.go | 4 +++- 15 files changed, 45 insertions(+), 25 deletions(-) diff --git a/config.yaml b/config.yaml index 391783d..51d3c87 100644 --- a/config.yaml +++ b/config.yaml @@ -27,7 +27,9 @@ youtube: - UC6sb0bkXREewXp2AkSOsOqg # Taomi event: - birthday_hour: 8 # Time to trigger daily birthday check (24h format) + # Time (24h format) to trigger daily events like birthday check and advent calendar post + morning_hour: 8 + morning_minute: 0 webserver: favicon: webserver/favicon.png diff --git a/config/config.go b/config/config.go index 0b8eeaf..7c85fb5 100644 --- a/config/config.go +++ b/config/config.go @@ -15,13 +15,15 @@ package config import ( - "log" + logger "log" "cake4everybot/data/lang" "github.com/spf13/viper" ) +var log = logger.New(logger.Writer(), "[Config] ", logger.LstdFlags|logger.Lmsgprefix) + // Load loads the given configuration file as the global config. It // also loads: // - the languages from lang.Load() (see cake4everybot/data/lang) diff --git a/data/lang/lang.go b/data/lang/lang.go index 1425f24..b181fdd 100644 --- a/data/lang/lang.go +++ b/data/lang/lang.go @@ -15,12 +15,13 @@ package lang import ( - "log" + logger "log" "strings" "github.com/spf13/viper" ) +var log = logger.New(logger.Writer(), "[Config] ", logger.LstdFlags|logger.Lmsgprefix) var langsMap = map[string]*viper.Viper{} // Unify takes and returns a string wich defines a language, i.e. diff --git a/database/connection.go b/database/connection.go index b1cf05e..4a2d7f0 100644 --- a/database/connection.go +++ b/database/connection.go @@ -17,7 +17,7 @@ package database import ( "database/sql" "fmt" - "log" + logger "log" "time" // mysql driver used for database @@ -25,6 +25,7 @@ import ( "github.com/spf13/viper" ) +var log = logger.New(logger.Writer(), "[Config] ", logger.LstdFlags|logger.Lmsgprefix) var db *sql.DB type connectionConfig struct { diff --git a/event/commands.go b/event/commands.go index e07e1b6..303d40e 100644 --- a/event/commands.go +++ b/event/commands.go @@ -16,7 +16,6 @@ package event import ( "fmt" - "log" "cake4everybot/event/command" "cake4everybot/event/command/birthday" diff --git a/event/event.go b/event/event.go index deeb614..8d9ac7f 100644 --- a/event/event.go +++ b/event/event.go @@ -16,8 +16,11 @@ package event import ( "github.com/bwmarrin/discordgo" + logger "log" ) +var log = *logger.New(logger.Writer(), "[Events] ", logger.LstdFlags|logger.Lmsgprefix) + // Register registers all events, like commands. func Register(s *discordgo.Session, guildID string) error { err := registerCommands(s, guildID) diff --git a/event/scheduledTriggers.go b/event/scheduledTriggers.go index 0bd810e..42efc02 100644 --- a/event/scheduledTriggers.go +++ b/event/scheduledTriggers.go @@ -25,24 +25,35 @@ import ( ) func addScheduledTriggers(s *discordgo.Session, webChan chan struct{}) { - go scheduleBirthdayCheck(s) + go scheduleFunction(s, 0, 0, + birthday.Check, + ) + + go scheduleFunction(s, viper.GetInt("event.morning_hour"), viper.GetInt("event.morning_minute"), + birthday.Check, + ) + go refreshYoutube(webChan) } -func scheduleBirthdayCheck(s *discordgo.Session) { - HOUR := viper.GetInt("event.birthday_hour") - +func scheduleFunction(s *discordgo.Session, hour, min int, f ...func(*discordgo.Session)) { + if len(f) == 0 { + return + } + log.Printf("scheduled %d function(s) for %2d:%02d!", len(f), hour, min) time.Sleep(time.Second * 5) for { now := time.Now() - nextRun := time.Date(now.Year(), now.Month(), now.Day(), HOUR, 0, 0, 0, now.Location()) + nextRun := time.Date(now.Year(), now.Month(), now.Day(), hour, min, 0, 0, now.Location()) if nextRun.Before(now) { nextRun = nextRun.Add(time.Hour * 24) } time.Sleep(nextRun.Sub(now)) - birthday.Check(s) + for _, f := range f { + f(s) + } } } diff --git a/event/voiceUpdates.go b/event/voiceUpdates.go index dcf5540..776005a 100644 --- a/event/voiceUpdates.go +++ b/event/voiceUpdates.go @@ -16,7 +16,6 @@ package event import ( "fmt" - "log" "cake4everybot/database" diff --git a/main.go b/main.go index 0d74c6c..7f6b3ff 100644 --- a/main.go +++ b/main.go @@ -16,7 +16,7 @@ package main import ( "context" - "log" + logger "log" "os/signal" "syscall" @@ -29,6 +29,8 @@ import ( "cake4everybot/webserver" ) +var log = logger.New(logger.Writer(), "[MAIN] ", logger.LstdFlags|logger.Lmsgprefix) + const banner string = "\n" + " ______ __ __ __ ______ \n" + " / ____/___ _/ /_____ / // / / ____/ _____ _______ ______ ____ ___ \n" + diff --git a/webserver/logger.go b/webserver/logger.go index ca22a93..4a858f1 100644 --- a/webserver/logger.go +++ b/webserver/logger.go @@ -16,7 +16,6 @@ package webserver import ( "fmt" - "log" "net/http" ) diff --git a/webserver/main.go b/webserver/main.go index 3533eb9..2dc4078 100644 --- a/webserver/main.go +++ b/webserver/main.go @@ -16,7 +16,7 @@ package webserver import ( "cake4everybot/webserver/youtube" - "log" + logger "log" "net/http" "time" @@ -24,6 +24,8 @@ import ( "github.com/spf13/viper" ) +var log = logger.New(logger.Writer(), "[WebServer] ", logger.LstdFlags|logger.Lmsgprefix) + func initHTTP() http.Handler { r := mux.NewRouter() r.Use(Logger) diff --git a/webserver/resonse404.go b/webserver/resonse404.go index 204221e..0d56219 100644 --- a/webserver/resonse404.go +++ b/webserver/resonse404.go @@ -15,7 +15,6 @@ package webserver import ( - "log" "net/http" ) diff --git a/webserver/youtube/api.go b/webserver/youtube/api.go index 20e56a0..422700c 100644 --- a/webserver/youtube/api.go +++ b/webserver/youtube/api.go @@ -17,7 +17,6 @@ package youtube import ( "encoding/xml" "io" - "log" "net/http" "net/url" "regexp" diff --git a/webserver/youtube/discord.go b/webserver/youtube/discord.go index c5b5d1d..39ce7d0 100644 --- a/webserver/youtube/discord.go +++ b/webserver/youtube/discord.go @@ -16,7 +16,6 @@ package youtube import ( "io" - "log" "net/http" "net/url" "strings" @@ -45,7 +44,7 @@ func SetDiscordHandler(f func(*discordgo.Session, *Video)) { func SubscribeChannel(channelID string) { if !subscribtions[channelID] { subscribtions[channelID] = true - log.Printf("YouTube: subscribed '%s' for announcements", channelID) + log.Printf("subscribed '%s' for announcements", channelID) } } @@ -54,14 +53,14 @@ func SubscribeChannel(channelID string) { func UnsubscribeChannel(channelID string) { if subscribtions[channelID] { delete(subscribtions, channelID) - log.Printf("YouTube: unsubscribed '%s' from announcements", channelID) + log.Printf("unsubscribed '%s' from announcements", channelID) } } // RefreshSubscriptions sends a subscription request to the youtube hub func RefreshSubscriptions() { for id := range subscribtions { - log.Printf("[YouTube] Requesting subscription refresh for id '%s'...", id) + log.Printf("Requesting subscription refresh for id '%s'...", id) reqURL := "https://pubsubhubbub.appspot.com/subscribe" @@ -80,16 +79,16 @@ func RefreshSubscriptions() { resp, err := http.DefaultClient.Do(req) if err != nil { - log.Printf("[YouTube] Refresh request failed: %v", err) + log.Printf("Refresh request failed: %v", err) } if resp.StatusCode < 200 || resp.StatusCode >= 300 { //delete(subscribtions, id) b, _ := io.ReadAll(resp.Body) - log.Printf("[YouTube] Refreshing for channel '%s' failed with status %d. Body: %s", id, resp.StatusCode, string(b)) + log.Printf("Refreshing for channel '%s' failed with status %d. Body: %s", id, resp.StatusCode, string(b)) continue } - log.Printf("[YouTube] Successfully refreshed subscription for channel '%s'", id) + log.Printf("Successfully refreshed subscription for channel '%s'", id) } } diff --git a/webserver/youtube/youtube.go b/webserver/youtube/youtube.go index 4929d8b..2a21cdb 100644 --- a/webserver/youtube/youtube.go +++ b/webserver/youtube/youtube.go @@ -18,13 +18,15 @@ import ( "encoding/json" "fmt" "io" - "log" + logger "log" "net/http" "time" "github.com/spf13/viper" ) +var log logger.Logger = *logger.New(logger.Writer(), "[WebYouTube] ", logger.LstdFlags|logger.Lmsgprefix) + type listResponse struct { Item []item `json:"items,omitempty"` } From 6ec39d753743be2ca91b04ebe8a6e7329c07c8a2 Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sat, 25 Nov 2023 00:30:37 +0100 Subject: [PATCH 02/10] added advent calendar base setup --- data/lang/de.yaml | 4 ++ data/lang/en.yaml | 4 ++ event/adventcalendar/adventCalendarBase.go | 36 ++++++++++++++++ event/adventcalendar/chatCommand.go | 50 ++++++++++++++++++++++ event/adventcalendar/component.go | 27 ++++++++++++ event/command/componentBase.go | 15 +++++++ event/commands.go | 37 ++++++++++++++-- event/event.go | 1 + 8 files changed, 171 insertions(+), 3 deletions(-) create mode 100644 event/adventcalendar/adventCalendarBase.go create mode 100644 event/adventcalendar/chatCommand.go create mode 100644 event/adventcalendar/component.go create mode 100644 event/command/componentBase.go diff --git a/data/lang/de.yaml b/data/lang/de.yaml index 6bd35e6..6e2e17f 100644 --- a/data/lang/de.yaml +++ b/data/lang/de.yaml @@ -112,6 +112,10 @@ discord.command: latency: Latenz version: Version + adventcalendar: + base: adventskalender + base.description: Admin Commands für das Adventskalender Giveaway + youtube: embed_footer: YouTube Glocke msg.new_vid: "%s hat ein neues Video hochgeladen" diff --git a/data/lang/en.yaml b/data/lang/en.yaml index 634decf..b9dc803 100644 --- a/data/lang/en.yaml +++ b/data/lang/en.yaml @@ -112,6 +112,10 @@ discord.command: latency: Latency version: Version + adventcalendar: + base: adventcalendar + base.description: Admin commands for the Advent Calendar Giveaway + youtube: embed_footer: YouTube notification bell msg.new_vid: "%s just uploaded a new video" diff --git a/event/adventcalendar/adventCalendarBase.go b/event/adventcalendar/adventCalendarBase.go new file mode 100644 index 0000000..cc7be60 --- /dev/null +++ b/event/adventcalendar/adventCalendarBase.go @@ -0,0 +1,36 @@ +// Copyright 2023 Kesuaheli +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package adventcalendar + +import ( + "cake4everybot/event/command/util" + logger "log" + + "github.com/bwmarrin/discordgo" +) + +const ( + // Prefix for translation key, i.e.: + // key := tp+"base" // => adventcalendar + tp = "discord.command.adventcalendar." +) + +var log = logger.New(logger.Writer(), "[Advent] ", logger.LstdFlags|logger.Lmsgprefix) + +type AdventCalendar struct { + util.InteractionUtil + member *discordgo.Member + user *discordgo.User +} diff --git a/event/adventcalendar/chatCommand.go b/event/adventcalendar/chatCommand.go new file mode 100644 index 0000000..556e5ff --- /dev/null +++ b/event/adventcalendar/chatCommand.go @@ -0,0 +1,50 @@ +package adventcalendar + +import ( + "cake4everybot/data/lang" + "cake4everybot/event/command/util" + + "github.com/bwmarrin/discordgo" +) + +type Chat struct { + AdventCalendar + ID string +} + +// AppCmd (ApplicationCommand) returns the definition of the chat command +func (Chat) AppCmd() *discordgo.ApplicationCommand { + return &discordgo.ApplicationCommand{ + Name: lang.GetDefault(tp + "base"), + NameLocalizations: util.TranslateLocalization(tp + "base"), + Description: lang.GetDefault(tp + "base.description"), + DescriptionLocalizations: util.TranslateLocalization(tp + "base.description"), + } +} + +// CmdHandler returns the functionality of a command +func (cmd Chat) CmdHandler() func(s *discordgo.Session, i *discordgo.InteractionCreate) { + return cmd.Handle +} + +// Handler handles the functionality of a command +func (cmd Chat) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { + cmd.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} + cmd.member = i.Member + cmd.user = i.User + if i.Member != nil { + cmd.user = i.Member.User + } else if i.User != nil { + cmd.member = &discordgo.Member{User: i.User} + } +} + +// SetID sets the registered command ID for internal uses after uploading to discord +func (cmd *Chat) SetID(id string) { + cmd.ID = id +} + +// GetID gets the registered command ID +func (cmd Chat) GetID() string { + return cmd.ID +} diff --git a/event/adventcalendar/component.go b/event/adventcalendar/component.go new file mode 100644 index 0000000..e01fb81 --- /dev/null +++ b/event/adventcalendar/component.go @@ -0,0 +1,27 @@ +package adventcalendar + +import ( + "cake4everybot/event/command/util" + + "github.com/bwmarrin/discordgo" +) + +type Component struct { + AdventCalendar +} + +func (c Component) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { + + c.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} + c.member = i.Member + c.user = i.User + if i.Member != nil { + c.user = i.Member.User + } else if i.User != nil { + c.member = &discordgo.Member{User: i.User} + } +} + +func (Component) ID() string { + return "adventcalendar" +} diff --git a/event/command/componentBase.go b/event/command/componentBase.go new file mode 100644 index 0000000..632258a --- /dev/null +++ b/event/command/componentBase.go @@ -0,0 +1,15 @@ +package command + +import "github.com/bwmarrin/discordgo" + +type Component interface { + // Function of a component. + // All things that should happen after submitting or + // pressing a button. + Handle(*discordgo.Session, *discordgo.InteractionCreate) + + // Custom ID of the modal to identify the module + ID() string +} + +var ComponentMap = make(map[string]Component) diff --git a/event/commands.go b/event/commands.go index 303d40e..0f7b2e9 100644 --- a/event/commands.go +++ b/event/commands.go @@ -16,7 +16,9 @@ package event import ( "fmt" + "strings" + "cake4everybot/event/adventcalendar" "cake4everybot/event/command" "cake4everybot/event/command/birthday" "cake4everybot/event/command/info" @@ -36,6 +38,7 @@ func registerCommands(s *discordgo.Session, guildID string) error { // chat (slash) commands commandsList = append(commandsList, &birthday.Chat{}) commandsList = append(commandsList, &info.Chat{}) + commandsList = append(commandsList, &adventcalendar.Chat{}) // messsage commands // user commands commandsList = append(commandsList, &birthday.UserShow{}) @@ -57,7 +60,7 @@ func registerCommands(s *discordgo.Session, guildID string) error { commandNames = append(commandNames, k) } - log.Printf("Adding used commands: %v...\n", commandNames) + log.Printf("Adding used commands: [%s]...\n", strings.Join(commandNames, ", ")) createdCommands, err := s.ApplicationCommandBulkOverwrite(s.State.User.ID, guildID, appCommandsList) if err != nil { return fmt.Errorf("failed on bulk overwrite commands: %v", err) @@ -96,10 +99,38 @@ func removeUnusedCommands(s *discordgo.Session, guildID string, createdCommands } +func registerComponents() { + // This is the list of components to use. Add a component via + // simply appending the struct (which must implement the + // interface command.Component) to the list, e.g.: + // + // componentList = append(componentList, mymodule.MyComponent{}) + var componentList []command.Component + + componentList = append(componentList, adventcalendar.Component{}) + + if len(componentList) == 0 { + return + } + for _, c := range componentList { + command.ComponentMap[c.ID()] = c + } + log.Printf("Added %d component handler(s)!", len(command.ComponentMap)) +} + func addCommandListeners(s *discordgo.Session) { s.AddHandler(func(s *discordgo.Session, event *discordgo.InteractionCreate) { - if cmd, ok := command.CommandMap[event.ApplicationCommandData().Name]; ok { - cmd.CmdHandler()(s, event) + switch event.Type { + case discordgo.InteractionApplicationCommand, discordgo.InteractionApplicationCommandAutocomplete: + data := event.ApplicationCommandData() + if cmd, ok := command.CommandMap[data.Name]; ok { + cmd.CmdHandler()(s, event) + } + case discordgo.InteractionMessageComponent: + data := event.MessageComponentData() + if c, ok := command.ComponentMap[strings.Split(data.CustomID, ".")[0]]; ok { + c.Handle(s, event) + } } }) diff --git a/event/event.go b/event/event.go index 8d9ac7f..e823b25 100644 --- a/event/event.go +++ b/event/event.go @@ -27,6 +27,7 @@ func Register(s *discordgo.Session, guildID string) error { if err != nil { return err } + registerComponents() return nil } From 9a8c7cb4f12d48142918bb59a389b3be4b1cad3d Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sat, 25 Nov 2023 00:42:08 +0100 Subject: [PATCH 03/10] refactored commands --- event/command/commandBase.go | 19 +++--- event/command/componentBase.go | 15 ----- event/commands.go | 20 +++--- event/component/componentBase.go | 17 +++++ event/scheduledTriggers.go | 6 +- event/youtube/announce.go | 10 +-- .../adventcalendar/adventcalendarbase.go | 4 +- .../adventcalendar/chatCommand.go | 14 ++-- .../adventcalendar/component.go | 7 +- .../birthday/birthdaybase.go | 42 +++++------- .../birthday/birthdaybase_test.go | 0 .../birthday/chatCommand.go | 64 +++++++++---------- .../birthday/chatCommandOptions.go | 2 +- {event/command => modules}/birthday/date.go | 28 ++++---- .../birthday/handleCheck.go | 16 ++--- .../birthday/handlerSubcommandAnnounce.go | 6 +- .../birthday/handlerSubcommandList.go | 11 ++-- .../birthday/handlerSubcommandRemove.go | 8 +-- .../birthday/handlerSubcommandSet.go | 11 ++-- .../birthday/handlerUserShow.go | 5 +- .../birthday/userCommandShow.go | 33 ++++------ .../command => modules}/info/chatCommand.go | 60 ++++++++--------- {event/command/util => util}/discord.go | 0 {event/command/util => util}/interaction.go | 0 {event/command/util => util}/translation.go | 0 {event/command/util => util}/universal.go | 0 26 files changed, 179 insertions(+), 219 deletions(-) delete mode 100644 event/command/componentBase.go create mode 100644 event/component/componentBase.go rename event/adventcalendar/adventCalendarBase.go => modules/adventcalendar/adventcalendarbase.go (93%) rename {event => modules}/adventcalendar/chatCommand.go (80%) rename {event => modules}/adventcalendar/component.go (69%) rename {event/command => modules}/birthday/birthdaybase.go (89%) rename {event/command => modules}/birthday/birthdaybase_test.go (100%) rename {event/command => modules}/birthday/chatCommand.go (59%) rename {event/command => modules}/birthday/chatCommandOptions.go (99%) rename {event/command => modules}/birthday/date.go (92%) rename {event/command => modules}/birthday/handleCheck.go (91%) rename {event/command => modules}/birthday/handlerSubcommandAnnounce.go (87%) rename {event/command => modules}/birthday/handlerSubcommandList.go (92%) rename {event/command => modules}/birthday/handlerSubcommandRemove.go (90%) rename {event/command => modules}/birthday/handlerSubcommandSet.go (97%) rename {event/command => modules}/birthday/handlerUserShow.go (98%) rename {event/command => modules}/birthday/userCommandShow.go (70%) rename {event/command => modules}/info/chatCommand.go (60%) rename {event/command/util => util}/discord.go (100%) rename {event/command/util => util}/interaction.go (100%) rename {event/command/util => util}/translation.go (100%) rename {event/command/util => util}/universal.go (100%) diff --git a/event/command/commandBase.go b/event/command/commandBase.go index 4ea6515..3e7bf4c 100644 --- a/event/command/commandBase.go +++ b/event/command/commandBase.go @@ -16,8 +16,8 @@ package command import "github.com/bwmarrin/discordgo" -// Command is an interface wrapper for all commands. Including chat- -// comamnds (slash-commands), message-commands, and user-commands. +// Command is an interface wrapper for all commands. Including chat-comamnds (slash-commands), +// message-commands, and user-commands. type Command interface { // Definition of a command. // E.g., name, description, options, subcommands. @@ -25,7 +25,7 @@ type Command interface { // Function of a command. // All things that should happen at execution. - CmdHandler() func(s *discordgo.Session, i *discordgo.InteractionCreate) + Handle(s *discordgo.Session, i *discordgo.InteractionCreate) // Sets the registered command ID for internal uses after uploading to discord SetID(id string) @@ -33,14 +33,13 @@ type Command interface { GetID() string } -// CommandMap holds all active commands. It maps them from a unique -// name identifier to the corresponding Command. +// CommandMap holds all active commands. It maps them from a unique name identifier to the +// corresponding Command. // -// Here the name is used, because Discord uses the name too to -// identify seperate commands. When a command is beeing registered -// with a name that already is beeing registerd as a command by this -// application (bot), then the new one will simply overwrite it and -// automatically ungerister the old one. +// Here the name is used, because Discord uses the name too to identify seperate commands. When a +// command is beeing registered with a name that already is beeing registerd as a command by this +// application (bot), then the new one will simply overwrite it and automatically ungerister the old +// one. var CommandMap map[string]Command func init() { diff --git a/event/command/componentBase.go b/event/command/componentBase.go deleted file mode 100644 index 632258a..0000000 --- a/event/command/componentBase.go +++ /dev/null @@ -1,15 +0,0 @@ -package command - -import "github.com/bwmarrin/discordgo" - -type Component interface { - // Function of a component. - // All things that should happen after submitting or - // pressing a button. - Handle(*discordgo.Session, *discordgo.InteractionCreate) - - // Custom ID of the modal to identify the module - ID() string -} - -var ComponentMap = make(map[string]Component) diff --git a/event/commands.go b/event/commands.go index 0f7b2e9..582396c 100644 --- a/event/commands.go +++ b/event/commands.go @@ -15,14 +15,14 @@ package event import ( + "cake4everybot/event/command" + "cake4everybot/event/component" + "cake4everybot/modules/adventcalendar" + "cake4everybot/modules/birthday" + "cake4everybot/modules/info" "fmt" "strings" - "cake4everybot/event/adventcalendar" - "cake4everybot/event/command" - "cake4everybot/event/command/birthday" - "cake4everybot/event/command/info" - "github.com/bwmarrin/discordgo" ) @@ -105,7 +105,7 @@ func registerComponents() { // interface command.Component) to the list, e.g.: // // componentList = append(componentList, mymodule.MyComponent{}) - var componentList []command.Component + var componentList []component.Component componentList = append(componentList, adventcalendar.Component{}) @@ -113,9 +113,9 @@ func registerComponents() { return } for _, c := range componentList { - command.ComponentMap[c.ID()] = c + component.ComponentMap[c.ID()] = c } - log.Printf("Added %d component handler(s)!", len(command.ComponentMap)) + log.Printf("Added %d component handler(s)!", len(component.ComponentMap)) } func addCommandListeners(s *discordgo.Session) { @@ -124,11 +124,11 @@ func addCommandListeners(s *discordgo.Session) { case discordgo.InteractionApplicationCommand, discordgo.InteractionApplicationCommandAutocomplete: data := event.ApplicationCommandData() if cmd, ok := command.CommandMap[data.Name]; ok { - cmd.CmdHandler()(s, event) + cmd.Handle(s, event) } case discordgo.InteractionMessageComponent: data := event.MessageComponentData() - if c, ok := command.ComponentMap[strings.Split(data.CustomID, ".")[0]]; ok { + if c, ok := component.ComponentMap[strings.Split(data.CustomID, ".")[0]]; ok { c.Handle(s, event) } } diff --git a/event/component/componentBase.go b/event/component/componentBase.go new file mode 100644 index 0000000..73b7156 --- /dev/null +++ b/event/component/componentBase.go @@ -0,0 +1,17 @@ +package component + +import "github.com/bwmarrin/discordgo" + +// Component is an interface wrapper for all message components. +type Component interface { + // Function of a component. + // All things that should happen after submitting or pressing a button. + Handle(*discordgo.Session, *discordgo.InteractionCreate) + + // Custom ID of the modal to identify the module + ID() string +} + +// ComponentMap holds all active components. It maps them from a unique string identifier to the +// corresponding Component. +var ComponentMap = make(map[string]Component) diff --git a/event/scheduledTriggers.go b/event/scheduledTriggers.go index 42efc02..1a98e78 100644 --- a/event/scheduledTriggers.go +++ b/event/scheduledTriggers.go @@ -15,11 +15,11 @@ package event import ( - "time" - - "cake4everybot/event/command/birthday" + "cake4everybot/modules/birthday" webYT "cake4everybot/webserver/youtube" + "time" + "github.com/bwmarrin/discordgo" "github.com/spf13/viper" ) diff --git a/event/youtube/announce.go b/event/youtube/announce.go index b13f812..5f23e0e 100644 --- a/event/youtube/announce.go +++ b/event/youtube/announce.go @@ -15,15 +15,15 @@ package youtube import ( - "fmt" - "log" - "strings" - "cake4everybot/data/lang" "cake4everybot/database" - "cake4everybot/event/command/util" + "cake4everybot/util" webYT "cake4everybot/webserver/youtube" + "fmt" + "log" + "strings" + "github.com/bwmarrin/discordgo" ) diff --git a/event/adventcalendar/adventCalendarBase.go b/modules/adventcalendar/adventcalendarbase.go similarity index 93% rename from event/adventcalendar/adventCalendarBase.go rename to modules/adventcalendar/adventcalendarbase.go index cc7be60..b3cc27e 100644 --- a/event/adventcalendar/adventCalendarBase.go +++ b/modules/adventcalendar/adventcalendarbase.go @@ -15,7 +15,7 @@ package adventcalendar import ( - "cake4everybot/event/command/util" + "cake4everybot/util" logger "log" "github.com/bwmarrin/discordgo" @@ -29,7 +29,7 @@ const ( var log = logger.New(logger.Writer(), "[Advent] ", logger.LstdFlags|logger.Lmsgprefix) -type AdventCalendar struct { +type adventcalendarBase struct { util.InteractionUtil member *discordgo.Member user *discordgo.User diff --git a/event/adventcalendar/chatCommand.go b/modules/adventcalendar/chatCommand.go similarity index 80% rename from event/adventcalendar/chatCommand.go rename to modules/adventcalendar/chatCommand.go index 556e5ff..18e4421 100644 --- a/event/adventcalendar/chatCommand.go +++ b/modules/adventcalendar/chatCommand.go @@ -2,13 +2,14 @@ package adventcalendar import ( "cake4everybot/data/lang" - "cake4everybot/event/command/util" + "cake4everybot/util" "github.com/bwmarrin/discordgo" ) +// The Chat (slash) command of the advent calendar package. type Chat struct { - AdventCalendar + adventcalendarBase ID string } @@ -22,12 +23,7 @@ func (Chat) AppCmd() *discordgo.ApplicationCommand { } } -// CmdHandler returns the functionality of a command -func (cmd Chat) CmdHandler() func(s *discordgo.Session, i *discordgo.InteractionCreate) { - return cmd.Handle -} - -// Handler handles the functionality of a command +// Handle handles the functionality of a command func (cmd Chat) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { cmd.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} cmd.member = i.Member @@ -37,6 +33,8 @@ func (cmd Chat) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { } else if i.User != nil { cmd.member = &discordgo.Member{User: i.User} } + + log.Print("currently unused command") } // SetID sets the registered command ID for internal uses after uploading to discord diff --git a/event/adventcalendar/component.go b/modules/adventcalendar/component.go similarity index 69% rename from event/adventcalendar/component.go rename to modules/adventcalendar/component.go index e01fb81..465769a 100644 --- a/event/adventcalendar/component.go +++ b/modules/adventcalendar/component.go @@ -1,15 +1,17 @@ package adventcalendar import ( - "cake4everybot/event/command/util" + "cake4everybot/util" "github.com/bwmarrin/discordgo" ) +// The Component of the advent calendar package. type Component struct { - AdventCalendar + adventcalendarBase } +// Handle handles the functionality of a component. func (c Component) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { c.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} @@ -22,6 +24,7 @@ func (c Component) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) } } +// ID returns the custom ID of the modal to identify the module func (Component) ID() string { return "adventcalendar" } diff --git a/event/command/birthday/birthdaybase.go b/modules/birthday/birthdaybase.go similarity index 89% rename from event/command/birthday/birthdaybase.go rename to modules/birthday/birthdaybase.go index 7793530..bdea91f 100644 --- a/event/command/birthday/birthdaybase.go +++ b/modules/birthday/birthdaybase.go @@ -15,16 +15,15 @@ package birthday import ( + "cake4everybot/data/lang" + "cake4everybot/database" + "cake4everybot/util" "fmt" "reflect" "sort" "strings" "time" - "cake4everybot/data/lang" - "cake4everybot/database" - "cake4everybot/event/command/util" - "github.com/bwmarrin/discordgo" ) @@ -74,8 +73,8 @@ func (b birthdayEntry) DOW() int { return int(b.Year+b.Year/4-b.Year/100+b.Year/400+int(monthKey[b.Month])+b.Day) % 7 } -// NextUnix returns the unix timestamp in seconds of the next -// birthday. This is just a shorthand for Next().Unix(). +// NextUnix returns the unix timestamp in seconds of the next birthday. This is just a shorthand for +// Next().Unix(). // // See Next() for more. func (b birthdayEntry) NextUnix() int64 { @@ -92,15 +91,13 @@ func (b birthdayEntry) Next() time.Time { return nextTime } -// ParseTime tries to parse the date (b.Day, b.Month, b.Year) to a -// time.Time object. +// ParseTime tries to parse the date (b.Day, b.Month, b.Year) to a time.Time object. func (b *birthdayEntry) ParseTime() (err error) { b.time, err = time.Parse(time.DateOnly, fmt.Sprintf("%04d-%02d-%02d", b.Year, b.Month, b.Day)) return err } -// Age returns the current age of the user. If no year is set, it -// returns 0. +// Age returns the current age of the user. If no year is set, it returns 0. func (b birthdayEntry) Age() int { if b.Year == 0 { return 0 @@ -108,11 +105,9 @@ func (b birthdayEntry) Age() int { return b.Next().Year() - b.Year - 1 } -// getBirthday copies all birthday fields into -// the struct pointed at by b. +// getBirthday copies all birthday fields into the struct pointed at by b. // -// If the user from b.ID is not found it returns -// sql.ErrNoRows. +// If the user from b.ID is not found it returns sql.ErrNoRows. func (cmd birthdayBase) getBirthday(b *birthdayEntry) (err error) { row := database.QueryRow("SELECT day,month,year,visible FROM birthdays WHERE id=?", b.ID) err = row.Scan(&b.Day, &b.Month, &b.Year, &b.Visible) @@ -122,22 +117,19 @@ func (cmd birthdayBase) getBirthday(b *birthdayEntry) (err error) { return b.ParseTime() } -// hasBirthday returns true whether the given -// user id has entered their birthday. +// hasBirthday returns true whether the given user id has entered their birthday. func (cmd birthdayBase) hasBirthday(id uint64) (hasBirthday bool, err error) { err = database.QueryRow("SELECT EXISTS(SELECT id FROM birthdays WHERE id=?)", id).Scan(&hasBirthday) return hasBirthday, err } -// setBirthday inserts a new database entry with -// the values from b. +// setBirthday inserts a new database entry with the values from b. func (cmd birthdayBase) setBirthday(b birthdayEntry) error { _, err := database.Exec("INSERT INTO birthdays(id,day,month,year,visible) VALUES(?,?,?,?,?);", b.ID, b.Day, b.Month, b.Year, b.Visible) return err } -// updateBirthday updates an existing database -// entry with the values from b. +// updateBirthday updates an existing database entry with the values from b. func (cmd birthdayBase) updateBirthday(b birthdayEntry) (before birthdayEntry, err error) { err = b.ParseTime() if err != nil { @@ -185,8 +177,8 @@ func (cmd birthdayBase) updateBirthday(b birthdayEntry) (before birthdayEntry, e return before, err } -// removeBirthday deletes the existing birthday entry for the given -// id and returns the previously entered birthday. +// removeBirthday deletes the existing birthday entry for the given id and returns the previously +// entered birthday. func (cmd birthdayBase) removeBirthday(id uint64) (birthdayEntry, error) { b := birthdayEntry{ID: id} err := cmd.getBirthday(&b) @@ -198,8 +190,7 @@ func (cmd birthdayBase) removeBirthday(id uint64) (birthdayEntry, error) { return b, err } -// getBirthdaysMonth return a sorted slice of -// birthday entries that matches the given month. +// getBirthdaysMonth return a sorted slice of birthday entries that matches the given month. func (cmd birthdayBase) getBirthdaysMonth(month int) (birthdays []birthdayEntry, err error) { var numOfEntries int64 err = database.QueryRow("SELECT COUNT(*) FROM birthdays WHERE month=?", month).Scan(&numOfEntries) @@ -244,8 +235,7 @@ func (cmd birthdayBase) getBirthdaysMonth(month int) (birthdays []birthdayEntry, return birthdays, nil } -// getBirthdaysDate return a slice of birthday -// entries that matches the given date. +// getBirthdaysDate return a slice of birthday entries that matches the given date. func getBirthdaysDate(day int, month int) (birthdays []birthdayEntry, err error) { var numOfEntries int64 err = database.QueryRow("SELECT COUNT(*) FROM birthdays WHERE day=? AND month=?", day, month).Scan(&numOfEntries) diff --git a/event/command/birthday/birthdaybase_test.go b/modules/birthday/birthdaybase_test.go similarity index 100% rename from event/command/birthday/birthdaybase_test.go rename to modules/birthday/birthdaybase_test.go diff --git a/event/command/birthday/chatCommand.go b/modules/birthday/chatCommand.go similarity index 59% rename from event/command/birthday/chatCommand.go rename to modules/birthday/chatCommand.go index b0dca22..cb62169 100644 --- a/event/command/birthday/chatCommand.go +++ b/modules/birthday/chatCommand.go @@ -16,15 +16,13 @@ package birthday import ( "cake4everybot/data/lang" - "cake4everybot/event/command/util" + "cake4everybot/util" "github.com/bwmarrin/discordgo" ) -// The Chat (slash) command of the birthday -// package. Has a few sub commands and options -// to use all features through a single chat -// command. +// The Chat (slash) command of the birthday package. Has a few sub commands and options to use all +// features through a single chat command. type Chat struct { birthdayBase @@ -35,8 +33,7 @@ type subcommand interface { handler() } -// AppCmd (ApplicationCommand) returns the definition of the chat -// command +// AppCmd (ApplicationCommand) returns the definition of the chat command func (cmd Chat) AppCmd() *discordgo.ApplicationCommand { options := []*discordgo.ApplicationCommandOption{ subCommandSet(), @@ -54,37 +51,34 @@ func (cmd Chat) AppCmd() *discordgo.ApplicationCommand { } } -// CmdHandler returns the functionality of a command -func (cmd Chat) CmdHandler() func(s *discordgo.Session, i *discordgo.InteractionCreate) { - - return func(s *discordgo.Session, i *discordgo.InteractionCreate) { - cmd.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} - cmd.member = i.Member - cmd.user = i.User - if i.Member != nil { - cmd.user = i.Member.User - } else if i.User != nil { - cmd.member = &discordgo.Member{User: i.User} - } - - subcommandName := i.ApplicationCommandData().Options[0].Name - var sub subcommand +// Handle handles the functionality of a command +func (cmd Chat) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { + cmd.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} + cmd.member = i.Member + cmd.user = i.User + if i.Member != nil { + cmd.user = i.Member.User + } else if i.User != nil { + cmd.member = &discordgo.Member{User: i.User} + } - switch subcommandName { - case lang.GetDefault(tp + "option.set"): - sub = cmd.subcommandSet() - case lang.GetDefault(tp + "option.remove"): - sub = cmd.subcommandRemove() - case lang.GetDefault(tp + "option.list"): - sub = cmd.subcommandList() - case lang.GetDefault(tp + "option.announce"): - sub = cmd.subcommandAnnounce() - default: - return - } + subcommandName := i.ApplicationCommandData().Options[0].Name + var sub subcommand - sub.handler() + switch subcommandName { + case lang.GetDefault(tp + "option.set"): + sub = cmd.subcommandSet() + case lang.GetDefault(tp + "option.remove"): + sub = cmd.subcommandRemove() + case lang.GetDefault(tp + "option.list"): + sub = cmd.subcommandList() + case lang.GetDefault(tp + "option.announce"): + sub = cmd.subcommandAnnounce() + default: + return } + + sub.handler() } // SetID sets the registered command ID for internal uses after uploading to discord diff --git a/event/command/birthday/chatCommandOptions.go b/modules/birthday/chatCommandOptions.go similarity index 99% rename from event/command/birthday/chatCommandOptions.go rename to modules/birthday/chatCommandOptions.go index 264cabb..c525dc9 100644 --- a/event/command/birthday/chatCommandOptions.go +++ b/modules/birthday/chatCommandOptions.go @@ -16,7 +16,7 @@ package birthday import ( "cake4everybot/data/lang" - "cake4everybot/event/command/util" + "cake4everybot/util" "github.com/bwmarrin/discordgo" ) diff --git a/event/command/birthday/date.go b/modules/birthday/date.go similarity index 92% rename from event/command/birthday/date.go rename to modules/birthday/date.go index a033580..c09bb18 100644 --- a/event/command/birthday/date.go +++ b/modules/birthday/date.go @@ -15,15 +15,14 @@ package birthday import ( + "cake4everybot/data/lang" + "cake4everybot/util" "fmt" "math" "strconv" "strings" "time" - "cake4everybot/data/lang" - "cake4everybot/event/command/util" - "github.com/bwmarrin/discordgo" ) @@ -42,8 +41,8 @@ var ( } ) -// dayChoices returns a list of choices of days that matches the -// given start string with the provided month and leap year values. +// dayChoices returns a list of choices of days that matches the given start string with the +// provided month and leap year values. func dayChoices(start string, month int, leapYear bool) (choices []*discordgo.ApplicationCommandOptionChoice) { i, _ := strconv.Atoi(start) if i < 0 || i > getDays(month, leapYear) { @@ -64,8 +63,8 @@ func dayChoices(start string, month int, leapYear bool) (choices []*discordgo.Ap return choices } -// monthChoices returns a list of choices of months that matches the -// given start string with the provided day and leap year values. +// monthChoices returns a list of choices of months that matches the given start string with the +// provided day and leap year values. func monthChoices(start string, day int, leapYear bool) (choices []*discordgo.ApplicationCommandOptionChoice) { i, err := strconv.Atoi(start) if err != nil { @@ -125,8 +124,8 @@ func monthChoices(start string, day int, leapYear bool) (choices []*discordgo.Ap return choices } -// yearChoices returns a list of choices of years that matches the -// given start string with the provided day and month value. +// yearChoices returns a list of choices of years that matches the given start string with the +// provided day and month value. func yearChoices(start string, day, month int) (choices []*discordgo.ApplicationCommandOptionChoice) { maxDate := time.Now().AddDate(-16, 0, 0) @@ -137,8 +136,7 @@ func yearChoices(start string, day, month int) (choices []*discordgo.Application decades = append(decades, y) } - // reply with list of decades when the start string isnt number - // or is zero + // reply with list of decades when the start string isnt a number or is zero y, err := strconv.Atoi(start) if err != nil || y == 0 { for _, dec := range decades { @@ -204,8 +202,7 @@ func intChoice(i int) (choice *discordgo.ApplicationCommandOptionChoice) { } } -// monthChoice returns a single choice with the name of the month -// defined by the given integer. +// monthChoice returns a single choice with the name of the month defined by the given integer. func monthChoice(month int) (choice *discordgo.ApplicationCommandOptionChoice) { key := fmt.Sprintf("%smonth.%d", tp, month-1) @@ -216,9 +213,8 @@ func monthChoice(month int) (choice *discordgo.ApplicationCommandOptionChoice) { } } -// getDays returns the maximum number of days in the given month. -// When the given month is february (month: 2), getDays returns 29, -// as it is the max. number of day the february can have. +// getDays returns the maximum number of days in the given month. When the given month is february +// (month: 2), getDays returns 29, as it is the max. number of day the february can have. func getDays(month int, leapYear bool) int { if util.ContainsInt([]int{2}, month) { if leapYear { diff --git a/event/command/birthday/handleCheck.go b/modules/birthday/handleCheck.go similarity index 91% rename from event/command/birthday/handleCheck.go rename to modules/birthday/handleCheck.go index e700ba3..02a57ef 100644 --- a/event/command/birthday/handleCheck.go +++ b/modules/birthday/handleCheck.go @@ -15,19 +15,18 @@ package birthday import ( + "cake4everybot/data/lang" + "cake4everybot/database" + "cake4everybot/util" "fmt" "log" "time" - "cake4everybot/data/lang" - "cake4everybot/database" - "cake4everybot/event/command/util" - "github.com/bwmarrin/discordgo" ) -// Check checks if there are any birthdays on the current date -// (time.Now()), if so announce them in the desired channel. +// Check checks if there are any birthdays on the current date (time.Now()), if so announce them +// in the desired channel. func Check(s *discordgo.Session) { var guildID, channelID uint64 rows, err := database.Query("SELECT id,birthday_id FROM guilds") @@ -71,9 +70,8 @@ func Check(s *discordgo.Session) { } } -// birthdayAnnounceEmbed returns the embed, that contains all -// birthdays and 'n' as the number of birthdays, which is always -// len(b) +// birthdayAnnounceEmbed returns the embed, that contains all birthdays and 'n' as the number of +// birthdays, which is always len(b) func birthdayAnnounceEmbed(s *discordgo.Session, b []birthdayEntry) (e *discordgo.MessageEmbed, n int) { var title, fValue string diff --git a/event/command/birthday/handlerSubcommandAnnounce.go b/modules/birthday/handlerSubcommandAnnounce.go similarity index 87% rename from event/command/birthday/handlerSubcommandAnnounce.go rename to modules/birthday/handlerSubcommandAnnounce.go index 41be662..2af3a06 100644 --- a/event/command/birthday/handlerSubcommandAnnounce.go +++ b/modules/birthday/handlerSubcommandAnnounce.go @@ -21,15 +21,13 @@ import ( "github.com/bwmarrin/discordgo" ) -// The announce subcommand. Used when executing the -// slash-command "/birthday announce". +// The announce subcommand. Used when executing the slash-command "/birthday announce". type subcommandAnnounce struct { Chat *discordgo.ApplicationCommandInteractionDataOption } -// Constructor for subcommandannounce, the struct for -// the slash-command "/birthday announce". +// Constructor for subcommandannounce, the struct for the slash-command "/birthday announce". func (cmd Chat) subcommandAnnounce() subcommandAnnounce { subcommand := cmd.Interaction.ApplicationCommandData().Options[0] return subcommandAnnounce{ diff --git a/event/command/birthday/handlerSubcommandList.go b/modules/birthday/handlerSubcommandList.go similarity index 92% rename from event/command/birthday/handlerSubcommandList.go rename to modules/birthday/handlerSubcommandList.go index 76d24b7..1e715b0 100644 --- a/event/command/birthday/handlerSubcommandList.go +++ b/modules/birthday/handlerSubcommandList.go @@ -15,18 +15,16 @@ package birthday import ( + "cake4everybot/data/lang" + "cake4everybot/util" "fmt" "log" "time" - "cake4everybot/data/lang" - "cake4everybot/event/command/util" - "github.com/bwmarrin/discordgo" ) -// The list subcommand. Used when executing the -// slash-command "/birthday list". +// The list subcommand. Used when executing the slash-command "/birthday list". type subcommandList struct { Chat *discordgo.ApplicationCommandInteractionDataOption @@ -34,8 +32,7 @@ type subcommandList struct { month *discordgo.ApplicationCommandInteractionDataOption // reqired } -// Constructor for subcommandList, the struct for -// the slash-command "/birthday remove". +// Constructor for subcommandList, the struct for the slash-command "/birthday remove". func (cmd Chat) subcommandList() subcommandList { subcommand := cmd.Interaction.ApplicationCommandData().Options[0] return subcommandList{ diff --git a/event/command/birthday/handlerSubcommandRemove.go b/modules/birthday/handlerSubcommandRemove.go similarity index 90% rename from event/command/birthday/handlerSubcommandRemove.go rename to modules/birthday/handlerSubcommandRemove.go index 0ef9d68..fc59808 100644 --- a/event/command/birthday/handlerSubcommandRemove.go +++ b/modules/birthday/handlerSubcommandRemove.go @@ -16,7 +16,7 @@ package birthday import ( "cake4everybot/data/lang" - "cake4everybot/event/command/util" + "cake4everybot/util" "fmt" "log" "strconv" @@ -24,15 +24,13 @@ import ( "github.com/bwmarrin/discordgo" ) -// The remove subcommand. Used when executing the -// slash-command "/birthday remove". +// The remove subcommand. Used when executing the slash-command "/birthday remove". type subcommandRemove struct { Chat *discordgo.ApplicationCommandInteractionDataOption } -// Constructor for subcommandremove, the struct for -// the slash-command "/birthday remove". +// Constructor for subcommandremove, the struct for the slash-command "/birthday remove". func (cmd Chat) subcommandRemove() subcommandRemove { subcommand := cmd.Interaction.ApplicationCommandData().Options[0] return subcommandRemove{ diff --git a/event/command/birthday/handlerSubcommandSet.go b/modules/birthday/handlerSubcommandSet.go similarity index 97% rename from event/command/birthday/handlerSubcommandSet.go rename to modules/birthday/handlerSubcommandSet.go index 2237a6f..6366bd1 100644 --- a/event/command/birthday/handlerSubcommandSet.go +++ b/modules/birthday/handlerSubcommandSet.go @@ -15,19 +15,17 @@ package birthday import ( + "cake4everybot/data/lang" + "cake4everybot/util" "fmt" "log" "strconv" "time" - "cake4everybot/data/lang" - "cake4everybot/event/command/util" - "github.com/bwmarrin/discordgo" ) -// The set subcommand. Used when executing the -// slash-command "/birthday set". +// The set subcommand. Used when executing the slash-command "/birthday set". type subcommandSet struct { Chat *discordgo.ApplicationCommandInteractionDataOption @@ -38,8 +36,7 @@ type subcommandSet struct { visible *discordgo.ApplicationCommandInteractionDataOption // optional } -// Constructor for subcommandSet, the struct for -// the slash-command "/birthday set". +// Constructor for subcommandSet, the struct for the slash-command "/birthday set". func (cmd Chat) subcommandSet() subcommandSet { subcommand := cmd.Interaction.ApplicationCommandData().Options[0] return subcommandSet{ diff --git a/event/command/birthday/handlerUserShow.go b/modules/birthday/handlerUserShow.go similarity index 98% rename from event/command/birthday/handlerUserShow.go rename to modules/birthday/handlerUserShow.go index 89b8256..0d0c1d9 100644 --- a/event/command/birthday/handlerUserShow.go +++ b/modules/birthday/handlerUserShow.go @@ -15,13 +15,12 @@ package birthday import ( + "cake4everybot/data/lang" + "cake4everybot/util" "fmt" "log" "strconv" - "cake4everybot/data/lang" - "cake4everybot/event/command/util" - "github.com/bwmarrin/discordgo" ) diff --git a/event/command/birthday/userCommandShow.go b/modules/birthday/userCommandShow.go similarity index 70% rename from event/command/birthday/userCommandShow.go rename to modules/birthday/userCommandShow.go index 124bcc3..5275efd 100644 --- a/event/command/birthday/userCommandShow.go +++ b/modules/birthday/userCommandShow.go @@ -16,14 +16,13 @@ package birthday import ( "cake4everybot/data/lang" - "cake4everybot/event/command/util" + "cake4everybot/util" "github.com/bwmarrin/discordgo" ) -// UserShow represents a user command of the birthday package. It -// adds the ability to directly show a users birthday through a -// simple context click. +// UserShow represents a user command of the birthday package. It adds the ability to directly show +// a users birthday through a simple context click. type UserShow struct { birthdayBase @@ -31,8 +30,7 @@ type UserShow struct { ID string } -// AppCmd (ApplicationCommand) returns the definition of the chat -// command +// AppCmd (ApplicationCommand) returns the definition of the chat command func (cmd UserShow) AppCmd() *discordgo.ApplicationCommand { return &discordgo.ApplicationCommand{ Type: discordgo.UserApplicationCommand, @@ -41,20 +39,17 @@ func (cmd UserShow) AppCmd() *discordgo.ApplicationCommand { } } -// CmdHandler returns the functionality of a command -func (cmd UserShow) CmdHandler() func(s *discordgo.Session, i *discordgo.InteractionCreate) { - - return func(s *discordgo.Session, i *discordgo.InteractionCreate) { - cmd.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} - cmd.member = i.Member - cmd.user = i.User - if i.Member != nil { - cmd.user = i.Member.User - } - - cmd.data = cmd.Interaction.ApplicationCommandData() - cmd.handler() +// Handle handles the functionality of a command +func (cmd UserShow) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { + cmd.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} + cmd.member = i.Member + cmd.user = i.User + if i.Member != nil { + cmd.user = i.Member.User } + + cmd.data = cmd.Interaction.ApplicationCommandData() + cmd.handler() } // SetID sets the registered command ID for internal uses after uploading to discord diff --git a/event/command/info/chatCommand.go b/modules/info/chatCommand.go similarity index 60% rename from event/command/info/chatCommand.go rename to modules/info/chatCommand.go index a639cb1..2ee1194 100644 --- a/event/command/info/chatCommand.go +++ b/modules/info/chatCommand.go @@ -16,8 +16,8 @@ package info import ( "cake4everybot/data/lang" - "cake4everybot/event/command/util" "cake4everybot/status" + "cake4everybot/util" "fmt" "github.com/bwmarrin/discordgo" @@ -38,8 +38,7 @@ type Chat struct { ID string } -// AppCmd (ApplicationCommand) returns the definition of the chat -// command +// AppCmd (ApplicationCommand) returns the definition of the chat command func (cmd Chat) AppCmd() *discordgo.ApplicationCommand { return &discordgo.ApplicationCommand{ Name: lang.GetDefault(tp + "base"), @@ -49,37 +48,34 @@ func (cmd Chat) AppCmd() *discordgo.ApplicationCommand { } } -// CmdHandler returns the functionality of a command -func (cmd Chat) CmdHandler() func(s *discordgo.Session, i *discordgo.InteractionCreate) { +// Handle handles the functionality of a command +func (cmd Chat) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { + cmd.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} - return func(s *discordgo.Session, i *discordgo.InteractionCreate) { - cmd.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} - - e := &discordgo.MessageEmbed{ - Title: lang.GetDefault(tp + "title"), - Color: 0x00FF00, - } - util.AddEmbedField(e, - lang.Get(tp+"start_time", lang.FallbackLang()), - fmt.Sprintf("", status.GetStartTime().Unix()), - true, - ) - util.AddEmbedField(e, - lang.Get(tp+"latency", lang.FallbackLang()), - fmt.Sprintf("%dms", s.LastHeartbeatAck.Sub(s.LastHeartbeatSent).Milliseconds()), - true, - ) - version := fmt.Sprintf("v%s", viper.GetString("version")) - versionURL := fmt.Sprintf("https://github.com/Kesuaheli/cake4everybotgo/releases/tag/%s", version) - util.AddEmbedField(e, - lang.Get(tp+"version", lang.FallbackLang()), - fmt.Sprintf("[%s](%s)", version, versionURL), - false, - ) - util.SetEmbedFooter(s, tp+"display", e) - - cmd.ReplyEmbed(e) + e := &discordgo.MessageEmbed{ + Title: lang.GetDefault(tp + "title"), + Color: 0x00FF00, } + util.AddEmbedField(e, + lang.Get(tp+"start_time", lang.FallbackLang()), + fmt.Sprintf("", status.GetStartTime().Unix()), + true, + ) + util.AddEmbedField(e, + lang.Get(tp+"latency", lang.FallbackLang()), + fmt.Sprintf("%dms", s.LastHeartbeatAck.Sub(s.LastHeartbeatSent).Milliseconds()), + true, + ) + version := fmt.Sprintf("v%s", viper.GetString("version")) + versionURL := fmt.Sprintf("https://github.com/Kesuaheli/cake4everybotgo/releases/tag/%s", version) + util.AddEmbedField(e, + lang.Get(tp+"version", lang.FallbackLang()), + fmt.Sprintf("[%s](%s)", version, versionURL), + false, + ) + util.SetEmbedFooter(s, tp+"display", e) + + cmd.ReplyEmbed(e) } // SetID sets the registered command ID for internal uses after uploading to discord diff --git a/event/command/util/discord.go b/util/discord.go similarity index 100% rename from event/command/util/discord.go rename to util/discord.go diff --git a/event/command/util/interaction.go b/util/interaction.go similarity index 100% rename from event/command/util/interaction.go rename to util/interaction.go diff --git a/event/command/util/translation.go b/util/translation.go similarity index 100% rename from event/command/util/translation.go rename to util/translation.go diff --git a/event/command/util/universal.go b/util/universal.go similarity index 100% rename from event/command/util/universal.go rename to util/universal.go From c09db2507bc2c84e23e8b034fbb061d82d7e91e7 Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sat, 25 Nov 2023 02:04:36 +0100 Subject: [PATCH 04/10] refactored util functions --- modules/birthday/handlerSubcommandAnnounce.go | 2 +- modules/birthday/handlerSubcommandRemove.go | 5 +- modules/birthday/handlerSubcommandSet.go | 5 +- modules/birthday/handlerUserShow.go | 5 +- util/discord.go | 7 +- util/interaction.go | 281 +++++++++++++----- util/modal.go | 163 ++++++++++ util/translation.go | 5 +- util/universal.go | 12 +- 9 files changed, 400 insertions(+), 85 deletions(-) create mode 100644 util/modal.go diff --git a/modules/birthday/handlerSubcommandAnnounce.go b/modules/birthday/handlerSubcommandAnnounce.go index 2af3a06..7aa0c9e 100644 --- a/modules/birthday/handlerSubcommandAnnounce.go +++ b/modules/birthday/handlerSubcommandAnnounce.go @@ -48,7 +48,7 @@ func (cmd subcommandAnnounce) handler() { e, n := birthdayAnnounceEmbed(cmd.Session, b) if n <= 0 { - cmd.ReplyHiddenEmbed(false, e) + cmd.ReplyHiddenEmbed(e) } else { cmd.ReplyEmbed(e) } diff --git a/modules/birthday/handlerSubcommandRemove.go b/modules/birthday/handlerSubcommandRemove.go index fc59808..80d378f 100644 --- a/modules/birthday/handlerSubcommandRemove.go +++ b/modules/birthday/handlerSubcommandRemove.go @@ -59,7 +59,7 @@ func (cmd subcommandRemove) handler() { if !hasBDay { e.Description = lang.Get(tp+"msg.remove.not_found", lang.FallbackLang()) e.Color = 0xFF0000 - cmd.ReplyHiddenEmbed(false, e) + cmd.ReplyHiddenEmbed(e) return } @@ -82,6 +82,7 @@ func (cmd subcommandRemove) handler() { if b.Visible { cmd.ReplyEmbed(e) } else { - cmd.ReplyHiddenEmbed(true, e) + util.AddReplyHiddenField(e) + cmd.ReplyHiddenEmbed(e) } } diff --git a/modules/birthday/handlerSubcommandSet.go b/modules/birthday/handlerSubcommandSet.go index 6366bd1..a62696a 100644 --- a/modules/birthday/handlerSubcommandSet.go +++ b/modules/birthday/handlerSubcommandSet.go @@ -142,7 +142,7 @@ func (cmd subcommandSet) interactionHandler() { log.Printf("WARNING: User (%d) entered an invalid date: %v\n", authorID, err) embed.Description = lang.Get(tp+"msg.invalid_date", lang.FallbackLang()) embed.Color = 0xFF0000 - cmd.ReplyHiddenEmbed(false, embed) + cmd.ReplyHiddenEmbed(embed) return } @@ -189,7 +189,8 @@ func (cmd subcommandSet) interactionHandler() { if b.Visible { cmd.ReplyEmbed(embed) } else { - cmd.ReplyHiddenEmbed(true, embed) + util.AddReplyHiddenField(embed) + cmd.ReplyHiddenEmbed(embed) } } diff --git a/modules/birthday/handlerUserShow.go b/modules/birthday/handlerUserShow.go index 0d0c1d9..97a53ee 100644 --- a/modules/birthday/handlerUserShow.go +++ b/modules/birthday/handlerUserShow.go @@ -68,7 +68,7 @@ func (cmd UserShow) handler() { embed.Description = fmt.Sprintf(format, target.Mention()) } embed.Color = 0xFF0000 - cmd.ReplyHiddenEmbed(false, embed) + cmd.ReplyHiddenEmbed(embed) return } @@ -84,6 +84,7 @@ func (cmd UserShow) handler() { if b.Visible { cmd.ReplyEmbed(embed) } else { - cmd.ReplyHiddenEmbed(true, embed) + util.AddReplyHiddenField(embed) + cmd.ReplyHiddenEmbed(embed) } } diff --git a/util/discord.go b/util/discord.go index b0087dc..136e2cd 100644 --- a/util/discord.go +++ b/util/discord.go @@ -57,8 +57,7 @@ func AuthoredEmbed[T *discordgo.User | *discordgo.Member](s *discordgo.Session, return embed } -// SetEmbedFooter takes a pointer to an embeds and sets the standard -// footer with the given name. +// SetEmbedFooter takes a pointer to an embeds and sets the standard footer with the given name. // // sectionName: // translation key for the name @@ -80,8 +79,8 @@ func AddEmbedField(e *discordgo.MessageEmbed, name, value string, inline bool) { e.Fields = append(e.Fields, &discordgo.MessageEmbedField{Name: name, Value: value, Inline: inline}) } -// AddReplyHiddenField appends the standard field for ephemral -// embeds to the existing fields of the given embed. +// AddReplyHiddenField appends the standard field for ephemral embeds to the existing fields of the +// given embed. func AddReplyHiddenField(e *discordgo.MessageEmbed) { AddEmbedField(e, lang.Get("discord.command.generic.msg.self_hidden", lang.FallbackLang()), diff --git a/util/interaction.go b/util/interaction.go index 773c7f0..5da4284 100644 --- a/util/interaction.go +++ b/util/interaction.go @@ -16,96 +16,238 @@ package util import ( "fmt" - "log" + "runtime/debug" "github.com/bwmarrin/discordgo" ) -// InteractionUtil is a helper for discords application interactions. -// It add useful methods for simpler and faster coding. +// InteractionUtil is a helper for discords application interactions. It add useful methods for +// simpler and faster coding. type InteractionUtil struct { - Session *discordgo.Session - Interaction *discordgo.InteractionCreate - response *discordgo.InteractionResponse + Session *discordgo.Session + Interaction *discordgo.InteractionCreate + response *discordgo.InteractionResponse + acknowledged bool } -// Replyf formats according to a format specifier -// and prints the result as reply to the user who +func (i *InteractionUtil) respond() { + if i.response.Type != discordgo.InteractionResponseDeferredChannelMessageWithSource && // deferred responses dont need contents + i.response.Type != discordgo.InteractionResponseDeferredMessageUpdate && + i.response.Data.Content == "" && + len(i.response.Data.Embeds) == 0 && + len(i.response.Data.Components) == 0 { + log.Printf("ERROR: Reply called without contens! Need at least one of Content, Embed, Component.\n%s", debug.Stack()) + i.ReplyError() + return + } + + if i.acknowledged { + _, err := i.Session.FollowupMessageCreate(i.Interaction.Interaction, true, &discordgo.WebhookParams{ + Content: i.response.Data.Content, + }) + if err != nil { + log.Printf("ERROR: could not send follow up message: %+v\n%s", err, debug.Stack()) + } + return + } + err := i.Session.InteractionRespond(i.Interaction.Interaction, i.response) + if err != nil { + log.Printf("ERROR could not send command response: %+v\n%s", err, debug.Stack()) + return + } + i.acknowledged = true +} + +func (i *InteractionUtil) respondMessage(update, deferred bool) (sucess bool) { + rType := discordgo.InteractionResponseChannelMessageWithSource + if update { + if i.Interaction.Type != discordgo.InteractionMessageComponent { + log.Printf("ERROR: Got message update on interaction type '%s', but is only allowed on %s", i.Interaction.Type.String(), discordgo.InteractionMessageComponent.String()) + i.ReplyError() + return false + } + if deferred { + rType = discordgo.InteractionResponseDeferredMessageUpdate + } else { + rType = discordgo.InteractionResponseUpdateMessage + } + } else if !update && deferred { + rType = discordgo.InteractionResponseDeferredChannelMessageWithSource + } + + i.response = &discordgo.InteractionResponse{ + Type: rType, + Data: &discordgo.InteractionResponseData{}, + } + return true +} + +// ReplyError sends a simple message to the user to indicate, that something failed or unexpected +// happened during the execution of the interaction. +func (i *InteractionUtil) ReplyError() { + i.ReplyHidden("Somthing went wrong :(") +} + +// ReplyErrorUpdate is like ReplyError but made for an update message for components. +func (i *InteractionUtil) ReplyErrorUpdate() { + i.ReplyUpdate("Somthing went wrong :(") +} + +// ReplyDefered points out to the user that the bots answer could take some time. It also allows the +// bot to extend this interaction for up to 15 minutes. +func (i *InteractionUtil) ReplyDefered() { + i.respondMessage(false, true) + i.respond() +} + +// ReplyDeferedHidden is like ReplyDefered but also ephemeral. +func (i *InteractionUtil) ReplyDeferedHidden() { + i.respondMessage(false, true) + i.response.Data.Flags = discordgo.MessageFlagsEphemeral + i.respond() +} + +// ReplyDeferedUpdate is like ReplyDefered but make for an update for components. +func (i *InteractionUtil) ReplyDeferedUpdate() { + i.respondMessage(true, true) + i.respond() +} + +// ReplyDeferedHiddenUpdate is like ReplyDeferedHidden but make for an update for components. +func (i *InteractionUtil) ReplyDeferedHiddenUpdate() { + i.respondMessage(true, true) + i.response.Data.Flags = discordgo.MessageFlagsEphemeral + i.respond() +} + +// Reply prints the given message as reply to the user who executes the command. +func (i *InteractionUtil) Reply(message string) { + i.respondMessage(false, false) + i.response.Data.Content = message + i.respond() +} + +// Replyf formats according to a format specifier and prints the result as reply to the user who // executes the command. func (i *InteractionUtil) Replyf(format string, a ...any) { i.Reply(fmt.Sprintf(format, a...)) } -// Reply prints the given message as reply to the -// user who executes the command. -func (i *InteractionUtil) Reply(message string) { - i.response = &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: message, - }, +// ReplyUpdate is like Reply but make for an update for components. +func (i *InteractionUtil) ReplyUpdate(message string) { + if !i.respondMessage(true, false) { + return } + i.response.Data.Content = message i.respond() } -// ReplyEmbed prints the given embeds as reply to the -// user who executes the command. -func (i *InteractionUtil) ReplyEmbed(embeds ...*discordgo.MessageEmbed) { - i.response = &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Embeds: embeds, - }, - } +// ReplyUpdatef is like Replyf but make for an update for components. +func (i *InteractionUtil) ReplyUpdatef(format string, a ...any) { + i.ReplyUpdate(fmt.Sprintf(format, a...)) +} + +// ReplyHidden prints the given message as ephemeral reply to the user who executes the command. +func (i *InteractionUtil) ReplyHidden(message string) { + i.respondMessage(false, false) + i.response.Data.Content = message + i.response.Data.Flags = discordgo.MessageFlagsEphemeral i.respond() } -// ReplyHiddenf formats according to a format specifier -// and prints the result as ephemral reply to +// ReplyHiddenf formats according to a format specifier and prints the result as ephemeral reply to // the user who executes the command. func (i *InteractionUtil) ReplyHiddenf(format string, a ...any) { i.ReplyHidden(fmt.Sprintf(format, a...)) } -// ReplyHidden prints the given message as ephemral reply -// to the user who executes the command. -func (i *InteractionUtil) ReplyHidden(message string) { - i.response = &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: message, - Flags: discordgo.MessageFlagsEphemeral, - }, - } +// ReplyEmbed prints the given embeds as reply to the user who executes the command. +func (i *InteractionUtil) ReplyEmbed(embeds ...*discordgo.MessageEmbed) { + i.respondMessage(false, false) + i.response.Data.Embeds = embeds i.respond() } -// ReplyHiddenEmbed prints the given embeds as ephemral reply to the user who -// executes the command. Automatically append "hidden reply note" to -// last embed if hiddenSelf is set tot true. See AddReplyHiddenField() for more. -func (i *InteractionUtil) ReplyHiddenEmbed(hiddenSelf bool, embeds ...*discordgo.MessageEmbed) { - l := len(embeds) - if l == 0 { +// ReplyEmbedUpdate is like ReplyEmbed but make for an update for components. +func (i *InteractionUtil) ReplyEmbedUpdate(embeds ...*discordgo.MessageEmbed) { + if !i.respondMessage(true, false) { return } - if hiddenSelf { - AddReplyHiddenField(embeds[l-1]) - } + i.response.Data.Embeds = embeds + i.respond() +} - i.response = &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Embeds: embeds, - Flags: discordgo.MessageFlagsEphemeral, - }, +// ReplyHiddenEmbed prints the given embeds as ephemeral reply to the user who executes the command. +func (i *InteractionUtil) ReplyHiddenEmbed(embeds ...*discordgo.MessageEmbed) { + i.respondMessage(false, false) + i.response.Data.Embeds = embeds + i.response.Data.Flags = discordgo.MessageFlagsEphemeral + i.respond() +} + +// ReplyComponents sends a message along with the provied message components. +func (i *InteractionUtil) ReplyComponents(components []discordgo.MessageComponent, message string) { + i.respondMessage(false, false) + i.response.Data.Content = message + i.response.Data.Components = components + i.respond() +} + +// ReplyComponentsf formats according to a format specifier and sends the result along with the +// provied message components. +func (i *InteractionUtil) ReplyComponentsf(components []discordgo.MessageComponent, format string, a ...any) { + i.ReplyComponents(components, fmt.Sprintf(format, a...)) +} + +// ReplyComponentsUpdate is like ReplyComponents but make for an update for components. +func (i *InteractionUtil) ReplyComponentsUpdate(components []discordgo.MessageComponent, message string) { + if !i.respondMessage(true, false) { + return } + i.response.Data.Content = message + i.response.Data.Components = components + i.respond() +} + +// ReplyComponentsUpdatef is like ReplyComponentsf but make for an update for components. +func (i *InteractionUtil) ReplyComponentsUpdatef(components []discordgo.MessageComponent, format string, a ...any) { + i.ReplyComponentsUpdate(components, fmt.Sprintf(format, a...)) +} + +// ReplyComponentsHidden sends an ephemeral message along with the provided message components. +func (i *InteractionUtil) ReplyComponentsHidden(components []discordgo.MessageComponent, message string) { + i.respondMessage(false, false) + i.response.Data.Content = message + i.response.Data.Components = components + i.response.Data.Flags = discordgo.MessageFlagsEphemeral i.respond() } -// ReplyAutocomplete returns the given choices to -// the user. When this is called on an interaction -// type outside form an applicationCommandAutocomplete -// nothing will happen. +// ReplyComponentsHiddenf is like ReplyComponentsf but sends an ephemeral message. +func (i *InteractionUtil) ReplyComponentsHiddenf(components []discordgo.MessageComponent, format string, a ...any) { + i.ReplyComponentsHidden(components, fmt.Sprintf(format, a...)) +} + +// ReplyComponentsEmbed sends one or more embeds along with the provied message components. +func (i *InteractionUtil) ReplyComponentsEmbed(components []discordgo.MessageComponent, embeds ...*discordgo.MessageEmbed) { + i.respondMessage(false, false) + i.response.Data.Embeds = embeds + i.response.Data.Components = components + i.respond() +} + +// ReplyComponentsHiddenEmbed sends the given embeds as ephemeral reply along with the provided message +// components. +func (i *InteractionUtil) ReplyComponentsHiddenEmbed(components []discordgo.MessageComponent, embeds ...*discordgo.MessageEmbed) { + i.respondMessage(false, false) + i.response.Data.Embeds = embeds + i.response.Data.Components = components + i.response.Data.Flags = discordgo.MessageFlagsEphemeral + i.respond() +} + +// ReplyAutocomplete returns the given choices to the user. When this is called on an interaction +// type outside form an applicationCommandAutocomplete nothing will happen. func (i *InteractionUtil) ReplyAutocomplete(choices []*discordgo.ApplicationCommandOptionChoice) { if i.Interaction.Type != discordgo.InteractionApplicationCommandAutocomplete { return @@ -120,16 +262,21 @@ func (i *InteractionUtil) ReplyAutocomplete(choices []*discordgo.ApplicationComm i.respond() } -// ReplyError sends a simple message to the user to indicate, that -// something failed or unexpected happened during the execution of -// the interaction. -func (i *InteractionUtil) ReplyError() { - i.ReplyHidden("Somthing went wrong :(") -} - -func (i *InteractionUtil) respond() { - err := i.Session.InteractionRespond(i.Interaction.Interaction, i.response) - if err != nil { - log.Printf("Error while sending command response: %v\n", err) +// ReplyModal displays a modal (popup) with the specified components to the user. +// +// Params: +// +// id // To identify the modal when parsing the interaction event +// title // Displayed title of the modal +// components // One or more message components to display in this modal +func (i *InteractionUtil) ReplyModal(id, title string, components ...discordgo.MessageComponent) { + i.response = &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseModal, + Data: &discordgo.InteractionResponseData{ + CustomID: id, + Title: title, + Components: components, + }, } + i.respond() } diff --git a/util/modal.go b/util/modal.go new file mode 100644 index 0000000..3d2acc3 --- /dev/null +++ b/util/modal.go @@ -0,0 +1,163 @@ +// Copyright 2023 Kesuaheli +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package util + +import "github.com/bwmarrin/discordgo" + +// CreateButtonComponent returns a simple button component with the specified configurations. +// Params: +// +// tp // The translation prefix used to generate ids and labels +// component_group_id // A group id to generate labels +// id // Custom id to identify the button when pressed (automatically prefixed) +// style // Style of the button (see https://discord.com/developers/docs/interactions/message-components#button-object-button-styles) +// Optional: emoji // An emoji to put in the label, can be empty +func CreateButtonComponent(id, label string, style discordgo.ButtonStyle, emoji discordgo.ComponentEmoji) discordgo.Button { + return discordgo.Button{ + CustomID: id, + Label: label, + Style: style, + Emoji: emoji, + } +} + +// CreateURLButtonComponent returns a URL button component with the specified configurations. +// Params: +// +// tp // The translation prefix used to generate ids and labels +// component_group_id // A group id to generate labels +// id // Custom id to generate labels +// url // The link to open when clicked +// Optional: emoji // An emoji to put in the label, can be empty +func CreateURLButtonComponent(id, label, url string, emoji discordgo.ComponentEmoji) discordgo.Button { + return discordgo.Button{ + CustomID: id, + Label: label, + Emoji: emoji, + URL: url, + } +} + +// CreateTextInputComponent returns a text input form for modals with the specified configurations. +// Params: +// +// tp // The translation prefix used to generate ids and labels +// component_group_id // A group id to generate labels +// id // Custom id to identify the input field after submitting +// style // Single or multi line +// required // If this has to be not empty +// minLength // Minimum number of characters that has to be entered +// maxLength // Maximum number of characters that are able to be entered +func CreateTextInputComponent(id, label, placeholder, value string, style discordgo.TextInputStyle, requred bool, minLength, maxLength int) discordgo.TextInput { + return discordgo.TextInput{ + CustomID: id, + Label: label, + Style: style, + Placeholder: placeholder, + Value: value, + Required: requred, + MinLength: minLength, + MaxLength: maxLength, + } +} + +// CreateChannelSelectMenuComponent returns a channel select menu form for modals with the specified +// configurations. Params: +// +// id // Custom id to identify the input field after submitting +// placeholder // Placeholder text if nothing is selected +// minValues // Minimum number of items that must be choosen +// maxValues // Maximum number of items that can be choosen +// channelTypes // Channel types to include in the select menu +func CreateChannelSelectMenuComponent(id, placeholder string, minValues, maxValues int, channelTypes ...discordgo.ChannelType) discordgo.SelectMenu { + return discordgo.SelectMenu{ + CustomID: id, + MenuType: discordgo.ChannelSelectMenu, + Placeholder: placeholder, + MinValues: &minValues, + MaxValues: maxValues, + ChannelTypes: channelTypes, + } +} + +// CreateMentionableSelectMenuComponent returns a user and roles select menu form for modals with +// the specified configurations. Params: +// +// id // Custom id to identify the input field after submitting +// placeholder // Placeholder text if nothing is selected +// minValues // Minimum number of items that must be choosen +// maxValues // Maximum number of items that can be choosen +func CreateMentionableSelectMenuComponent(id, placeholder string, minValues, maxValues int) discordgo.SelectMenu { + return discordgo.SelectMenu{ + CustomID: id, + MenuType: discordgo.MentionableSelectMenu, + Placeholder: placeholder, + MinValues: &minValues, + MaxValues: maxValues, + } +} + +// CreateRoleSelectMenuComponent returns a roles select menu form for modals with the specified +// configurations. Params: +// +// id // Custom id to identify the input field after submitting +// placeholder // Placeholder text if nothing is selected +// minValues // Minimum number of items that must be choosen +// maxValues // Maximum number of items that can be choosen +func CreateRoleSelectMenuComponent(id, placeholder string, minValues, maxValues int) discordgo.SelectMenu { + return discordgo.SelectMenu{ + CustomID: id, + MenuType: discordgo.RoleSelectMenu, + Placeholder: placeholder, + MinValues: &minValues, + MaxValues: maxValues, + } +} + +// CreateStringSelectMenuComponent returns a string select menu form for modals with the specified +// configurations. Params: +// +// id // Custom id to identify the input field after submitting +// placeholder // Placeholder text if nothing is selected +// minValues // Minimum number of items that must be choosen +// maxValues // Maximum number of items that can be choosen +// options // Choices for the select menu +func CreateStringSelectMenuComponent(id, placeholder string, minValues, maxValues int, options ...discordgo.SelectMenuOption) discordgo.SelectMenu { + return discordgo.SelectMenu{ + CustomID: id, + MenuType: discordgo.StringSelectMenu, + Placeholder: placeholder, + MinValues: &minValues, + MaxValues: maxValues, + Options: options, + } +} + +// CreateUserSelectMenuComponent returns a user select menu form for modals with the specified +// configurations. Params: +// +// id // Custom id to identify the input field after submitting +// placeholder // Placeholder text if nothing is selected +// minValues // Minimum number of items that must be choosen +// maxValues // Maximum number of items that can be choosen +func CreateUserSelectMenuComponent(id, placeholder string, minValues, maxValues int) discordgo.SelectMenu { + return discordgo.SelectMenu{ + CustomID: id, + MenuType: discordgo.UserSelectMenu, + Placeholder: placeholder, + MinValues: &minValues, + MaxValues: maxValues, + } +} diff --git a/util/translation.go b/util/translation.go index d9fb92a..5224979 100644 --- a/util/translation.go +++ b/util/translation.go @@ -20,9 +20,8 @@ import ( "github.com/bwmarrin/discordgo" ) -// TranslateLocalization returns a pointer to a map of all -// translations for the given key from discord languages that are -// loaded in the lang package. +// TranslateLocalization returns a pointer to a map of all translations for the given key from +// discord languages that are loaded in the lang package. func TranslateLocalization(key string) *map[discordgo.Locale]string { translateMap := map[discordgo.Locale]string{} for locale := range discordgo.Locales { diff --git a/util/universal.go b/util/universal.go index 74403a9..f361ec1 100644 --- a/util/universal.go +++ b/util/universal.go @@ -14,8 +14,13 @@ package util -// ContainsInt reports whether at least one of num is at least once -// anywhere in i. +import ( + logger "log" +) + +var log = logger.New(logger.Writer(), "[Util] ", logger.LstdFlags|logger.Lmsgprefix) + +// ContainsInt reports whether at least one of num is at least once anywhere in i. func ContainsInt(i []int, num ...int) bool { for _, x := range i { for _, y := range num { @@ -27,8 +32,7 @@ func ContainsInt(i []int, num ...int) bool { return false } -// ContainsString reports whether at least one of str is at least -// once anywhere in s. +// ContainsString reports whether at least one of str is at least once anywhere in s. func ContainsString(s []string, str ...string) bool { for _, x := range s { for _, y := range str { From eb624d89add0a65f74a20099536da5aa0bcf5ae8 Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sat, 25 Nov 2023 03:08:55 +0100 Subject: [PATCH 05/10] move command and component package --- event/command/commandBase.go | 93 +++++++++++++++++++++- event/commands.go | 127 +++++-------------------------- event/component/componentBase.go | 27 ++++++- event/event.go | 11 ++- util/discord.go | 14 +++- 5 files changed, 153 insertions(+), 119 deletions(-) diff --git a/event/command/commandBase.go b/event/command/commandBase.go index 3e7bf4c..e89f770 100644 --- a/event/command/commandBase.go +++ b/event/command/commandBase.go @@ -14,7 +14,17 @@ package command -import "github.com/bwmarrin/discordgo" +import ( + "cake4everybot/modules/adventcalendar" + "cake4everybot/modules/birthday" + "cake4everybot/modules/info" + "cake4everybot/util" + "fmt" + "log" + "strings" + + "github.com/bwmarrin/discordgo" +) // Command is an interface wrapper for all commands. Including chat-comamnds (slash-commands), // message-commands, and user-commands. @@ -45,3 +55,84 @@ var CommandMap map[string]Command func init() { CommandMap = make(map[string]Command) } + +// Register registers all application commands +func Register(s *discordgo.Session, guildID string) error { + + // This is the list of commands to use. Add a command via simply appending the struct (which + // must implement the Command interface) to the list, i.e.: + // + // commandsList = append(commandsList, command.MyCommand{}) + var commandsList []Command + + // chat (slash) commands + commandsList = append(commandsList, &birthday.Chat{}) + commandsList = append(commandsList, &info.Chat{}) + commandsList = append(commandsList, &adventcalendar.Chat{}) + // messsage commands + // user commands + commandsList = append(commandsList, &birthday.UserShow{}) + + // early return when there're no commands to add, and remove all previously registered commands + if len(commandsList) == 0 { + removeUnusedCommands(s, guildID, nil) + return nil + } + + // make an array of ApplicationCommands and perform a bulk change using it + appCommandsList := make([]*discordgo.ApplicationCommand, 0, len(commandsList)) + for _, cmd := range commandsList { + appCommandsList = append(appCommandsList, cmd.AppCmd()) + CommandMap[cmd.AppCmd().Name] = cmd + } + commandNames := make([]string, 0, len(CommandMap)) + for k := range CommandMap { + commandNames = append(commandNames, k) + } + + log.Printf("Adding used commands: [%s]...\n", strings.Join(commandNames, ", ")) + createdCommands, err := s.ApplicationCommandBulkOverwrite(s.State.User.ID, guildID, appCommandsList) + if err != nil { + return fmt.Errorf("failed on bulk overwrite commands: %v", err) + } + + for _, cmd := range createdCommands { + CommandMap[cmd.Name].SetID(cmd.ID) + } + + removeUnusedCommands(s, guildID, createdCommands) + + // set the utility map + cmdIDMap := make(map[string]string) + for k, v := range CommandMap { + cmdIDMap[k] = v.GetID() + } + util.SetCommandMap(cmdIDMap) + + return err +} + +func removeUnusedCommands(s *discordgo.Session, guildID string, createdCommands []*discordgo.ApplicationCommand) { + allRegisteredCommands, err := s.ApplicationCommands(s.State.User.ID, guildID) + if err != nil { + log.Printf("Error while removing unused commands: Could not get registered commands from guild '%s'. Err: %v\n", guildID, err) + return + } + newCmdIds := make(map[string]bool) + for _, cmd := range createdCommands { + newCmdIds[cmd.ID] = true + } + + // Find unused commands by iterating over all commands and check if the ID is in the currently registered commands. If not, remove it. + for _, cmd := range allRegisteredCommands { + if !newCmdIds[cmd.ID] { + err = s.ApplicationCommandDelete(s.State.User.ID, guildID, cmd.ID) + if err != nil { + log.Printf("Error while removing unused commands: Could not delete comand '%s' ('/%s'). Err: %v\n", cmd.ID, cmd.Name, err) + continue + } + log.Printf("Removed unused command: '/%s'\n", cmd.Name) + } + } + +} diff --git a/event/commands.go b/event/commands.go index 582396c..86c350d 100644 --- a/event/commands.go +++ b/event/commands.go @@ -17,121 +17,28 @@ package event import ( "cake4everybot/event/command" "cake4everybot/event/component" - "cake4everybot/modules/adventcalendar" - "cake4everybot/modules/birthday" - "cake4everybot/modules/info" - "fmt" "strings" "github.com/bwmarrin/discordgo" ) -func registerCommands(s *discordgo.Session, guildID string) error { - - // This is the list of commands to use. Add a command via simply - // appending the struct (which must implement the interface - // command.Command) to the list, i.e.: - // - // commandsList = append(commandsList, command.MyCommand{}) - var commandsList []command.Command - - // chat (slash) commands - commandsList = append(commandsList, &birthday.Chat{}) - commandsList = append(commandsList, &info.Chat{}) - commandsList = append(commandsList, &adventcalendar.Chat{}) - // messsage commands - // user commands - commandsList = append(commandsList, &birthday.UserShow{}) - - // early return when there're no commands to add, and remove all previously registered commands - if len(commandsList) == 0 { - removeUnusedCommands(s, guildID, nil) - return nil - } - - // make an array of ApplicationCommands and perform a bulk change using it - appCommandsList := make([]*discordgo.ApplicationCommand, 0, len(commandsList)) - for _, cmd := range commandsList { - appCommandsList = append(appCommandsList, cmd.AppCmd()) - command.CommandMap[cmd.AppCmd().Name] = cmd - } - commandNames := make([]string, 0, len(command.CommandMap)) - for k := range command.CommandMap { - commandNames = append(commandNames, k) - } - - log.Printf("Adding used commands: [%s]...\n", strings.Join(commandNames, ", ")) - createdCommands, err := s.ApplicationCommandBulkOverwrite(s.State.User.ID, guildID, appCommandsList) - if err != nil { - return fmt.Errorf("failed on bulk overwrite commands: %v", err) - } - - for _, cmd := range createdCommands { - command.CommandMap[cmd.Name].SetID(cmd.ID) - } - - removeUnusedCommands(s, guildID, createdCommands) - return err -} - -func removeUnusedCommands(s *discordgo.Session, guildID string, createdCommands []*discordgo.ApplicationCommand) { - allRegisteredCommands, err := s.ApplicationCommands(s.State.User.ID, guildID) - if err != nil { - log.Printf("Error while removing unused commands: Could not get registered commands from guild '%s'. Err: %v\n", guildID, err) - return - } - newCmdIds := make(map[string]bool) - for _, cmd := range createdCommands { - newCmdIds[cmd.ID] = true - } - - // Find unused commands by iterating over all commands and check if the ID is in the currently registered commands. If not, remove it. - for _, cmd := range allRegisteredCommands { - if !newCmdIds[cmd.ID] { - err = s.ApplicationCommandDelete(s.State.User.ID, guildID, cmd.ID) - if err != nil { - log.Printf("Error while removing unused commands: Could not delete comand '%s' ('/%s'). Err: %v\n", cmd.ID, cmd.Name, err) - continue - } - log.Printf("Removed unused command: '/%s'\n", cmd.Name) +func handleInteractionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) { + switch i.Type { + case discordgo.InteractionApplicationCommand, discordgo.InteractionApplicationCommandAutocomplete: + data := i.ApplicationCommandData() + if cmd, ok := command.CommandMap[data.Name]; ok { + cmd.Handle(s, i) } - } - -} - -func registerComponents() { - // This is the list of components to use. Add a component via - // simply appending the struct (which must implement the - // interface command.Component) to the list, e.g.: - // - // componentList = append(componentList, mymodule.MyComponent{}) - var componentList []component.Component - - componentList = append(componentList, adventcalendar.Component{}) - - if len(componentList) == 0 { - return - } - for _, c := range componentList { - component.ComponentMap[c.ID()] = c - } - log.Printf("Added %d component handler(s)!", len(component.ComponentMap)) -} - -func addCommandListeners(s *discordgo.Session) { - s.AddHandler(func(s *discordgo.Session, event *discordgo.InteractionCreate) { - switch event.Type { - case discordgo.InteractionApplicationCommand, discordgo.InteractionApplicationCommandAutocomplete: - data := event.ApplicationCommandData() - if cmd, ok := command.CommandMap[data.Name]; ok { - cmd.Handle(s, event) - } - case discordgo.InteractionMessageComponent: - data := event.MessageComponentData() - if c, ok := component.ComponentMap[strings.Split(data.CustomID, ".")[0]]; ok { - c.Handle(s, event) - } + // TODO: Add seperate handler for autocomplete + //case discordgo.InteractionApplicationCommandAutocomplete: + // data := i.ApplicationCommandData() + // if cmd, ok := command.CommandMap[data.Name]; ok { + // cmd.HandleAutocomplete(s, i) + // } + case discordgo.InteractionMessageComponent: + data := i.MessageComponentData() + if c, ok := component.ComponentMap[strings.Split(data.CustomID, ".")[0]]; ok { + c.Handle(s, i) } - }) - + } } diff --git a/event/component/componentBase.go b/event/component/componentBase.go index 73b7156..dd54eb2 100644 --- a/event/component/componentBase.go +++ b/event/component/componentBase.go @@ -1,6 +1,11 @@ package component -import "github.com/bwmarrin/discordgo" +import ( + "cake4everybot/modules/adventcalendar" + "log" + + "github.com/bwmarrin/discordgo" +) // Component is an interface wrapper for all message components. type Component interface { @@ -15,3 +20,23 @@ type Component interface { // ComponentMap holds all active components. It maps them from a unique string identifier to the // corresponding Component. var ComponentMap = make(map[string]Component) + +// Register registers add message components +func Register() { + // This is the list of components to use. Add a component via + // simply appending the struct (which must implement the + // interface command.Component) to the list, e.g.: + // + // componentList = append(componentList, mymodule.MyComponent{}) + var componentList []Component + + componentList = append(componentList, adventcalendar.Component{}) + + if len(componentList) == 0 { + return + } + for _, c := range componentList { + ComponentMap[c.ID()] = c + } + log.Printf("Added %d component handler(s)!", len(ComponentMap)) +} diff --git a/event/event.go b/event/event.go index e823b25..e93f167 100644 --- a/event/event.go +++ b/event/event.go @@ -15,26 +15,29 @@ package event import ( - "github.com/bwmarrin/discordgo" + "cake4everybot/event/command" + "cake4everybot/event/component" logger "log" + + "github.com/bwmarrin/discordgo" ) var log = *logger.New(logger.Writer(), "[Events] ", logger.LstdFlags|logger.Lmsgprefix) // Register registers all events, like commands. func Register(s *discordgo.Session, guildID string) error { - err := registerCommands(s, guildID) + err := command.Register(s, guildID) if err != nil { return err } - registerComponents() + component.Register() return nil } // AddListeners adds all event handlers to the given session s. func AddListeners(s *discordgo.Session, webChan chan struct{}) { - addCommandListeners(s) + s.AddHandler(handleInteractionCreate) addVoiceStateListeners(s) addYouTubeListeners(s) diff --git a/util/discord.go b/util/discord.go index 136e2cd..59778e9 100644 --- a/util/discord.go +++ b/util/discord.go @@ -18,12 +18,19 @@ import ( "fmt" "cake4everybot/data/lang" - "cake4everybot/event/command" "github.com/bwmarrin/discordgo" "github.com/spf13/viper" ) +var commandIDMap map[string]string + +// SetCommandMap sets the map from command names to ther registered ID. +// TODO: move the original command.CommandMap in a seperate Package to avoid this. +func SetCommandMap(m map[string]string) { + commandIDMap = m +} + // AuthoredEmbed returns a new Embed with an author and footer set. // // author: @@ -92,10 +99,11 @@ func AddReplyHiddenField(e *discordgo.MessageEmbed) { // MentionCommand returns the mention string for a slashcommand func MentionCommand(base string, subcommand ...string) string { cBase := lang.GetDefault(base) - if command.CommandMap[cBase] == nil { + + cID := commandIDMap[cBase] + if cID == "" { return "" } - cID := command.CommandMap[cBase].GetID() var cSub string for _, sub := range subcommand { From 950ce5f74ce044bb6203eea7cd594cf68e8b8451 Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sun, 26 Nov 2023 03:06:08 +0100 Subject: [PATCH 06/10] added advent calendar functionality --- config.yaml | 3 + data/lang/de.yaml | 12 ++ data/lang/en.yaml | 12 ++ event/commands.go | 2 + event/scheduledTriggers.go | 4 +- modules/adventcalendar/adventcalendarbase.go | 16 +++ modules/adventcalendar/chatCommand.go | 38 +++++- modules/adventcalendar/component.go | 70 +++++++++- modules/adventcalendar/database.go | 127 +++++++++++++++++++ modules/adventcalendar/midnight.go | 57 +++++++++ modules/adventcalendar/post.go | 99 +++++++++++++++ util/discord.go | 39 ++++++ util/interaction.go | 56 ++++++++ util/universal.go | 21 +++ 14 files changed, 553 insertions(+), 3 deletions(-) create mode 100644 modules/adventcalendar/database.go create mode 100644 modules/adventcalendar/midnight.go create mode 100644 modules/adventcalendar/post.go diff --git a/config.yaml b/config.yaml index 51d3c87..998f2aa 100644 --- a/config.yaml +++ b/config.yaml @@ -31,5 +31,8 @@ event: morning_hour: 8 morning_minute: 0 + adventcalendar: + images: data/images/adventcalendar + webserver: favicon: webserver/favicon.png diff --git a/data/lang/de.yaml b/data/lang/de.yaml index 6e2e17f..5656f07 100644 --- a/data/lang/de.yaml +++ b/data/lang/de.yaml @@ -116,6 +116,18 @@ discord.command: base: adventskalender base.description: Admin Commands für das Adventskalender Giveaway +module: + adventcalendar: + post.message: Noch %d Mal schlafen bis Heilig Abend! Heute öffnet sich das **Türchen %d**. + post.message.day_23: Fast geschafft! Nur noch einmal schlafen! + post.message.day_24: Ho Ho Ho! Heute ist Heilig Abend! + post.message2: Klicke unten auf den Knopf um dich im Gewinnspiel einzutragen! + post.button: Gewinnspiel + + enter.invalid: Das ist eine alte Nachricht, du kannst dich hier nicht mehr eintragen! + enter.success: Du hast dich erfolgreich eingetragen! Du hast jetzt %d Tickets. + enter.already_entered: Du hast dich heute bereits eingetragen! (Du hast momentan %d Tickets) + youtube: embed_footer: YouTube Glocke msg.new_vid: "%s hat ein neues Video hochgeladen" diff --git a/data/lang/en.yaml b/data/lang/en.yaml index b9dc803..17b3fae 100644 --- a/data/lang/en.yaml +++ b/data/lang/en.yaml @@ -116,6 +116,18 @@ discord.command: base: adventcalendar base.description: Admin commands for the Advent Calendar Giveaway +module: + adventcalendar: + post.message: Just sleep %d more times! Its time for **door %d**. + post.message.day_23: Almost there! Just sleep once more! + post.message.day_24: Ho Ho Ho! Its Christmas Eve! + post.message2: Click the button below to join the Giveaway! + post.button: Join + + enter.invalid: This is an old message, you cannot join here anymore! + enter.success: You successfully joined! You know have %d tickets. + enter.already_entered: You already joined for today. (You have %d tickets) + youtube: embed_footer: YouTube notification bell msg.new_vid: "%s just uploaded a new video" diff --git a/event/commands.go b/event/commands.go index 86c350d..78f199b 100644 --- a/event/commands.go +++ b/event/commands.go @@ -39,6 +39,8 @@ func handleInteractionCreate(s *discordgo.Session, i *discordgo.InteractionCreat data := i.MessageComponentData() if c, ok := component.ComponentMap[strings.Split(data.CustomID, ".")[0]]; ok { c.Handle(s, i) + } else { + log.Printf("got component interaction from unknown module '%s' (full id '%s')", strings.Split(data.CustomID, ".")[0], data.CustomID) } } } diff --git a/event/scheduledTriggers.go b/event/scheduledTriggers.go index 1a98e78..5cb2ab0 100644 --- a/event/scheduledTriggers.go +++ b/event/scheduledTriggers.go @@ -15,6 +15,7 @@ package event import ( + "cake4everybot/modules/adventcalendar" "cake4everybot/modules/birthday" webYT "cake4everybot/webserver/youtube" @@ -26,11 +27,12 @@ import ( func addScheduledTriggers(s *discordgo.Session, webChan chan struct{}) { go scheduleFunction(s, 0, 0, - birthday.Check, + adventcalendar.Midnight, ) go scheduleFunction(s, viper.GetInt("event.morning_hour"), viper.GetInt("event.morning_minute"), birthday.Check, + adventcalendar.Post, ) go refreshYoutube(webChan) diff --git a/modules/adventcalendar/adventcalendarbase.go b/modules/adventcalendar/adventcalendarbase.go index b3cc27e..86e21d1 100644 --- a/modules/adventcalendar/adventcalendarbase.go +++ b/modules/adventcalendar/adventcalendarbase.go @@ -16,7 +16,9 @@ package adventcalendar import ( "cake4everybot/util" + "fmt" logger "log" + "time" "github.com/bwmarrin/discordgo" ) @@ -34,3 +36,17 @@ type adventcalendarBase struct { member *discordgo.Member user *discordgo.User } + +type giveawayEntry struct { + userID string + weight int + lastEntry time.Time +} + +func (e giveawayEntry) toEmbedField(s *discordgo.Session) (f *discordgo.MessageEmbedField) { + return &discordgo.MessageEmbedField{ + Name: e.userID, + Value: fmt.Sprintf("<@%s>\n%d tickets\nlast entry: %s", e.userID, e.weight, e.lastEntry.Format(time.DateOnly)), + Inline: true, + } +} diff --git a/modules/adventcalendar/chatCommand.go b/modules/adventcalendar/chatCommand.go index 18e4421..7fe9af5 100644 --- a/modules/adventcalendar/chatCommand.go +++ b/modules/adventcalendar/chatCommand.go @@ -1,3 +1,17 @@ +// Copyright 2023 Kesuaheli +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package adventcalendar import ( @@ -20,6 +34,18 @@ func (Chat) AppCmd() *discordgo.ApplicationCommand { NameLocalizations: util.TranslateLocalization(tp + "base"), Description: lang.GetDefault(tp + "base.description"), DescriptionLocalizations: util.TranslateLocalization(tp + "base.description"), + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionSubCommand, + Name: "midnight", + Description: "Midnight trigger", + }, + { + Type: discordgo.ApplicationCommandOptionSubCommand, + Name: "morning", + Description: "Morning trigger", + }, + }, } } @@ -34,7 +60,17 @@ func (cmd Chat) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { cmd.member = &discordgo.Member{User: i.User} } - log.Print("currently unused command") + switch i.ApplicationCommandData().Options[0].Name { + case "midnight": + Midnight(s) + cmd.ReplyHidden("Midnight()") + return + case "morning": + Post(s) + cmd.ReplyHidden("Post()") + return + } + } // SetID sets the registered command ID for internal uses after uploading to discord diff --git a/modules/adventcalendar/component.go b/modules/adventcalendar/component.go index 465769a..6ad2bcc 100644 --- a/modules/adventcalendar/component.go +++ b/modules/adventcalendar/component.go @@ -1,7 +1,25 @@ +// Copyright 2023 Kesuaheli +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + package adventcalendar import ( + "cake4everybot/data/lang" "cake4everybot/util" + "fmt" + "strings" + "time" "github.com/bwmarrin/discordgo" ) @@ -9,11 +27,11 @@ import ( // The Component of the advent calendar package. type Component struct { adventcalendarBase + data discordgo.MessageComponentInteractionData } // Handle handles the functionality of a component. func (c Component) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) { - c.InteractionUtil = util.InteractionUtil{Session: s, Interaction: i} c.member = i.Member c.user = i.User @@ -22,9 +40,59 @@ func (c Component) Handle(s *discordgo.Session, i *discordgo.InteractionCreate) } else if i.User != nil { c.member = &discordgo.Member{User: i.User} } + c.data = i.MessageComponentData() + + ids := strings.Split(c.data.CustomID, ".") + // pop the first level identifier + util.ShiftL(ids) + + switch util.ShiftL(ids) { + case "post": + c.handlePost(s, ids) + return + default: + log.Printf("Unknown component interaction ID: %s", c.data.CustomID) + } } // ID returns the custom ID of the modal to identify the module func (Component) ID() string { return "adventcalendar" } + +func (c *Component) handlePost(s *discordgo.Session, ids []string) { + var ( + buttonYear = util.ShiftL(ids) + buttonMonth = util.ShiftL(ids) + buttonDay = util.ShiftL(ids) + ) + timeValue := fmt.Sprintf("%s-%s-%s", buttonYear, buttonMonth, buttonDay) + postTime, err := time.Parse(time.DateOnly, timeValue) + if err != nil { + log.Printf("ERROR: could not parse date: %s: %+v", timeValue, err) + c.ReplyError() + return + } + + if now := time.Now(); now.Year() != postTime.Year() || + now.Month() != postTime.Month() || + now.Day() != postTime.Day() { + c.ReplyHiddenSimpleEmbedf(0xFF0000, lang.GetDefault("module.adventcalendar.enter.invalid")) + return + } + + entry := getEntry(c.user.ID) + if entry.userID != c.user.ID { + log.Printf("ERROR: getEntry() returned with userID '%s' but want '%s'", entry.userID, c.user.ID) + c.ReplyError() + return + } + if entry.lastEntry.Equal(postTime) { + c.ReplyHiddenSimpleEmbedf(0x5865f2, lang.GetDefault("module.adventcalendar.enter.already_entered"), entry.weight) + return + } + + entry = addGiveawayWeight(c.user.ID, 1) + + c.ReplyHiddenSimpleEmbedf(0x00FF00, lang.GetDefault("module.adventcalendar.enter.success"), entry.weight) +} diff --git a/modules/adventcalendar/database.go b/modules/adventcalendar/database.go new file mode 100644 index 0000000..22cedd8 --- /dev/null +++ b/modules/adventcalendar/database.go @@ -0,0 +1,127 @@ +// Copyright 2023 Kesuaheli +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package adventcalendar + +import ( + "cake4everybot/database" + "database/sql" + "fmt" + "strings" + "time" +) + +func getEntry(userID string) giveawayEntry { + var ( + weight int + lastEntryID string + ) + err := database.QueryRow("SELECT weight,last_entry_id FROM giveaway WHERE id=?", userID).Scan(&weight, &lastEntryID) + if err == sql.ErrNoRows { + return giveawayEntry{userID: userID, weight: 0} + } + if err != nil { + log.Printf("Database failed to get giveaway entries for '%s': %+v", userID, err) + return giveawayEntry{} + } + + if lastEntryID == "" { + return giveawayEntry{userID: userID, weight: weight} + } + + dateValue, ok := strings.CutPrefix(lastEntryID, "xmas-") + if !ok { + return giveawayEntry{} + } + + lastEntry, err := time.Parse(time.DateOnly, dateValue) + if err != nil { + log.Printf("could not convert last_entry_id '%s' to time: %+v", lastEntryID, err) + return giveawayEntry{} + } + return giveawayEntry{userID, weight, lastEntry} +} + +func addGiveawayWeight(userID string, amount int) giveawayEntry { + var weight int + var new bool + err := database.QueryRow("SELECT weight FROM giveaway WHERE id=?", userID).Scan(&weight) + if err == sql.ErrNoRows { + new = true + } else if err != nil { + log.Printf("Database failed to get giveaway weight for '%s': %+v", userID, err) + return giveawayEntry{} + } + + weight += amount + dateValue := time.Now().Format(time.DateOnly) + lastEntryID := fmt.Sprintf("xmas-%s", dateValue) + lastEntry, _ := time.Parse(time.DateOnly, dateValue) + + if new { + _, err = database.Exec("INSERT INTO giveaway (id,weight,last_entry_id) VALUES (?,?,?)", userID, weight, lastEntryID) + if err != nil { + log.Printf("Database failed to insert giveaway for '%s': %+v", userID, err) + return giveawayEntry{} + } + return giveawayEntry{userID, weight, lastEntry} + } + _, err = database.Exec("UPDATE giveaway SET weight=?,last_entry_id=? WHERE id=?", weight, lastEntryID, userID) + if err != nil { + log.Printf("Database failed to update weight (new: %d) for '%s': %+v", weight, userID, err) + return giveawayEntry{} + } + return giveawayEntry{userID, weight, lastEntry} +} + +func getGetAllEntries() []giveawayEntry { + rows, err := database.Query("SELECT id,weight,last_entry_id FROM giveaway") + if err != nil { + log.Printf("ERROR: could not get entries from database: %+v", err) + return []giveawayEntry{} + } + defer rows.Close() + + var entries []giveawayEntry + for rows.Next() { + var ( + userID string + weight int + lastEntryID string + ) + err = rows.Scan(&userID, &weight, &lastEntryID) + if err != nil { + log.Printf("Warning: could not scan variables from row") + continue + } + + if lastEntryID == "" { + entries = append(entries, giveawayEntry{userID: userID, weight: weight}) + continue + } + + dateValue, ok := strings.CutPrefix(lastEntryID, "xmas-") + if !ok { + continue + } + + lastEntry, err := time.Parse(time.DateOnly, dateValue) + if err != nil { + log.Printf("ERROR: could not convert last_entry_id '%s' to time: %+v", lastEntryID, err) + continue + } + entries = append(entries, giveawayEntry{userID, weight, lastEntry}) + } + return entries +} diff --git a/modules/adventcalendar/midnight.go b/modules/adventcalendar/midnight.go new file mode 100644 index 0000000..d9e6747 --- /dev/null +++ b/modules/adventcalendar/midnight.go @@ -0,0 +1,57 @@ +// Copyright 2023 Kesuaheli +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package adventcalendar + +import ( + "cake4everybot/util" + "time" + + "github.com/bwmarrin/discordgo" +) + +// Midnight is a scheduled function to run everyday at 0:00 +func Midnight(s *discordgo.Session) { + t := time.Now() + if t.Month() != 12 || t.Day() > 24 { + return + } + log.Printf("New Post for %s", t.Format("_2. Jan")) + + var fields []*discordgo.MessageEmbedField + for _, e := range getGetAllEntries() { + fields = append(fields, e.toEmbedField(s)) + } + + data := &discordgo.MessageSend{ + Embeds: []*discordgo.MessageEmbed{{ + Title: "Current Tickets", + Fields: fields, + }}, + } + + channels, err := util.GetChannelsFromDatabase(s, "log_channel") + if err != nil { + log.Printf("ERROR: Could not get advent calendar channel: %+v", err) + return + } + + for _, channelID := range channels { + _, err = s.ChannelMessageSendComplex(channelID, data) + if err != nil { + log.Printf("ERROR: could not send log message to channel '%s': %+v", channelID, err) + continue + } + } +} diff --git a/modules/adventcalendar/post.go b/modules/adventcalendar/post.go new file mode 100644 index 0000000..4fdf594 --- /dev/null +++ b/modules/adventcalendar/post.go @@ -0,0 +1,99 @@ +// Copyright 2023 Kesuaheli +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package adventcalendar + +import ( + "cake4everybot/data/lang" + "cake4everybot/util" + "fmt" + "os" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/spf13/viper" +) + +// Post is a scheduled function to run everyday at 8:00 +func Post(s *discordgo.Session) { + t := time.Now() + if t.Month() != 12 || t.Day() > 24 { + return + } + log.Printf("New Post for %s", t.Format("_2. Jan")) + + channels, err := util.GetChannelsFromDatabase(s, "adventcalendar_channel") + if err != nil { + log.Printf("ERROR: Could not get advent calendar channel: %+v", err) + return + } + + data := postData(t) + if data == nil { + log.Printf("ERROR: Message data for new post is nil!") + return + } + for _, channelID := range channels { + _, err = s.ChannelMessageSendComplex(channelID, data) + if err != nil { + log.Printf("Failed to send new post for advent calendar in channel '%s': %+v", channelID, err) + return + } + } +} + +func postData(t time.Time) *discordgo.MessageSend { + var line1 string + if t.Day() == 23 && t.Month() == 12 { + line1 = lang.GetDefault("module.adventcalendar.post.message.day_23") + } else if t.Day() == 24 && t.Month() == 12 { + line1 = lang.GetDefault("module.adventcalendar.post.message.day_24") + } else { + format := lang.GetDefault("module.adventcalendar.post.message") + line1 = fmt.Sprintf(format, 24-t.Day(), t.Day()) + } + line2 := lang.GetDefault("module.adventcalendar.post.message2") + message := fmt.Sprintf("%s\n%s", line1, line2) + + components := []discordgo.MessageComponent{ + discordgo.ActionsRow{Components: []discordgo.MessageComponent{ + util.CreateButtonComponent( + fmt.Sprintf("%s.post.%s", Component.ID(Component{}), t.Format("2006.01.02")), + lang.GetDefault("module.adventcalendar.post.button"), + discordgo.PrimaryButton, + discordgo.ComponentEmoji{ID: "1061438652179300373"}, + ), + }}, + } + + filepath := fmt.Sprintf("%s/%d.png", viper.GetString("event.adventcalendar.images"), t.Day()) + log.Printf("image path: %s", filepath) + file, err := os.OpenFile(filepath, os.O_RDONLY, os.ModePerm) + if err != nil { + log.Printf("Failed to open advent calendar image: %+v", err) + return nil + } + + return &discordgo.MessageSend{ + Content: message, + Components: components, + Files: []*discordgo.File{ + { + Name: fmt.Sprintf("c4e_advent_calendar_%d.png", t.Day()), + ContentType: "image/png", + Reader: file, + }, + }, + } +} diff --git a/util/discord.go b/util/discord.go index 59778e9..4724f92 100644 --- a/util/discord.go +++ b/util/discord.go @@ -18,6 +18,7 @@ import ( "fmt" "cake4everybot/data/lang" + "cake4everybot/database" "github.com/bwmarrin/discordgo" "github.com/spf13/viper" @@ -112,3 +113,41 @@ func MentionCommand(base string, subcommand ...string) string { return fmt.Sprintf("", cBase, cSub, cID) } + +// GetChannelsFromDatabase returns a map from guild IDs to channel IDs +func GetChannelsFromDatabase(s *discordgo.Session, channelName string) (map[string]string, error) { + rows, err := database.Query("SELECT id," + channelName + " FROM guilds") + if err != nil { + return nil, err + } + defer rows.Close() + + IDMap := map[string]string{} + for rows.Next() { + var guildInt, channelInt uint64 + err := rows.Scan(&guildInt, &channelInt) + if err != nil { + return nil, err + } + if channelInt == 0 { + continue + } + guildID := fmt.Sprint(guildInt) + channelID := fmt.Sprint(channelInt) + + // validate channel + channel, err := s.Channel(channelID) + if err != nil { + log.Printf("Warning: could not get %s channel for id '%s: %+v\n", channelName, channelID, err) + continue + } + if channel.GuildID != guildID { + log.Printf("Warning: tried to get %s channel (from channel/%s/%s), but this channel is from guild: '%s'\n", channelName, guildID, channelID, channel.GuildID) + continue + } + + IDMap[guildID] = channelID + } + + return IDMap, nil +} diff --git a/util/interaction.go b/util/interaction.go index 5da4284..3d5d3a8 100644 --- a/util/interaction.go +++ b/util/interaction.go @@ -193,6 +193,62 @@ func (i *InteractionUtil) ReplyComponents(components []discordgo.MessageComponen i.respond() } +// ReplySimpleEmbed is a shortcut for replying with a simple embed that only contains a single text +// and has a color. +func (i *InteractionUtil) ReplySimpleEmbed(color int, content string) { + e := &discordgo.MessageEmbed{ + Description: content, + Color: color, + } + i.ReplyEmbed(e) +} + +// ReplySimpleEmbedf formats according to a format specifier and is a shortcut for replying with a +// simple embed that only contains a single text and has a color. +func (i *InteractionUtil) ReplySimpleEmbedf(color int, format string, a ...any) { + e := &discordgo.MessageEmbed{ + Description: fmt.Sprintf(format, a...), + Color: color, + } + i.ReplyEmbed(e) +} + +// ReplyHiddenSimpleEmbed is like ReplySimpleEmbed but also ephemeral. +func (i *InteractionUtil) ReplyHiddenSimpleEmbed(color int, content string) { + e := &discordgo.MessageEmbed{ + Description: content, + Color: color, + } + i.ReplyHiddenEmbed(e) +} + +// ReplyHiddenSimpleEmbedf is like ReplySimpleEmbedf but also ephemeral. +func (i *InteractionUtil) ReplyHiddenSimpleEmbedf(color int, format string, a ...any) { + e := &discordgo.MessageEmbed{ + Description: fmt.Sprintf(format, a...), + Color: color, + } + i.ReplyHiddenEmbed(e) +} + +// ReplySimpleEmbedUpdate is like ReplySimpleEmbed but make for an update for components. +func (i *InteractionUtil) ReplySimpleEmbedUpdate(color int, content string) { + e := &discordgo.MessageEmbed{ + Description: content, + Color: color, + } + i.ReplyEmbedUpdate(e) +} + +// ReplySimpleEmbedUpdatef is like ReplySimpleEmbedf but make for an update for components. +func (i *InteractionUtil) ReplySimpleEmbedUpdatef(color int, format string, a ...any) { + e := &discordgo.MessageEmbed{ + Description: fmt.Sprintf(format, a...), + Color: color, + } + i.ReplyEmbedUpdate(e) +} + // ReplyComponentsf formats according to a format specifier and sends the result along with the // provied message components. func (i *InteractionUtil) ReplyComponentsf(components []discordgo.MessageComponent, format string, a ...any) { diff --git a/util/universal.go b/util/universal.go index f361ec1..ba67796 100644 --- a/util/universal.go +++ b/util/universal.go @@ -54,3 +54,24 @@ func Btoi(b bool) int { } return 0 } + +// ShiftL takes a slice and shifts all elements to the left. The first element pops out and is +// returned. If s is an empty slice the zero value of the given type is returned. If t is given it +// will be inserted at the last position instead of an element with its zero value. +func ShiftL[T any](s []T, t ...T) (first T) { + for i, v := range s { + if i == 0 { + first = v + continue + } + s[i-1] = s[i] + if i == len(s)-1 { + var last T + if len(t) > 0 { + last = t[0] + } + s[i] = last + } + } + return first +} From 5ccb9c6161516ad34d4e9d5d81c65de73e1fc4d7 Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sun, 26 Nov 2023 13:13:55 +0100 Subject: [PATCH 07/10] updated go to 1.21 --- .github/workflows/gotest.yml | 2 +- go.mod | 4 ++-- go.sum | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/.github/workflows/gotest.yml b/.github/workflows/gotest.yml index b567955..ca8c4b5 100644 --- a/.github/workflows/gotest.yml +++ b/.github/workflows/gotest.yml @@ -18,7 +18,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: "1.20" + go-version: "1.21" - name: Set up dependencies run: go mod download diff --git a/go.mod b/go.mod index f45cddd..9bc4292 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module cake4everybot -go 1.20 +go 1.21 require ( github.com/bwmarrin/discordgo v0.27.1 @@ -22,7 +22,7 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/subosito/gotenv v1.6.0 // indirect golang.org/x/crypto v0.13.0 // indirect - golang.org/x/sys v0.12.0 // indirect + golang.org/x/sys v0.14.0 // indirect golang.org/x/text v0.13.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 9bcf873..dfedd1a 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= +github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -102,6 +103,7 @@ github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -137,9 +139,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -153,6 +157,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= @@ -315,8 +320,8 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= From 93c9753e37f8d4efbc8570787655b980c105a6db Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sun, 26 Nov 2023 13:31:35 +0100 Subject: [PATCH 08/10] sorted entries list --- modules/adventcalendar/adventcalendarbase.go | 2 +- modules/adventcalendar/midnight.go | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/modules/adventcalendar/adventcalendarbase.go b/modules/adventcalendar/adventcalendarbase.go index 86e21d1..b2a83cb 100644 --- a/modules/adventcalendar/adventcalendarbase.go +++ b/modules/adventcalendar/adventcalendarbase.go @@ -43,7 +43,7 @@ type giveawayEntry struct { lastEntry time.Time } -func (e giveawayEntry) toEmbedField(s *discordgo.Session) (f *discordgo.MessageEmbedField) { +func (e giveawayEntry) toEmbedField() (f *discordgo.MessageEmbedField) { return &discordgo.MessageEmbedField{ Name: e.userID, Value: fmt.Sprintf("<@%s>\n%d tickets\nlast entry: %s", e.userID, e.weight, e.lastEntry.Format(time.DateOnly)), diff --git a/modules/adventcalendar/midnight.go b/modules/adventcalendar/midnight.go index d9e6747..8493f9a 100644 --- a/modules/adventcalendar/midnight.go +++ b/modules/adventcalendar/midnight.go @@ -16,6 +16,7 @@ package adventcalendar import ( "cake4everybot/util" + "slices" "time" "github.com/bwmarrin/discordgo" @@ -29,9 +30,24 @@ func Midnight(s *discordgo.Session) { } log.Printf("New Post for %s", t.Format("_2. Jan")) + entries := getGetAllEntries() + slices.SortFunc(entries, func(a, b giveawayEntry) int { + if a.weight < b.weight { + return -1 + } else if a.weight > b.weight { + return 1 + } + if a.lastEntry.Before(b.lastEntry) { + return -1 + } else if a.lastEntry.After(b.lastEntry) { + return 1 + } + return 0 + }) + slices.Reverse(entries) var fields []*discordgo.MessageEmbedField - for _, e := range getGetAllEntries() { - fields = append(fields, e.toEmbedField(s)) + for _, e := range entries { + fields = append(fields, e.toEmbedField()) } data := &discordgo.MessageSend{ From c9ab3a59c95e85e2248369c456c1853381b58632 Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sun, 26 Nov 2023 13:37:26 +0100 Subject: [PATCH 09/10] changed order of workflow --- .github/workflows/gotest.yml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/gotest.yml b/.github/workflows/gotest.yml index ca8c4b5..0e2b300 100644 --- a/.github/workflows/gotest.yml +++ b/.github/workflows/gotest.yml @@ -21,25 +21,22 @@ jobs: go-version: "1.21" - name: Set up dependencies - run: go mod download - - - name: Build - run: go build -v ./... + run: | + go mod download + go install honnef.co/go/tools/cmd/staticcheck@latest + go install golang.org/x/lint/golint@latest - name: Run go vet run: go vet -v ./... - - name: Install staticcheck - run: go install honnef.co/go/tools/cmd/staticcheck@latest - - name: Run staticcheck run: staticcheck ./... - - name: Install golint - run: go install golang.org/x/lint/golint@latest - - name: Run golint run: golint -set_exit_status ./... + - name: Build + run: go build -v ./... + - name: Run tests run: go test -v -race -vet=off ./... From 9b7e85b9c23c04904306cec876c9e2fecb895145 Mon Sep 17 00:00:00 2001 From: Kesuaheli Date: Sun, 26 Nov 2023 15:12:00 +0100 Subject: [PATCH 10/10] made emoji configurable --- config.yaml | 6 ++++++ modules/adventcalendar/post.go | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/config.yaml b/config.yaml index 998f2aa..ccbf3b5 100644 --- a/config.yaml +++ b/config.yaml @@ -33,6 +33,12 @@ event: adventcalendar: images: data/images/adventcalendar + # Name: The name of this emoji, e.g. '🎅', '❤️' when a default emoji + # ID: The snowflake ID if when a custom emoji + # Animated: Whether this emoji is animated. Defaults to false + emoji.name: ✅ + #emoji.id: + #emoji.animated: true webserver: favicon: webserver/favicon.png diff --git a/modules/adventcalendar/post.go b/modules/adventcalendar/post.go index 4fdf594..760e839 100644 --- a/modules/adventcalendar/post.go +++ b/modules/adventcalendar/post.go @@ -72,7 +72,11 @@ func postData(t time.Time) *discordgo.MessageSend { fmt.Sprintf("%s.post.%s", Component.ID(Component{}), t.Format("2006.01.02")), lang.GetDefault("module.adventcalendar.post.button"), discordgo.PrimaryButton, - discordgo.ComponentEmoji{ID: "1061438652179300373"}, + discordgo.ComponentEmoji{ + Name: viper.GetString("module.adventcalendar.post.emoji.name"), + ID: viper.GetString("module.adventcalendar.post.emoji.id"), + Animated: viper.GetBool("module.adventcalendar.post.emoji.animated"), + }, ), }}, }