From e446ae85d2bc724ffbbc987e9b944d0b6282a2d4 Mon Sep 17 00:00:00 2001 From: jo_kil Date: Wed, 3 Jan 2024 01:19:18 +0100 Subject: [PATCH 01/20] simple ui images & fix disconect bug --- EsefexApi/api/public/simpleui/index.css | 45 ++++++++++++++++++- EsefexApi/api/public/simpleui/index.html | 4 +- EsefexApi/api/public/simpleui/index.js | 14 +++++- .../discordplayer/discordplayer.go | 2 +- .../audioprocessing/s16leReferenceReader.go | 4 +- EsefexApi/docker-compose.yml | 1 + 6 files changed, 64 insertions(+), 6 deletions(-) diff --git a/EsefexApi/api/public/simpleui/index.css b/EsefexApi/api/public/simpleui/index.css index f9f389a..58f26ed 100644 --- a/EsefexApi/api/public/simpleui/index.css +++ b/EsefexApi/api/public/simpleui/index.css @@ -4,4 +4,47 @@ body { font-size: 14px; line-height: 1.42857143; color: #333; -} \ No newline at end of file +} + +button { + margin-top: 10px; + height: 2rem; + /* width: 10rem; */ + font-size: 1rem; + color: #333; + /* background-color: #f5f5f5; */ + border: 1px solid #929292; + border-radius: 3px; + margin: 0.25rem; + transition: 0.2s ease-in-out; +} + +button:hover { + background-color: rgb(192, 192, 192); +} + +button:active { + background-color: rgb(94, 94, 94); +} + +.sfxButton { + display: flex; + flex-direction: row; + align-items: center; +} + +#sounds { + display: flex; + flex-direction: row; + flex-wrap: wrap; +} + +.icon { + height: 1.5rem; + width: 1.5rem; +} + +.label { + margin-left: 0.5rem; +} + diff --git a/EsefexApi/api/public/simpleui/index.html b/EsefexApi/api/public/simpleui/index.html index cc33127..f05ac27 100644 --- a/EsefexApi/api/public/simpleui/index.html +++ b/EsefexApi/api/public/simpleui/index.html @@ -11,7 +11,9 @@

Esefex Simple UI

Simple UI for Esefex for Dev Purposes

- +

Sounds

diff --git a/EsefexApi/api/public/simpleui/index.js b/EsefexApi/api/public/simpleui/index.js index f9fd6f2..98f8469 100644 --- a/EsefexApi/api/public/simpleui/index.js +++ b/EsefexApi/api/public/simpleui/index.js @@ -22,7 +22,19 @@ async function init() { sounds.forEach(sound => { let soundButton = document.createElement('button'); - soundButton.innerText = sound.name; + soundButton.classList.add('sfxButton'); + + let buttonImage = document.createElement('img'); + buttonImage.src = sound.icon.url; + buttonImage.alt = sound.icon.name; + buttonImage.classList.add('icon'); + soundButton.appendChild(buttonImage); + + let buttonLabel = document.createElement('p'); + buttonLabel.innerText = sound.name; + buttonLabel.classList.add('label'); + soundButton.appendChild(buttonLabel); + soundButton.addEventListener('click', async () => { await fetch(`/api/playsound/${sound.id}`, { method: 'POST', diff --git a/EsefexApi/audioplayer/discordplayer/discordplayer.go b/EsefexApi/audioplayer/discordplayer/discordplayer.go index 6ce4f74..40c19c8 100644 --- a/EsefexApi/audioplayer/discordplayer/discordplayer.go +++ b/EsefexApi/audioplayer/discordplayer/discordplayer.go @@ -50,7 +50,7 @@ func NewDiscordPlayer(ds *discordgo.Session, dbs *db.Databases, useTimeouts bool ds.AddHandler(func(s *discordgo.Session, e *discordgo.VoiceStateUpdate) { // check if previous state has a vcon associated with it and close it, make sure that it is not closed twice - if e.BeforeUpdate == nil || e.ChannelID != "" { + if e.BeforeUpdate == nil || e.ChannelID != "" || e.UserID != s.State.User.ID { return } diff --git a/EsefexApi/audioprocessing/s16leReferenceReader.go b/EsefexApi/audioprocessing/s16leReferenceReader.go index 6593392..78dd416 100644 --- a/EsefexApi/audioprocessing/s16leReferenceReader.go +++ b/EsefexApi/audioprocessing/s16leReferenceReader.go @@ -2,7 +2,7 @@ package audioprocessing import ( "io" - "log" + // "log" ) type S16leReferenceReader struct { @@ -12,7 +12,7 @@ type S16leReferenceReader struct { func (s *S16leReferenceReader) Read(p []byte) (n int, err error) { if s.cursor >= len(*s.data)*2 { - log.Println("EOF") + // log.Println("EOF") return 0, io.EOF } diff --git a/EsefexApi/docker-compose.yml b/EsefexApi/docker-compose.yml index 4e298ca..659188e 100644 --- a/EsefexApi/docker-compose.yml +++ b/EsefexApi/docker-compose.yml @@ -7,5 +7,6 @@ services: - "8080:8080" volumes: - db-data:/api/data + restart: always volumes: db-data: {} \ No newline at end of file From 33e11a144cdafee8fdd66ecb5d790dafa04a886f Mon Sep 17 00:00:00 2001 From: jo_kil Date: Wed, 3 Jan 2024 18:51:21 +0100 Subject: [PATCH 02/20] timing functions for delay investigation --- EsefexApi/api/routes/postplaysound.go | 5 +- .../audioplayer/discordplayer/ensurevcon.go | 9 ++ .../audioplayer/discordplayer/playsound.go | 5 ++ .../audioplayer/discordplayer/register.go | 5 +- .../audioplayer/discordplayer/vcon/vcon.go | 83 ++++++++++--------- EsefexApi/timer/timer.go | 53 ++++++++++++ 6 files changed, 120 insertions(+), 40 deletions(-) create mode 100644 EsefexApi/timer/timer.go diff --git a/EsefexApi/api/routes/postplaysound.go b/EsefexApi/api/routes/postplaysound.go index 536f613..177b41d 100644 --- a/EsefexApi/api/routes/postplaysound.go +++ b/EsefexApi/api/routes/postplaysound.go @@ -1,6 +1,7 @@ package routes import ( + "esefexapi/timer" "fmt" "io" "log" @@ -11,7 +12,8 @@ import ( // api/playsound/ func (h *RouteHandlers) PostPlaySound(w http.ResponseWriter, r *http.Request, userID string) { - log.Printf("got /playsound request\n") + // log.Printf("got /playsound request\n") + timer.SetStart() vars := mux.Vars(r) sound_id := vars["sound_id"] @@ -26,4 +28,5 @@ func (h *RouteHandlers) PostPlaySound(w http.ResponseWriter, r *http.Request, us } io.WriteString(w, "Play sound!\n") + timer.MessageElapsed("Played sound") } diff --git a/EsefexApi/audioplayer/discordplayer/ensurevcon.go b/EsefexApi/audioplayer/discordplayer/ensurevcon.go index e67db1e..be71946 100644 --- a/EsefexApi/audioplayer/discordplayer/ensurevcon.go +++ b/EsefexApi/audioplayer/discordplayer/ensurevcon.go @@ -2,6 +2,7 @@ package discordplayer import ( "esefexapi/audioplayer" + "esefexapi/timer" "esefexapi/util/dcgoutil" "github.com/pkg/errors" @@ -16,20 +17,28 @@ func (c *DiscordPlayer) ensureVCon(serverID, userID string) (*VconData, error) { } usrChan := OusrChan.Unwrap() + timer.MessageElapsed("Got user's voice channel") + ObotChan, err := dcgoutil.GetBotVC(c.ds, serverID) if err != nil { return nil, errors.Wrap(err, "Error getting bot voice channel") } + timer.MessageElapsed("Got bot's voice channel") + botInGuild := ObotChan.IsSome() if botInGuild && ObotChan.Unwrap().ChannelID == usrChan.ChannelID { return c.vds[ChannelID(usrChan.ChannelID)], nil } + timer.MessageElapsed("Checked if bot is in guild") + vc, err := c.RegisterVcon(serverID, usrChan.ChannelID) if err != nil { return nil, errors.Wrap(err, "Error registering VCon") } + timer.MessageElapsed("Registered VCon") + return vc, nil } diff --git a/EsefexApi/audioplayer/discordplayer/playsound.go b/EsefexApi/audioplayer/discordplayer/playsound.go index 478c5b3..baf04cd 100644 --- a/EsefexApi/audioplayer/discordplayer/playsound.go +++ b/EsefexApi/audioplayer/discordplayer/playsound.go @@ -3,6 +3,7 @@ package discordplayer import ( "esefexapi/audioplayer" "esefexapi/sounddb" + "esefexapi/timer" "esefexapi/util/dcgoutil" "log" @@ -20,11 +21,15 @@ func (c *DiscordPlayer) PlaySound(soundID string, userID string) error { } userVC := OuserVc.Unwrap() + timer.MessageElapsed("Got user's voice channel") + vd, err := c.ensureVCon(userVC.GuildID, userID) if err != nil { return errors.Wrap(err, "Error ensuring voice connection") } + timer.MessageElapsed("Got voice connection") + vd.vcon.PlaySound(sounddb.SuidFromStrings(userVC.GuildID, soundID)) vd.AfkTimeoutIn = vd.AfkTimeoutIn.Add(c.timeout) diff --git a/EsefexApi/audioplayer/discordplayer/register.go b/EsefexApi/audioplayer/discordplayer/register.go index 596b436..3c7e7ab 100644 --- a/EsefexApi/audioplayer/discordplayer/register.go +++ b/EsefexApi/audioplayer/discordplayer/register.go @@ -2,6 +2,7 @@ package discordplayer import ( "esefexapi/audioplayer/discordplayer/vcon" + "esefexapi/timer" "github.com/pkg/errors" ) @@ -12,6 +13,8 @@ func (c *DiscordPlayer) RegisterVcon(serverID string, channelID string) (*VconDa return nil, errors.Wrap(err, "Error creating new VCon") } + timer.MessageElapsed("Created new VCon") + vd := &VconData{ ChannelID: channelID, ServerID: serverID, @@ -19,7 +22,7 @@ func (c *DiscordPlayer) RegisterVcon(serverID string, channelID string) (*VconDa } c.vds[ChannelID(channelID)] = vd - vc.Run() + go vc.Run() return vd, nil } diff --git a/EsefexApi/audioplayer/discordplayer/vcon/vcon.go b/EsefexApi/audioplayer/discordplayer/vcon/vcon.go index 154511d..b9243cc 100644 --- a/EsefexApi/audioplayer/discordplayer/vcon/vcon.go +++ b/EsefexApi/audioplayer/discordplayer/vcon/vcon.go @@ -3,6 +3,8 @@ package vcon import ( "esefexapi/audioprocessing" "esefexapi/sounddb" + "esefexapi/timer" + "io" "log" "time" @@ -46,9 +48,10 @@ func NewVCon(dc *discordgo.Session, db sounddb.ISoundDB, guildID string, channel } func (a *VCon) PlaySound(uid sounddb.SoundUID) { - log.Printf("channel: %s\n", a.vc.ChannelID) - log.Print(a.playSound) + // log.Printf("channel: %s\n", a.vc.ChannelID) + // log.Print(a.playSound) a.playSound <- uid + timer.MessageElapsed("Sent sound to playSound") } // this is the main loop of the audio queue @@ -56,46 +59,50 @@ func (a *VCon) Run() { log.Println("Running VCon") a.vc.Speaking(true) - go func() { - for { - // log.Println("Looping...") - select { - case sound := <-a.playSound: - // log.Printf("Playing sound %s\n", sound) - ok, err := a.db.SoundExists(sound) - if err != nil { - log.Printf("Error checking if sound exists: %+v\n", err) - continue - } else if !ok { - log.Printf("Sound does not exist: %+v\n", sound) - continue - } - pcm, err := a.db.GetSoundPcm(sound) - if err != nil { - log.Printf("Error getting sound pcm: %+v\n", err) - continue - } + for { + // log.Println("Looping...") + select { + case sound := <-a.playSound: + timer.MessageElapsed("Got sound to play") + // log.Printf("Playing sound %s\n", sound) + ok, err := a.db.SoundExists(sound) + if err != nil { + log.Printf("Error checking if sound exists: %+v\n", err) + continue + } else if !ok { + log.Printf("Sound does not exist: %+v\n", sound) + continue + } + timer.MessageElapsed("Checked if sound exists") - s := audioprocessing.NewS16leReferenceReaderFromRef(pcm) - a.mixer.AddSource(s) - // log.Println("Added sound to mixer") - case <-a.stop: - return - default: - ff := time.After(time.Duration(audioprocessing.FrameLengthMs)) - - if !a.mixer.Empty() { - buf, err := a.enc.EncodeNext() - if err != nil { - log.Printf("Error encoding next: %+v\n", err) - } - a.vc.OpusSend <- buf + pcm, err := a.db.GetSoundPcm(sound) + if err != nil { + log.Printf("Error getting sound pcm: %+v\n", err) + continue + } + timer.MessageElapsed("Got sound pcm") + + s := audioprocessing.NewS16leReferenceReaderFromRef(pcm) + a.mixer.AddSource(s) + // log.Println("Added sound to mixer") + timer.MessageElapsed("Added sound to mixer") + case <-a.stop: + return + default: + ff := time.After(time.Duration(audioprocessing.FrameLengthMs)) + + if !a.mixer.Empty() { + buf, err := a.enc.EncodeNext() + if err != nil && errors.Cause(err) != io.EOF { + log.Printf("Error encoding next: %+v\n", err) } - - <-ff + a.vc.OpusSend <- buf } + + <-ff } - }() + + } } func (a *VCon) Close() { diff --git a/EsefexApi/timer/timer.go b/EsefexApi/timer/timer.go new file mode 100644 index 0000000..087c42c --- /dev/null +++ b/EsefexApi/timer/timer.go @@ -0,0 +1,53 @@ +package timer + +import ( + "log" + "sync" + "time" +) + +var rw sync.RWMutex +var start time.Time +var logEnabled bool + +func SetStart() { + rw.Lock() + defer rw.Unlock() + start = time.Now() +} + +func Elapsed() time.Duration { + rw.RLock() + defer rw.RUnlock() + return time.Since(start) +} + +func PrintElapsed() { + rw.RLock() + defer rw.RUnlock() + + if logEnabled { + log.Printf("Elapsed: %v\n", time.Since(start)) + } +} + +func MessageElapsed(msg string) { + rw.RLock() + defer rw.RUnlock() + + if logEnabled { + log.Printf("%s: %v\n", msg, time.Since(start)) + } +} + +func EnableLog() { + rw.Lock() + defer rw.Unlock() + logEnabled = true +} + +func DisableLog() { + rw.Lock() + defer rw.Unlock() + logEnabled = false +} From 14a711ef66ea2dc5d516bab8e13d2074aa9ae256 Mon Sep 17 00:00:00 2001 From: jo_kil Date: Wed, 3 Jan 2024 22:47:29 +0100 Subject: [PATCH 03/20] upload sound limit, less verbose user error reporting --- EsefexApi/audioplayer/audioplayer.go | 5 ++--- EsefexApi/audioplayer/discordplayer/register.go | 3 ++- EsefexApi/bot/commands/commands.go | 3 ++- EsefexApi/bot/emojilut.go | 8 -------- EsefexApi/bot/util.go | 6 ++++-- EsefexApi/linktokenstore/linktokenstore.go | 6 +++--- EsefexApi/userdb/userdb.go | 2 +- EsefexApi/util/download2pcm.go | 9 +++++++++ 8 files changed, 23 insertions(+), 19 deletions(-) delete mode 100644 EsefexApi/bot/emojilut.go diff --git a/EsefexApi/audioplayer/audioplayer.go b/EsefexApi/audioplayer/audioplayer.go index fa1f6fe..1211bf2 100644 --- a/EsefexApi/audioplayer/audioplayer.go +++ b/EsefexApi/audioplayer/audioplayer.go @@ -2,8 +2,7 @@ package audioplayer import ( "esefexapi/sounddb" - - "github.com/pkg/errors" + "fmt" ) type IAudioPlayer interface { @@ -11,4 +10,4 @@ type IAudioPlayer interface { PlaySound(soundID string, userID string) error } -var UserNotInVC = errors.New("User is not in a voice channel") +var UserNotInVC = fmt.Errorf("User is not in a voice channel") diff --git a/EsefexApi/audioplayer/discordplayer/register.go b/EsefexApi/audioplayer/discordplayer/register.go index 3c7e7ab..8007e24 100644 --- a/EsefexApi/audioplayer/discordplayer/register.go +++ b/EsefexApi/audioplayer/discordplayer/register.go @@ -3,6 +3,7 @@ package discordplayer import ( "esefexapi/audioplayer/discordplayer/vcon" "esefexapi/timer" + "fmt" "github.com/pkg/errors" ) @@ -27,7 +28,7 @@ func (c *DiscordPlayer) RegisterVcon(serverID string, channelID string) (*VconDa return vd, nil } -var VconNotFound = errors.New("VCon not found") +var VconNotFound = fmt.Errorf("VCon not found") func (c *DiscordPlayer) UnregisterVcon(channelID string) error { vd, ok := c.vds[ChannelID(channelID)] diff --git a/EsefexApi/bot/commands/commands.go b/EsefexApi/bot/commands/commands.go index 85865cd..3dfee8a 100644 --- a/EsefexApi/bot/commands/commands.go +++ b/EsefexApi/bot/commands/commands.go @@ -6,6 +6,7 @@ import ( "log" "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" ) type CommandHandlers struct { @@ -50,7 +51,7 @@ func WithErrorHandling(h func(s *discordgo.Session, i *discordgo.InteractionCrea s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("An error has occurred while executing the command: %+v", err), + Content: fmt.Sprintf("An error has occurred while executing the command: \n```%+v```", errors.Cause(err)), }, }) } diff --git a/EsefexApi/bot/emojilut.go b/EsefexApi/bot/emojilut.go deleted file mode 100644 index 063e0c2..0000000 --- a/EsefexApi/bot/emojilut.go +++ /dev/null @@ -1,8 +0,0 @@ -package bot - -var EmojiLut = map[string]string{ - "😀": "https://discord.com/assets/503f3c92fca30bb4275f.svg", - "😃": "https://discord.com/assets/3b22aa2c934f8fe9c5e1.svg", - "😄": "https://discord.com/assets/bdccffa05933afeb52c4.svg", - "😁": "https://discord.com/assets/967207cb2562c712da07.svg", -} diff --git a/EsefexApi/bot/util.go b/EsefexApi/bot/util.go index 08084fe..93b7215 100644 --- a/EsefexApi/bot/util.go +++ b/EsefexApi/bot/util.go @@ -1,10 +1,12 @@ package bot import ( - "github.com/pkg/errors" + "fmt" "log" "os" + "github.com/pkg/errors" + "github.com/bwmarrin/discordgo" ) @@ -61,7 +63,7 @@ func (b *DiscordBot) DeleteGuildCommands(guildID string) { } } -var BotTokenNotSet = errors.New("BOT_TOKEN is not set") +var BotTokenNotSet = fmt.Errorf("BOT_TOKEN is not set") func CreateSession() (*discordgo.Session, error) { token := os.Getenv("BOT_TOKEN") diff --git a/EsefexApi/linktokenstore/linktokenstore.go b/EsefexApi/linktokenstore/linktokenstore.go index 8fc1b46..4d7a06a 100644 --- a/EsefexApi/linktokenstore/linktokenstore.go +++ b/EsefexApi/linktokenstore/linktokenstore.go @@ -1,12 +1,12 @@ package linktokenstore import ( - "github.com/pkg/errors" + "fmt" "time" ) -var ErrTokenNotFound = errors.New("Token not found") -var ErrTokenExpired = errors.New("Token expired") +var ErrTokenNotFound = fmt.Errorf("Token not found") +var ErrTokenExpired = fmt.Errorf("Token expired") type ILinkTokenStore interface { // Get a token for a user diff --git a/EsefexApi/userdb/userdb.go b/EsefexApi/userdb/userdb.go index 3e21023..ccd6f3f 100644 --- a/EsefexApi/userdb/userdb.go +++ b/EsefexApi/userdb/userdb.go @@ -5,7 +5,7 @@ import ( // "github.com/pkg/errors" ) -// var ErrUserNotFound = errors.New("User not found") +// var ErrUserNotFound = fmt.Errorf("User not found") type IUserDB interface { GetUser(userID string) (opt.Option[*User], error) diff --git a/EsefexApi/util/download2pcm.go b/EsefexApi/util/download2pcm.go index 13b73fa..6b4cba4 100644 --- a/EsefexApi/util/download2pcm.go +++ b/EsefexApi/util/download2pcm.go @@ -2,6 +2,7 @@ package util import ( "fmt" + // "log" "net/http" "os/exec" "regexp" @@ -32,6 +33,14 @@ func Download2PCM(url string) ([]int16, error) { return nil, errors.Wrapf(err, "Error downloading sound: %v", resp.StatusCode) } + // reject if content length is too large + + var maxFileSize int64 = 5_000_000 + + if resp.ContentLength > maxFileSize { + return nil, fmt.Errorf("file is too large (%v bytes > %v bytes)", resp.ContentLength, maxFileSize) + } + cmd := exec.Command("ffmpeg", "-hide_banner", "-loglevel", "error", "-i", "pipe:0", "-f", "s16le", "-ac", "2", "-ar", "48000", "-") cmd.Stdin = resp.Body From 3eec60568ad8535a3c8dfdd50497ae4e4b183970 Mon Sep 17 00:00:00 2001 From: jo_kil Date: Fri, 5 Jan 2024 20:28:23 +0100 Subject: [PATCH 04/20] permission system framework --- EsefexApi/permissions/merge.go | 44 +++++++++++ EsefexApi/permissions/permissions.go | 84 ++++++++++++++++++++ EsefexApi/permissions/stack.go | 110 +++++++++++++++++++++++++++ EsefexApi/types/types.go | 11 +++ 4 files changed, 249 insertions(+) create mode 100644 EsefexApi/permissions/merge.go create mode 100644 EsefexApi/permissions/permissions.go create mode 100644 EsefexApi/permissions/stack.go create mode 100644 EsefexApi/types/types.go diff --git a/EsefexApi/permissions/merge.go b/EsefexApi/permissions/merge.go new file mode 100644 index 0000000..2c305c5 --- /dev/null +++ b/EsefexApi/permissions/merge.go @@ -0,0 +1,44 @@ +package permissions + +// Merge returns the permission state that results from merging two permission states. +// otherPS has precedence over ps. +func (ps PermissionState) Merge(otherPS PermissionState) PermissionState { + if otherPS == Allow { + return Allow + } else if otherPS == Deny { + return Deny + } else { + return ps + } +} + +func (p Permissions) Merge(otherP *Permissions) Permissions { + return Permissions{ + Sound: p.Sound.Merge(otherP.Sound), + Bot: p.Bot.Merge(otherP.Bot), + Server: p.Server.Merge(otherP.Server), + } +} + +func (p SoundPermissions) Merge(otherP SoundPermissions) SoundPermissions { + return SoundPermissions{ + Play: p.Play.Merge(otherP.Play), + Upload: p.Upload.Merge(otherP.Upload), + Modify: p.Modify.Merge(otherP.Modify), + Delete: p.Delete.Merge(otherP.Delete), + } +} + +func (p BotPermissions) Merge(otherP BotPermissions) BotPermissions { + return BotPermissions{ + Join: p.Join.Merge(otherP.Join), + Leave: p.Leave.Merge(otherP.Leave), + } +} + +func (p ServerPermissions) Merge(otherP ServerPermissions) ServerPermissions { + return ServerPermissions{ + ManageBot: p.ManageBot.Merge(otherP.ManageBot), + ManageUser: p.ManageUser.Merge(otherP.ManageUser), + } +} diff --git a/EsefexApi/permissions/permissions.go b/EsefexApi/permissions/permissions.go new file mode 100644 index 0000000..767856d --- /dev/null +++ b/EsefexApi/permissions/permissions.go @@ -0,0 +1,84 @@ +package permissions + +type PermissionState int + +const ( + Allow PermissionState = iota + Deny + Unset +) + +func (ps PermissionState) String() string { + switch ps { + case Allow: + return "allow" + case Deny: + return "deny" + case Unset: + return "unset" + default: + return "unknown" + } +} + +type Permissions struct { + Sound SoundPermissions + Bot BotPermissions + Server ServerPermissions +} + +type SoundPermissions struct { + Play PermissionState + Upload PermissionState + Modify PermissionState + Delete PermissionState +} + +type BotPermissions struct { + Join PermissionState + Leave PermissionState +} + +type ServerPermissions struct { + ManageBot PermissionState + ManageUser PermissionState +} + +// Default returns a Permissions struct with all permissions set to Allow. +func NewDefault() Permissions { + return Permissions{ + Sound: SoundPermissions{ + Play: Allow, + Upload: Allow, + Modify: Allow, + Delete: Allow, + }, + Bot: BotPermissions{ + Join: Allow, + Leave: Allow, + }, + Server: ServerPermissions{ + ManageBot: Allow, + ManageUser: Allow, + }, + } +} + +func NewUnset() Permissions { + return Permissions{ + Sound: SoundPermissions{ + Play: Unset, + Upload: Unset, + Modify: Unset, + Delete: Unset, + }, + Bot: BotPermissions{ + Join: Unset, + Leave: Unset, + }, + Server: ServerPermissions{ + ManageBot: Unset, + ManageUser: Unset, + }, + } +} diff --git a/EsefexApi/permissions/stack.go b/EsefexApi/permissions/stack.go new file mode 100644 index 0000000..61745de --- /dev/null +++ b/EsefexApi/permissions/stack.go @@ -0,0 +1,110 @@ +package permissions + +import ( + "esefexapi/types" + "slices" +) + +type PermissionType int + +const ( + User PermissionType = iota + Role + Channel +) + +func (pt PermissionType) String() string { + switch pt { + case User: + return "user" + case Role: + return "role" + case Channel: + return "channel" + default: + return "unknown" + } +} + +type PermissionStack struct { + User map[types.UserID]Permissions + Role map[types.RoleID]Permissions + Channel map[types.ChannelID]Permissions +} + +func NewPermissionStack() *PermissionStack { + return &PermissionStack{ + User: make(map[types.UserID]Permissions), + Role: make(map[types.RoleID]Permissions), + Channel: make(map[types.ChannelID]Permissions), + } +} + +func (ps *PermissionStack) SetUser(user types.UserID, p Permissions) { + ps.User[user] = p +} + +func (ps *PermissionStack) SetRole(role types.RoleID, p Permissions) { + ps.Role[role] = p +} + +func (ps *PermissionStack) SetChannel(channel types.ChannelID, p Permissions) { + ps.Channel[channel] = p +} + +func (ps *PermissionStack) UnsetUser(user types.UserID) { + delete(ps.User, user) +} + +func (ps *PermissionStack) UnsetRole(role types.RoleID) { + delete(ps.Role, role) +} + +func (ps *PermissionStack) UnsetChannel(channel types.ChannelID) { + delete(ps.Channel, channel) +} + +func (ps *PermissionStack) UpdateUser(user types.UserID, p Permissions) { + if _, ok := ps.User[user]; !ok { + ps.User[user] = NewUnset() + } + + ps.User[user] = ps.User[user].Merge(&p) +} + +func (ps *PermissionStack) UpdateRole(role types.RoleID, p Permissions) { + if _, ok := ps.Role[role]; !ok { + ps.Role[role] = NewUnset() + } + + ps.Role[role] = ps.Role[role].Merge(&p) +} + +func (ps *PermissionStack) UpdateChannel(channel types.ChannelID, p Permissions) { + if _, ok := ps.Channel[channel]; !ok { + ps.Channel[channel] = NewUnset() + } + + ps.Channel[channel] = ps.Channel[channel].Merge(&p) +} + +// Query returns the permission state for a given user, role, and channel by merging them together. +// The order of precedence is user > channel > role. +// This means that if a user has a permission set, it will override the channel and role permissions. +// If a channel has a permission set, it will override the role permissions. +// roles is a list of roles that the user has in order of precedence. +func (ps *PermissionStack) Query(user types.UserID, roles []types.RoleID, channel types.ChannelID) Permissions { + userPS := ps.User[user] + + slices.Reverse(roles) + rolesPS := NewUnset() + for _, role := range roles { + r := ps.Role[role] + rolesPS = rolesPS.Merge(&r) + } + + channelPS := ps.Channel[channel] + + return NewDefault().Merge(&rolesPS).Merge(&channelPS).Merge(&userPS) + +} diff --git a/EsefexApi/types/types.go b/EsefexApi/types/types.go new file mode 100644 index 0000000..b239ae6 --- /dev/null +++ b/EsefexApi/types/types.go @@ -0,0 +1,11 @@ +package types + +type UserID string + +type RoleID string + +type ChannelID string + +type GuildID string + +type SoundID string From d2c22376684e0fd9dfa92c42a83aa8e1dc434015 Mon Sep 17 00:00:00 2001 From: jo_kil Date: Fri, 5 Jan 2024 23:14:27 +0100 Subject: [PATCH 05/20] major fucking refactor --- EsefexApi/api/api.go | 10 +-- EsefexApi/api/middleware/auth.go | 3 +- EsefexApi/api/public/simpleui/index.js | 8 +-- EsefexApi/api/routes/getguild.go | 25 +++++++ EsefexApi/api/routes/getguilds.go | 39 +++++++++++ EsefexApi/api/routes/getjoinsession.go | 6 +- EsefexApi/api/routes/getlinkdefer.go | 2 +- EsefexApi/api/routes/getlinkredirect.go | 2 +- EsefexApi/api/routes/getserver.go | 24 ------- EsefexApi/api/routes/getservers.go | 38 ----------- EsefexApi/api/routes/getsounds.go | 7 +- EsefexApi/api/routes/postplaysound.go | 5 +- EsefexApi/api/routes/postplaysoundinsecure.go | 11 +-- EsefexApi/audioplayer/audioplayer.go | 5 +- .../discordplayer/discordplayer.go | 19 +++--- .../audioplayer/discordplayer/ensurevcon.go | 11 +-- .../audioplayer/discordplayer/playsound.go | 7 +- .../discordplayer/playsoundinsecure.go | 5 +- .../audioplayer/discordplayer/register.go | 15 +++-- .../audioplayer/discordplayer/vcon/vcon.go | 13 ++-- .../audioplayer/mockplayer/mockplayer.go | 7 +- EsefexApi/bot/commands/delete.go | 2 +- EsefexApi/bot/commands/link.go | 3 +- EsefexApi/bot/commands/list.go | 11 +-- EsefexApi/bot/commands/unlink.go | 5 +- EsefexApi/bot/commands/upload.go | 5 +- .../cmd/download_testing/download_testing.go | 2 +- EsefexApi/go.mod | 5 +- EsefexApi/go.sum | 7 ++ EsefexApi/linktokenstore/linktokenstore.go | 11 +-- .../memorylinktokenstore.go | 15 +++-- EsefexApi/permissions/merge.go | 10 +-- EsefexApi/permissions/permissions.go | 12 ++-- EsefexApi/sounddb/apimockdb/apimockdb.go | 67 ++++++++++--------- EsefexApi/sounddb/dbcache/dbcache.go | 59 ++++++++-------- EsefexApi/sounddb/filesounddb/addsound.go | 29 ++++---- EsefexApi/sounddb/filesounddb/deletesound.go | 6 +- EsefexApi/sounddb/filesounddb/filedb_test.go | 27 ++++---- .../{getserverids.go => getguildids.go} | 9 +-- EsefexApi/sounddb/filesounddb/getsoundmeta.go | 4 +- EsefexApi/sounddb/filesounddb/getsoundpcm.go | 4 +- EsefexApi/sounddb/filesounddb/getsounduids.go | 11 +-- EsefexApi/sounddb/filesounddb/internal.go | 7 +- EsefexApi/sounddb/filesounddb/soundexists.go | 4 +- EsefexApi/sounddb/sounddb.go | 30 +++++---- EsefexApi/sounddb/util.go | 25 ++++--- EsefexApi/types/types.go | 20 ++++++ EsefexApi/userdb/fileuserdb/fileuserdb.go | 6 +- EsefexApi/userdb/fileuserdb/impluserdb.go | 13 ++-- EsefexApi/userdb/user.go | 6 +- EsefexApi/userdb/userdb.go | 7 +- EsefexApi/util/dcgoutil/dcgoutil.go | 49 +++++++------- 52 files changed, 404 insertions(+), 329 deletions(-) create mode 100644 EsefexApi/api/routes/getguild.go create mode 100644 EsefexApi/api/routes/getguilds.go delete mode 100644 EsefexApi/api/routes/getserver.go delete mode 100644 EsefexApi/api/routes/getservers.go rename EsefexApi/sounddb/filesounddb/{getserverids.go => getguildids.go} (61%) diff --git a/EsefexApi/api/api.go b/EsefexApi/api/api.go index fcecba1..baa5b92 100644 --- a/EsefexApi/api/api.go +++ b/EsefexApi/api/api.go @@ -50,15 +50,15 @@ func (api *HttpApi) run() { cors := api.mw.Cors h := api.handlers - router.HandleFunc("/api/sounds/{server_id}", cors(h.GetSounds)).Methods("GET") + router.HandleFunc("/api/sounds/{guild_id}", cors(h.GetSounds)).Methods("GET") - router.HandleFunc("/api/server", cors(auth(h.GetServer))).Methods("GET").Headers("Cookie", "") - router.HandleFunc("/api/servers", cors(auth(h.GetServers))).Methods("GET").Headers("Cookie", "") + router.HandleFunc("/api/guild", cors(auth(h.GetGuild))).Methods("GET").Headers("Cookie", "") + router.HandleFunc("/api/guilds", cors(auth(h.GetGuilds))).Methods("GET").Headers("Cookie", "") - router.HandleFunc("/api/playsound/{user_id}/{server_id}/{sound_id}", cors(h.PostPlaySoundInsecure)).Methods("POST") + router.HandleFunc("/api/playsound/{user_id}/{guild_id}/{sound_id}", cors(h.PostPlaySoundInsecure)).Methods("POST") router.HandleFunc("/api/playsound/{sound_id}", cors(auth(h.PostPlaySound))).Methods("POST").Headers("Cookie", "") - router.HandleFunc("/joinsession/{server_id}", cors(h.GetJoinSession)).Methods("GET") + router.HandleFunc("/joinsession/{guild_id}", cors(h.GetJoinSession)).Methods("GET") router.HandleFunc("/link", cors(h.GetLinkDefer)).Methods("GET").Queries("t", "{t}") router.HandleFunc("/api/link", cors(h.GetLinkRedirect)).Methods("GET").Queries("t", "{t}") diff --git a/EsefexApi/api/middleware/auth.go b/EsefexApi/api/middleware/auth.go index 2e3fbe6..a9d3f07 100644 --- a/EsefexApi/api/middleware/auth.go +++ b/EsefexApi/api/middleware/auth.go @@ -1,6 +1,7 @@ package middleware import ( + "esefexapi/types" "esefexapi/userdb" "fmt" "log" @@ -8,7 +9,7 @@ import ( ) // Auth middleware checks if the user is authenticated and injects the user into the request context -func (m *Middleware) Auth(next func(w http.ResponseWriter, r *http.Request, userID string)) func(w http.ResponseWriter, r *http.Request) { +func (m *Middleware) Auth(next func(w http.ResponseWriter, r *http.Request, userID types.UserID)) func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user_token, err := r.Cookie("User-Token") if err != nil { diff --git a/EsefexApi/api/public/simpleui/index.js b/EsefexApi/api/public/simpleui/index.js index 98f8469..964cc16 100644 --- a/EsefexApi/api/public/simpleui/index.js +++ b/EsefexApi/api/public/simpleui/index.js @@ -2,19 +2,19 @@ async function init() { const soundsDiv = document.getElementById('sounds'); const userTokenInput = document.getElementById('userTokenInput'); - let serverRequest = await fetch('/api/server', { + let guildRequest = await fetch('/api/guild', { method: 'GET', credentials: 'same-origin', }); - if (serverRequest.status != 200) { + if (guildRequest.status != 200) { let errorP = document.createElement('p'); - errorP.innerText = 'Error: ' + serverRequest.status + ' ' + serverRequest.statusText + '\n' + await serverRequest.text(); + errorP.innerText = 'Error: ' + guildRequest.status + ' ' + guildRequest.statusText + '\n' + await guildRequest.text(); soundsDiv.appendChild(errorP); return; } - let soundsRequest = await fetch(`/api/sounds/${await serverRequest.text()}`, { + let soundsRequest = await fetch(`/api/sounds/${await guildRequest.text()}`, { method: 'GET', credentials: 'same-origin', }); diff --git a/EsefexApi/api/routes/getguild.go b/EsefexApi/api/routes/getguild.go new file mode 100644 index 0000000..203a5ee --- /dev/null +++ b/EsefexApi/api/routes/getguild.go @@ -0,0 +1,25 @@ +package routes + +import ( + "esefexapi/types" + "esefexapi/util/dcgoutil" + "net/http" +) + +// api/guild +// returns the guild a user is connected to +func (h *RouteHandlers) GetGuild(w http.ResponseWriter, r *http.Request, userID types.UserID) { + Ovs, err := dcgoutil.UserVCAny(h.ds, userID) + if err != nil { + errorMsg := "Error getting user Voice State" + http.Error(w, errorMsg, http.StatusInternalServerError) + return + } + if Ovs.IsNone() { + http.Error(w, "User not connected to guild channel", http.StatusForbidden) + return + } + + guildID := Ovs.Unwrap().GuildID + w.Write([]byte(guildID)) +} diff --git a/EsefexApi/api/routes/getguilds.go b/EsefexApi/api/routes/getguilds.go new file mode 100644 index 0000000..a40eab5 --- /dev/null +++ b/EsefexApi/api/routes/getguilds.go @@ -0,0 +1,39 @@ +package routes + +import ( + "encoding/json" + "esefexapi/types" + "esefexapi/util/dcgoutil" + "log" + "net/http" +) + +type GuildInfo struct { + GuildID string `json:"guildID"` + GuildName string `json:"guildName"` +} + +func (h *RouteHandlers) GetGuilds(w http.ResponseWriter, r *http.Request, userID types.UserID) { + guilds, err := dcgoutil.UserGuilds(h.ds, userID) + if err != nil { + log.Println(err) + http.Error(w, "Error getting user guilds", http.StatusInternalServerError) + return + } + + si := []GuildInfo{} + + for _, g := range guilds { + si = append(si, GuildInfo{GuildID: g.ID, GuildName: g.Name}) + } + + js, err := json.Marshal(si) + if err != nil { + log.Println(err) + http.Error(w, "Error marshalling json", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.Write(js) +} diff --git a/EsefexApi/api/routes/getjoinsession.go b/EsefexApi/api/routes/getjoinsession.go index 25b3c22..e59067c 100644 --- a/EsefexApi/api/routes/getjoinsession.go +++ b/EsefexApi/api/routes/getjoinsession.go @@ -8,12 +8,12 @@ import ( "github.com/gorilla/mux" ) -// joinsession/ +// joinsession/ func (h *RouteHandlers) GetJoinSession(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - server_id := vars["server_id"] + guild_id := vars["guild_id"] - redirectUrl := fmt.Sprintf("%s://joinsession/%s", h.cProto, server_id) + redirectUrl := fmt.Sprintf("%s://joinsession/%s", h.cProto, guild_id) response := fmt.Sprintf(``, redirectUrl) w.Header().Set("Content-Type", "text/html") diff --git a/EsefexApi/api/routes/getlinkdefer.go b/EsefexApi/api/routes/getlinkdefer.go index 06e9a89..a521267 100644 --- a/EsefexApi/api/routes/getlinkdefer.go +++ b/EsefexApi/api/routes/getlinkdefer.go @@ -9,7 +9,7 @@ type LinkDeferData struct { LinkToken string } -// link? +// link? func (h *RouteHandlers) GetLinkDefer(w http.ResponseWriter, r *http.Request) { linkToken := r.URL.Query().Get("t") diff --git a/EsefexApi/api/routes/getlinkredirect.go b/EsefexApi/api/routes/getlinkredirect.go index 0464cae..53270fe 100644 --- a/EsefexApi/api/routes/getlinkredirect.go +++ b/EsefexApi/api/routes/getlinkredirect.go @@ -11,7 +11,7 @@ type LinkRedirect struct { RedirectUrl string } -// api/link? +// api/link? func (h *RouteHandlers) GetLinkRedirect(w http.ResponseWriter, r *http.Request) { linkToken := r.URL.Query().Get("t") diff --git a/EsefexApi/api/routes/getserver.go b/EsefexApi/api/routes/getserver.go deleted file mode 100644 index feadd4c..0000000 --- a/EsefexApi/api/routes/getserver.go +++ /dev/null @@ -1,24 +0,0 @@ -package routes - -import ( - "esefexapi/util/dcgoutil" - "net/http" -) - -// api/server -// returns the server a user is connected to -func (h *RouteHandlers) GetServer(w http.ResponseWriter, r *http.Request, userID string) { - Oserver, err := dcgoutil.UserVCAny(h.ds, userID) - if err != nil { - errorMsg := "Error getting user server" - http.Error(w, errorMsg, http.StatusInternalServerError) - return - } - if Oserver.IsNone() { - http.Error(w, "User not connected to server", http.StatusForbidden) - return - } - - serverID := Oserver.Unwrap().GuildID - w.Write([]byte(serverID)) -} diff --git a/EsefexApi/api/routes/getservers.go b/EsefexApi/api/routes/getservers.go deleted file mode 100644 index b990f94..0000000 --- a/EsefexApi/api/routes/getservers.go +++ /dev/null @@ -1,38 +0,0 @@ -package routes - -import ( - "encoding/json" - "esefexapi/util/dcgoutil" - "log" - "net/http" -) - -type ServerInfo struct { - ServerID string `json:"serverID"` - ServerName string `json:"serverName"` -} - -func (h *RouteHandlers) GetServers(w http.ResponseWriter, r *http.Request, userID string) { - guilds, err := dcgoutil.UserServers(h.ds, userID) - if err != nil { - log.Println(err) - http.Error(w, "Error getting user servers", http.StatusInternalServerError) - return - } - - si := []ServerInfo{} - - for _, g := range guilds { - si = append(si, ServerInfo{ServerID: g.ID, ServerName: g.Name}) - } - - js, err := json.Marshal(si) - if err != nil { - log.Println(err) - http.Error(w, "Error marshalling json", http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.Write(js) -} diff --git a/EsefexApi/api/routes/getsounds.go b/EsefexApi/api/routes/getsounds.go index 4484891..b432fae 100644 --- a/EsefexApi/api/routes/getsounds.go +++ b/EsefexApi/api/routes/getsounds.go @@ -3,6 +3,7 @@ package routes import ( "encoding/json" "esefexapi/sounddb" + "esefexapi/types" "fmt" "log" "net/http" @@ -10,12 +11,12 @@ import ( "github.com/gorilla/mux" ) -// api/sounds/ +// api/sounds/ func (h *RouteHandlers) GetSounds(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) - server_id := vars["server_id"] + guild_id := types.GuildID(vars["guild_id"]) - uids, err := h.dbs.SoundDB.GetSoundUIDs(server_id) + uids, err := h.dbs.SoundDB.GetSoundUIDs(guild_id) if err != nil { errorMsg := fmt.Sprintf("Error getting sound uids: %+v", err) diff --git a/EsefexApi/api/routes/postplaysound.go b/EsefexApi/api/routes/postplaysound.go index 177b41d..0d61de5 100644 --- a/EsefexApi/api/routes/postplaysound.go +++ b/EsefexApi/api/routes/postplaysound.go @@ -2,6 +2,7 @@ package routes import ( "esefexapi/timer" + "esefexapi/types" "fmt" "io" "log" @@ -11,12 +12,12 @@ import ( ) // api/playsound/ -func (h *RouteHandlers) PostPlaySound(w http.ResponseWriter, r *http.Request, userID string) { +func (h *RouteHandlers) PostPlaySound(w http.ResponseWriter, r *http.Request, userID types.UserID) { // log.Printf("got /playsound request\n") timer.SetStart() vars := mux.Vars(r) - sound_id := vars["sound_id"] + sound_id := types.SoundID(vars["sound_id"]) err := h.a.PlaySound(sound_id, userID) if err != nil { diff --git a/EsefexApi/api/routes/postplaysoundinsecure.go b/EsefexApi/api/routes/postplaysoundinsecure.go index 10b9564..cc4772e 100644 --- a/EsefexApi/api/routes/postplaysoundinsecure.go +++ b/EsefexApi/api/routes/postplaysoundinsecure.go @@ -2,6 +2,7 @@ package routes import ( "esefexapi/sounddb" + "esefexapi/types" "fmt" "io" "log" @@ -10,16 +11,16 @@ import ( "github.com/gorilla/mux" ) -// api/playsound/// +// api/playsound/// func (h *RouteHandlers) PostPlaySoundInsecure(w http.ResponseWriter, r *http.Request) { log.Printf("got /playsound request\n") vars := mux.Vars(r) - user_id := vars["user_id"] - server_id := vars["server_id"] - sound_id := vars["sound_id"] + user_id := types.UserID(vars["user_id"]) + guild_id := types.GuildID(vars["guild_id"]) + sound_id := types.SoundID(vars["sound_id"]) - err := h.a.PlaySoundInsecure(sounddb.SuidFromStrings(server_id, sound_id), server_id, user_id) + err := h.a.PlaySoundInsecure(sounddb.New(guild_id, sound_id), guild_id, user_id) if err != nil { errorMsg := fmt.Sprintf("Error playing sound: %+v", err) log.Println(errorMsg) diff --git a/EsefexApi/audioplayer/audioplayer.go b/EsefexApi/audioplayer/audioplayer.go index 1211bf2..76ec045 100644 --- a/EsefexApi/audioplayer/audioplayer.go +++ b/EsefexApi/audioplayer/audioplayer.go @@ -2,12 +2,13 @@ package audioplayer import ( "esefexapi/sounddb" + "esefexapi/types" "fmt" ) type IAudioPlayer interface { - PlaySoundInsecure(uid sounddb.SoundUID, guildID string, userID string) error - PlaySound(soundID string, userID string) error + PlaySoundInsecure(uid sounddb.SoundURI, guildID types.GuildID, userID types.UserID) error + PlaySound(soundID types.SoundID, userID types.UserID) error } var UserNotInVC = fmt.Errorf("User is not in a voice channel") diff --git a/EsefexApi/audioplayer/discordplayer/discordplayer.go b/EsefexApi/audioplayer/discordplayer/discordplayer.go index 40c19c8..692c8fc 100644 --- a/EsefexApi/audioplayer/discordplayer/discordplayer.go +++ b/EsefexApi/audioplayer/discordplayer/discordplayer.go @@ -3,6 +3,7 @@ package discordplayer import ( "esefexapi/audioplayer/discordplayer/vcon" "esefexapi/db" + "esefexapi/types" "esefexapi/util/dcgoutil" "log" @@ -17,11 +18,9 @@ import ( var _ service.IService = &DiscordPlayer{} var _ audioplayer.IAudioPlayer = &DiscordPlayer{} -type ChannelID string - // DiscordPlayer implements PlaybackManager type DiscordPlayer struct { - vds map[ChannelID]*VconData + vds map[types.ChannelID]*VconData ds *discordgo.Session dbs db.Databases stop chan struct{} @@ -31,15 +30,15 @@ type DiscordPlayer struct { } type VconData struct { - ChannelID string - ServerID string + ChannelID types.ChannelID + GuildID types.GuildID AfkTimeoutIn time.Time vcon *vcon.VCon } func NewDiscordPlayer(ds *discordgo.Session, dbs *db.Databases, useTimeouts bool, timeout time.Duration) *DiscordPlayer { dp := &DiscordPlayer{ - vds: make(map[ChannelID]*VconData), + vds: make(map[types.ChannelID]*VconData), ds: ds, dbs: *dbs, stop: make(chan struct{}), @@ -54,9 +53,9 @@ func NewDiscordPlayer(ds *discordgo.Session, dbs *db.Databases, useTimeouts bool return } - if _, ok := dp.vds[ChannelID(e.BeforeUpdate.ChannelID)]; ok { + if _, ok := dp.vds[types.ChannelID(e.BeforeUpdate.ChannelID)]; ok { log.Printf("Closing VCon: %s", e.BeforeUpdate.ChannelID) - dp.UnregisterVcon(e.BeforeUpdate.ChannelID) + dp.UnregisterVcon(types.ChannelID(e.BeforeUpdate.ChannelID)) } }) @@ -66,7 +65,7 @@ func NewDiscordPlayer(ds *discordgo.Session, dbs *db.Databases, useTimeouts bool return } - if _, ok := dp.vds[ChannelID(e.BeforeUpdate.ChannelID)]; !ok { + if _, ok := dp.vds[types.ChannelID(e.BeforeUpdate.ChannelID)]; !ok { return } @@ -80,7 +79,7 @@ func NewDiscordPlayer(ds *discordgo.Session, dbs *db.Databases, useTimeouts bool if len(users) == 1 { log.Printf("Channel empty, closing vcon: %s", e.BeforeUpdate.ChannelID) - dp.UnregisterVcon(e.BeforeUpdate.ChannelID) + dp.UnregisterVcon(types.ChannelID(e.BeforeUpdate.ChannelID)) } }) diff --git a/EsefexApi/audioplayer/discordplayer/ensurevcon.go b/EsefexApi/audioplayer/discordplayer/ensurevcon.go index be71946..32fb240 100644 --- a/EsefexApi/audioplayer/discordplayer/ensurevcon.go +++ b/EsefexApi/audioplayer/discordplayer/ensurevcon.go @@ -3,13 +3,14 @@ package discordplayer import ( "esefexapi/audioplayer" "esefexapi/timer" + "esefexapi/types" "esefexapi/util/dcgoutil" "github.com/pkg/errors" ) -func (c *DiscordPlayer) ensureVCon(serverID, userID string) (*VconData, error) { - OusrChan, err := dcgoutil.UserServerVC(c.ds, serverID, userID) +func (c *DiscordPlayer) ensureVCon(guildID types.GuildID, userID types.UserID) (*VconData, error) { + OusrChan, err := dcgoutil.UserGuildVC(c.ds, guildID, userID) if err != nil { return nil, errors.Wrap(err, "Error getting user voice channel") } else if OusrChan.IsNone() { @@ -19,7 +20,7 @@ func (c *DiscordPlayer) ensureVCon(serverID, userID string) (*VconData, error) { timer.MessageElapsed("Got user's voice channel") - ObotChan, err := dcgoutil.GetBotVC(c.ds, serverID) + ObotChan, err := dcgoutil.GetBotVC(c.ds, guildID) if err != nil { return nil, errors.Wrap(err, "Error getting bot voice channel") } @@ -28,12 +29,12 @@ func (c *DiscordPlayer) ensureVCon(serverID, userID string) (*VconData, error) { botInGuild := ObotChan.IsSome() if botInGuild && ObotChan.Unwrap().ChannelID == usrChan.ChannelID { - return c.vds[ChannelID(usrChan.ChannelID)], nil + return c.vds[types.ChannelID(usrChan.ChannelID)], nil } timer.MessageElapsed("Checked if bot is in guild") - vc, err := c.RegisterVcon(serverID, usrChan.ChannelID) + vc, err := c.RegisterVcon(guildID, types.ChannelID(usrChan.ChannelID)) if err != nil { return nil, errors.Wrap(err, "Error registering VCon") } diff --git a/EsefexApi/audioplayer/discordplayer/playsound.go b/EsefexApi/audioplayer/discordplayer/playsound.go index baf04cd..33f9406 100644 --- a/EsefexApi/audioplayer/discordplayer/playsound.go +++ b/EsefexApi/audioplayer/discordplayer/playsound.go @@ -4,13 +4,14 @@ import ( "esefexapi/audioplayer" "esefexapi/sounddb" "esefexapi/timer" + "esefexapi/types" "esefexapi/util/dcgoutil" "log" "github.com/pkg/errors" ) -func (c *DiscordPlayer) PlaySound(soundID string, userID string) error { +func (c *DiscordPlayer) PlaySound(soundID types.SoundID, userID types.UserID) error { log.Printf("Playing sound '%v' for user '%v'", soundID, userID) OuserVc, err := dcgoutil.UserVCAny(c.ds, userID) @@ -23,14 +24,14 @@ func (c *DiscordPlayer) PlaySound(soundID string, userID string) error { timer.MessageElapsed("Got user's voice channel") - vd, err := c.ensureVCon(userVC.GuildID, userID) + vd, err := c.ensureVCon(types.GuildID(userVC.GuildID), userID) if err != nil { return errors.Wrap(err, "Error ensuring voice connection") } timer.MessageElapsed("Got voice connection") - vd.vcon.PlaySound(sounddb.SuidFromStrings(userVC.GuildID, soundID)) + vd.vcon.PlaySound(sounddb.SuidFromStrings(userVC.GuildID, soundID.String())) vd.AfkTimeoutIn = vd.AfkTimeoutIn.Add(c.timeout) return nil diff --git a/EsefexApi/audioplayer/discordplayer/playsoundinsecure.go b/EsefexApi/audioplayer/discordplayer/playsoundinsecure.go index c751546..373ad4d 100644 --- a/EsefexApi/audioplayer/discordplayer/playsoundinsecure.go +++ b/EsefexApi/audioplayer/discordplayer/playsoundinsecure.go @@ -2,15 +2,16 @@ package discordplayer import ( "esefexapi/sounddb" + "esefexapi/types" "log" "github.com/pkg/errors" ) -func (c *DiscordPlayer) PlaySoundInsecure(uid sounddb.SoundUID, serverID, userID string) error { +func (c *DiscordPlayer) PlaySoundInsecure(uid sounddb.SoundURI, guildID types.GuildID, userID types.UserID) error { log.Printf("Playing sound %s\n", uid) - vd, err := c.ensureVCon(serverID, userID) + vd, err := c.ensureVCon(guildID, userID) if err != nil { return errors.Wrap(err, "Error ensuring voice connection") } diff --git a/EsefexApi/audioplayer/discordplayer/register.go b/EsefexApi/audioplayer/discordplayer/register.go index 8007e24..312eb2b 100644 --- a/EsefexApi/audioplayer/discordplayer/register.go +++ b/EsefexApi/audioplayer/discordplayer/register.go @@ -3,13 +3,14 @@ package discordplayer import ( "esefexapi/audioplayer/discordplayer/vcon" "esefexapi/timer" + "esefexapi/types" "fmt" "github.com/pkg/errors" ) -func (c *DiscordPlayer) RegisterVcon(serverID string, channelID string) (*VconData, error) { - vc, err := vcon.NewVCon(c.ds, c.dbs.SoundDB, serverID, channelID) +func (c *DiscordPlayer) RegisterVcon(guildID types.GuildID, channelID types.ChannelID) (*VconData, error) { + vc, err := vcon.NewVCon(c.ds, c.dbs.SoundDB, guildID, channelID) if err != nil { return nil, errors.Wrap(err, "Error creating new VCon") } @@ -18,11 +19,11 @@ func (c *DiscordPlayer) RegisterVcon(serverID string, channelID string) (*VconDa vd := &VconData{ ChannelID: channelID, - ServerID: serverID, + GuildID: guildID, vcon: vc, } - c.vds[ChannelID(channelID)] = vd + c.vds[types.ChannelID(channelID)] = vd go vc.Run() return vd, nil @@ -30,13 +31,13 @@ func (c *DiscordPlayer) RegisterVcon(serverID string, channelID string) (*VconDa var VconNotFound = fmt.Errorf("VCon not found") -func (c *DiscordPlayer) UnregisterVcon(channelID string) error { - vd, ok := c.vds[ChannelID(channelID)] +func (c *DiscordPlayer) UnregisterVcon(channelID types.ChannelID) error { + vd, ok := c.vds[types.ChannelID(channelID)] if !ok { return VconNotFound } - delete(c.vds, ChannelID(channelID)) + delete(c.vds, types.ChannelID(channelID)) vd.vcon.Close() return nil diff --git a/EsefexApi/audioplayer/discordplayer/vcon/vcon.go b/EsefexApi/audioplayer/discordplayer/vcon/vcon.go index b9243cc..0d33586 100644 --- a/EsefexApi/audioplayer/discordplayer/vcon/vcon.go +++ b/EsefexApi/audioplayer/discordplayer/vcon/vcon.go @@ -4,6 +4,7 @@ import ( "esefexapi/audioprocessing" "esefexapi/sounddb" "esefexapi/timer" + "esefexapi/types" "io" "log" @@ -14,7 +15,7 @@ import ( ) type VCon struct { - playSound chan sounddb.SoundUID + playSound chan sounddb.SoundURI stop chan struct{} mixer *audioprocessing.S16leMixReader enc *audioprocessing.GopusEncoder @@ -23,8 +24,8 @@ type VCon struct { db sounddb.ISoundDB } -func NewVCon(dc *discordgo.Session, db sounddb.ISoundDB, guildID string, channelID string) (*VCon, error) { - vc, err := dc.ChannelVoiceJoin(guildID, channelID, false, true) +func NewVCon(ds *discordgo.Session, db sounddb.ISoundDB, guildID types.GuildID, channelID types.ChannelID) (*VCon, error) { + vc, err := ds.ChannelVoiceJoin(guildID.String(), channelID.String(), false, true) if err != nil { return nil, errors.Wrap(err, "Error joining voice channel") } @@ -37,17 +38,17 @@ func NewVCon(dc *discordgo.Session, db sounddb.ISoundDB, guildID string, channel } return &VCon{ - playSound: make(chan sounddb.SoundUID), + playSound: make(chan sounddb.SoundURI), stop: make(chan struct{}), mixer: mixer, enc: enc, vc: vc, - dc: dc, + dc: ds, db: db, }, nil } -func (a *VCon) PlaySound(uid sounddb.SoundUID) { +func (a *VCon) PlaySound(uid sounddb.SoundURI) { // log.Printf("channel: %s\n", a.vc.ChannelID) // log.Print(a.playSound) a.playSound <- uid diff --git a/EsefexApi/audioplayer/mockplayer/mockplayer.go b/EsefexApi/audioplayer/mockplayer/mockplayer.go index a8db503..ca8a86b 100644 --- a/EsefexApi/audioplayer/mockplayer/mockplayer.go +++ b/EsefexApi/audioplayer/mockplayer/mockplayer.go @@ -3,6 +3,7 @@ package mockplayer import ( "esefexapi/audioplayer" "esefexapi/sounddb" + "esefexapi/types" "log" ) @@ -18,12 +19,12 @@ func NewMockPlayer() *MockPlayer { } // PlaySoundInsecure implements audioplayer.AudioPlayer. -func (*MockPlayer) PlaySoundInsecure(uid sounddb.SoundUID, serverID string, userID string) error { - log.Printf("MockPlayer: Playing sound insecurely '%v' on server '%v' for user '%v'", uid, serverID, userID) +func (*MockPlayer) PlaySoundInsecure(uid sounddb.SoundURI, guildID types.GuildID, userID types.UserID) error { + log.Printf("MockPlayer: Playing sound insecurely '%v' on guild '%v' for user '%v'", uid, guildID, userID) return nil } -func (*MockPlayer) PlaySound(soundID string, userID string) error { +func (*MockPlayer) PlaySound(soundID types.SoundID, userID types.UserID) error { log.Printf("MockPlayer: Playing sound '%v' for user '%v'", soundID, userID) return nil } diff --git a/EsefexApi/bot/commands/delete.go b/EsefexApi/bot/commands/delete.go index a1f8b70..1cc5610 100644 --- a/EsefexApi/bot/commands/delete.go +++ b/EsefexApi/bot/commands/delete.go @@ -50,7 +50,7 @@ func (c *CommandHandlers) Delete(s *discordgo.Session, i *discordgo.InteractionC return nil, errors.Wrap(err, "Error deleting sound") } - log.Printf("Deleted sound effect %v from server %v", soundID.Value, i.GuildID) + log.Printf("Deleted sound effect %v from guild %v", soundID.Value, i.GuildID) return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, diff --git a/EsefexApi/bot/commands/link.go b/EsefexApi/bot/commands/link.go index 3140217..415595d 100644 --- a/EsefexApi/bot/commands/link.go +++ b/EsefexApi/bot/commands/link.go @@ -1,6 +1,7 @@ package commands import ( + "esefexapi/types" "fmt" "time" @@ -16,7 +17,7 @@ var ( ) func (c *CommandHandlers) Link(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - linkToken, err := c.dbs.LinkTokenStore.CreateToken(i.Member.User.ID) + linkToken, err := c.dbs.LinkTokenStore.CreateToken(types.UserID(i.Member.User.ID)) if err != nil { return nil, errors.Wrap(err, "Error creating link token") } diff --git a/EsefexApi/bot/commands/list.go b/EsefexApi/bot/commands/list.go index 48ca005..4579368 100644 --- a/EsefexApi/bot/commands/list.go +++ b/EsefexApi/bot/commands/list.go @@ -2,6 +2,7 @@ package commands import ( "esefexapi/sounddb" + "esefexapi/types" "fmt" "github.com/bwmarrin/discordgo" @@ -11,12 +12,12 @@ import ( var ( ListCommand = &discordgo.ApplicationCommand{ Name: "list", - Description: "List all sound effects in the server", + Description: "List all sound effects in the guild", } ) func (c *CommandHandlers) List(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - uids, err := c.dbs.SoundDB.GetSoundUIDs(i.GuildID) + uids, err := c.dbs.SoundDB.GetSoundUIDs(types.GuildID(i.GuildID)) if err != nil { return nil, errors.Wrap(err, "Error getting sound UIDs") } @@ -35,7 +36,7 @@ func (c *CommandHandlers) List(s *discordgo.Session, i *discordgo.InteractionCre return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: "There are no sounds in this server", + Content: "There are no sounds in this guild", }, }, nil } @@ -44,7 +45,7 @@ func (c *CommandHandlers) List(s *discordgo.Session, i *discordgo.InteractionCre return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("There is 1 sound in this server: \n%s", fmtMetaList(metas)), + Content: fmt.Sprintf("There is 1 sound in this guild: \n%s", fmtMetaList(metas)), }, }, nil } @@ -52,7 +53,7 @@ func (c *CommandHandlers) List(s *discordgo.Session, i *discordgo.InteractionCre return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("There are %d sounds in this server: \n%s", len(metas), fmtMetaList(metas)), + Content: fmt.Sprintf("There are %d sounds in this guild: \n%s", len(metas), fmtMetaList(metas)), }, }, nil } diff --git a/EsefexApi/bot/commands/unlink.go b/EsefexApi/bot/commands/unlink.go index f8bf5b2..b76a766 100644 --- a/EsefexApi/bot/commands/unlink.go +++ b/EsefexApi/bot/commands/unlink.go @@ -1,6 +1,7 @@ package commands import ( + "esefexapi/types" "esefexapi/userdb" "github.com/bwmarrin/discordgo" @@ -14,9 +15,9 @@ var ( ) func (c *CommandHandlers) Unlink(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - c.dbs.UserDB.DeleteUser(i.Member.User.ID) + c.dbs.UserDB.DeleteUser(types.UserID(i.Member.User.ID)) c.dbs.UserDB.SetUser(userdb.User{ - ID: i.Member.User.ID, + ID: types.UserID(i.Member.User.ID), Tokens: []userdb.Token{}, }) diff --git a/EsefexApi/bot/commands/upload.go b/EsefexApi/bot/commands/upload.go index 7d0063a..374fd19 100644 --- a/EsefexApi/bot/commands/upload.go +++ b/EsefexApi/bot/commands/upload.go @@ -2,6 +2,7 @@ package commands import ( "esefexapi/sounddb" + "esefexapi/types" "esefexapi/util" "fmt" "log" @@ -54,12 +55,12 @@ func (c *CommandHandlers) Upload(s *discordgo.Session, i *discordgo.InteractionC return nil, errors.Wrap(err, "Error downloading sound file") } - uid, err := c.dbs.SoundDB.AddSound(i.GuildID, fmt.Sprint(options["name"].Value), icon, pcm) + uid, err := c.dbs.SoundDB.AddSound(types.GuildID(i.GuildID), fmt.Sprint(options["name"].Value), icon, pcm) if err != nil { return nil, errors.Wrap(err, "Error adding sound") } - log.Printf("Uploaded sound effect %v to server %v", uid.SoundID, i.GuildID) + log.Printf("Uploaded sound effect %v to guild %v", uid.SoundID, i.GuildID) return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ diff --git a/EsefexApi/cmd/download_testing/download_testing.go b/EsefexApi/cmd/download_testing/download_testing.go index 4363022..af36ff6 100644 --- a/EsefexApi/cmd/download_testing/download_testing.go +++ b/EsefexApi/cmd/download_testing/download_testing.go @@ -70,5 +70,5 @@ func main() { // os.WriteFile("test.mp3", buf, os.ModePerm) - // filedb.AddSound("testserver", "test", ":sus:", buf) + // filedb.AddSound("testguild", "test", ":sus:", buf) } diff --git a/EsefexApi/go.mod b/EsefexApi/go.mod index f307116..35ac19e 100644 --- a/EsefexApi/go.mod +++ b/EsefexApi/go.mod @@ -14,15 +14,18 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect + golang.org/x/text v0.4.0 // indirect + gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( github.com/gorilla/websocket v1.4.2 // indirect github.com/pkg/errors v0.9.1 + github.com/robertkrimen/otto v0.3.0 github.com/samber/lo v1.39.0 github.com/stretchr/testify v1.8.4 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect - golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect + golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32 ) diff --git a/EsefexApi/go.sum b/EsefexApi/go.sum index fef364d..5b20e60 100644 --- a/EsefexApi/go.sum +++ b/EsefexApi/go.sum @@ -14,6 +14,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robertkrimen/otto v0.3.0 h1:5RI+8860NSxvXywDY9ddF5HcPw0puRsd8EgbXV0oqRE= +github.com/robertkrimen/otto v0.3.0/go.mod h1:uW9yN1CYflmUQYvAMS0m+ZiNo3dMzRUDQJX0jWbzgxw= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= @@ -26,10 +28,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= +gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32 h1:/S1gOotFo2sADAIdSGk1sDq1VxetoCWr6f5nxOG0dpY= diff --git a/EsefexApi/linktokenstore/linktokenstore.go b/EsefexApi/linktokenstore/linktokenstore.go index 4d7a06a..a013f39 100644 --- a/EsefexApi/linktokenstore/linktokenstore.go +++ b/EsefexApi/linktokenstore/linktokenstore.go @@ -1,6 +1,7 @@ package linktokenstore import ( + "esefexapi/types" "fmt" "time" ) @@ -10,15 +11,15 @@ var ErrTokenExpired = fmt.Errorf("Token expired") type ILinkTokenStore interface { // Get a token for a user - GetToken(userID string) (LinkToken, error) + GetToken(userID types.UserID) (LinkToken, error) // Get a user for a token - GetUser(tokenStr string) (string, error) + GetUser(tokenStr string) (types.UserID, error) // Set a token for a user - SetToken(userID string, token LinkToken) error + SetToken(userID types.UserID, token LinkToken) error // Delete a token for a user - DeleteToken(userID string) error + DeleteToken(userID types.UserID) error // Create a new token for a user (the token must be unique) - CreateToken(userID string) (LinkToken, error) + CreateToken(userID types.UserID) (LinkToken, error) // Validate a token ValidateToken(tokenStr string) (bool, error) } diff --git a/EsefexApi/linktokenstore/memorylinktokenstore/memorylinktokenstore.go b/EsefexApi/linktokenstore/memorylinktokenstore/memorylinktokenstore.go index ffb2b4a..8240b30 100644 --- a/EsefexApi/linktokenstore/memorylinktokenstore/memorylinktokenstore.go +++ b/EsefexApi/linktokenstore/memorylinktokenstore/memorylinktokenstore.go @@ -2,6 +2,7 @@ package memorylinktokenstore import ( "esefexapi/linktokenstore" + "esefexapi/types" "esefexapi/util" "time" @@ -11,12 +12,12 @@ import ( var _ linktokenstore.ILinkTokenStore = &MemoryLinkTokenStore{} type MemoryLinkTokenStore struct { - linkTokens map[string]linktokenstore.LinkToken + linkTokens map[types.UserID]linktokenstore.LinkToken expireAfter time.Duration } // CreateToken implements linktokenstore.ILinkTokenStore. -func (m *MemoryLinkTokenStore) CreateToken(userID string) (linktokenstore.LinkToken, error) { +func (m *MemoryLinkTokenStore) CreateToken(userID types.UserID) (linktokenstore.LinkToken, error) { for { token := util.RandomString(util.TokenCharset, 32) // check if token exists @@ -37,16 +38,16 @@ func (m *MemoryLinkTokenStore) CreateToken(userID string) (linktokenstore.LinkTo func NewMemoryLinkTokenStore(expireAfter time.Duration) *MemoryLinkTokenStore { return &MemoryLinkTokenStore{ - linkTokens: map[string]linktokenstore.LinkToken{}, + linkTokens: map[types.UserID]linktokenstore.LinkToken{}, expireAfter: expireAfter, } } -func (m *MemoryLinkTokenStore) GetToken(userID string) (linktokenstore.LinkToken, error) { +func (m *MemoryLinkTokenStore) GetToken(userID types.UserID) (linktokenstore.LinkToken, error) { return m.linkTokens[userID], nil } -func (m *MemoryLinkTokenStore) GetUser(token string) (string, error) { +func (m *MemoryLinkTokenStore) GetUser(token string) (types.UserID, error) { for k, v := range m.linkTokens { if v.Token == token { return k, nil @@ -56,12 +57,12 @@ func (m *MemoryLinkTokenStore) GetUser(token string) (string, error) { return "", linktokenstore.ErrTokenNotFound } -func (m *MemoryLinkTokenStore) SetToken(userID string, token linktokenstore.LinkToken) error { +func (m *MemoryLinkTokenStore) SetToken(userID types.UserID, token linktokenstore.LinkToken) error { m.linkTokens[userID] = token return nil } -func (m *MemoryLinkTokenStore) DeleteToken(userID string) error { +func (m *MemoryLinkTokenStore) DeleteToken(userID types.UserID) error { delete(m.linkTokens, userID) return nil } diff --git a/EsefexApi/permissions/merge.go b/EsefexApi/permissions/merge.go index 2c305c5..4ca6626 100644 --- a/EsefexApi/permissions/merge.go +++ b/EsefexApi/permissions/merge.go @@ -14,9 +14,9 @@ func (ps PermissionState) Merge(otherPS PermissionState) PermissionState { func (p Permissions) Merge(otherP *Permissions) Permissions { return Permissions{ - Sound: p.Sound.Merge(otherP.Sound), - Bot: p.Bot.Merge(otherP.Bot), - Server: p.Server.Merge(otherP.Server), + Sound: p.Sound.Merge(otherP.Sound), + Bot: p.Bot.Merge(otherP.Bot), + Guild: p.Guild.Merge(otherP.Guild), } } @@ -36,8 +36,8 @@ func (p BotPermissions) Merge(otherP BotPermissions) BotPermissions { } } -func (p ServerPermissions) Merge(otherP ServerPermissions) ServerPermissions { - return ServerPermissions{ +func (p GuildPermissions) Merge(otherP GuildPermissions) GuildPermissions { + return GuildPermissions{ ManageBot: p.ManageBot.Merge(otherP.ManageBot), ManageUser: p.ManageUser.Merge(otherP.ManageUser), } diff --git a/EsefexApi/permissions/permissions.go b/EsefexApi/permissions/permissions.go index 767856d..ffb749d 100644 --- a/EsefexApi/permissions/permissions.go +++ b/EsefexApi/permissions/permissions.go @@ -22,9 +22,9 @@ func (ps PermissionState) String() string { } type Permissions struct { - Sound SoundPermissions - Bot BotPermissions - Server ServerPermissions + Sound SoundPermissions + Bot BotPermissions + Guild GuildPermissions } type SoundPermissions struct { @@ -39,7 +39,7 @@ type BotPermissions struct { Leave PermissionState } -type ServerPermissions struct { +type GuildPermissions struct { ManageBot PermissionState ManageUser PermissionState } @@ -57,7 +57,7 @@ func NewDefault() Permissions { Join: Allow, Leave: Allow, }, - Server: ServerPermissions{ + Guild: GuildPermissions{ ManageBot: Allow, ManageUser: Allow, }, @@ -76,7 +76,7 @@ func NewUnset() Permissions { Join: Unset, Leave: Unset, }, - Server: ServerPermissions{ + Guild: GuildPermissions{ ManageBot: Unset, ManageUser: Unset, }, diff --git a/EsefexApi/sounddb/apimockdb/apimockdb.go b/EsefexApi/sounddb/apimockdb/apimockdb.go index c63df7c..791ba8c 100644 --- a/EsefexApi/sounddb/apimockdb/apimockdb.go +++ b/EsefexApi/sounddb/apimockdb/apimockdb.go @@ -1,13 +1,16 @@ package apimockdb -import "esefexapi/sounddb" +import ( + "esefexapi/sounddb" + "esefexapi/types" +) var mockData = map[string]map[string]sounddb.SoundMeta{ - "server1": { + "guild1": { "sound1": { - SoundID: "sound1", - ServerID: "server1", - Name: "sound1Name", + SoundID: "sound1", + GuildID: "guild1", + Name: "sound1Name", Icon: sounddb.Icon{ Name: "icon1", ID: "icon1ID", @@ -15,9 +18,9 @@ var mockData = map[string]map[string]sounddb.SoundMeta{ }, }, "sound2": { - SoundID: "sound2", - ServerID: "server1", - Name: "sound2Name", + SoundID: "sound2", + GuildID: "guild1", + Name: "sound2Name", Icon: sounddb.Icon{ Name: "icon2", ID: "icon2ID", @@ -25,11 +28,11 @@ var mockData = map[string]map[string]sounddb.SoundMeta{ }, }, }, - "server2": { + "guild2": { "sound3": { - SoundID: "sound3", - ServerID: "server2", - Name: "sound3Name", + SoundID: "sound3", + GuildID: "guild2", + Name: "sound3Name", Icon: sounddb.Icon{ Name: "icon3", ID: "icon3ID", @@ -37,9 +40,9 @@ var mockData = map[string]map[string]sounddb.SoundMeta{ }, }, "sound4": { - SoundID: "sound4", - ServerID: "server2", - Name: "sound4Name", + SoundID: "sound4", + GuildID: "guild2", + Name: "sound4Name", Icon: sounddb.Icon{ Name: "icon4", ID: "icon4ID", @@ -59,48 +62,48 @@ func NewApiMockDB() *ApiMockDB { } // AddSound implements sounddb.ISoundDB. -func (*ApiMockDB) AddSound(serverID string, name string, icon sounddb.Icon, pcm []int16) (sounddb.SoundUID, error) { +func (*ApiMockDB) AddSound(guildID types.GuildID, name string, icon sounddb.Icon, pcm []int16) (sounddb.SoundURI, error) { panic("unimplemented") } // DeleteSound implements sounddb.ISoundDB. -func (*ApiMockDB) DeleteSound(uid sounddb.SoundUID) error { +func (*ApiMockDB) DeleteSound(uid sounddb.SoundURI) error { panic("unimplemented") } -// GetServerIDs implements sounddb.ISoundDB. -func (*ApiMockDB) GetServerIDs() ([]string, error) { - ids := make([]string, 0, len(mockData)) +// GetGuildIDs implements sounddb.ISoundDB. +func (*ApiMockDB) GetGuildIDs() ([]types.GuildID, error) { + ids := make([]types.GuildID, 0, len(mockData)) for id := range mockData { - ids = append(ids, id) + ids = append(ids, types.GuildID(id)) } return ids, nil } // GetSoundMeta implements sounddb.ISoundDB. -func (*ApiMockDB) GetSoundMeta(uid sounddb.SoundUID) (sounddb.SoundMeta, error) { - return mockData[uid.ServerID][uid.SoundID], nil +func (*ApiMockDB) GetSoundMeta(uid sounddb.SoundURI) (sounddb.SoundMeta, error) { + return mockData[uid.GuildID.String()][uid.SoundID.String()], nil } // GetSoundPcm implements sounddb.ISoundDB. -func (*ApiMockDB) GetSoundPcm(uid sounddb.SoundUID) (*[]int16, error) { +func (*ApiMockDB) GetSoundPcm(uid sounddb.SoundURI) (*[]int16, error) { panic("unimplemented") } // GetSoundUIDs implements sounddb.ISoundDB. -func (*ApiMockDB) GetSoundUIDs(serverID string) ([]sounddb.SoundUID, error) { - uids := make([]sounddb.SoundUID, 0, len(mockData[serverID])) - for id := range mockData[serverID] { - uids = append(uids, sounddb.SoundUID{ - ServerID: serverID, - SoundID: id, +func (*ApiMockDB) GetSoundUIDs(guildID types.GuildID) ([]sounddb.SoundURI, error) { + uids := make([]sounddb.SoundURI, 0, len(mockData[guildID.String()])) + for id := range mockData[guildID.String()] { + uids = append(uids, sounddb.SoundURI{ + GuildID: guildID, + SoundID: types.SoundID(id), }) } return uids, nil } // SoundExists implements sounddb.ISoundDB. -func (*ApiMockDB) SoundExists(uid sounddb.SoundUID) (bool, error) { - _, ok := mockData[uid.ServerID][uid.SoundID] +func (*ApiMockDB) SoundExists(uid sounddb.SoundURI) (bool, error) { + _, ok := mockData[uid.GuildID.String()][uid.SoundID.String()] return ok, nil } diff --git a/EsefexApi/sounddb/dbcache/dbcache.go b/EsefexApi/sounddb/dbcache/dbcache.go index fc3cac7..15576ec 100644 --- a/EsefexApi/sounddb/dbcache/dbcache.go +++ b/EsefexApi/sounddb/dbcache/dbcache.go @@ -2,6 +2,7 @@ package dbcache import ( "esefexapi/sounddb" + "esefexapi/types" "sync" "github.com/pkg/errors" @@ -12,7 +13,7 @@ var _ sounddb.ISoundDB = &SoundDBCache{} // DB Cache loads all sounds into memory and caches them. // SoundDBCache implements db.SoundDB type SoundDBCache struct { - sounds map[sounddb.SoundUID]*CachedSound + sounds map[sounddb.SoundURI]*CachedSound db sounddb.ISoundDB rw sync.RWMutex } @@ -25,7 +26,7 @@ type CachedSound struct { // NewSoundDBCache creates a new DBCache. func NewSoundDBCache(db sounddb.ISoundDB) *SoundDBCache { c := &SoundDBCache{ - sounds: make(map[sounddb.SoundUID]*CachedSound), + sounds: make(map[sounddb.SoundURI]*CachedSound), db: db, } c.CacheAll() @@ -33,22 +34,22 @@ func NewSoundDBCache(db sounddb.ISoundDB) *SoundDBCache { } // AddSound implements db.SoundDB. -func (c *SoundDBCache) AddSound(serverID string, name string, icon sounddb.Icon, pcm []int16) (sounddb.SoundUID, error) { +func (c *SoundDBCache) AddSound(guildID types.GuildID, name string, icon sounddb.Icon, pcm []int16) (sounddb.SoundURI, error) { c.rw.Lock() defer c.rw.Unlock() - uid, err := c.db.AddSound(serverID, name, icon, pcm) + uid, err := c.db.AddSound(guildID, name, icon, pcm) if err != nil { - return sounddb.SoundUID{}, errors.Wrap(err, "Error adding sound") + return sounddb.SoundURI{}, errors.Wrap(err, "Error adding sound") } c.sounds[uid] = &CachedSound{ Data: &pcm, Meta: sounddb.SoundMeta{ - SoundID: uid.SoundID, - ServerID: serverID, - Name: name, - Icon: icon, + SoundID: uid.SoundID, + GuildID: guildID, + Name: name, + Icon: icon, }, } @@ -56,7 +57,7 @@ func (c *SoundDBCache) AddSound(serverID string, name string, icon sounddb.Icon, } // DeleteSound implements db.SoundDB. -func (c *SoundDBCache) DeleteSound(uid sounddb.SoundUID) error { +func (c *SoundDBCache) DeleteSound(uid sounddb.SoundURI) error { c.rw.Lock() defer c.rw.Unlock() @@ -70,27 +71,27 @@ func (c *SoundDBCache) DeleteSound(uid sounddb.SoundUID) error { return nil } -// GetServerIDs implements db.SoundDB. -func (c *SoundDBCache) GetServerIDs() ([]string, error) { +// GetGuildIDs implements db.SoundDB. +func (c *SoundDBCache) GetGuildIDs() ([]types.GuildID, error) { c.rw.RLock() defer c.rw.RUnlock() - uniqueServerIDs := make(map[string]struct{}) + uniqueGuildIDs := make(map[types.GuildID]struct{}) for uid := range c.sounds { - uniqueServerIDs[uid.ServerID] = struct{}{} + uniqueGuildIDs[uid.GuildID] = struct{}{} } - serverIDs := make([]string, 0, len(uniqueServerIDs)) - for serverID := range uniqueServerIDs { - serverIDs = append(serverIDs, serverID) + guildIDs := make([]types.GuildID, 0, len(uniqueGuildIDs)) + for guildID := range uniqueGuildIDs { + guildIDs = append(guildIDs, guildID) } - return serverIDs, nil + return guildIDs, nil } // GetSoundMeta implements db.SoundDB. -func (c *SoundDBCache) GetSoundMeta(uid sounddb.SoundUID) (sounddb.SoundMeta, error) { +func (c *SoundDBCache) GetSoundMeta(uid sounddb.SoundURI) (sounddb.SoundMeta, error) { c.rw.RLock() if sound, ok := c.sounds[uid]; ok { @@ -110,7 +111,7 @@ func (c *SoundDBCache) GetSoundMeta(uid sounddb.SoundUID) (sounddb.SoundMeta, er } // GetSoundPcm implements db.SoundDB. -func (c *SoundDBCache) GetSoundPcm(uid sounddb.SoundUID) (*[]int16, error) { +func (c *SoundDBCache) GetSoundPcm(uid sounddb.SoundURI) (*[]int16, error) { c.rw.RLock() if sound, ok := c.sounds[uid]; ok { @@ -132,14 +133,14 @@ func (c *SoundDBCache) GetSoundPcm(uid sounddb.SoundUID) (*[]int16, error) { } // GetSoundUIDs implements db.SoundDB. -func (c *SoundDBCache) GetSoundUIDs(serverID string) ([]sounddb.SoundUID, error) { +func (c *SoundDBCache) GetSoundUIDs(guildID types.GuildID) ([]sounddb.SoundURI, error) { c.rw.RLock() defer c.rw.RUnlock() - uids := make([]sounddb.SoundUID, 0) + uids := make([]sounddb.SoundURI, 0) for uid := range c.sounds { - if uid.ServerID == serverID { + if uid.GuildID == guildID { uids = append(uids, uid) } } @@ -148,13 +149,13 @@ func (c *SoundDBCache) GetSoundUIDs(serverID string) ([]sounddb.SoundUID, error) } func (c *SoundDBCache) CacheAll() error { - servers, err := c.db.GetServerIDs() + guilds, err := c.db.GetGuildIDs() if err != nil { - return errors.Wrap(err, "Error getting server ids") + return errors.Wrap(err, "Error getting guild ids") } - for _, serverID := range servers { - uids, err := c.db.GetSoundUIDs(serverID) + for _, guildID := range guilds { + uids, err := c.db.GetSoundUIDs(guildID) if err != nil { return errors.Wrap(err, "Error getting sound uids") } @@ -170,7 +171,7 @@ func (c *SoundDBCache) CacheAll() error { return nil } -func (c *SoundDBCache) LoadSound(uid sounddb.SoundUID) (*CachedSound, error) { +func (c *SoundDBCache) LoadSound(uid sounddb.SoundURI) (*CachedSound, error) { c.rw.Lock() defer c.rw.Unlock() @@ -199,7 +200,7 @@ func (c *SoundDBCache) LoadSound(uid sounddb.SoundUID) (*CachedSound, error) { } -func (c *SoundDBCache) SoundExists(uid sounddb.SoundUID) (bool, error) { +func (c *SoundDBCache) SoundExists(uid sounddb.SoundURI) (bool, error) { c.rw.RLock() defer c.rw.RUnlock() diff --git a/EsefexApi/sounddb/filesounddb/addsound.go b/EsefexApi/sounddb/filesounddb/addsound.go index 15160d0..4ea609b 100644 --- a/EsefexApi/sounddb/filesounddb/addsound.go +++ b/EsefexApi/sounddb/filesounddb/addsound.go @@ -4,6 +4,7 @@ import ( "encoding/binary" "encoding/json" "esefexapi/sounddb" + "esefexapi/types" "fmt" "log" "os" @@ -12,35 +13,35 @@ import ( ) // AddSound implements sounddb.SoundDB. -func (f *FileDB) AddSound(serverID string, name string, icon sounddb.Icon, pcm []int16) (sounddb.SoundUID, error) { - sid, err := f.generateSoundID(serverID) +func (f *FileDB) AddSound(guildID types.GuildID, name string, icon sounddb.Icon, pcm []int16) (sounddb.SoundURI, error) { + sid, err := f.generateSoundID(guildID) if err != nil { - return sounddb.SoundUID{}, errors.Wrap(err, "Error generating sound ID") + return sounddb.SoundURI{}, errors.Wrap(err, "Error generating sound ID") } sound := sounddb.SoundMeta{ - SoundID: sid, - ServerID: serverID, - Name: name, - Icon: icon, + SoundID: sid, + GuildID: guildID, + Name: name, + Icon: icon, } // Make sure the db folder exists - path := fmt.Sprintf("%s/%s", f.location, serverID) + path := fmt.Sprintf("%s/%s", f.location, guildID) os.MkdirAll(path, os.ModePerm) // write meta file - path = fmt.Sprintf("%s/%s/%s_meta.json", f.location, serverID, sound.SoundID) + path = fmt.Sprintf("%s/%s/%s_meta.json", f.location, guildID, sound.SoundID) metaFile, err := os.Create(path) if err != nil { log.Printf("Error creating meta file: %+v", err) - return sounddb.SoundUID{}, errors.Wrap(err, "Error creating meta file") + return sounddb.SoundURI{}, errors.Wrap(err, "Error creating meta file") } metaJson, err := json.Marshal(sound) if err != nil { log.Printf("Error marshalling meta: %+v", err) - return sounddb.SoundUID{}, errors.Wrap(err, "Error marshalling meta") + return sounddb.SoundURI{}, errors.Wrap(err, "Error marshalling meta") } metaFile.Write(metaJson) @@ -48,18 +49,18 @@ func (f *FileDB) AddSound(serverID string, name string, icon sounddb.Icon, pcm [ // write sound file - path = fmt.Sprintf("%s/%s/%s_sound.s16le", f.location, serverID, sound.SoundID) + path = fmt.Sprintf("%s/%s/%s_sound.s16le", f.location, guildID, sound.SoundID) soundFile, err := os.Create(path) if err != nil { log.Printf("Error creating sound file: %+v", err) - return sounddb.SoundUID{}, errors.Wrap(err, "Error creating sound file") + return sounddb.SoundURI{}, errors.Wrap(err, "Error creating sound file") } err = binary.Write(soundFile, binary.LittleEndian, pcm) if err != nil { log.Printf("Error writing sound file: %+v", err) - return sounddb.SoundUID{}, errors.Wrap(err, "Error writing sound file") + return sounddb.SoundURI{}, errors.Wrap(err, "Error writing sound file") } return sound.GetUID(), nil diff --git a/EsefexApi/sounddb/filesounddb/deletesound.go b/EsefexApi/sounddb/filesounddb/deletesound.go index c9029e0..b6733e3 100644 --- a/EsefexApi/sounddb/filesounddb/deletesound.go +++ b/EsefexApi/sounddb/filesounddb/deletesound.go @@ -10,15 +10,15 @@ import ( ) // DeleteSound implements sounddb.SoundDB. -func (f *FileDB) DeleteSound(uid sounddb.SoundUID) error { - path := fmt.Sprintf("%s/%s/%s_meta.json", f.location, uid.ServerID, uid.SoundID) +func (f *FileDB) DeleteSound(uid sounddb.SoundURI) error { + path := fmt.Sprintf("%s/%s/%s_meta.json", f.location, uid.GuildID, uid.SoundID) err := os.Remove(path) if err != nil { log.Printf("Error removing meta file: %+v", err) return errors.Wrap(err, "Error removing meta file") } - path = fmt.Sprintf("%s/%s/%s_sound.s16le", f.location, uid.ServerID, uid.SoundID) + path = fmt.Sprintf("%s/%s/%s_sound.s16le", f.location, uid.GuildID, uid.SoundID) err = os.Remove(path) if err != nil { log.Printf("Error removing sound file: %+v", err) diff --git a/EsefexApi/sounddb/filesounddb/filedb_test.go b/EsefexApi/sounddb/filesounddb/filedb_test.go index e2cd8cf..a994808 100644 --- a/EsefexApi/sounddb/filesounddb/filedb_test.go +++ b/EsefexApi/sounddb/filesounddb/filedb_test.go @@ -2,6 +2,7 @@ package filesounddb import ( "esefexapi/sounddb" + "esefexapi/types" "fmt" "log" "math/rand" @@ -20,7 +21,7 @@ func TestFileDB(t *testing.T) { Url: "https://raw.githubusercontent.com/EsefexBot/Esefex/main/EsefexApi/test/staticfiles/icon.webp", } - serverID := "server1" + guildID := types.GuildID("guild1") soundName := "sound1" soundPcm := []int16{115, 117, 115} @@ -29,12 +30,12 @@ func TestFileDB(t *testing.T) { assert.Nil(t, err) // Test that we can add a sound - uid, err := db.AddSound(serverID, soundName, icon, soundPcm) + uid, err := db.AddSound(guildID, soundName, icon, soundPcm) assert.Nil(t, err) - _, err = os.Stat(fmt.Sprintf("%s/%s/%s_meta.json", location, serverID, uid.SoundID)) + _, err = os.Stat(fmt.Sprintf("%s/%s/%s_meta.json", location, guildID, uid.SoundID)) assert.Nil(t, err) - _, err = os.Stat(fmt.Sprintf("%s/%s/%s_sound.s16le", location, serverID, uid.SoundID)) + _, err = os.Stat(fmt.Sprintf("%s/%s/%s_sound.s16le", location, guildID, uid.SoundID)) assert.Nil(t, err) // Test that the sound exists @@ -46,10 +47,10 @@ func TestFileDB(t *testing.T) { sound, err := db.GetSoundMeta(uid) assert.Nil(t, err) assert.Equal(t, sound, sounddb.SoundMeta{ - SoundID: uid.SoundID, - ServerID: serverID, - Name: soundName, - Icon: icon, + SoundID: uid.SoundID, + GuildID: guildID, + Name: soundName, + Icon: icon, }) // Test that we can get the sound pcm @@ -57,15 +58,15 @@ func TestFileDB(t *testing.T) { assert.Nil(t, err) assert.Equal(t, &soundPcm, soundPcm2) - // Test that we can get the server ids - ids, err := db.GetServerIDs() + // Test that we can get the guild ids + ids, err := db.GetGuildIDs() assert.Nil(t, err) - assert.Equal(t, []string{serverID}, ids) + assert.Equal(t, []types.GuildID{guildID}, ids) // Test that we can get the sound uids - uids, err := db.GetSoundUIDs(serverID) + uids, err := db.GetSoundUIDs(guildID) assert.Nil(t, err) - assert.Equal(t, []sounddb.SoundUID{uid}, uids) + assert.Equal(t, []sounddb.SoundURI{uid}, uids) // Test that we can delete the sound err = db.DeleteSound(uid) diff --git a/EsefexApi/sounddb/filesounddb/getserverids.go b/EsefexApi/sounddb/filesounddb/getguildids.go similarity index 61% rename from EsefexApi/sounddb/filesounddb/getserverids.go rename to EsefexApi/sounddb/filesounddb/getguildids.go index 363b22a..04af2bb 100644 --- a/EsefexApi/sounddb/filesounddb/getserverids.go +++ b/EsefexApi/sounddb/filesounddb/getguildids.go @@ -1,25 +1,26 @@ package filesounddb import ( + "esefexapi/types" "log" "os" "github.com/pkg/errors" ) -// GetServerIDs implements sounddb.SoundDB. -func (f *FileDB) GetServerIDs() ([]string, error) { +// GetGuildIDs implements sounddb.SoundDB. +func (f *FileDB) GetGuildIDs() ([]types.GuildID, error) { files, err := os.ReadDir(f.location) if err != nil { log.Printf("Error reading directory: %+v", err) return nil, errors.Wrap(err, "Error reading directory") } - ids := make([]string, 0) + ids := make([]types.GuildID, 0) for _, file := range files { if file.IsDir() { - ids = append(ids, file.Name()) + ids = append(ids, types.GuildID(file.Name())) } } diff --git a/EsefexApi/sounddb/filesounddb/getsoundmeta.go b/EsefexApi/sounddb/filesounddb/getsoundmeta.go index 61979c4..53b3f39 100644 --- a/EsefexApi/sounddb/filesounddb/getsoundmeta.go +++ b/EsefexApi/sounddb/filesounddb/getsoundmeta.go @@ -9,8 +9,8 @@ import ( ) // GetSoundMeta implements sounddb.SoundDB. -func (f *FileDB) GetSoundMeta(uid sounddb.SoundUID) (sounddb.SoundMeta, error) { - path := fmt.Sprintf("%s/%s/%s_meta.json", f.location, uid.ServerID, uid.SoundID) +func (f *FileDB) GetSoundMeta(uid sounddb.SoundURI) (sounddb.SoundMeta, error) { + path := fmt.Sprintf("%s/%s/%s_meta.json", f.location, uid.GuildID, uid.SoundID) metaFile, err := os.Open(path) if err != nil { diff --git a/EsefexApi/sounddb/filesounddb/getsoundpcm.go b/EsefexApi/sounddb/filesounddb/getsoundpcm.go index 67c1ff1..75220f9 100644 --- a/EsefexApi/sounddb/filesounddb/getsoundpcm.go +++ b/EsefexApi/sounddb/filesounddb/getsoundpcm.go @@ -11,8 +11,8 @@ import ( ) // GetSoundPcm implements sounddb.SoundDB. -func (f *FileDB) GetSoundPcm(uid sounddb.SoundUID) (*[]int16, error) { - path := fmt.Sprintf("%s/%s/%s_sound.s16le", f.location, uid.ServerID, uid.SoundID) +func (f *FileDB) GetSoundPcm(uid sounddb.SoundURI) (*[]int16, error) { + path := fmt.Sprintf("%s/%s/%s_sound.s16le", f.location, uid.GuildID, uid.SoundID) sf, err := os.Open(path) if err != nil { return nil, errors.Wrap(err, "Error opening sound file") diff --git a/EsefexApi/sounddb/filesounddb/getsounduids.go b/EsefexApi/sounddb/filesounddb/getsounduids.go index a7f3433..ade6e2e 100644 --- a/EsefexApi/sounddb/filesounddb/getsounduids.go +++ b/EsefexApi/sounddb/filesounddb/getsounduids.go @@ -2,6 +2,7 @@ package filesounddb import ( "esefexapi/sounddb" + "esefexapi/types" "esefexapi/util" "fmt" "log" @@ -12,10 +13,10 @@ import ( ) // GetSoundUIDs implements sounddb.SoundDB. -func (f *FileDB) GetSoundUIDs(serverID string) ([]sounddb.SoundUID, error) { - path := fmt.Sprintf("%s/%s", f.location, serverID) +func (f *FileDB) GetSoundUIDs(guildID types.GuildID) ([]sounddb.SoundURI, error) { + path := fmt.Sprintf("%s/%s", f.location, guildID) if !util.PathExists(path) { - return make([]sounddb.SoundUID, 0), nil + return make([]sounddb.SoundURI, 0), nil } files, err := os.ReadDir(path) @@ -24,14 +25,14 @@ func (f *FileDB) GetSoundUIDs(serverID string) ([]sounddb.SoundUID, error) { return nil, errors.Wrap(err, "Error reading directory") } - uids := make([]sounddb.SoundUID, 0) + uids := make([]sounddb.SoundURI, 0) for _, file := range files { name := file.Name() nameSplits := strings.Split(name, "_") if len(nameSplits) == 2 && nameSplits[1] == "meta.json" { - uids = append(uids, sounddb.SuidFromStrings(serverID, nameSplits[0])) + uids = append(uids, sounddb.SuidFromStrings(guildID.String(), nameSplits[0])) } } diff --git a/EsefexApi/sounddb/filesounddb/internal.go b/EsefexApi/sounddb/filesounddb/internal.go index 280fcf9..bf54422 100644 --- a/EsefexApi/sounddb/filesounddb/internal.go +++ b/EsefexApi/sounddb/filesounddb/internal.go @@ -2,13 +2,14 @@ package filesounddb import ( "esefexapi/sounddb" + "esefexapi/types" "math/rand" "strconv" "github.com/pkg/errors" ) -func (f *FileDB) generateSoundID(serverId string) (string, error) { +func (f *FileDB) generateSoundID(guildID types.GuildID) (types.SoundID, error) { // generate random number with 16 digits min := 100000000 max := 999999999 @@ -16,13 +17,13 @@ func (f *FileDB) generateSoundID(serverId string) (string, error) { for { id := strconv.FormatInt(int64(rand.Intn(max-min)+min), 10) - exists, err := f.SoundExists(sounddb.SuidFromStrings(serverId, id)) + exists, err := f.SoundExists(sounddb.SuidFromStrings(guildID.String(), id)) if err != nil { return "", errors.Wrap(err, "Error checking if sound exists") } if !exists { - return id, nil + return types.SoundID(id), nil } } } diff --git a/EsefexApi/sounddb/filesounddb/soundexists.go b/EsefexApi/sounddb/filesounddb/soundexists.go index bebe949..6847304 100644 --- a/EsefexApi/sounddb/filesounddb/soundexists.go +++ b/EsefexApi/sounddb/filesounddb/soundexists.go @@ -7,8 +7,8 @@ import ( "github.com/pkg/errors" ) -func (f *FileDB) SoundExists(uid sounddb.SoundUID) (bool, error) { - uids, err := f.GetSoundUIDs(uid.ServerID) +func (f *FileDB) SoundExists(uid sounddb.SoundURI) (bool, error) { + uids, err := f.GetSoundUIDs(uid.GuildID) if err != nil { return false, errors.Wrap(err, "Error getting sound uids") } diff --git a/EsefexApi/sounddb/sounddb.go b/EsefexApi/sounddb/sounddb.go index 31fafa0..a0c6b32 100644 --- a/EsefexApi/sounddb/sounddb.go +++ b/EsefexApi/sounddb/sounddb.go @@ -1,23 +1,25 @@ package sounddb +import "esefexapi/types" + type ISoundDB interface { - AddSound(serverID string, name string, icon Icon, pcm []int16) (SoundUID, error) - DeleteSound(uid SoundUID) error - GetSoundMeta(uid SoundUID) (SoundMeta, error) - GetSoundPcm(uid SoundUID) (*[]int16, error) - GetSoundUIDs(serverID string) ([]SoundUID, error) - GetServerIDs() ([]string, error) - SoundExists(uid SoundUID) (bool, error) + AddSound(guildID types.GuildID, name string, icon Icon, pcm []int16) (SoundURI, error) + DeleteSound(uid SoundURI) error + GetSoundMeta(uid SoundURI) (SoundMeta, error) + GetSoundPcm(uid SoundURI) (*[]int16, error) + GetSoundUIDs(guildID types.GuildID) ([]SoundURI, error) + GetGuildIDs() ([]types.GuildID, error) + SoundExists(uid SoundURI) (bool, error) } -type SoundUID struct { - ServerID string - SoundID string +type SoundURI struct { + GuildID types.GuildID + SoundID types.SoundID } type SoundMeta struct { - SoundID string `json:"id"` - ServerID string `json:"serverId"` - Name string `json:"name"` - Icon Icon `json:"icon"` + SoundID types.SoundID `json:"id"` + GuildID types.GuildID `json:"guildId"` + Name string `json:"name"` + Icon Icon `json:"icon"` } diff --git a/EsefexApi/sounddb/util.go b/EsefexApi/sounddb/util.go index a30761e..54df539 100644 --- a/EsefexApi/sounddb/util.go +++ b/EsefexApi/sounddb/util.go @@ -1,15 +1,24 @@ package sounddb -func SuidFromStrings(serverId string, soundId string) SoundUID { - return SoundUID{ - ServerID: serverId, - SoundID: soundId, +import "esefexapi/types" + +func SuidFromStrings(guildID string, soundID string) SoundURI { + return SoundURI{ + GuildID: types.GuildID(guildID), + SoundID: types.SoundID(soundID), + } +} + +func New(guildID types.GuildID, soundID types.SoundID) SoundURI { + return SoundURI{ + GuildID: guildID, + SoundID: soundID, } } -func (sMeta SoundMeta) GetUID() SoundUID { - return SoundUID{ - ServerID: sMeta.ServerID, - SoundID: sMeta.SoundID, +func (sMeta SoundMeta) GetUID() SoundURI { + return SoundURI{ + GuildID: sMeta.GuildID, + SoundID: sMeta.SoundID, } } diff --git a/EsefexApi/types/types.go b/EsefexApi/types/types.go index b239ae6..ce25c4d 100644 --- a/EsefexApi/types/types.go +++ b/EsefexApi/types/types.go @@ -2,10 +2,30 @@ package types type UserID string +func (u UserID) String() string { + return string(u) +} + type RoleID string +func (r RoleID) String() string { + return string(r) +} + type ChannelID string +func (c ChannelID) String() string { + return string(c) +} + type GuildID string +func (g GuildID) String() string { + return string(g) +} + type SoundID string + +func (s SoundID) String() string { + return string(s) +} diff --git a/EsefexApi/userdb/fileuserdb/fileuserdb.go b/EsefexApi/userdb/fileuserdb/fileuserdb.go index a06bda1..e4a4102 100644 --- a/EsefexApi/userdb/fileuserdb/fileuserdb.go +++ b/EsefexApi/userdb/fileuserdb/fileuserdb.go @@ -2,6 +2,7 @@ package fileuserdb import ( "encoding/json" + "esefexapi/types" "esefexapi/userdb" "log" "os" @@ -14,7 +15,7 @@ import ( var _ userdb.IUserDB = &FileUserDB{} type FileUserDB struct { - Users map[string]userdb.User + Users map[types.UserID]userdb.User file *os.File fileLock sync.Mutex } @@ -46,7 +47,7 @@ func NewFileUserDB(filePath string) (*FileUserDB, error) { _ = json.NewDecoder(file).Decode(&users) // create map - userMap := make(map[string]userdb.User) + userMap := make(map[types.UserID]userdb.User) for _, user := range users { userMap[user.ID] = user } @@ -67,6 +68,7 @@ func (f *FileUserDB) Close() error { func (f *FileUserDB) Save() error { f.fileLock.Lock() defer f.fileLock.Unlock() + // reset file f.file.Seek(0, 0) f.file.Truncate(0) diff --git a/EsefexApi/userdb/fileuserdb/impluserdb.go b/EsefexApi/userdb/fileuserdb/impluserdb.go index d26528c..4ba3854 100644 --- a/EsefexApi/userdb/fileuserdb/impluserdb.go +++ b/EsefexApi/userdb/fileuserdb/impluserdb.go @@ -2,6 +2,7 @@ package fileuserdb import ( "esefexapi/opt" + "esefexapi/types" "esefexapi/userdb" "esefexapi/util" "log" @@ -11,9 +12,9 @@ import ( ) // GetUser implements userdb.UserDB. -func (f *FileUserDB) GetUser(id string) (opt.Option[*userdb.User], error) { +func (f *FileUserDB) GetUser(userID types.UserID) (opt.Option[*userdb.User], error) { for _, user := range f.Users { - if user.ID == id { + if user.ID == userID { return opt.Some(&user), nil } } @@ -30,8 +31,8 @@ func (f *FileUserDB) SetUser(user userdb.User) error { } // DeleteUser implements userdb.UserDB. -func (f *FileUserDB) DeleteUser(id string) error { - delete(f.Users, id) +func (f *FileUserDB) DeleteUser(userID types.UserID) error { + delete(f.Users, userID) go f.Save() @@ -58,7 +59,7 @@ func (f *FileUserDB) GetUserByToken(token userdb.Token) (opt.Option[*userdb.User return opt.None[*userdb.User](), nil } -func (f *FileUserDB) NewToken(userID string) (userdb.Token, error) { +func (f *FileUserDB) NewToken(userID types.UserID) (userdb.Token, error) { token := util.RandomString(util.TokenCharset, 32) user, err := f.getOrCreateUser(userID) @@ -77,7 +78,7 @@ func (f *FileUserDB) NewToken(userID string) (userdb.Token, error) { return userdb.Token(token), nil } -func (f *FileUserDB) getOrCreateUser(userID string) (*userdb.User, error) { +func (f *FileUserDB) getOrCreateUser(userID types.UserID) (*userdb.User, error) { Ouser, err := f.GetUser(userID) if Ouser.IsNone() { f.SetUser(userdb.User{ diff --git a/EsefexApi/userdb/user.go b/EsefexApi/userdb/user.go index 446c084..5335956 100644 --- a/EsefexApi/userdb/user.go +++ b/EsefexApi/userdb/user.go @@ -1,8 +1,10 @@ package userdb +import "esefexapi/types" + type User struct { - ID string `json:"id"` - Tokens []Token `json:"tokens"` + ID types.UserID `json:"id"` + Tokens []Token `json:"tokens"` } type Token string diff --git a/EsefexApi/userdb/userdb.go b/EsefexApi/userdb/userdb.go index ccd6f3f..eb28e1a 100644 --- a/EsefexApi/userdb/userdb.go +++ b/EsefexApi/userdb/userdb.go @@ -2,16 +2,17 @@ package userdb import ( "esefexapi/opt" + "esefexapi/types" // "github.com/pkg/errors" ) // var ErrUserNotFound = fmt.Errorf("User not found") type IUserDB interface { - GetUser(userID string) (opt.Option[*User], error) + GetUser(userID types.UserID) (opt.Option[*User], error) SetUser(user User) error - DeleteUser(userID string) error + DeleteUser(userID types.UserID) error GetAllUsers() ([]*User, error) GetUserByToken(token Token) (opt.Option[*User], error) - NewToken(userID string) (Token, error) + NewToken(userID types.UserID) (Token, error) } diff --git a/EsefexApi/util/dcgoutil/dcgoutil.go b/EsefexApi/util/dcgoutil/dcgoutil.go index 5eeb294..342e474 100644 --- a/EsefexApi/util/dcgoutil/dcgoutil.go +++ b/EsefexApi/util/dcgoutil/dcgoutil.go @@ -4,6 +4,7 @@ import ( // "log" "esefexapi/opt" + "esefexapi/types" "github.com/bwmarrin/discordgo" // "github.com/davecgh/go-spew/spew" @@ -12,9 +13,9 @@ import ( // util functions for discordgo -// checks if the bot is in a voice channel in a server -func BotInVC(ds *discordgo.Session, serverID, channelID string) (bool, error) { - vs, err := ds.State.VoiceState(serverID, ds.State.User.ID) +// checks if the bot is in a voice channel in a guild +func BotInVC(ds *discordgo.Session, guildID, channelID string) (bool, error) { + vs, err := ds.State.VoiceState(guildID, ds.State.User.ID) if err == discordgo.ErrStateNotFound { return false, nil } else if err != nil { @@ -23,9 +24,9 @@ func BotInVC(ds *discordgo.Session, serverID, channelID string) (bool, error) { return vs.ChannelID == channelID, nil } -// gets the voice state of the bot in a server -func GetBotVC(ds *discordgo.Session, serverID string) (opt.Option[*discordgo.VoiceState], error) { - vc, err := ds.State.VoiceState(serverID, ds.State.User.ID) +// gets the voice state of the bot in a guild +func GetBotVC(ds *discordgo.Session, guildID types.GuildID) (opt.Option[*discordgo.VoiceState], error) { + vc, err := ds.State.VoiceState(guildID.String(), ds.State.User.ID) if err == discordgo.ErrStateNotFound { return opt.None[*discordgo.VoiceState](), nil } else if err != nil { @@ -36,9 +37,9 @@ func GetBotVC(ds *discordgo.Session, serverID string) (opt.Option[*discordgo.Voi } // gets a list of users in a the channel the bot is in -func GetVCUsers(ds *discordgo.Session, serverID, channelID string) ([]*discordgo.VoiceState, error) { +func GetVCUsers(ds *discordgo.Session, guildID, channelID string) ([]*discordgo.VoiceState, error) { // Get the Guild object - guild, err := ds.State.Guild(serverID) + guild, err := ds.State.Guild(guildID) if err != nil { return nil, errors.Wrap(err, "Error getting guild") } @@ -62,9 +63,9 @@ func GetVCUsers(ds *discordgo.Session, serverID, channelID string) ([]*discordgo return channelUsers, nil } -// gets the voice state of a user in a server -func UserServerVC(ds *discordgo.Session, serverID, userID string) (opt.Option[*discordgo.VoiceState], error) { - vs, err := ds.State.VoiceState(serverID, userID) +// gets the voice state of a user in a guild +func UserGuildVC(ds *discordgo.Session, guildID types.GuildID, userID types.UserID) (opt.Option[*discordgo.VoiceState], error) { + vs, err := ds.State.VoiceState(guildID.String(), userID.String()) if err == discordgo.ErrStateNotFound { return opt.None[*discordgo.VoiceState](), nil } else if err != nil { @@ -74,10 +75,10 @@ func UserServerVC(ds *discordgo.Session, serverID, userID string) (opt.Option[*d return opt.Some(vs), nil } -// gets the voice state of a user in any server -func UserVCAny(ds *discordgo.Session, userID string) (opt.Option[*discordgo.VoiceState], error) { +// gets the voice state of a user in any guild +func UserVCAny(ds *discordgo.Session, userID types.UserID) (opt.Option[*discordgo.VoiceState], error) { for _, guild := range ds.State.Guilds { - vs, err := ds.State.VoiceState(guild.ID, userID) + vs, err := ds.State.VoiceState(guild.ID, userID.String()) if err == discordgo.ErrStateNotFound { continue } else if err != nil { @@ -100,7 +101,7 @@ func UserInBotVC(ds *discordgo.Session, userID string) (bool, error) { return false, errors.Wrap(err, "Error getting voice state") } - botVCopt, err := GetBotVC(ds, guild.ID) + botVCopt, err := GetBotVC(ds, types.GuildID(guild.ID)) if err != nil { return false, errors.Wrap(err, "Error getting bot voice state") } else if botVCopt.IsNone() { @@ -114,8 +115,8 @@ func UserInBotVC(ds *discordgo.Session, userID string) (bool, error) { return false, nil } -// gets the server a user is connected to (if any) -func UserVCGuild(ds *discordgo.Session, userID string) (opt.Option[*discordgo.Guild], error) { +// gets the guild a user is connected to (if any) +func UserVCGuild(ds *discordgo.Session, userID types.UserID) (opt.Option[*discordgo.Guild], error) { Ochan, err := UserVCAny(ds, userID) if err != nil { return opt.None[*discordgo.Guild](), errors.Wrap(err, "Error getting user voice state") @@ -133,22 +134,22 @@ func UserVCGuild(ds *discordgo.Session, userID string) (opt.Option[*discordgo.Gu return opt.Some(guild), nil } -// get the list of servers a user is a member of -func UserServers(ds *discordgo.Session, userID string) ([]*discordgo.Guild, error) { - servers := []*discordgo.Guild{} +// get the list of guilds a user is a member of +func UserGuilds(ds *discordgo.Session, userID types.UserID) ([]*discordgo.Guild, error) { + guilds := []*discordgo.Guild{} for _, guild := range ds.State.Guilds { - member, err := ds.State.Member(guild.ID, userID) + member, err := ds.State.Member(guild.ID, userID.String()) if err == discordgo.ErrStateNotFound { continue } else if err != nil { return nil, errors.Wrap(err, "Error getting member") } - if member.User.ID == userID { - servers = append(servers, guild) + if member.User.ID == userID.String() { + guilds = append(guilds, guild) } } - return servers, nil + return guilds, nil } From b95b27f5c3a8446357c7d0f600262bd8eeda3db1 Mon Sep 17 00:00:00 2001 From: jo_kil Date: Sun, 7 Jan 2024 02:52:29 +0100 Subject: [PATCH 06/20] golangci-lint setup and fixing --- EsefexApi/.golangci.yaml | 22 ++++++++++ EsefexApi/api/api.go | 1 + EsefexApi/api/routes/getguild.go | 7 +++- EsefexApi/api/routes/getguilds.go | 6 ++- EsefexApi/api/routes/getindex.go | 2 +- EsefexApi/api/routes/getlinkredirect.go | 6 ++- EsefexApi/api/routes/getsounds.go | 8 +++- EsefexApi/api/routes/postplaysound.go | 7 +++- EsefexApi/api/routes/postplaysoundinsecure.go | 5 ++- .../discordplayer/discordplayer.go | 10 ++++- EsefexApi/audioplayer/discordplayer/util.go | 6 ++- .../audioplayer/discordplayer/vcon/vcon.go | 23 +++++++++-- EsefexApi/audioprocessing/s16leCacheReader.go | 9 +++-- EsefexApi/bot/actions/actions.go | 19 --------- EsefexApi/bot/bot.go | 13 +++++- EsefexApi/bot/commands/commands.go | 10 ++++- EsefexApi/bot/commands/link.go | 2 +- EsefexApi/bot/commands/unlink.go | 11 ++++- EsefexApi/bot/util.go | 31 ++++++++++---- .../airhorn_example/airhorn_example.go | 0 .../config_testing/config_testing.go | 0 .../discord_state_cache_testing.go | 0 .../download_testing/download_testing.go | 0 .../emoji_testing/emoji_testing.go | 0 .../encoder_stream_testing.go | 6 ++- .../gopus_testing/gopus_testing.go | 0 .../objectmethod_testing.go | 0 .../opus_testing/opus_testing.go | 0 .../pcm_mixer/pcm_mixer_testing.go} | 2 +- .../service_testing/service_testing.go | 0 .../stack_trace_testing.go | 0 EsefexApi/config.toml | 2 + EsefexApi/config/config.go | 11 +++-- EsefexApi/go.mod | 20 +++++----- EsefexApi/go.sum | 40 +++++++++++-------- .../memorylinktokenstore.go | 10 ++++- EsefexApi/main.go | 38 ++++++++---------- EsefexApi/permissions/permissions_test.go | 8 ++++ EsefexApi/sounddb/dbcache/dbcache.go | 10 +++-- EsefexApi/sounddb/filesounddb/addsound.go | 12 +++++- EsefexApi/sounddb/filesounddb/getsoundmeta.go | 5 ++- EsefexApi/userdb/fileuserdb/fileuserdb.go | 20 ++++++++-- .../userdb/fileuserdb/fileuserdb_test.go | 2 + EsefexApi/userdb/fileuserdb/impluserdb.go | 32 ++++++++++++--- EsefexApi/util/must/must.go | 9 +++++ 45 files changed, 302 insertions(+), 123 deletions(-) create mode 100644 EsefexApi/.golangci.yaml delete mode 100644 EsefexApi/bot/actions/actions.go rename EsefexApi/cmd/{ => testing}/airhorn_example/airhorn_example.go (100%) rename EsefexApi/cmd/{ => testing}/config_testing/config_testing.go (100%) rename EsefexApi/cmd/{ => testing}/discord_state_cache_testing/discord_state_cache_testing.go (100%) rename EsefexApi/cmd/{ => testing}/download_testing/download_testing.go (100%) rename EsefexApi/cmd/{ => testing}/emoji_testing/emoji_testing.go (100%) rename EsefexApi/cmd/{ => testing}/encoder_stream_testing/encoder_stream_testing.go (73%) rename EsefexApi/cmd/{ => testing}/gopus_testing/gopus_testing.go (100%) rename EsefexApi/cmd/{ => testing}/objectmethod_testing/objectmethod_testing.go (100%) rename EsefexApi/cmd/{ => testing}/opus_testing/opus_testing.go (100%) rename EsefexApi/cmd/{pcm_mixer/pcm_mixer.go => testing/pcm_mixer/pcm_mixer_testing.go} (93%) rename EsefexApi/cmd/{ => testing}/service_testing/service_testing.go (100%) rename EsefexApi/cmd/{ => testing}/stack_trace_testing/stack_trace_testing.go (100%) create mode 100644 EsefexApi/permissions/permissions_test.go create mode 100644 EsefexApi/util/must/must.go diff --git a/EsefexApi/.golangci.yaml b/EsefexApi/.golangci.yaml new file mode 100644 index 0000000..b7495fc --- /dev/null +++ b/EsefexApi/.golangci.yaml @@ -0,0 +1,22 @@ +linters: + # enable-all: true + disable: + - forbidigo + - golint + - depguard + - varnamelen + - ifshort + - nosnakecase + - deadcode + - varcheck + - exhaustivestruct + - maligned + - interfacer + - scopelint + - structcheck + +run: + skip-files: + - ".+_testing.go$" # skip files for quick testing of fuctionality + skip-dirs: + - "cmd/testing" \ No newline at end of file diff --git a/EsefexApi/api/api.go b/EsefexApi/api/api.go index baa5b92..a175fd0 100644 --- a/EsefexApi/api/api.go +++ b/EsefexApi/api/api.go @@ -69,6 +69,7 @@ func (api *HttpApi) run() { log.Printf("Webserver started on port %d (http://localhost:%d)\n", api.apiPort, api.apiPort) + // nolint:errcheck go http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", api.apiPort), router) close(api.ready) diff --git a/EsefexApi/api/routes/getguild.go b/EsefexApi/api/routes/getguild.go index 203a5ee..f513caa 100644 --- a/EsefexApi/api/routes/getguild.go +++ b/EsefexApi/api/routes/getguild.go @@ -3,6 +3,7 @@ package routes import ( "esefexapi/types" "esefexapi/util/dcgoutil" + "log" "net/http" ) @@ -21,5 +22,9 @@ func (h *RouteHandlers) GetGuild(w http.ResponseWriter, r *http.Request, userID } guildID := Ovs.Unwrap().GuildID - w.Write([]byte(guildID)) + _, err = w.Write([]byte(guildID)) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } } diff --git a/EsefexApi/api/routes/getguilds.go b/EsefexApi/api/routes/getguilds.go index a40eab5..f0c798b 100644 --- a/EsefexApi/api/routes/getguilds.go +++ b/EsefexApi/api/routes/getguilds.go @@ -35,5 +35,9 @@ func (h *RouteHandlers) GetGuilds(w http.ResponseWriter, r *http.Request, userID } w.Header().Set("Content-Type", "application/json") - w.Write(js) + _, err = w.Write(js) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } } diff --git a/EsefexApi/api/routes/getindex.go b/EsefexApi/api/routes/getindex.go index ec66354..77be657 100644 --- a/EsefexApi/api/routes/getindex.go +++ b/EsefexApi/api/routes/getindex.go @@ -7,5 +7,5 @@ import ( // / func (h *RouteHandlers) GetIndex(w http.ResponseWriter, r *http.Request) { - io.WriteString(w, "Index!\n") + _, _ = io.WriteString(w, "Index!\n") } diff --git a/EsefexApi/api/routes/getlinkredirect.go b/EsefexApi/api/routes/getlinkredirect.go index 53270fe..052dc14 100644 --- a/EsefexApi/api/routes/getlinkredirect.go +++ b/EsefexApi/api/routes/getlinkredirect.go @@ -66,7 +66,11 @@ func (h *RouteHandlers) GetLinkRedirect(w http.ResponseWriter, r *http.Request) return } - h.dbs.LinkTokenStore.DeleteToken(userID) + err = h.dbs.LinkTokenStore.DeleteToken(userID) + if err != nil { + log.Printf("Error deleting link token: %s", err) + http.Error(w, fmt.Sprintf("Error deleting link token: %s", err), http.StatusInternalServerError) + } log.Printf("got /joinsession request\n") } diff --git a/EsefexApi/api/routes/getsounds.go b/EsefexApi/api/routes/getsounds.go index b432fae..2ffd754 100644 --- a/EsefexApi/api/routes/getsounds.go +++ b/EsefexApi/api/routes/getsounds.go @@ -49,7 +49,11 @@ func (h *RouteHandlers) GetSounds(w http.ResponseWriter, r *http.Request) { } w.Header().Set("Content-Type", "application/json") - w.Write(jsonResponse) + _, err = w.Write(jsonResponse) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } - log.Println("got /sounds request") + // log.Println("got /sounds request") } diff --git a/EsefexApi/api/routes/postplaysound.go b/EsefexApi/api/routes/postplaysound.go index 0d61de5..5871a5c 100644 --- a/EsefexApi/api/routes/postplaysound.go +++ b/EsefexApi/api/routes/postplaysound.go @@ -28,6 +28,11 @@ func (h *RouteHandlers) PostPlaySound(w http.ResponseWriter, r *http.Request, us return } - io.WriteString(w, "Play sound!\n") + _, err = io.WriteString(w, "Play sound!\n") + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + timer.MessageElapsed("Played sound") } diff --git a/EsefexApi/api/routes/postplaysoundinsecure.go b/EsefexApi/api/routes/postplaysoundinsecure.go index cc4772e..5b8bb7b 100644 --- a/EsefexApi/api/routes/postplaysoundinsecure.go +++ b/EsefexApi/api/routes/postplaysoundinsecure.go @@ -28,5 +28,8 @@ func (h *RouteHandlers) PostPlaySoundInsecure(w http.ResponseWriter, r *http.Req return } - io.WriteString(w, "Play sound!\n") + _, err = io.WriteString(w, "Play sound!\n") + if err != nil { + log.Println(err) + } } diff --git a/EsefexApi/audioplayer/discordplayer/discordplayer.go b/EsefexApi/audioplayer/discordplayer/discordplayer.go index 692c8fc..142fa4b 100644 --- a/EsefexApi/audioplayer/discordplayer/discordplayer.go +++ b/EsefexApi/audioplayer/discordplayer/discordplayer.go @@ -55,7 +55,10 @@ func NewDiscordPlayer(ds *discordgo.Session, dbs *db.Databases, useTimeouts bool if _, ok := dp.vds[types.ChannelID(e.BeforeUpdate.ChannelID)]; ok { log.Printf("Closing VCon: %s", e.BeforeUpdate.ChannelID) - dp.UnregisterVcon(types.ChannelID(e.BeforeUpdate.ChannelID)) + err := dp.UnregisterVcon(types.ChannelID(e.BeforeUpdate.ChannelID)) + if err != nil { + log.Printf("Error unregistering vcon: %+v", err) + } } }) @@ -79,7 +82,10 @@ func NewDiscordPlayer(ds *discordgo.Session, dbs *db.Databases, useTimeouts bool if len(users) == 1 { log.Printf("Channel empty, closing vcon: %s", e.BeforeUpdate.ChannelID) - dp.UnregisterVcon(types.ChannelID(e.BeforeUpdate.ChannelID)) + err := dp.UnregisterVcon(types.ChannelID(e.BeforeUpdate.ChannelID)) + if err != nil { + log.Printf("Error unregistering vcon: %+v", err) + } } }) diff --git a/EsefexApi/audioplayer/discordplayer/util.go b/EsefexApi/audioplayer/discordplayer/util.go index 82ca985..22223bc 100644 --- a/EsefexApi/audioplayer/discordplayer/util.go +++ b/EsefexApi/audioplayer/discordplayer/util.go @@ -5,6 +5,7 @@ import ( "time" ) +// TODO: better error handling func (p *DiscordPlayer) afkKick() { for { time.Sleep(1 * time.Second) @@ -22,7 +23,10 @@ func (p *DiscordPlayer) afkKick() { vd.AfkTimeoutIn = time.Now().Add(p.timeout) } else { log.Printf("Kicking bot from %s", vd.ChannelID) - p.UnregisterVcon(vd.ChannelID) + err := p.UnregisterVcon(vd.ChannelID) + if err != nil { + log.Printf("Error unregistering vcon: %+v", err) + } } } } diff --git a/EsefexApi/audioplayer/discordplayer/vcon/vcon.go b/EsefexApi/audioplayer/discordplayer/vcon/vcon.go index 0d33586..0e033ea 100644 --- a/EsefexApi/audioplayer/discordplayer/vcon/vcon.go +++ b/EsefexApi/audioplayer/discordplayer/vcon/vcon.go @@ -58,7 +58,13 @@ func (a *VCon) PlaySound(uid sounddb.SoundURI) { // this is the main loop of the audio queue func (a *VCon) Run() { log.Println("Running VCon") - a.vc.Speaking(true) + err := a.vc.Speaking(true) + if err != nil { + // TODO: handle this better + // for example, if this function returns an error, the vcon should be closed + log.Printf("Error setting speaking: %+v\n", err) + return + } for { // log.Println("Looping...") @@ -106,11 +112,20 @@ func (a *VCon) Run() { } } -func (a *VCon) Close() { +func (a *VCon) Close() error { log.Println("Closing VCon") close(a.stop) - a.vc.Speaking(false) - a.vc.Disconnect() + err := a.vc.Speaking(false) + if err != nil { + return errors.Wrap(err, "Error stopping speaking") + } + + err = a.vc.Disconnect() + if err != nil { + return errors.Wrap(err, "Error disconnecting") + } + + return nil } func (a *VCon) IsPlaying() bool { diff --git a/EsefexApi/audioprocessing/s16leCacheReader.go b/EsefexApi/audioprocessing/s16leCacheReader.go index f24b59e..5e72d94 100644 --- a/EsefexApi/audioprocessing/s16leCacheReader.go +++ b/EsefexApi/audioprocessing/s16leCacheReader.go @@ -72,14 +72,17 @@ func NewS16leCacheReaderFromPCM(pcm []int16) *S16leCacheReader { return reader } -func NewS16leCacheReaderFromFile(p string) *S16leCacheReader { +func NewS16leCacheReaderFromFile(p string) (*S16leCacheReader, error) { f, err := os.Open(p) if err != nil { panic(err) } reader := &S16leCacheReader{} - reader.LoadFromReader(f) + err = reader.LoadFromReader(f) + if err != nil { + return nil, errors.Wrap(err, "Error loading from reader") + } - return reader + return reader, nil } diff --git a/EsefexApi/bot/actions/actions.go b/EsefexApi/bot/actions/actions.go deleted file mode 100644 index 8d4b02f..0000000 --- a/EsefexApi/bot/actions/actions.go +++ /dev/null @@ -1,19 +0,0 @@ -package actions - -import ( - // "log" - - "github.com/bwmarrin/discordgo" -) - -var ( - channelID string = "777344211828604950" -) - -func UnprovokedMessage(s *discordgo.Session) { - s.ChannelMessageSend(channelID, "Hello, world! This is a message invoked by a channel message event.") -} - -func JoinChannelVoice(s *discordgo.Session, gID string, cID string) { - s.ChannelVoiceJoin(gID, cID, false, false) -} diff --git a/EsefexApi/bot/bot.go b/EsefexApi/bot/bot.go index cbb6446..d56cf0a 100644 --- a/EsefexApi/bot/bot.go +++ b/EsefexApi/bot/bot.go @@ -50,8 +50,17 @@ func (b *DiscordBot) run() { <-ready log.Println("Registering commands...") - b.RegisterComands() - defer b.DeleteAllCommands() + err = b.RegisterComands() + if err != nil { + log.Printf("Cannot register commands: %+v", err) + } + + defer func() { + err = b.DeleteAllCommands() + if err != nil { + log.Printf("Cannot delete commands: %+v", err) + } + }() log.Println("Bot Ready.") close(b.ready) diff --git a/EsefexApi/bot/commands/commands.go b/EsefexApi/bot/commands/commands.go index 3dfee8a..3242b5e 100644 --- a/EsefexApi/bot/commands/commands.go +++ b/EsefexApi/bot/commands/commands.go @@ -48,16 +48,22 @@ func WithErrorHandling(h func(s *discordgo.Session, i *discordgo.InteractionCrea if err != nil { log.Printf("Cannot execute command: %+v", err) - s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + err = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, Data: &discordgo.InteractionResponseData{ Content: fmt.Sprintf("An error has occurred while executing the command: \n```%+v```", errors.Cause(err)), }, }) + if err != nil { + log.Printf("Cannot respond to interaction: %+v", err) + } } if r != nil { - s.InteractionRespond(i.Interaction, r) + err = s.InteractionRespond(i.Interaction, r) + if err != nil { + log.Printf("Cannot respond to interaction: %+v", err) + } } } } diff --git a/EsefexApi/bot/commands/link.go b/EsefexApi/bot/commands/link.go index 415595d..65935b3 100644 --- a/EsefexApi/bot/commands/link.go +++ b/EsefexApi/bot/commands/link.go @@ -29,7 +29,7 @@ func (c *CommandHandlers) Link(s *discordgo.Session, i *discordgo.InteractionCre // https://esefex.com/link? linkUrl := fmt.Sprintf("%s/link?t=%s", c.domain, linkToken.Token) - expiresIn := linkToken.Expiry.Sub(time.Now()) + expiresIn := time.Until(linkToken.Expiry) _, err = s.ChannelMessageSend(channel.ID, fmt.Sprintf("Click this link to link your Discord account to Esefex (expires in %d Minutes): \n%s", int(expiresIn.Minutes()), linkUrl)) if err != nil { return nil, errors.Wrap(err, "Error sending DM message") diff --git a/EsefexApi/bot/commands/unlink.go b/EsefexApi/bot/commands/unlink.go index b76a766..87c579e 100644 --- a/EsefexApi/bot/commands/unlink.go +++ b/EsefexApi/bot/commands/unlink.go @@ -15,11 +15,18 @@ var ( ) func (c *CommandHandlers) Unlink(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - c.dbs.UserDB.DeleteUser(types.UserID(i.Member.User.ID)) - c.dbs.UserDB.SetUser(userdb.User{ + err := c.dbs.UserDB.DeleteUser(types.UserID(i.Member.User.ID)) + if err != nil { + return nil, err + } + + err = c.dbs.UserDB.SetUser(userdb.User{ ID: types.UserID(i.Member.User.ID), Tokens: []userdb.Token{}, }) + if err != nil { + return nil, err + } return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, diff --git a/EsefexApi/bot/util.go b/EsefexApi/bot/util.go index 93b7215..c5931fc 100644 --- a/EsefexApi/bot/util.go +++ b/EsefexApi/bot/util.go @@ -22,45 +22,60 @@ func (b *DiscordBot) RegisterComandHandlers() { } // Call after opening the session -func (b *DiscordBot) RegisterComands() { +func (b *DiscordBot) RegisterComands() error { ds := b.Session for _, v := range b.cmdh.Commands { _, err := ds.ApplicationCommandCreate(ds.State.User.ID, "", v) if err != nil { - log.Printf("Cannot create '%v' command: %v", v.Name, err) + return errors.Wrapf(err, "Cannot create '%v' command", v.Name) } log.Printf("Registered '%v' command", v.Name) } + + return nil } -func (b *DiscordBot) DeleteAllCommands() { +func (b *DiscordBot) DeleteAllCommands() error { ds := b.Session log.Println("Deleting all commands...") for _, g := range ds.State.Guilds { - b.DeleteGuildCommands(g.ID) + err := b.DeleteGuildCommands(g.ID) + if err != nil { + return errors.Wrapf(err, "Cannot delete commands for guild '%v'", g.ID) + } } - b.DeleteGuildCommands("") + err := b.DeleteGuildCommands("") + if err != nil { + return errors.Wrap(err, "Cannot delete global commands") + } log.Println("Deleted all commands") + + return nil } -func (b *DiscordBot) DeleteGuildCommands(guildID string) { +func (b *DiscordBot) DeleteGuildCommands(guildID string) error { ds := b.Session cmds, err := ds.ApplicationCommands(ds.State.User.ID, guildID) if err != nil { - log.Printf("Cannot get commands for guild '%v': %v", guildID, err) + return errors.Wrapf(err, "Cannot get commands for guild '%v'", guildID) } for _, v := range cmds { - ds.ApplicationCommandDelete(ds.State.User.ID, guildID, v.ID) + err = ds.ApplicationCommandDelete(ds.State.User.ID, guildID, v.ID) + if err != nil { + return errors.Wrapf(err, "Cannot delete '%v' command", v.Name) + } log.Printf("Deleted '%v' command", v.Name) } + + return nil } var BotTokenNotSet = fmt.Errorf("BOT_TOKEN is not set") diff --git a/EsefexApi/cmd/airhorn_example/airhorn_example.go b/EsefexApi/cmd/testing/airhorn_example/airhorn_example.go similarity index 100% rename from EsefexApi/cmd/airhorn_example/airhorn_example.go rename to EsefexApi/cmd/testing/airhorn_example/airhorn_example.go diff --git a/EsefexApi/cmd/config_testing/config_testing.go b/EsefexApi/cmd/testing/config_testing/config_testing.go similarity index 100% rename from EsefexApi/cmd/config_testing/config_testing.go rename to EsefexApi/cmd/testing/config_testing/config_testing.go diff --git a/EsefexApi/cmd/discord_state_cache_testing/discord_state_cache_testing.go b/EsefexApi/cmd/testing/discord_state_cache_testing/discord_state_cache_testing.go similarity index 100% rename from EsefexApi/cmd/discord_state_cache_testing/discord_state_cache_testing.go rename to EsefexApi/cmd/testing/discord_state_cache_testing/discord_state_cache_testing.go diff --git a/EsefexApi/cmd/download_testing/download_testing.go b/EsefexApi/cmd/testing/download_testing/download_testing.go similarity index 100% rename from EsefexApi/cmd/download_testing/download_testing.go rename to EsefexApi/cmd/testing/download_testing/download_testing.go diff --git a/EsefexApi/cmd/emoji_testing/emoji_testing.go b/EsefexApi/cmd/testing/emoji_testing/emoji_testing.go similarity index 100% rename from EsefexApi/cmd/emoji_testing/emoji_testing.go rename to EsefexApi/cmd/testing/emoji_testing/emoji_testing.go diff --git a/EsefexApi/cmd/encoder_stream_testing/encoder_stream_testing.go b/EsefexApi/cmd/testing/encoder_stream_testing/encoder_stream_testing.go similarity index 73% rename from EsefexApi/cmd/encoder_stream_testing/encoder_stream_testing.go rename to EsefexApi/cmd/testing/encoder_stream_testing/encoder_stream_testing.go index 49ef799..04adf86 100644 --- a/EsefexApi/cmd/encoder_stream_testing/encoder_stream_testing.go +++ b/EsefexApi/cmd/testing/encoder_stream_testing/encoder_stream_testing.go @@ -7,7 +7,11 @@ import ( ) func main() { - src := audioprocessing.NewS16leCacheReaderFromFile("testsounds/test1.s16le") + src, err := audioprocessing.NewS16leCacheReaderFromFile("testsounds/test1.s16le") + if err != nil { + panic(err) + } + enc, err := audioprocessing.NewOpusCliEncoder(src) if err != nil { panic(err) diff --git a/EsefexApi/cmd/gopus_testing/gopus_testing.go b/EsefexApi/cmd/testing/gopus_testing/gopus_testing.go similarity index 100% rename from EsefexApi/cmd/gopus_testing/gopus_testing.go rename to EsefexApi/cmd/testing/gopus_testing/gopus_testing.go diff --git a/EsefexApi/cmd/objectmethod_testing/objectmethod_testing.go b/EsefexApi/cmd/testing/objectmethod_testing/objectmethod_testing.go similarity index 100% rename from EsefexApi/cmd/objectmethod_testing/objectmethod_testing.go rename to EsefexApi/cmd/testing/objectmethod_testing/objectmethod_testing.go diff --git a/EsefexApi/cmd/opus_testing/opus_testing.go b/EsefexApi/cmd/testing/opus_testing/opus_testing.go similarity index 100% rename from EsefexApi/cmd/opus_testing/opus_testing.go rename to EsefexApi/cmd/testing/opus_testing/opus_testing.go diff --git a/EsefexApi/cmd/pcm_mixer/pcm_mixer.go b/EsefexApi/cmd/testing/pcm_mixer/pcm_mixer_testing.go similarity index 93% rename from EsefexApi/cmd/pcm_mixer/pcm_mixer.go rename to EsefexApi/cmd/testing/pcm_mixer/pcm_mixer_testing.go index f0f8674..f574b21 100644 --- a/EsefexApi/cmd/pcm_mixer/pcm_mixer.go +++ b/EsefexApi/cmd/testing/pcm_mixer/pcm_mixer_testing.go @@ -18,7 +18,7 @@ func main() { } cacheReader := audioprocessing.S16leCacheReader{} - cacheReader.LoadFromReader(file) + err = cacheReader.LoadFromReader(file) mixReader.AddSource(&cacheReader) } diff --git a/EsefexApi/cmd/service_testing/service_testing.go b/EsefexApi/cmd/testing/service_testing/service_testing.go similarity index 100% rename from EsefexApi/cmd/service_testing/service_testing.go rename to EsefexApi/cmd/testing/service_testing/service_testing.go diff --git a/EsefexApi/cmd/stack_trace_testing/stack_trace_testing.go b/EsefexApi/cmd/testing/stack_trace_testing/stack_trace_testing.go similarity index 100% rename from EsefexApi/cmd/stack_trace_testing/stack_trace_testing.go rename to EsefexApi/cmd/testing/stack_trace_testing/stack_trace_testing.go diff --git a/EsefexApi/config.toml b/EsefexApi/config.toml index be42c4f..08998ce 100644 --- a/EsefexApi/config.toml +++ b/EsefexApi/config.toml @@ -1,3 +1,5 @@ +verification_expiry = 5.0 # minutes + [http_api] port = 8080 custom_protocol = "esefex" diff --git a/EsefexApi/config/config.go b/EsefexApi/config/config.go index 835a79f..eced46e 100644 --- a/EsefexApi/config/config.go +++ b/EsefexApi/config/config.go @@ -8,13 +8,12 @@ import ( "github.com/pkg/errors" ) -var instance *Config - type Config struct { - HttpApi HttpApi `toml:"http_api"` - FileSoundDB FileSoundDatabase `toml:"file_sound_database"` - FileUserDB FileUserDatabase `toml:"file_user_database"` - Bot Bot `toml:"bot"` + VerificationExpiry float32 `toml:"verification_expiry"` + HttpApi HttpApi `toml:"http_api"` + FileSoundDB FileSoundDatabase `toml:"file_sound_database"` + FileUserDB FileUserDatabase `toml:"file_user_database"` + Bot Bot `toml:"bot"` } type HttpApi struct { diff --git a/EsefexApi/go.mod b/EsefexApi/go.mod index 35ac19e..5b87150 100644 --- a/EsefexApi/go.mod +++ b/EsefexApi/go.mod @@ -8,24 +8,26 @@ require ( github.com/joho/godotenv v1.5.1 ) -require github.com/pelletier/go-toml v1.9.5 +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc + github.com/pelletier/go-toml v1.9.5 +) require ( - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect - golang.org/x/text v0.4.0 // indirect - gopkg.in/sourcemap.v1 v1.0.5 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rogpeppe/go-internal v1.11.0 // indirect + golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( github.com/gorilla/websocket v1.4.2 // indirect github.com/pkg/errors v0.9.1 - github.com/robertkrimen/otto v0.3.0 github.com/samber/lo v1.39.0 github.com/stretchr/testify v1.8.4 - golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect - golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect + golang.org/x/crypto v0.16.0 // indirect + golang.org/x/sys v0.16.0 // indirect layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32 ) diff --git a/EsefexApi/go.sum b/EsefexApi/go.sum index 5b20e60..92bedf2 100644 --- a/EsefexApi/go.sum +++ b/EsefexApi/go.sum @@ -1,42 +1,50 @@ github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY= github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +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/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/robertkrimen/otto v0.3.0 h1:5RI+8860NSxvXywDY9ddF5HcPw0puRsd8EgbXV0oqRE= -github.com/robertkrimen/otto v0.3.0/go.mod h1:uW9yN1CYflmUQYvAMS0m+ZiNo3dMzRUDQJX0jWbzgxw= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM= -golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= +golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= +golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= +golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0= -golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.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.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= -gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= layeh.com/gopus v0.0.0-20210501142526-1ee02d434e32 h1:/S1gOotFo2sADAIdSGk1sDq1VxetoCWr6f5nxOG0dpY= diff --git a/EsefexApi/linktokenstore/memorylinktokenstore/memorylinktokenstore.go b/EsefexApi/linktokenstore/memorylinktokenstore/memorylinktokenstore.go index 8240b30..42d892d 100644 --- a/EsefexApi/linktokenstore/memorylinktokenstore/memorylinktokenstore.go +++ b/EsefexApi/linktokenstore/memorylinktokenstore/memorylinktokenstore.go @@ -30,7 +30,10 @@ func (m *MemoryLinkTokenStore) CreateToken(userID types.UserID) (linktokenstore. Expiry: time.Now().Add(time.Hour * 24), } - m.SetToken(userID, token) + err = m.SetToken(userID, token) + if err != nil { + return linktokenstore.LinkToken{}, errors.Wrap(err, "Error setting token") + } return token, nil } } @@ -79,7 +82,10 @@ func (m *MemoryLinkTokenStore) ValidateToken(tokenStr string) (bool, error) { } if token.Expiry.Before(time.Now()) { - m.DeleteToken(user) + err = m.DeleteToken(user) + if err != nil { + return false, errors.Wrap(err, "Error deleting token") + } return false, linktokenstore.ErrTokenExpired } diff --git a/EsefexApi/main.go b/EsefexApi/main.go index e8a725c..73ad686 100644 --- a/EsefexApi/main.go +++ b/EsefexApi/main.go @@ -11,7 +11,9 @@ import ( "esefexapi/sounddb/filesounddb" "esefexapi/userdb/fileuserdb" "esefexapi/util" + . "esefexapi/util/must" + "fmt" "log" "os" "time" @@ -19,39 +21,32 @@ import ( "github.com/joho/godotenv" ) -func init() { - godotenv.Load() - log.SetFlags(log.LstdFlags | log.Lshortfile) -} - func main() { log.Printf("Starting Esefex API with PID: %d", os.Getpid()) + Must(godotenv.Load()) + + log.SetFlags(log.LstdFlags | log.Lshortfile) + cfg, err := config.LoadConfig("config.toml") - if err != nil { - log.Fatal(err) - } + Must(err) domain := os.Getenv("DOMAIN") ds, err := bot.CreateSession() - if err != nil { - log.Fatal(err) - } + Must(err) sdb, err := filesounddb.NewFileDB(cfg.FileSoundDB.Location) - if err != nil { - log.Fatal(err) - } + Must(err) - sdbc := dbcache.NewSoundDBCache(sdb) + sdbc, err := dbcache.NewSoundDBCache(sdb) + Must(err) udb, err := fileuserdb.NewFileUserDB(cfg.FileUserDB.Location) - if err != nil { - log.Fatal(err) - } + Must(err) - ldb := memorylinktokenstore.NewMemoryLinkTokenStore(time.Minute * 5) + verT := time.Duration(cfg.VerificationExpiry * float32(time.Minute)) + ldb := memorylinktokenstore.NewMemoryLinkTokenStore(verT) dbs := &db.Databases{ SoundDB: sdbc, @@ -59,7 +54,8 @@ func main() { LinkTokenStore: ldb, } - plr := discordplayer.NewDiscordPlayer(ds, dbs, cfg.Bot.UseTimeouts, time.Duration(cfg.Bot.Timeout)*time.Second) + botT := time.Duration(cfg.Bot.Timeout * float32(time.Minute)) + plr := discordplayer.NewDiscordPlayer(ds, dbs, cfg.Bot.UseTimeouts, botT) api := api.NewHttpApi(dbs, plr, ds, cfg.HttpApi.Port, cfg.HttpApi.CustomProtocol) bot := bot.NewDiscordBot(ds, dbs, domain) @@ -73,7 +69,7 @@ func main() { log.Println("All components started successfully :)") log.Println("Press Ctrl+C to exit") <-util.Interrupt() - println() + fmt.Println() log.Println("Gracefully shutting down...") <-api.Stop() diff --git a/EsefexApi/permissions/permissions_test.go b/EsefexApi/permissions/permissions_test.go new file mode 100644 index 0000000..987f88f --- /dev/null +++ b/EsefexApi/permissions/permissions_test.go @@ -0,0 +1,8 @@ +package permissions + +import ( + "testing" +) + +func MergeTest(t *testing.T) { +} diff --git a/EsefexApi/sounddb/dbcache/dbcache.go b/EsefexApi/sounddb/dbcache/dbcache.go index 15576ec..fd93d02 100644 --- a/EsefexApi/sounddb/dbcache/dbcache.go +++ b/EsefexApi/sounddb/dbcache/dbcache.go @@ -24,13 +24,17 @@ type CachedSound struct { } // NewSoundDBCache creates a new DBCache. -func NewSoundDBCache(db sounddb.ISoundDB) *SoundDBCache { +func NewSoundDBCache(db sounddb.ISoundDB) (*SoundDBCache, error) { c := &SoundDBCache{ sounds: make(map[sounddb.SoundURI]*CachedSound), db: db, } - c.CacheAll() - return c + err := c.CacheAll() + if err != nil { + return nil, errors.Wrap(err, "Error caching all sounds") + } + + return c, nil } // AddSound implements db.SoundDB. diff --git a/EsefexApi/sounddb/filesounddb/addsound.go b/EsefexApi/sounddb/filesounddb/addsound.go index 4ea609b..57b63d2 100644 --- a/EsefexApi/sounddb/filesounddb/addsound.go +++ b/EsefexApi/sounddb/filesounddb/addsound.go @@ -28,7 +28,11 @@ func (f *FileDB) AddSound(guildID types.GuildID, name string, icon sounddb.Icon, // Make sure the db folder exists path := fmt.Sprintf("%s/%s", f.location, guildID) - os.MkdirAll(path, os.ModePerm) + err = os.MkdirAll(path, os.ModePerm) + if err != nil { + log.Printf("Error creating guild folder: %+v", err) + return sounddb.SoundURI{}, errors.Wrap(err, "Error creating guild folder") + } // write meta file path = fmt.Sprintf("%s/%s/%s_meta.json", f.location, guildID, sound.SoundID) @@ -44,7 +48,11 @@ func (f *FileDB) AddSound(guildID types.GuildID, name string, icon sounddb.Icon, return sounddb.SoundURI{}, errors.Wrap(err, "Error marshalling meta") } - metaFile.Write(metaJson) + _, err = metaFile.Write(metaJson) + if err != nil { + log.Printf("Error writing meta file: %+v", err) + return sounddb.SoundURI{}, errors.Wrap(err, "Error writing meta file") + } metaFile.Close() // write sound file diff --git a/EsefexApi/sounddb/filesounddb/getsoundmeta.go b/EsefexApi/sounddb/filesounddb/getsoundmeta.go index 53b3f39..ade34f9 100644 --- a/EsefexApi/sounddb/filesounddb/getsoundmeta.go +++ b/EsefexApi/sounddb/filesounddb/getsoundmeta.go @@ -20,7 +20,10 @@ func (f *FileDB) GetSoundMeta(uid sounddb.SoundURI) (sounddb.SoundMeta, error) { var sound sounddb.SoundMeta byteValue, _ := io.ReadAll(metaFile) - json.Unmarshal(byteValue, &sound) + err = json.Unmarshal(byteValue, &sound) + if err != nil { + return sounddb.SoundMeta{}, err + } metaFile.Close() return sound, nil diff --git a/EsefexApi/userdb/fileuserdb/fileuserdb.go b/EsefexApi/userdb/fileuserdb/fileuserdb.go index e4a4102..685666a 100644 --- a/EsefexApi/userdb/fileuserdb/fileuserdb.go +++ b/EsefexApi/userdb/fileuserdb/fileuserdb.go @@ -22,7 +22,10 @@ type FileUserDB struct { func NewFileUserDB(filePath string) (*FileUserDB, error) { // get file handle - os.MkdirAll(path.Dir(filePath), os.ModePerm) + err := os.MkdirAll(path.Dir(filePath), os.ModePerm) + if err != nil { + return nil, errors.Wrap(err, "Error creating directory") + } file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, os.ModePerm) if err != nil { @@ -61,7 +64,10 @@ func NewFileUserDB(filePath string) (*FileUserDB, error) { func (f *FileUserDB) Close() error { log.Println("Closing userdb") - f.Save() + err := f.Save() + if err != nil { + return errors.Wrap(err, "Error saving userdb") + } return f.file.Close() } @@ -70,8 +76,14 @@ func (f *FileUserDB) Save() error { defer f.fileLock.Unlock() // reset file - f.file.Seek(0, 0) - f.file.Truncate(0) + _, err := f.file.Seek(0, 0) + if err != nil { + return errors.Wrap(err, "Error seeking to start of file") + } + err = f.file.Truncate(0) + if err != nil { + return errors.Wrap(err, "Error truncating file") + } usrArr := make([]userdb.User, 0, len(f.Users)) for _, user := range f.Users { diff --git a/EsefexApi/userdb/fileuserdb/fileuserdb_test.go b/EsefexApi/userdb/fileuserdb/fileuserdb_test.go index 9f4d41b..6990737 100644 --- a/EsefexApi/userdb/fileuserdb/fileuserdb_test.go +++ b/EsefexApi/userdb/fileuserdb/fileuserdb_test.go @@ -70,10 +70,12 @@ func TestFileUserDB(t *testing.T) { assert.Equal(t, &user1, user.Unwrap()) err = udb.DeleteUser("1") + assert.Nil(t, err) assert.Equal(t, 1, len(udb.Users)) Ouser, err := udb.GetUser("1") + assert.Nil(t, err) assert.True(t, Ouser.IsNone()) err = udb.DeleteUser("2") diff --git a/EsefexApi/userdb/fileuserdb/impluserdb.go b/EsefexApi/userdb/fileuserdb/impluserdb.go index 4ba3854..e57733f 100644 --- a/EsefexApi/userdb/fileuserdb/impluserdb.go +++ b/EsefexApi/userdb/fileuserdb/impluserdb.go @@ -25,7 +25,12 @@ func (f *FileUserDB) GetUser(userID types.UserID) (opt.Option[*userdb.User], err func (f *FileUserDB) SetUser(user userdb.User) error { f.Users[user.ID] = user - go f.Save() + go func() { + err := f.Save() + if err != nil { + log.Printf("Error saving userdb: %+v", err) + } + }() return nil } @@ -34,7 +39,12 @@ func (f *FileUserDB) SetUser(user userdb.User) error { func (f *FileUserDB) DeleteUser(userID types.UserID) error { delete(f.Users, userID) - go f.Save() + go func() { + err := f.Save() + if err != nil { + log.Printf("Error saving userdb: %+v", err) + } + }() return nil } @@ -68,12 +78,20 @@ func (f *FileUserDB) NewToken(userID types.UserID) (userdb.Token, error) { } user.Tokens = append(user.Tokens, userdb.Token(token)) - f.SetUser(*user) + err = f.SetUser(*user) + if err != nil { + return "", errors.Wrap(err, "Error setting user") + } log.Printf("New token for user %s: %s\n", userID, token) log.Printf("%v", f) - go f.Save() + go func() { + err := f.Save() + if err != nil { + log.Printf("Error saving userdb: %+v", err) + } + }() return userdb.Token(token), nil } @@ -81,10 +99,14 @@ func (f *FileUserDB) NewToken(userID types.UserID) (userdb.Token, error) { func (f *FileUserDB) getOrCreateUser(userID types.UserID) (*userdb.User, error) { Ouser, err := f.GetUser(userID) if Ouser.IsNone() { - f.SetUser(userdb.User{ + err = f.SetUser(userdb.User{ ID: userID, Tokens: []userdb.Token{}, }) + if err != nil { + return nil, errors.Wrap(err, "Error setting user") + } + Ouser, err = f.GetUser(userID) } if err != nil { diff --git a/EsefexApi/util/must/must.go b/EsefexApi/util/must/must.go new file mode 100644 index 0000000..5cda149 --- /dev/null +++ b/EsefexApi/util/must/must.go @@ -0,0 +1,9 @@ +package must + +import "log" + +func Must(err error) { + if err != nil { + log.Fatal(err) + } +} From fcb5fa1e8a264ef29db1b6a0ca0a820e8285201a Mon Sep 17 00:00:00 2001 From: jo_kil Date: Mon, 8 Jan 2024 19:20:02 +0100 Subject: [PATCH 07/20] permission system adjustments & tests --- EsefexApi/permissions/merge.go | 34 ++++---- EsefexApi/permissions/permissions.go | 25 +++++- EsefexApi/permissions/permissions_test.go | 68 +++++++++++++++- EsefexApi/permissions/stack.go | 96 ++++++++++++----------- EsefexApi/permissions/stack_test.go | 41 ++++++++++ 5 files changed, 199 insertions(+), 65 deletions(-) create mode 100644 EsefexApi/permissions/stack_test.go diff --git a/EsefexApi/permissions/merge.go b/EsefexApi/permissions/merge.go index 4ca6626..ac51d0d 100644 --- a/EsefexApi/permissions/merge.go +++ b/EsefexApi/permissions/merge.go @@ -1,8 +1,8 @@ package permissions -// Merge returns the permission state that results from merging two permission states. +// MergeParent returns the permission state that results from merging two permission states. // otherPS has precedence over ps. -func (ps PermissionState) Merge(otherPS PermissionState) PermissionState { +func (ps PermissionState) MergeParent(otherPS PermissionState) PermissionState { if otherPS == Allow { return Allow } else if otherPS == Deny { @@ -12,33 +12,33 @@ func (ps PermissionState) Merge(otherPS PermissionState) PermissionState { } } -func (p Permissions) Merge(otherP *Permissions) Permissions { +func (p Permissions) MergeParent(otherP Permissions) Permissions { return Permissions{ - Sound: p.Sound.Merge(otherP.Sound), - Bot: p.Bot.Merge(otherP.Bot), - Guild: p.Guild.Merge(otherP.Guild), + Sound: p.Sound.MergeParent(otherP.Sound), + Bot: p.Bot.MergeParent(otherP.Bot), + Guild: p.Guild.MergeParent(otherP.Guild), } } -func (p SoundPermissions) Merge(otherP SoundPermissions) SoundPermissions { +func (p SoundPermissions) MergeParent(otherP SoundPermissions) SoundPermissions { return SoundPermissions{ - Play: p.Play.Merge(otherP.Play), - Upload: p.Upload.Merge(otherP.Upload), - Modify: p.Modify.Merge(otherP.Modify), - Delete: p.Delete.Merge(otherP.Delete), + Play: p.Play.MergeParent(otherP.Play), + Upload: p.Upload.MergeParent(otherP.Upload), + Modify: p.Modify.MergeParent(otherP.Modify), + Delete: p.Delete.MergeParent(otherP.Delete), } } -func (p BotPermissions) Merge(otherP BotPermissions) BotPermissions { +func (p BotPermissions) MergeParent(otherP BotPermissions) BotPermissions { return BotPermissions{ - Join: p.Join.Merge(otherP.Join), - Leave: p.Leave.Merge(otherP.Leave), + Join: p.Join.MergeParent(otherP.Join), + Leave: p.Leave.MergeParent(otherP.Leave), } } -func (p GuildPermissions) Merge(otherP GuildPermissions) GuildPermissions { +func (p GuildPermissions) MergeParent(otherP GuildPermissions) GuildPermissions { return GuildPermissions{ - ManageBot: p.ManageBot.Merge(otherP.ManageBot), - ManageUser: p.ManageUser.Merge(otherP.ManageUser), + ManageBot: p.ManageBot.MergeParent(otherP.ManageBot), + ManageUser: p.ManageUser.MergeParent(otherP.ManageUser), } } diff --git a/EsefexApi/permissions/permissions.go b/EsefexApi/permissions/permissions.go index ffb749d..a3d6a7a 100644 --- a/EsefexApi/permissions/permissions.go +++ b/EsefexApi/permissions/permissions.go @@ -21,6 +21,10 @@ func (ps PermissionState) String() string { } } +func (ps PermissionState) Allowed() bool { + return ps == Allow +} + type Permissions struct { Sound SoundPermissions Bot BotPermissions @@ -45,7 +49,7 @@ type GuildPermissions struct { } // Default returns a Permissions struct with all permissions set to Allow. -func NewDefault() Permissions { +func NewAllow() Permissions { return Permissions{ Sound: SoundPermissions{ Play: Allow, @@ -82,3 +86,22 @@ func NewUnset() Permissions { }, } } + +func NewDeny() Permissions { + return Permissions{ + Sound: SoundPermissions{ + Play: Deny, + Upload: Deny, + Modify: Deny, + Delete: Deny, + }, + Bot: BotPermissions{ + Join: Deny, + Leave: Deny, + }, + Guild: GuildPermissions{ + ManageBot: Deny, + ManageUser: Deny, + }, + } +} diff --git a/EsefexApi/permissions/permissions_test.go b/EsefexApi/permissions/permissions_test.go index 987f88f..8be83cd 100644 --- a/EsefexApi/permissions/permissions_test.go +++ b/EsefexApi/permissions/permissions_test.go @@ -2,7 +2,73 @@ package permissions import ( "testing" + + "github.com/stretchr/testify/assert" ) -func MergeTest(t *testing.T) { +func TestMerge(t *testing.T) { + assert.Equal(t, Allow, Deny.MergeParent(Allow)) + assert.Equal(t, Deny, Allow.MergeParent(Deny)) + assert.Equal(t, Allow, Allow.MergeParent(Allow)) + assert.Equal(t, Allow, Allow.MergeParent(Unset)) + + assert.Equal(t, NewAllow(), NewAllow().MergeParent(NewAllow())) + assert.Equal(t, NewAllow(), NewAllow().MergeParent(NewUnset())) + assert.Equal(t, NewAllow(), NewUnset().MergeParent(NewAllow())) + assert.Equal(t, NewDeny(), NewAllow().MergeParent(NewDeny())) + assert.Equal(t, NewAllow(), NewDeny().MergeParent(NewAllow())) + assert.Equal(t, NewDeny(), NewDeny().MergeParent(NewDeny())) + assert.Equal(t, NewDeny(), NewDeny().MergeParent(NewUnset())) + assert.Equal(t, NewUnset(), NewUnset().MergeParent(NewUnset())) + + bp1 := BotPermissions{ + Join: Allow, + Leave: Allow, + } + + bp2 := BotPermissions{ + Join: Deny, + Leave: Deny, + } + + assert.Equal(t, bp2, bp1.MergeParent(bp2)) + assert.Equal(t, bp1, bp2.MergeParent(bp1)) + + assert.Equal(t, BotPermissions{ + Join: Allow, + Leave: Deny, + }, BotPermissions{ + Join: Allow, + Leave: Unset, + }.MergeParent(BotPermissions{ + Join: Unset, + Leave: Deny, + })) + + p1 := NewUnset() + p2 := NewUnset() + + p1.Sound.Play = Deny + p1.Guild.ManageBot = Deny + + p2.Sound.Play = Allow + + merged := Permissions{ + Sound: SoundPermissions{ + Play: Allow, + Upload: Unset, + Modify: Unset, + Delete: Unset, + }, + Bot: BotPermissions{ + Join: Unset, + Leave: Unset, + }, + Guild: GuildPermissions{ + ManageBot: Deny, + ManageUser: Unset, + }, + } + + assert.Equal(t, merged, p1.MergeParent(p2)) } diff --git a/EsefexApi/permissions/stack.go b/EsefexApi/permissions/stack.go index 61745de..58a5622 100644 --- a/EsefexApi/permissions/stack.go +++ b/EsefexApi/permissions/stack.go @@ -1,91 +1,92 @@ package permissions import ( + "esefexapi/opt" "esefexapi/types" "slices" ) -type PermissionType int +type PermissionStack struct { + user map[types.UserID]Permissions + role map[types.RoleID]Permissions + channel map[types.ChannelID]Permissions +} -const ( - User PermissionType = iota - Role - Channel -) +func NewPermissionStack() *PermissionStack { + return &PermissionStack{ + user: make(map[types.UserID]Permissions), + role: make(map[types.RoleID]Permissions), + channel: make(map[types.ChannelID]Permissions), + } +} -func (pt PermissionType) String() string { - switch pt { - case User: - return "user" - case Role: - return "role" - case Channel: - return "channel" - default: - return "unknown" +func (ps *PermissionStack) GetUser(userID types.UserID) Permissions { + if u, ok := ps.user[userID]; ok { + return u } + return NewUnset() } -type PermissionStack struct { - User map[types.UserID]Permissions - Role map[types.RoleID]Permissions - Channel map[types.ChannelID]Permissions +func (ps *PermissionStack) GetRole(roleID types.RoleID) Permissions { + if r, ok := ps.role[roleID]; ok { + return r + } + return NewUnset() } -func NewPermissionStack() *PermissionStack { - return &PermissionStack{ - User: make(map[types.UserID]Permissions), - Role: make(map[types.RoleID]Permissions), - Channel: make(map[types.ChannelID]Permissions), +func (ps *PermissionStack) GetChannel(channelID types.ChannelID) Permissions { + if c, ok := ps.channel[channelID]; ok { + return c } + return NewUnset() } func (ps *PermissionStack) SetUser(user types.UserID, p Permissions) { - ps.User[user] = p + ps.user[user] = p } func (ps *PermissionStack) SetRole(role types.RoleID, p Permissions) { - ps.Role[role] = p + ps.role[role] = p } func (ps *PermissionStack) SetChannel(channel types.ChannelID, p Permissions) { - ps.Channel[channel] = p + ps.channel[channel] = p } func (ps *PermissionStack) UnsetUser(user types.UserID) { - delete(ps.User, user) + delete(ps.user, user) } func (ps *PermissionStack) UnsetRole(role types.RoleID) { - delete(ps.Role, role) + delete(ps.role, role) } func (ps *PermissionStack) UnsetChannel(channel types.ChannelID) { - delete(ps.Channel, channel) + delete(ps.channel, channel) } func (ps *PermissionStack) UpdateUser(user types.UserID, p Permissions) { - if _, ok := ps.User[user]; !ok { - ps.User[user] = NewUnset() + if _, ok := ps.user[user]; !ok { + ps.user[user] = NewUnset() } - ps.User[user] = ps.User[user].Merge(&p) + ps.user[user] = ps.user[user].MergeParent(p) } func (ps *PermissionStack) UpdateRole(role types.RoleID, p Permissions) { - if _, ok := ps.Role[role]; !ok { - ps.Role[role] = NewUnset() + if _, ok := ps.role[role]; !ok { + ps.role[role] = NewUnset() } - ps.Role[role] = ps.Role[role].Merge(&p) + ps.role[role] = ps.role[role].MergeParent(p) } func (ps *PermissionStack) UpdateChannel(channel types.ChannelID, p Permissions) { - if _, ok := ps.Channel[channel]; !ok { - ps.Channel[channel] = NewUnset() + if _, ok := ps.channel[channel]; !ok { + ps.channel[channel] = NewUnset() } - ps.Channel[channel] = ps.Channel[channel].Merge(&p) + ps.channel[channel] = ps.channel[channel].MergeParent(p) } // Query returns the permission state for a given user, role, and channel by merging them together. @@ -93,18 +94,21 @@ func (ps *PermissionStack) UpdateChannel(channel types.ChannelID, p Permissions) // This means that if a user has a permission set, it will override the channel and role permissions. // If a channel has a permission set, it will override the role permissions. // roles is a list of roles that the user has in order of precedence. -func (ps *PermissionStack) Query(user types.UserID, roles []types.RoleID, channel types.ChannelID) Permissions { - userPS := ps.User[user] +func (ps *PermissionStack) Query(user types.UserID, roles []types.RoleID, channel opt.Option[types.ChannelID]) Permissions { + userPS := ps.user[user] slices.Reverse(roles) rolesPS := NewUnset() for _, role := range roles { - r := ps.Role[role] - rolesPS = rolesPS.Merge(&r) + r := ps.role[role] + rolesPS = rolesPS.MergeParent(r) } - channelPS := ps.Channel[channel] + var channelPS Permissions = NewUnset() + if channel.IsSome() { + channelPS = ps.channel[channel.Unwrap()] + } - return NewDefault().Merge(&rolesPS).Merge(&channelPS).Merge(&userPS) + return NewUnset().MergeParent(rolesPS).MergeParent(channelPS).MergeParent(userPS) } diff --git a/EsefexApi/permissions/stack_test.go b/EsefexApi/permissions/stack_test.go new file mode 100644 index 0000000..af84653 --- /dev/null +++ b/EsefexApi/permissions/stack_test.go @@ -0,0 +1,41 @@ +package permissions + +import ( + "esefexapi/opt" + "esefexapi/types" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestStack(t *testing.T) { + s := NewPermissionStack() + + u1 := types.UserID(rune(1)) + + s.SetUser(u1, NewAllow()) + assert.Equal(t, NewAllow(), s.GetUser(u1)) + + u2 := types.UserID(rune(2)) + up2 := Permissions{ + Sound: SoundPermissions{ + Play: Allow, + }, + } + s.SetUser(u2, up2) + + assert.Equal(t, up2, s.GetUser(u2)) + + r1 := types.RoleID(rune(1)) + rp1 := Permissions{ + Sound: SoundPermissions{ + Upload: Allow, + Delete: Allow, + }, + } + + s.SetRole(r1, NewAllow()) + assert.Equal(t, NewAllow(), s.GetRole(r1)) + + assert.Equal(t, NewUnset().MergeParent(rp1).MergeParent(up2), s.Query(u2, []types.RoleID{r1}, opt.None[types.ChannelID]())) +} From b6a3618e67392f5c724e9218f1e922cd438ddc91 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Wed, 10 Jan 2024 11:28:23 +0100 Subject: [PATCH 08/20] permission system command framework --- EsefexApi/bot/commands/commands.go | 3 + .../airhorn_example/airhorn_example.go | 0 .../slash_command_example/commands.go | 601 ++++++++++++++++++ 3 files changed, 604 insertions(+) rename EsefexApi/cmd/{testing => examples}/airhorn_example/airhorn_example.go (100%) create mode 100644 EsefexApi/cmd/examples/slash_command_example/commands.go diff --git a/EsefexApi/bot/commands/commands.go b/EsefexApi/bot/commands/commands.go index 3242b5e..fe827ac 100644 --- a/EsefexApi/bot/commands/commands.go +++ b/EsefexApi/bot/commands/commands.go @@ -39,6 +39,9 @@ func NewCommandHandlers(dbs *db.Databases, domain string) *CommandHandlers { ch.Commands["unlink"] = UnlinkCommand ch.Handlers["unlink"] = WithErrorHandling(ch.Unlink) + ch.Commands["permissions"] = PermissionCommand + ch.Handlers["permissions"] = WithErrorHandling(ch.Permission) + return ch } diff --git a/EsefexApi/cmd/testing/airhorn_example/airhorn_example.go b/EsefexApi/cmd/examples/airhorn_example/airhorn_example.go similarity index 100% rename from EsefexApi/cmd/testing/airhorn_example/airhorn_example.go rename to EsefexApi/cmd/examples/airhorn_example/airhorn_example.go diff --git a/EsefexApi/cmd/examples/slash_command_example/commands.go b/EsefexApi/cmd/examples/slash_command_example/commands.go new file mode 100644 index 0000000..deb0eb3 --- /dev/null +++ b/EsefexApi/cmd/examples/slash_command_example/commands.go @@ -0,0 +1,601 @@ +package main + +import ( + "errors" + "flag" + "fmt" + "log" + "os" + "os/signal" + "strings" + "time" + + "github.com/bwmarrin/discordgo" +) + +// Bot parameters +var ( + GuildID = flag.String("guild", "", "Test guild ID. If not passed - bot registers commands globally") + BotToken = flag.String("token", "", "Bot access token") + RemoveCommands = flag.Bool("rmcmd", true, "Remove all commands after shutdowning or not") +) + +var s *discordgo.Session + +func init() { flag.Parse() } + +func init() { + var err error + s, err = discordgo.New("Bot " + *BotToken) + if err != nil { + log.Fatalf("Invalid bot parameters: %v", err) + } +} + +var ( + integerOptionMinValue = 1.0 + dmPermission = false + defaultMemberPermissions int64 = discordgo.PermissionManageServer + + commands = []*discordgo.ApplicationCommand{ + { + Name: "basic-command", + // All commands and options must have a description + // Commands/options without description will fail the registration + // of the command. + Description: "Basic command", + }, + { + Name: "permission-overview", + Description: "Command for demonstration of default command permissions", + DefaultMemberPermissions: &defaultMemberPermissions, + DMPermission: &dmPermission, + }, + { + Name: "basic-command-with-files", + Description: "Basic command with files", + }, + { + Name: "localized-command", + Description: "Localized command. Description and name may vary depending on the Language setting", + NameLocalizations: &map[discordgo.Locale]string{ + discordgo.ChineseCN: "本地化的命令", + }, + DescriptionLocalizations: &map[discordgo.Locale]string{ + discordgo.ChineseCN: "这是一个本地化的命令", + }, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "localized-option", + Description: "Localized option. Description and name may vary depending on the Language setting", + NameLocalizations: map[discordgo.Locale]string{ + discordgo.ChineseCN: "一个本地化的选项", + }, + DescriptionLocalizations: map[discordgo.Locale]string{ + discordgo.ChineseCN: "这是一个本地化的选项", + }, + Type: discordgo.ApplicationCommandOptionInteger, + Choices: []*discordgo.ApplicationCommandOptionChoice{ + { + Name: "First", + NameLocalizations: map[discordgo.Locale]string{ + discordgo.ChineseCN: "一的", + }, + Value: 1, + }, + { + Name: "Second", + NameLocalizations: map[discordgo.Locale]string{ + discordgo.ChineseCN: "二的", + }, + Value: 2, + }, + }, + }, + }, + }, + { + Name: "options", + Description: "Command for demonstrating options", + Options: []*discordgo.ApplicationCommandOption{ + + { + Type: discordgo.ApplicationCommandOptionString, + Name: "string-option", + Description: "String option", + Required: true, + }, + { + Type: discordgo.ApplicationCommandOptionInteger, + Name: "integer-option", + Description: "Integer option", + MinValue: &integerOptionMinValue, + MaxValue: 10, + Required: true, + }, + { + Type: discordgo.ApplicationCommandOptionNumber, + Name: "number-option", + Description: "Float option", + MaxValue: 10.1, + Required: true, + }, + { + Type: discordgo.ApplicationCommandOptionBoolean, + Name: "bool-option", + Description: "Boolean option", + Required: true, + }, + + // Required options must be listed first since optional parameters + // always come after when they're used. + // The same concept applies to Discord's Slash-commands API + + { + Type: discordgo.ApplicationCommandOptionChannel, + Name: "channel-option", + Description: "Channel option", + // Channel type mask + ChannelTypes: []discordgo.ChannelType{ + discordgo.ChannelTypeGuildText, + discordgo.ChannelTypeGuildVoice, + }, + Required: false, + }, + { + Type: discordgo.ApplicationCommandOptionUser, + Name: "user-option", + Description: "User option", + Required: false, + }, + { + Type: discordgo.ApplicationCommandOptionRole, + Name: "role-option", + Description: "Role option", + Required: false, + }, + }, + }, + { + Name: "subcommands", + Description: "Subcommands and command groups example", + Options: []*discordgo.ApplicationCommandOption{ + // When a command has subcommands/subcommand groups + // It must not have top-level options, they aren't accesible in the UI + // in this case (at least not yet), so if a command has + // subcommands/subcommand any groups registering top-level options + // will cause the registration of the command to fail + + { + Name: "subcommand-group", + Description: "Subcommands group", + Options: []*discordgo.ApplicationCommandOption{ + // Also, subcommand groups aren't capable of + // containing options, by the name of them, you can see + // they can only contain subcommands + { + Name: "nested-subcommand", + Description: "Nested subcommand", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + }, + Type: discordgo.ApplicationCommandOptionSubCommandGroup, + }, + // Also, you can create both subcommand groups and subcommands + // in the command at the same time. But, there's some limits to + // nesting, count of subcommands (top level and nested) and options. + // Read the intro of slash-commands docs on Discord dev portal + // to get more information + { + Name: "subcommand", + Description: "Top-level subcommand", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "subcommand-option", + Description: "Subcommand Option", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + }, + }, + }, + }, + }, + { + Name: "responses", + Description: "Interaction responses testing initiative", + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "resp-type", + Description: "Response type", + Type: discordgo.ApplicationCommandOptionInteger, + Choices: []*discordgo.ApplicationCommandOptionChoice{ + { + Name: "Channel message with source", + Value: 4, + }, + { + Name: "Deferred response With Source", + Value: 5, + }, + }, + Required: true, + }, + }, + }, + { + Name: "followups", + Description: "Followup messages", + }, + } + + commandHandlers = map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){ + "basic-command": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Hey there! Congratulations, you just executed your first slash command", + }, + }) + }, + "basic-command-with-files": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Hey there! Congratulations, you just executed your first slash command with a file in the response", + Files: []*discordgo.File{ + { + ContentType: "text/plain", + Name: "test.txt", + Reader: strings.NewReader("Hello Discord!!"), + }, + }, + }, + }) + }, + "localized-command": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + responses := map[discordgo.Locale]string{ + discordgo.ChineseCN: "你好! 这是一个本地化的命令", + } + response := "Hi! This is a localized message" + if r, ok := responses[i.Locale]; ok { + response = r + } + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: response, + }, + }) + if err != nil { + panic(err) + } + }, + "options": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + // Access options in the order provided by the user. + options := i.ApplicationCommandData().Options + + // Or convert the slice into a map + optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) + for _, opt := range options { + optionMap[opt.Name] = opt + } + + // This example stores the provided arguments in an []interface{} + // which will be used to format the bot's response + margs := make([]interface{}, 0, len(options)) + msgformat := "You learned how to use command options! " + + "Take a look at the value(s) you entered:\n" + + // Get the value from the option map. + // When the option exists, ok = true + if option, ok := optionMap["string-option"]; ok { + // Option values must be type asserted from interface{}. + // Discordgo provides utility functions to make this simple. + margs = append(margs, option.StringValue()) + msgformat += "> string-option: %s\n" + } + + if opt, ok := optionMap["integer-option"]; ok { + margs = append(margs, opt.IntValue()) + msgformat += "> integer-option: %d\n" + } + + if opt, ok := optionMap["number-option"]; ok { + margs = append(margs, opt.FloatValue()) + msgformat += "> number-option: %f\n" + } + + if opt, ok := optionMap["bool-option"]; ok { + margs = append(margs, opt.BoolValue()) + msgformat += "> bool-option: %v\n" + } + + if opt, ok := optionMap["channel-option"]; ok { + margs = append(margs, opt.ChannelValue(nil).ID) + msgformat += "> channel-option: <#%s>\n" + } + + if opt, ok := optionMap["user-option"]; ok { + margs = append(margs, opt.UserValue(nil).ID) + msgformat += "> user-option: <@%s>\n" + } + + if opt, ok := optionMap["role-option"]; ok { + margs = append(margs, opt.RoleValue(nil, "").ID) + msgformat += "> role-option: <@&%s>\n" + } + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + // Ignore type for now, they will be discussed in "responses" + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf( + msgformat, + margs..., + ), + }, + }) + }, + "permission-overview": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + perms, err := s.ApplicationCommandPermissions(s.State.User.ID, i.GuildID, i.ApplicationCommandData().ID) + + var restError *discordgo.RESTError + if errors.As(err, &restError) && restError.Message != nil && restError.Message.Code == discordgo.ErrCodeUnknownApplicationCommandPermissions { + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: ":x: No permission overwrites", + }, + }) + return + } else if err != nil { + panic(err) + } + + if err != nil { + panic(err) + } + format := "- %s %s\n" + + channels := "" + users := "" + roles := "" + + for _, o := range perms.Permissions { + emoji := "❌" + if o.Permission { + emoji = "☑" + } + + switch o.Type { + case discordgo.ApplicationCommandPermissionTypeUser: + users += fmt.Sprintf(format, emoji, "<@!"+o.ID+">") + case discordgo.ApplicationCommandPermissionTypeChannel: + allChannels, _ := discordgo.GuildAllChannelsID(i.GuildID) + + if o.ID == allChannels { + channels += fmt.Sprintf(format, emoji, "All channels") + } else { + channels += fmt.Sprintf(format, emoji, "<#"+o.ID+">") + } + case discordgo.ApplicationCommandPermissionTypeRole: + if o.ID == i.GuildID { + roles += fmt.Sprintf(format, emoji, "@everyone") + } else { + roles += fmt.Sprintf(format, emoji, "<@&"+o.ID+">") + } + } + } + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Embeds: []*discordgo.MessageEmbed{ + { + Title: "Permissions overview", + Description: "Overview of permissions for this command", + Fields: []*discordgo.MessageEmbedField{ + { + Name: "Users", + Value: users, + }, + { + Name: "Channels", + Value: channels, + }, + { + Name: "Roles", + Value: roles, + }, + }, + }, + }, + AllowedMentions: &discordgo.MessageAllowedMentions{}, + }, + }) + }, + "subcommands": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + options := i.ApplicationCommandData().Options + content := "" + + // As you can see, names of subcommands (nested, top-level) + // and subcommand groups are provided through the arguments. + switch options[0].Name { + case "subcommand": + content = "The top-level subcommand is executed. Now try to execute the nested one." + case "subcommand-group": + options = options[0].Options + switch options[0].Name { + case "nested-subcommand": + content = "Nice, now you know how to execute nested commands too" + default: + content = "Oops, something went wrong.\n" + + "Hol' up, you aren't supposed to see this message." + } + } + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: content, + }, + }) + }, + "responses": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + // Responses to a command are very important. + // First of all, because you need to react to the interaction + // by sending the response in 3 seconds after receiving, otherwise + // interaction will be considered invalid and you can no longer + // use the interaction token and ID for responding to the user's request + + content := "" + // As you can see, the response type names used here are pretty self-explanatory, + // but for those who want more information see the official documentation + switch i.ApplicationCommandData().Options[0].IntValue() { + case int64(discordgo.InteractionResponseChannelMessageWithSource): + content = + "You just responded to an interaction, sent a message and showed the original one. " + + "Congratulations!" + content += + "\nAlso... you can edit your response, wait 5 seconds and this message will be changed" + default: + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseType(i.ApplicationCommandData().Options[0].IntValue()), + }) + if err != nil { + s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: "Something went wrong", + }) + } + return + } + + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseType(i.ApplicationCommandData().Options[0].IntValue()), + Data: &discordgo.InteractionResponseData{ + Content: content, + }, + }) + if err != nil { + s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: "Something went wrong", + }) + return + } + time.AfterFunc(time.Second*5, func() { + content := content + "\n\nWell, now you know how to create and edit responses. " + + "But you still don't know how to delete them... so... wait 10 seconds and this " + + "message will be deleted." + _, err = s.InteractionResponseEdit(i.Interaction, &discordgo.WebhookEdit{ + Content: &content, + }) + if err != nil { + s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: "Something went wrong", + }) + return + } + time.Sleep(time.Second * 10) + s.InteractionResponseDelete(i.Interaction) + }) + }, + "followups": func(s *discordgo.Session, i *discordgo.InteractionCreate) { + // Followup messages are basically regular messages (you can create as many of them as you wish) + // but work as they are created by webhooks and their functionality + // is for handling additional messages after sending a response. + + s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + // Note: this isn't documented, but you can use that if you want to. + // This flag just allows you to create messages visible only for the caller of the command + // (user who triggered the command) + Flags: discordgo.MessageFlagsEphemeral, + Content: "Surprise!", + }, + }) + msg, err := s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: "Followup message has been created, after 5 seconds it will be edited", + }) + if err != nil { + s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: "Something went wrong", + }) + return + } + time.Sleep(time.Second * 5) + + content := "Now the original message is gone and after 10 seconds this message will ~~self-destruct~~ be deleted." + s.FollowupMessageEdit(i.Interaction, msg.ID, &discordgo.WebhookEdit{ + Content: &content, + }) + + time.Sleep(time.Second * 10) + + s.FollowupMessageDelete(i.Interaction, msg.ID) + + s.FollowupMessageCreate(i.Interaction, true, &discordgo.WebhookParams{ + Content: "For those, who didn't skip anything and followed tutorial along fairly, " + + "take a unicorn :unicorn: as reward!\n" + + "Also, as bonus... look at the original interaction response :D", + }) + }, + } +) + +func init() { + s.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { + if h, ok := commandHandlers[i.ApplicationCommandData().Name]; ok { + h(s, i) + } + }) +} + +func main() { + s.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { + log.Printf("Logged in as: %v#%v", s.State.User.Username, s.State.User.Discriminator) + }) + err := s.Open() + if err != nil { + log.Fatalf("Cannot open the session: %v", err) + } + + log.Println("Adding commands...") + registeredCommands := make([]*discordgo.ApplicationCommand, len(commands)) + for i, v := range commands { + cmd, err := s.ApplicationCommandCreate(s.State.User.ID, *GuildID, v) + if err != nil { + log.Panicf("Cannot create '%v' command: %v", v.Name, err) + } + registeredCommands[i] = cmd + } + + defer s.Close() + + stop := make(chan os.Signal, 1) + signal.Notify(stop, os.Interrupt) + log.Println("Press Ctrl+C to exit") + <-stop + + if *RemoveCommands { + log.Println("Removing commands...") + // // We need to fetch the commands, since deleting requires the command ID. + // // We are doing this from the returned commands on line 375, because using + // // this will delete all the commands, which might not be desirable, so we + // // are deleting only the commands that we added. + // registeredCommands, err := s.ApplicationCommands(s.State.User.ID, *GuildID) + // if err != nil { + // log.Fatalf("Could not fetch registered commands: %v", err) + // } + + for _, v := range registeredCommands { + err := s.ApplicationCommandDelete(s.State.User.ID, *GuildID, v.ID) + if err != nil { + log.Panicf("Cannot delete '%v' command: %v", v.Name, err) + } + } + } + + log.Println("Gracefully shutting down.") +} From 4cd1b5ed262ca0d4f2d6a3b79a5c8a087eba55c0 Mon Sep 17 00:00:00 2001 From: jokil123 <46920345+jokil123@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:45:02 +0000 Subject: [PATCH 09/20] use defer to ensure cleanup of api --- EsefexApi/main.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/EsefexApi/main.go b/EsefexApi/main.go index 23a044e..0169446 100644 --- a/EsefexApi/main.go +++ b/EsefexApi/main.go @@ -68,17 +68,19 @@ func main() { <-bot.Start() <-plr.Start() + defer func() { + <-api.Stop() + <-bot.Stop() + <-plr.Stop() + + udb.Close() + + log.Println("All components stopped, exiting...") + }() + log.Println("All components started successfully :)") log.Println("Press Ctrl+C to exit") <-util.Interrupt() println() log.Println("Gracefully shutting down...") - - <-api.Stop() - <-bot.Stop() - <-plr.Stop() - - udb.Close() - - log.Println("All components stopped, exiting...") } From 1ac3fb344ad54616d0304fd86320d5cde1c5070a Mon Sep 17 00:00:00 2001 From: jokil123 <46920345+jokil123@users.noreply.github.com> Date: Fri, 12 Jan 2024 15:05:11 +0000 Subject: [PATCH 10/20] permission application command --- EsefexApi/README.md | 31 +++++ EsefexApi/bot/commands/commands.go | 31 +++-- EsefexApi/bot/commands/delete.go | 24 ++-- EsefexApi/bot/commands/link.go | 10 +- EsefexApi/bot/commands/list.go | 10 +- EsefexApi/bot/commands/permission.go | 110 ++++++++++++++++++ .../bot/commands/subcommandbuilder/build.go | 96 +++++++++++++++ .../subcommandbuilder/subcommandbuilder.go | 80 +++++++++++++ EsefexApi/bot/commands/unlink.go | 10 +- EsefexApi/bot/commands/upload.go | 48 ++++---- 10 files changed, 384 insertions(+), 66 deletions(-) create mode 100644 EsefexApi/README.md create mode 100644 EsefexApi/bot/commands/permission.go create mode 100644 EsefexApi/bot/commands/subcommandbuilder/build.go create mode 100644 EsefexApi/bot/commands/subcommandbuilder/subcommandbuilder.go diff --git a/EsefexApi/README.md b/EsefexApi/README.md new file mode 100644 index 0000000..79e026b --- /dev/null +++ b/EsefexApi/README.md @@ -0,0 +1,31 @@ +# Discord Commands + +```go +/help -> shows general Help + +/sound -> shows sound help +/sound upload -> upload sound +/sound list -> list sounds +/sound delete -> delete sounds +/sound modify -> modify sounds + +/permission -> shows permissions help +/permission set role -> sets permissions for user +/permission set user +/permission set channel + +/permission get role/user/channel -> gets permission value for role/user/channel +/permission list role/user/channel -> list all permissions for role/user/channel +/permission clear role/user/channel -> clears permissions for role/user/channel + +/bot -> shows bot help +/bot join -> makes bot join +/bot leave -> makes bot leave +/bot stats -> displays bot stats + +/config -> change bot config + +/user link -> link user +/user unlink -> unlink user +/user stats -> show user stats +``` diff --git a/EsefexApi/bot/commands/commands.go b/EsefexApi/bot/commands/commands.go index fe827ac..de172dc 100644 --- a/EsefexApi/bot/commands/commands.go +++ b/EsefexApi/bot/commands/commands.go @@ -9,6 +9,17 @@ import ( "github.com/pkg/errors" ) +type Command struct { + ApplicationCommand discordgo.ApplicationCommand + Handler func(s *discordgo.Session, i *discordgo.InteractionCreate) +} + +type SubcommandGroup struct { + Name string + Description string + Commands []*Command +} + type CommandHandlers struct { dbs *db.Databases domain string @@ -24,20 +35,20 @@ func NewCommandHandlers(dbs *db.Databases, domain string) *CommandHandlers { Handlers: map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){}, } - ch.Commands["upload"] = UploadCommand - ch.Handlers["upload"] = WithErrorHandling(ch.Upload) + // ch.Commands["upload"] = UploadCommand + // ch.Handlers["upload"] = WithErrorHandling(ch.Upload) - ch.Commands["list"] = ListCommand - ch.Handlers["list"] = WithErrorHandling(ch.List) + // ch.Commands["list"] = ListCommand + // ch.Handlers["list"] = WithErrorHandling(ch.List) - ch.Commands["delete"] = DeleteCommand - ch.Handlers["delete"] = WithErrorHandling(ch.Delete) + // ch.Commands["delete"] = DeleteCommand + // ch.Handlers["delete"] = WithErrorHandling(ch.Delete) - ch.Commands["link"] = LinkCommand - ch.Handlers["link"] = WithErrorHandling(ch.Link) + // ch.Commands["link"] = LinkCommand + // ch.Handlers["link"] = WithErrorHandling(ch.Link) - ch.Commands["unlink"] = UnlinkCommand - ch.Handlers["unlink"] = WithErrorHandling(ch.Unlink) + // ch.Commands["unlink"] = UnlinkCommand + // ch.Handlers["unlink"] = WithErrorHandling(ch.Unlink) ch.Commands["permissions"] = PermissionCommand ch.Handlers["permissions"] = WithErrorHandling(ch.Permission) diff --git a/EsefexApi/bot/commands/delete.go b/EsefexApi/bot/commands/delete.go index 1cc5610..4ce49d4 100644 --- a/EsefexApi/bot/commands/delete.go +++ b/EsefexApi/bot/commands/delete.go @@ -9,20 +9,18 @@ import ( "github.com/pkg/errors" ) -var ( - DeleteCommand = &discordgo.ApplicationCommand{ - Name: "delete", - Description: "Delete a sound effect", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionString, - Name: "sound-id", - Description: "The sound effect to delete", - Required: true, - }, +var DeleteCommand = &discordgo.ApplicationCommand{ + Name: "delete", + Description: "Delete a sound effect", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "sound-id", + Description: "The sound effect to delete", + Required: true, }, - } -) + }, +} func (c *CommandHandlers) Delete(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { options := OptionsMap(i) diff --git a/EsefexApi/bot/commands/link.go b/EsefexApi/bot/commands/link.go index 65935b3..d62b448 100644 --- a/EsefexApi/bot/commands/link.go +++ b/EsefexApi/bot/commands/link.go @@ -9,12 +9,10 @@ import ( "github.com/pkg/errors" ) -var ( - LinkCommand = &discordgo.ApplicationCommand{ - Name: "link", - Description: "Link your Discord account to Esefex", - } -) +var LinkCommand = &discordgo.ApplicationCommand{ + Name: "link", + Description: "Link your Discord account to Esefex", +} func (c *CommandHandlers) Link(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { linkToken, err := c.dbs.LinkTokenStore.CreateToken(types.UserID(i.Member.User.ID)) diff --git a/EsefexApi/bot/commands/list.go b/EsefexApi/bot/commands/list.go index 4579368..e0cccbf 100644 --- a/EsefexApi/bot/commands/list.go +++ b/EsefexApi/bot/commands/list.go @@ -9,12 +9,10 @@ import ( "github.com/pkg/errors" ) -var ( - ListCommand = &discordgo.ApplicationCommand{ - Name: "list", - Description: "List all sound effects in the guild", - } -) +var ListCommand = &discordgo.ApplicationCommand{ + Name: "list", + Description: "List all sound effects in the guild", +} func (c *CommandHandlers) List(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { uids, err := c.dbs.SoundDB.GetSoundUIDs(types.GuildID(i.GuildID)) diff --git a/EsefexApi/bot/commands/permission.go b/EsefexApi/bot/commands/permission.go new file mode 100644 index 0000000..d3198d6 --- /dev/null +++ b/EsefexApi/bot/commands/permission.go @@ -0,0 +1,110 @@ +package commands + +import ( + "fmt" + + "github.com/bwmarrin/discordgo" + "github.com/davecgh/go-spew/spew" + "github.com/pkg/errors" +) + +var PermissionCommand = &discordgo.ApplicationCommand{ + Name: "permission", + Description: "All commands related to permissions.", + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "set", + Description: "Set a permission for a user, a channel or a role.", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "user-role-channel", + Description: "The user, role or channel to set the permission for.", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + }, + { + Name: "permission", + Description: "The permission to set.", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + // TODO: Dynamically get the choices from the permission type on startup using reflection. + // Choices: []*discordgo.ApplicationCommandOptionChoice{} + }, + { + Name: "value", + Description: "The value to set the permission to. (Allow,Deny or Unset)", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + Choices: []*discordgo.ApplicationCommandOptionChoice{ + { + Name: "Allow", + Value: "Allow", + }, + { + Name: "Deny", + Value: "Deny", + }, + { + Name: "Unset", + Value: "Unset", + }, + }, + }, + }, + }, + { + Name: "get", + Description: "Get the value of a permission for a user, a channel or a role.", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "user-role-channel", + Description: "The user, role or channel to get the permission for.", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + }, + { + Name: "permission", + Description: "The permission to get.", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + // TODO: Dynamically get the choices from the permission type on startup using reflection. + // Choices: []*discordgo.ApplicationCommandOptionChoice{} + }, + }, + }, + { + Name: "clear", + Description: "Clear all permissions for a user, a channel or a role.", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "user-role-channel", + Description: "The user, role or channel to clear the permissions for.", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + }, + }, + }, + { + Name: "list", + Description: "List all permissions for a user, a channel or a role.", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "user-role-channel", + Description: "The user, role or channel to list the permissions for.", + Type: discordgo.ApplicationCommandOptionString, + Required: true, + }, + }, + }, + }, +} + +func (c *CommandHandlers) Permission(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + spew.Dump(i.ApplicationCommandData().Options) + + return nil, errors.Wrap(fmt.Errorf("not implemented"), "permission") +} diff --git a/EsefexApi/bot/commands/subcommandbuilder/build.go b/EsefexApi/bot/commands/subcommandbuilder/build.go new file mode 100644 index 0000000..8172299 --- /dev/null +++ b/EsefexApi/bot/commands/subcommandbuilder/build.go @@ -0,0 +1,96 @@ +package subcommandbuilder + +import ( + "esefexapi/bot/commands" + "log" + + "github.com/bwmarrin/discordgo" +) + +// Build builds the subcommand +// Any errors encountered during any of the steps will be returned here to improve usability +func (b *SubCommandBuilder) Build() (commands.Command, error) { + cmd := commands.Command{ + ApplicationCommand: b.applicationCommand(), + Handler: b.handlerProxy(), + } + + if len(b.errors) > 0 { + return cmd, b.errors[0] + } + + return cmd, nil +} + +func (b *SubCommandBuilder) applicationCommand() discordgo.ApplicationCommand { + cmd := discordgo.ApplicationCommand{ + Name: b.name, + Description: b.description, + Options: []*discordgo.ApplicationCommandOption{}, + } + + // for _, sc := range b.subcommands { + // aco := discordgo.ApplicationCommandOption{ + // Type: discordgo.ApplicationCommandOptionSubCommand, + // Name: sc.ApplicationCommand.Name, + // Description: , + // } + + // cmd.Options = append(cmd.Options, &sc.ApplicationCommand) + // } + + return cmd +} + +func (b *SubCommandBuilder) handlerProxy() func(s *discordgo.Session, i *discordgo.InteractionCreate) { + return func(s *discordgo.Session, i *discordgo.InteractionCreate) { + opt0 := i.ApplicationCommandData().Options[0] + + // check if subcommand exists + for _, c := range b.subcommands { + if c.ApplicationCommand.Name == opt0.Name { + // remove the subcommand from the options (so it doesn't get passed to the handler) + // and replace it with the subcommand's options + + newI := *i + newData := newI.ApplicationCommandData() + newData.Options = opt0.Options[1:] + newI.Data = newData + + c.Handler(s, &newI) + return + } + } + + // otherwise check if subcommand exists in group + for _, g := range b.groups { + if g.Name == opt0.Name { + opt1 := opt0.Options[0] + + for _, c := range g.Commands { + if c.ApplicationCommand.Name == opt1.Name { + c.Handler(s, i) + return + } + } + } + } + + // if we get here, the subcommand doesn't exist + // that means we have a bug somewhere + + log.Println("SubCommandBuilder: subcommand not found") + log.Println("SubCommandBuilder: subcommand name:", opt0.Name) + + err := s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Subcommand not found, this is a bug, please report it to the bot developer", + }, + }) + + if err != nil { + log.Println("SubCommandBuilder: error sending response:", err) + } + } +} diff --git a/EsefexApi/bot/commands/subcommandbuilder/subcommandbuilder.go b/EsefexApi/bot/commands/subcommandbuilder/subcommandbuilder.go new file mode 100644 index 0000000..5467971 --- /dev/null +++ b/EsefexApi/bot/commands/subcommandbuilder/subcommandbuilder.go @@ -0,0 +1,80 @@ +package subcommandbuilder + +import ( + "esefexapi/bot/commands" + "fmt" + + "github.com/pkg/errors" +) + +// SubCommandBuilder is a builder for creating subcommands +type SubCommandBuilder struct { + errors []error + name string + description string + subcommands []commands.Command + groups []commands.SubcommandGroup +} + +func New(name string, description string) *SubCommandBuilder { + return &SubCommandBuilder{ + name: name, + description: description, + subcommands: []commands.Command{}, + groups: []commands.SubcommandGroup{}, + } +} + +func (b *SubCommandBuilder) SubCommand(cmd commands.Command) *SubCommandBuilder { + // check if subcommand already exists (by name) + for _, c := range b.subcommands { + if c.ApplicationCommand.Name == cmd.ApplicationCommand.Name { + b.errors = append(b.errors, errors.WithStack(fmt.Errorf("SubCommand: subcommand %s already exists", cmd.ApplicationCommand.Name))) + return b + } + } + + b.subcommands = append(b.subcommands, cmd) + return b +} + +func (b *SubCommandBuilder) SubCommandGroup(name string, description string) *SubCommandBuilder { + // if group already exists, return error + for _, g := range b.groups { + if g.Name == name { + b.errors = append(b.errors, errors.WithStack(fmt.Errorf("SubCommandGroup: group %s already exists", name))) + return b + } + } + + b.groups = append(b.groups, commands.SubcommandGroup{ + Name: name, + Description: description, + Commands: []*commands.Command{}, + }) + return b +} + +func (b *SubCommandBuilder) SubCommandGroupCommand(gname string, cmd commands.Command) *SubCommandBuilder { + // check if subcommand already exists in group (by name) + for _, c := range b.groups { + if c.Name == gname { + for _, sc := range c.Commands { + if sc.ApplicationCommand.Name == cmd.ApplicationCommand.Name { + b.errors = append(b.errors, errors.WithStack(fmt.Errorf("SubCommandGroupCommand: subcommand %s already exists in group %s", cmd.ApplicationCommand.Name, gname))) + return b + } + } + } + } + + for i, g := range b.groups { + if g.Name == gname { + b.groups[i].Commands = append(b.groups[i].Commands, &cmd) + return b + } + } + + b.errors = append(b.errors, errors.WithStack(fmt.Errorf("SubCommandGroupCommand: could not find group %s", gname))) + return b +} diff --git a/EsefexApi/bot/commands/unlink.go b/EsefexApi/bot/commands/unlink.go index 87c579e..009fd1b 100644 --- a/EsefexApi/bot/commands/unlink.go +++ b/EsefexApi/bot/commands/unlink.go @@ -7,12 +7,10 @@ import ( "github.com/bwmarrin/discordgo" ) -var ( - UnlinkCommand = &discordgo.ApplicationCommand{ - Name: "unlink", - Description: "Unlink your Discord account from Esefex. Useful if you think your account has been compromised.", - } -) +var UnlinkCommand = &discordgo.ApplicationCommand{ + Name: "unlink", + Description: "Unlink your Discord account from Esefex. Useful if you think your account has been compromised.", +} func (c *CommandHandlers) Unlink(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { err := c.dbs.UserDB.DeleteUser(types.UserID(i.Member.User.ID)) diff --git a/EsefexApi/bot/commands/upload.go b/EsefexApi/bot/commands/upload.go index 374fd19..95e5ddd 100644 --- a/EsefexApi/bot/commands/upload.go +++ b/EsefexApi/bot/commands/upload.go @@ -11,32 +11,30 @@ import ( "github.com/pkg/errors" ) -var ( - UploadCommand = &discordgo.ApplicationCommand{ - Name: "upload", - Description: "Upload a sound effect to the bot", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionAttachment, - Name: "sound-file", - Description: "The sound file to upload", - Required: true, - }, - { - Type: discordgo.ApplicationCommandOptionString, - Name: "name", - Description: "The name of the sound effect", - Required: true, - }, - { - Type: discordgo.ApplicationCommandOptionString, - Name: "icon", - Description: "The icon to use for the sound effect", - Required: true, - }, +var UploadCommand = &discordgo.ApplicationCommand{ + Name: "upload", + Description: "Upload a sound effect to the bot", + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionAttachment, + Name: "sound-file", + Description: "The sound file to upload", + Required: true, }, - } -) + { + Type: discordgo.ApplicationCommandOptionString, + Name: "name", + Description: "The name of the sound effect", + Required: true, + }, + { + Type: discordgo.ApplicationCommandOptionString, + Name: "icon", + Description: "The icon to use for the sound effect", + Required: true, + }, + }, +} func (c *CommandHandlers) Upload(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { options := OptionsMap(i) From ebdac617af6cba6039c911673106e8a781f1f2b6 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Fri, 12 Jan 2024 22:45:38 +0100 Subject: [PATCH 11/20] better command grouping, help, ping, stats cmd --- EsefexApi/bot/commands/bot.go | 106 ++++++++++ EsefexApi/bot/commands/commands.go | 27 +-- EsefexApi/bot/commands/delete.go | 59 ------ EsefexApi/bot/commands/help.go | 64 ++++++ .../commands/helpmsg/Command:Permission.txt | 18 ++ .../bot/commands/helpmsg/Commands:Bot.txt | 21 ++ .../bot/commands/helpmsg/Commands:Sound.txt | 16 ++ .../bot/commands/helpmsg/Commands:User.txt | 22 ++ EsefexApi/bot/commands/helpmsg/General.txt | 19 ++ EsefexApi/bot/commands/helpmsg/UI.txt | 5 + EsefexApi/bot/commands/helpmsg/helpmsg.go | 35 +++ EsefexApi/bot/commands/link.go | 43 ---- EsefexApi/bot/commands/list.go | 68 ------ EsefexApi/bot/commands/permission.go | 2 + EsefexApi/bot/commands/sound.go | 200 ++++++++++++++++++ EsefexApi/bot/commands/unlink.go | 35 --- EsefexApi/bot/commands/upload.go | 68 ------ EsefexApi/bot/commands/user.go | 130 ++++++++++++ EsefexApi/bot/commands/util.go | 17 ++ 19 files changed, 666 insertions(+), 289 deletions(-) create mode 100644 EsefexApi/bot/commands/bot.go delete mode 100644 EsefexApi/bot/commands/delete.go create mode 100644 EsefexApi/bot/commands/help.go create mode 100644 EsefexApi/bot/commands/helpmsg/Command:Permission.txt create mode 100644 EsefexApi/bot/commands/helpmsg/Commands:Bot.txt create mode 100644 EsefexApi/bot/commands/helpmsg/Commands:Sound.txt create mode 100644 EsefexApi/bot/commands/helpmsg/Commands:User.txt create mode 100644 EsefexApi/bot/commands/helpmsg/General.txt create mode 100644 EsefexApi/bot/commands/helpmsg/UI.txt create mode 100644 EsefexApi/bot/commands/helpmsg/helpmsg.go delete mode 100644 EsefexApi/bot/commands/link.go delete mode 100644 EsefexApi/bot/commands/list.go create mode 100644 EsefexApi/bot/commands/sound.go delete mode 100644 EsefexApi/bot/commands/unlink.go delete mode 100644 EsefexApi/bot/commands/upload.go create mode 100644 EsefexApi/bot/commands/user.go create mode 100644 EsefexApi/bot/commands/util.go diff --git a/EsefexApi/bot/commands/bot.go b/EsefexApi/bot/commands/bot.go new file mode 100644 index 0000000..7b03820 --- /dev/null +++ b/EsefexApi/bot/commands/bot.go @@ -0,0 +1,106 @@ +package commands + +import ( + "fmt" + + "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" +) + +var BotCommand = &discordgo.ApplicationCommand{ + Name: "bot", + Description: "All commands related to the bot.", + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "invite", + Description: "Get the invite link for the bot.", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + { + Name: "ping", + Description: "Get the ping of the bot.", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + { + Name: "stats", + Description: "Get the stats of the bot.", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + { + Name: "join", + Description: "Join the voice channel you are in.", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + { + Name: "leave", + Description: "Leave the voice channel you are in.", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + { + Name: "config", + Description: "All commands related to the configuration of the bot.", + Type: discordgo.ApplicationCommandOptionSubCommandGroup, + }, + }, +} + +func (c *CommandHandlers) Bot(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + switch i.ApplicationCommandData().Options[0].Name { + case "invite": + return c.BotInvite(s, i) + case "ping": + return c.BotPing(s, i) + case "stats": + return c.BotStats(s, i) + case "join": + return c.BotJoin(s, i) + case "leave": + return c.BotLeave(s, i) + case "config": + return c.BotConfig(s, i) + default: + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Bot") + } +} + +func (c *CommandHandlers) BotInvite(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + permissions := 8 + inviteUrl := fmt.Sprintf("https://discord.com/api/oauth2/authorize?client_id=%s&permissions=%d&scope=bot%%20applications.commands", s.State.User.ID, permissions) + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Invite me to your server with this link: \n%s", inviteUrl), + }, + }, nil +} + +// TODO: Add a way to get the ping of the bot (currently not updating) +func (c *CommandHandlers) BotPing(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Pong! %dms", s.HeartbeatLatency().Milliseconds()), + }, + }, nil +} + +// TODO: Implement BotStats +func (c *CommandHandlers) BotStats(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "BotStats") +} + +// TODO: Implement BotJoin +func (c *CommandHandlers) BotJoin(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "BotJoin") +} + +// TODO: Implement BotLeave +func (c *CommandHandlers) BotLeave(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "BotLeave") +} + +// TODO: Implement BotConfig +func (c *CommandHandlers) BotConfig(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "BotConfig") +} diff --git a/EsefexApi/bot/commands/commands.go b/EsefexApi/bot/commands/commands.go index de172dc..c6590e6 100644 --- a/EsefexApi/bot/commands/commands.go +++ b/EsefexApi/bot/commands/commands.go @@ -35,23 +35,20 @@ func NewCommandHandlers(dbs *db.Databases, domain string) *CommandHandlers { Handlers: map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){}, } - // ch.Commands["upload"] = UploadCommand - // ch.Handlers["upload"] = WithErrorHandling(ch.Upload) + ch.Commands["bot"] = BotCommand + ch.Handlers["bot"] = WithErrorHandling(ch.Bot) - // ch.Commands["list"] = ListCommand - // ch.Handlers["list"] = WithErrorHandling(ch.List) + ch.Commands["help"] = HelpCommand + ch.Handlers["help"] = WithErrorHandling(ch.Help) - // ch.Commands["delete"] = DeleteCommand - // ch.Handlers["delete"] = WithErrorHandling(ch.Delete) + ch.Commands["permission"] = PermissionCommand + ch.Handlers["permission"] = WithErrorHandling(ch.Permission) - // ch.Commands["link"] = LinkCommand - // ch.Handlers["link"] = WithErrorHandling(ch.Link) + ch.Commands["sound"] = SoundCommand + ch.Handlers["sound"] = WithErrorHandling(ch.Sound) - // ch.Commands["unlink"] = UnlinkCommand - // ch.Handlers["unlink"] = WithErrorHandling(ch.Unlink) - - ch.Commands["permissions"] = PermissionCommand - ch.Handlers["permissions"] = WithErrorHandling(ch.Permission) + ch.Commands["user"] = UserCommand + ch.Handlers["user"] = WithErrorHandling(ch.User) return ch } @@ -82,9 +79,7 @@ func WithErrorHandling(h func(s *discordgo.Session, i *discordgo.InteractionCrea } } -func OptionsMap(i *discordgo.InteractionCreate) map[string]*discordgo.ApplicationCommandInteractionDataOption { - options := i.ApplicationCommandData().Options - +func OptionsMap(options []*discordgo.ApplicationCommandInteractionDataOption) map[string]*discordgo.ApplicationCommandInteractionDataOption { optionMap := make(map[string]*discordgo.ApplicationCommandInteractionDataOption, len(options)) for _, opt := range options { optionMap[opt.Name] = opt diff --git a/EsefexApi/bot/commands/delete.go b/EsefexApi/bot/commands/delete.go deleted file mode 100644 index 4ce49d4..0000000 --- a/EsefexApi/bot/commands/delete.go +++ /dev/null @@ -1,59 +0,0 @@ -package commands - -import ( - "esefexapi/sounddb" - "fmt" - "log" - - "github.com/bwmarrin/discordgo" - "github.com/pkg/errors" -) - -var DeleteCommand = &discordgo.ApplicationCommand{ - Name: "delete", - Description: "Delete a sound effect", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionString, - Name: "sound-id", - Description: "The sound effect to delete", - Required: true, - }, - }, -} - -func (c *CommandHandlers) Delete(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - options := OptionsMap(i) - soundID := options["sound-id"] - - uid := sounddb.SuidFromStrings(i.GuildID, fmt.Sprint(soundID.Value)) - - exists, err := c.dbs.SoundDB.SoundExists(uid) - if err != nil { - return nil, errors.Wrap(err, "Error checking if sound exists") - } - if !exists { - return &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("Sound effect `%s` does not exist", soundID.Value), - }, - }, nil - } - - log.Print("a") - - err = c.dbs.SoundDB.DeleteSound(uid) - if err != nil { - return nil, errors.Wrap(err, "Error deleting sound") - } - - log.Printf("Deleted sound effect %v from guild %v", soundID.Value, i.GuildID) - - return &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("Deleted sound effect `%s`", soundID.Value), - }, - }, nil -} diff --git a/EsefexApi/bot/commands/help.go b/EsefexApi/bot/commands/help.go new file mode 100644 index 0000000..f0622ea --- /dev/null +++ b/EsefexApi/bot/commands/help.go @@ -0,0 +1,64 @@ +package commands + +import ( + "esefexapi/bot/commands/helpmsg" + + "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" +) + +var HelpCommand = &discordgo.ApplicationCommand{ + Name: "help", + Description: "Get help about the bot.", + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "help-category", + Description: "The category to get help about. Leave empty to get general help", + Type: discordgo.ApplicationCommandOptionString, + Required: false, + Choices: []*discordgo.ApplicationCommandOptionChoice{ + { + Name: "UI", + Value: "UI", + }, + { + Name: "Commands:Bot", + Value: "Commands:Bot", + }, + { + Name: "Commands:Sound", + Value: "Commands:Sound", + }, + { + Name: "Commands:User", + Value: "Commands:User", + }, + { + Name: "Commands:Permission", + Value: "Commands:Permission", + }, + }, + }, + }, +} + +func (c *CommandHandlers) Help(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + category := "" + if len(i.ApplicationCommandData().Options) > 0 { + category = i.ApplicationCommandData().Options[0].StringValue() + } else { + category = "" + } + + msg, err := helpmsg.GetHelpMessage(category) + if err != nil { + return nil, errors.Wrap(err, "failed to get help message") + } + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: msg, + }, + }, nil +} diff --git a/EsefexApi/bot/commands/helpmsg/Command:Permission.txt b/EsefexApi/bot/commands/helpmsg/Command:Permission.txt new file mode 100644 index 0000000..83024b4 --- /dev/null +++ b/EsefexApi/bot/commands/helpmsg/Command:Permission.txt @@ -0,0 +1,18 @@ +**Command: Permission** +The permission commands are used to manage the permissions of the user. The bot uses its own permission system that works similarly to the one in Discord. +There is a list of permissions that can be set to either `Allow`, `Deny`, or `Unset`. +A set of permissions can be assigned to a user, role, or channel. The order of precedence is `User > Channel > Role`, exaclty like in Discord. +This means that if a user has a permission set to `Allow`, but the channel has it set to `Deny`, the user will not be able to perform that command in that channel. +Having a permission set to `Unset` is the same as not having it set at all. + +**`/permission set`** +This command is used to set the permission value for a user, role, or channel. + +**`/permission get`** +This command is used to get the permission value for a user, role, or channel. + +**`/permission clear`** +This command is used to clear all permissions for a user, role, or channel. + +**`/permission list`** +This command is used to list all permissions for a user, role, or channel. diff --git a/EsefexApi/bot/commands/helpmsg/Commands:Bot.txt b/EsefexApi/bot/commands/helpmsg/Commands:Bot.txt new file mode 100644 index 0000000..f995555 --- /dev/null +++ b/EsefexApi/bot/commands/helpmsg/Commands:Bot.txt @@ -0,0 +1,21 @@ +**Commands: Bot** +Bot commands are used to control the bot. + +**`/bot invite`** +Generate an invite link for the bot. Use this if you want to invite the bot to your server. + +**`/bot ping`** +Check the bot's latency. + +**`/bot stats`** +Get some stats about the bot. + +**`/bot join`** +Join the voice channel you are in. + +**`/bot leave`** +Leave the voice channel you are in. + +**`/bot config`** +Configure the bot for your server. +For more information, use the `/help Commands:Bot:Config` command or visit the [wiki](https://github.com/EsefexBot/Esefex/wiki). diff --git a/EsefexApi/bot/commands/helpmsg/Commands:Sound.txt b/EsefexApi/bot/commands/helpmsg/Commands:Sound.txt new file mode 100644 index 0000000..50ee6ed --- /dev/null +++ b/EsefexApi/bot/commands/helpmsg/Commands:Sound.txt @@ -0,0 +1,16 @@ +**Commands: Sound** +The sound commands can be used to play and manage sounds. +Sounds exist per guild, so you can have different sounds on different servers. + +**`/sound upload`** +Upload a sound file to the bot. The file must be one of the following formats: mp3, wav, ogg, flac, m4a, aac. +The file must be smaller than 5MB. + +**`/sound delete`** +Delete a sound file from the bot. + +**`/sound play`** +Play a sound file in the current voice channel. If the bot is not in your voice channel, it will join it. + +**`/sound list`** +List all available sounds. diff --git a/EsefexApi/bot/commands/helpmsg/Commands:User.txt b/EsefexApi/bot/commands/helpmsg/Commands:User.txt new file mode 100644 index 0000000..182f539 --- /dev/null +++ b/EsefexApi/bot/commands/helpmsg/Commands:User.txt @@ -0,0 +1,22 @@ +**Command: User** +All commands that related to the user, such as user info, user settings, etc. + +**`/user stats`** +Shows the user's stats. + +**`/user link`** +Link your Discord account to Esefex. +The bot will send you a DM with a link to the website. +Open the link and click on the button to link your account. +Afterwards you can either open the Desktop app or the WebUI. + +Esefex will not be able to send you DMs if you have disabled DMs from server members. +Esefex does not store any information about you, except for your Discord ID and your username. +This is needed so the bot knows in what channel to play the sound and what permissions you have. + +**`/user unlink`** +Unlink your Discord account from Esefex. +This will remove all tokens and sessions from the database meaning you will have to link your account again. +This command is useful if you think your Esefex Token/Session has been compromised. +It has to be noted though that a compromised token/session is not the biggest deal as it can only be used to play sounds in voices channels you are connected to. +**Important Reminder: This command will not help you if your Discord account has been compromised. Please contact Discord Support instead.** diff --git a/EsefexApi/bot/commands/helpmsg/General.txt b/EsefexApi/bot/commands/helpmsg/General.txt new file mode 100644 index 0000000..114aa57 --- /dev/null +++ b/EsefexApi/bot/commands/helpmsg/General.txt @@ -0,0 +1,19 @@ +**General Information** +Esefex is a bot based soundboard that allows you to play sounds in your voice channel. +It is currently in development and is not yet ready for public use. +If you would like to contribute, please contact me on Discord `@jo_kil`. + +**Getting Started** +To get started, use the `/user link` command to link your Discord account to Esefex. +This is required for the bot to know who you are and what permissions you have. +It will not save any personal information about you, only your Discord ID and your username. +You can unlink your account at any time by using the `/user unlink` command. + +**Sounds** +Sounds are the main feature of Esefex. +You can play sounds by using the Web UI or Desktop Client. +Sounds can be added, removed, and edited using the `/sound` commands. + +**Permissions** +Esefex uses a permission system to determine what you can and cannot do. +As the slash commands are rather limited, you will need to use the `/permisssion` commands to change permissions. diff --git a/EsefexApi/bot/commands/helpmsg/UI.txt b/EsefexApi/bot/commands/helpmsg/UI.txt new file mode 100644 index 0000000..2bca22d --- /dev/null +++ b/EsefexApi/bot/commands/helpmsg/UI.txt @@ -0,0 +1,5 @@ +**UI** +Esefex allows you to easily play sounds using either the Web or the Desktop UI. +The Web UI is available at http://localhost:8080/, the Desktop UI is can be downloaded from the [releases](https://github.com/EsefexBot/Esefex/releases) page. +The Desktop UI is a standalone application that can be used to play sounds without having to open a browser. +It also allows you to set a global hotkey to play a sound. Otherwise it is very similar to the Web UI. \ No newline at end of file diff --git a/EsefexApi/bot/commands/helpmsg/helpmsg.go b/EsefexApi/bot/commands/helpmsg/helpmsg.go new file mode 100644 index 0000000..6a80cc5 --- /dev/null +++ b/EsefexApi/bot/commands/helpmsg/helpmsg.go @@ -0,0 +1,35 @@ +package helpmsg + +import ( + "fmt" + "os" + + "github.com/pkg/errors" +) + +var helpMessages = map[string]string{ + "": "General.txt", + "UI": "UI.txt", + "Commands:Bot": "Commands:Bot.txt", + "Commands:Sound": "Commands:Sound.txt", + "Commands:User": "Commands:User.txt", + "Commands:Permission": "Commands:Permission.txt", +} + +// GetHelpMessage returns the help message for the given category. +// If the category is empty, it returns the general help message. +// it will open the file in the helpmsg folder with the name of the category. +func GetHelpMessage(category string) (string, error) { + fname, ok := helpMessages[category] + if !ok { + fname = helpMessages[""] + } + + p := fmt.Sprintf("bot/commands/helpmsg/%s", fname) + buf, err := os.ReadFile(p) + if err != nil { + return "", errors.Wrap(err, "Error reading help message") + } + + return string(buf), nil +} diff --git a/EsefexApi/bot/commands/link.go b/EsefexApi/bot/commands/link.go deleted file mode 100644 index d62b448..0000000 --- a/EsefexApi/bot/commands/link.go +++ /dev/null @@ -1,43 +0,0 @@ -package commands - -import ( - "esefexapi/types" - "fmt" - "time" - - "github.com/bwmarrin/discordgo" - "github.com/pkg/errors" -) - -var LinkCommand = &discordgo.ApplicationCommand{ - Name: "link", - Description: "Link your Discord account to Esefex", -} - -func (c *CommandHandlers) Link(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - linkToken, err := c.dbs.LinkTokenStore.CreateToken(types.UserID(i.Member.User.ID)) - if err != nil { - return nil, errors.Wrap(err, "Error creating link token") - } - - channel, err := s.UserChannelCreate(i.Member.User.ID) - if err != nil { - return nil, errors.Wrap(err, "Error creating DM channel") - } - - // https://esefex.com/link? - linkUrl := fmt.Sprintf("%s/link?t=%s", c.domain, linkToken.Token) - expiresIn := time.Until(linkToken.Expiry) - _, err = s.ChannelMessageSend(channel.ID, fmt.Sprintf("Click this link to link your Discord account to Esefex (expires in %d Minutes): \n%s", int(expiresIn.Minutes()), linkUrl)) - if err != nil { - return nil, errors.Wrap(err, "Error sending DM message") - } - - return &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Please check your DMs for a link to link your Discord account to Esefex", - }, - }, nil - -} diff --git a/EsefexApi/bot/commands/list.go b/EsefexApi/bot/commands/list.go deleted file mode 100644 index e0cccbf..0000000 --- a/EsefexApi/bot/commands/list.go +++ /dev/null @@ -1,68 +0,0 @@ -package commands - -import ( - "esefexapi/sounddb" - "esefexapi/types" - "fmt" - - "github.com/bwmarrin/discordgo" - "github.com/pkg/errors" -) - -var ListCommand = &discordgo.ApplicationCommand{ - Name: "list", - Description: "List all sound effects in the guild", -} - -func (c *CommandHandlers) List(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - uids, err := c.dbs.SoundDB.GetSoundUIDs(types.GuildID(i.GuildID)) - if err != nil { - return nil, errors.Wrap(err, "Error getting sound UIDs") - } - // log.Printf("List: %v", uids) - - var metas = make([]sounddb.SoundMeta, 0, len(uids)) - for _, uid := range uids { - meta, err := c.dbs.SoundDB.GetSoundMeta(uid) - if err != nil { - return nil, errors.Wrap(err, "Error getting sound meta") - } - metas = append(metas, meta) - } - - if len(metas) == 0 { - return &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "There are no sounds in this guild", - }, - }, nil - } - - if len(metas) == 1 { - return &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("There is 1 sound in this guild: \n%s", fmtMetaList(metas)), - }, - }, nil - } - - return &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("There are %d sounds in this guild: \n%s", len(metas), fmtMetaList(metas)), - }, - }, nil -} - -func fmtMetaList(metas []sounddb.SoundMeta) string { - // log.Printf("fmtMetaList: %v", metas) - var str string - for _, meta := range metas { - str += fmt.Sprintf("- %s %s `%s`\n", meta.Icon.String(), meta.Name, meta.SoundID) - } - - // log.Println(str) - return str -} diff --git a/EsefexApi/bot/commands/permission.go b/EsefexApi/bot/commands/permission.go index d3198d6..6c548b2 100644 --- a/EsefexApi/bot/commands/permission.go +++ b/EsefexApi/bot/commands/permission.go @@ -2,6 +2,7 @@ package commands import ( "fmt" + "log" "github.com/bwmarrin/discordgo" "github.com/davecgh/go-spew/spew" @@ -104,6 +105,7 @@ var PermissionCommand = &discordgo.ApplicationCommand{ } func (c *CommandHandlers) Permission(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + log.Println("Permission command called with options:") spew.Dump(i.ApplicationCommandData().Options) return nil, errors.Wrap(fmt.Errorf("not implemented"), "permission") diff --git a/EsefexApi/bot/commands/sound.go b/EsefexApi/bot/commands/sound.go new file mode 100644 index 0000000..5daccb8 --- /dev/null +++ b/EsefexApi/bot/commands/sound.go @@ -0,0 +1,200 @@ +package commands + +import ( + "esefexapi/sounddb" + "esefexapi/types" + "esefexapi/util" + "fmt" + "log" + + "github.com/bwmarrin/discordgo" + "github.com/davecgh/go-spew/spew" + "github.com/pkg/errors" +) + +var SoundCommand = &discordgo.ApplicationCommand{ + Name: "sound", + Description: "All commands related to sounds.", + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "upload", + Description: "Upload a sound effect to the bot", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionAttachment, + Name: "sound-file", + Description: "The sound file to upload", + Required: true, + }, + { + Type: discordgo.ApplicationCommandOptionString, + Name: "name", + Description: "The name of the sound effect", + Required: true, + }, + { + Type: discordgo.ApplicationCommandOptionString, + Name: "icon", + Description: "The icon to use for the sound effect", + Required: true, + }, + }, + }, + { + Name: "delete", + Description: "Delete a sound effect", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Type: discordgo.ApplicationCommandOptionString, + Name: "sound-id", + Description: "The sound effect to delete", + Required: true, + }, + }, + }, + { + Name: "list", + Description: "List all sound effects in the guild", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + { + Name: "play", + Description: "Play a sound effect", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + }, +} + +func (c *CommandHandlers) Sound(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + switch i.ApplicationCommandData().Options[0].Name { + case "upload": + return c.SoundUpload(s, i) + case "delete": + return c.SoundDelete(s, i) + case "list": + return c.SoundList(s, i) + case "play": + return c.SoundPlay(s, i) + } + + log.Println("User command called with options:") + spew.Dump(i.ApplicationCommandData().Options) + + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Sound") +} + +func (c *CommandHandlers) SoundUpload(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + options := OptionsMap(i.ApplicationCommandData().Options[0].Options) + + iconOption := options["icon"] + icon, err := sounddb.ExtractIcon(fmt.Sprint(iconOption.Value)) + if err != nil { + return nil, errors.Wrap(err, "Error extracting icon") + } + + soundFile := options["sound-file"] + soundFileUrl := i.ApplicationCommandData().Resolved.Attachments[fmt.Sprint(soundFile.Value)].URL + + pcm, err := util.Download2PCM(soundFileUrl) + if err != nil { + return nil, errors.Wrap(err, "Error downloading sound file") + } + + uid, err := c.dbs.SoundDB.AddSound(types.GuildID(i.GuildID), fmt.Sprint(options["name"].Value), icon, pcm) + if err != nil { + return nil, errors.Wrap(err, "Error adding sound") + } + + log.Printf("Uploaded sound effect %v to guild %v", uid.SoundID, i.GuildID) + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Uploaded sound effect %s %s", uid.SoundID, icon.Name), + }, + }, nil +} + +func (c *CommandHandlers) SoundDelete(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + options := OptionsMap(i.ApplicationCommandData().Options[0].Options) + soundID := options["sound-id"] + + uid := sounddb.SuidFromStrings(i.GuildID, fmt.Sprint(soundID.Value)) + + exists, err := c.dbs.SoundDB.SoundExists(uid) + if err != nil { + return nil, errors.Wrap(err, "Error checking if sound exists") + } + if !exists { + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Sound effect `%s` does not exist", soundID.Value), + }, + }, nil + } + + log.Print("a") + + err = c.dbs.SoundDB.DeleteSound(uid) + if err != nil { + return nil, errors.Wrap(err, "Error deleting sound") + } + + log.Printf("Deleted sound effect %v from guild %v", soundID.Value, i.GuildID) + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Deleted sound effect `%s`", soundID.Value), + }, + }, nil +} + +func (c *CommandHandlers) SoundList(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + uids, err := c.dbs.SoundDB.GetSoundUIDs(types.GuildID(i.GuildID)) + if err != nil { + return nil, errors.Wrap(err, "Error getting sound UIDs") + } + // log.Printf("List: %v", uids) + + var metas = make([]sounddb.SoundMeta, 0, len(uids)) + for _, uid := range uids { + meta, err := c.dbs.SoundDB.GetSoundMeta(uid) + if err != nil { + return nil, errors.Wrap(err, "Error getting sound meta") + } + metas = append(metas, meta) + } + + if len(metas) == 0 { + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "There are no sounds in this guild", + }, + }, nil + } + + if len(metas) == 1 { + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("There is 1 sound in this guild: \n%s", fmtMetaList(metas)), + }, + }, nil + } + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("There are %d sounds in this guild: \n%s", len(metas), fmtMetaList(metas)), + }, + }, nil +} + +// TODO: Implement SoundPlay +func (c *CommandHandlers) SoundPlay(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Sound") +} diff --git a/EsefexApi/bot/commands/unlink.go b/EsefexApi/bot/commands/unlink.go deleted file mode 100644 index 009fd1b..0000000 --- a/EsefexApi/bot/commands/unlink.go +++ /dev/null @@ -1,35 +0,0 @@ -package commands - -import ( - "esefexapi/types" - "esefexapi/userdb" - - "github.com/bwmarrin/discordgo" -) - -var UnlinkCommand = &discordgo.ApplicationCommand{ - Name: "unlink", - Description: "Unlink your Discord account from Esefex. Useful if you think your account has been compromised.", -} - -func (c *CommandHandlers) Unlink(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - err := c.dbs.UserDB.DeleteUser(types.UserID(i.Member.User.ID)) - if err != nil { - return nil, err - } - - err = c.dbs.UserDB.SetUser(userdb.User{ - ID: types.UserID(i.Member.User.ID), - Tokens: []userdb.Token{}, - }) - if err != nil { - return nil, err - } - - return &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: "Your account has been unlinked from Esefex. You can now link your account again.", - }, - }, nil -} diff --git a/EsefexApi/bot/commands/upload.go b/EsefexApi/bot/commands/upload.go deleted file mode 100644 index 95e5ddd..0000000 --- a/EsefexApi/bot/commands/upload.go +++ /dev/null @@ -1,68 +0,0 @@ -package commands - -import ( - "esefexapi/sounddb" - "esefexapi/types" - "esefexapi/util" - "fmt" - "log" - - "github.com/bwmarrin/discordgo" - "github.com/pkg/errors" -) - -var UploadCommand = &discordgo.ApplicationCommand{ - Name: "upload", - Description: "Upload a sound effect to the bot", - Options: []*discordgo.ApplicationCommandOption{ - { - Type: discordgo.ApplicationCommandOptionAttachment, - Name: "sound-file", - Description: "The sound file to upload", - Required: true, - }, - { - Type: discordgo.ApplicationCommandOptionString, - Name: "name", - Description: "The name of the sound effect", - Required: true, - }, - { - Type: discordgo.ApplicationCommandOptionString, - Name: "icon", - Description: "The icon to use for the sound effect", - Required: true, - }, - }, -} - -func (c *CommandHandlers) Upload(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - options := OptionsMap(i) - - iconOption := options["icon"] - icon, err := sounddb.ExtractIcon(fmt.Sprint(iconOption.Value)) - if err != nil { - return nil, errors.Wrap(err, "Error extracting icon") - } - - soundFile := options["sound-file"] - soundFileUrl := i.ApplicationCommandData().Resolved.Attachments[fmt.Sprint(soundFile.Value)].URL - - pcm, err := util.Download2PCM(soundFileUrl) - if err != nil { - return nil, errors.Wrap(err, "Error downloading sound file") - } - - uid, err := c.dbs.SoundDB.AddSound(types.GuildID(i.GuildID), fmt.Sprint(options["name"].Value), icon, pcm) - if err != nil { - return nil, errors.Wrap(err, "Error adding sound") - } - - log.Printf("Uploaded sound effect %v to guild %v", uid.SoundID, i.GuildID) - return &discordgo.InteractionResponse{ - Type: discordgo.InteractionResponseChannelMessageWithSource, - Data: &discordgo.InteractionResponseData{ - Content: fmt.Sprintf("Uploaded sound effect %s %s", uid.SoundID, icon.Name), - }, - }, nil -} diff --git a/EsefexApi/bot/commands/user.go b/EsefexApi/bot/commands/user.go new file mode 100644 index 0000000..f3c0f74 --- /dev/null +++ b/EsefexApi/bot/commands/user.go @@ -0,0 +1,130 @@ +package commands + +import ( + "esefexapi/types" + "esefexapi/userdb" + "fmt" + "log" + "time" + + "github.com/bwmarrin/discordgo" + "github.com/davecgh/go-spew/spew" + "github.com/pkg/errors" +) + +var UserCommand = &discordgo.ApplicationCommand{ + Name: "user", + Description: "All commands related to users.", + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "stats", + Description: "Get the stats of a user.", + Type: discordgo.ApplicationCommandOptionSubCommand, + Options: []*discordgo.ApplicationCommandOption{ + { + Name: "user", + Description: "The user to get the stats of. (Leave empty to get your own stats)", + Type: discordgo.ApplicationCommandOptionUser, + Required: false, + }, + }, + }, + { + Name: "link", + Description: "Link your Discord account to Esefex", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + { + Name: "unlink", + Description: "Unlink your Discord account from Esefex. Useful if you think your account has been compromised.", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, + }, +} + +func (c *CommandHandlers) User(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + switch i.ApplicationCommandData().Options[0].Name { + case "stats": + return c.UserStats(s, i) + case "link": + return c.UserLink(s, i) + case "unlink": + return c.UserUnlink(s, i) + } + + log.Println("User command called with options:") + spew.Dump(i.ApplicationCommandData().Options) + + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Sound") +} + +func (c *CommandHandlers) UserStats(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + // TODO: index out of range error when no user is specified + var user *discordgo.User + + if len(i.ApplicationCommandData().Options[0].Options) == 0 { + user = i.Member.User + } else { + user = i.ApplicationCommandData().Options[0].Options[0].UserValue(s) + } + + stats := fmt.Sprintf("Stats for %s:\n", user.Username) + stats += fmt.Sprintf("ID: %s\n", user.ID) + stats += "No more stats for now... :(" + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: stats, + }, + }, nil +} + +func (c *CommandHandlers) UserLink(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + linkToken, err := c.dbs.LinkTokenStore.CreateToken(types.UserID(i.Member.User.ID)) + if err != nil { + return nil, errors.Wrap(err, "Error creating link token") + } + + channel, err := s.UserChannelCreate(i.Member.User.ID) + if err != nil { + return nil, errors.Wrap(err, "Error creating DM channel") + } + + // https://esefex.com/link? + linkUrl := fmt.Sprintf("%s/link?t=%s", c.domain, linkToken.Token) + expiresIn := time.Until(linkToken.Expiry) + _, err = s.ChannelMessageSend(channel.ID, fmt.Sprintf("Click this link to link your Discord account to Esefex (expires in %d Minutes): \n%s", int(expiresIn.Minutes()), linkUrl)) + if err != nil { + return nil, errors.Wrap(err, "Error sending DM message") + } + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Please check your DMs for a link to link your Discord account to Esefex", + }, + }, nil +} + +func (c *CommandHandlers) UserUnlink(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + err := c.dbs.UserDB.DeleteUser(types.UserID(i.Member.User.ID)) + if err != nil { + return nil, err + } + + err = c.dbs.UserDB.SetUser(userdb.User{ + ID: types.UserID(i.Member.User.ID), + Tokens: []userdb.Token{}, + }) + if err != nil { + return nil, err + } + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: "Your account has been unlinked from Esefex. You can now link your account again.", + }, + }, nil +} diff --git a/EsefexApi/bot/commands/util.go b/EsefexApi/bot/commands/util.go new file mode 100644 index 0000000..8b33898 --- /dev/null +++ b/EsefexApi/bot/commands/util.go @@ -0,0 +1,17 @@ +package commands + +import ( + "esefexapi/sounddb" + "fmt" +) + +func fmtMetaList(metas []sounddb.SoundMeta) string { + // log.Printf("fmtMetaList: %v", metas) + var str string + for _, meta := range metas { + str += fmt.Sprintf("- %s %s `%s`\n", meta.Icon.String(), meta.Name, meta.SoundID) + } + + // log.Println(str) + return str +} From 214ee1a6d676aefab79e426f070604768bf3a980 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Sat, 13 Jan 2024 16:23:09 +0100 Subject: [PATCH 12/20] permissionstore, permission list cmd, --- EsefexApi/bot/commands/bot.go | 2 +- EsefexApi/bot/commands/permission.go | 68 +++++++++- EsefexApi/bot/commands/sound.go | 2 + EsefexApi/bot/commands/user.go | 9 +- EsefexApi/bot/commands/util.go | 126 ++++++++++++++++++ .../testing/config_testing/config_testing.go | 4 +- EsefexApi/config.toml | 9 +- EsefexApi/config/config.go | 19 ++- EsefexApi/db/db.go | 2 + EsefexApi/main.go | 8 +- .../filepermisssiondb/filepermisssiondb.go | 116 ++++++++++++++++ EsefexApi/permissiondb/permissiondb.go | 15 +++ EsefexApi/permissions/permissions.go | 21 +++ EsefexApi/permissions/stack.go | 54 ++++---- 14 files changed, 395 insertions(+), 60 deletions(-) create mode 100644 EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go create mode 100644 EsefexApi/permissiondb/permissiondb.go diff --git a/EsefexApi/bot/commands/bot.go b/EsefexApi/bot/commands/bot.go index 7b03820..6caf383 100644 --- a/EsefexApi/bot/commands/bot.go +++ b/EsefexApi/bot/commands/bot.go @@ -59,7 +59,7 @@ func (c *CommandHandlers) Bot(s *discordgo.Session, i *discordgo.InteractionCrea case "config": return c.BotConfig(s, i) default: - return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Bot") + return nil, errors.Wrap(fmt.Errorf("Unknown subcommand %s", i.ApplicationCommandData().Options[0].Name), "Error handling user command") } } diff --git a/EsefexApi/bot/commands/permission.go b/EsefexApi/bot/commands/permission.go index 6c548b2..361fa65 100644 --- a/EsefexApi/bot/commands/permission.go +++ b/EsefexApi/bot/commands/permission.go @@ -1,11 +1,11 @@ package commands import ( + "esefexapi/permissions" + "esefexapi/types" "fmt" - "log" "github.com/bwmarrin/discordgo" - "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" ) @@ -105,8 +105,66 @@ var PermissionCommand = &discordgo.ApplicationCommand{ } func (c *CommandHandlers) Permission(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - log.Println("Permission command called with options:") - spew.Dump(i.ApplicationCommandData().Options) + switch i.ApplicationCommandData().Options[0].Name { + case "set": + return c.PermissionSet(s, i) + case "get": + return c.PermissionGet(s, i) + case "clear": + return c.PermissionClear(s, i) + case "list": + return c.PermissionList(s, i) + default: + return nil, errors.Wrap(fmt.Errorf("Unknown subcommand %s", i.ApplicationCommandData().Options[0].Name), "Error handling user command") + } +} + +func (c *CommandHandlers) PermissionSet(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Error handling user command PermissionSet") +} + +func (c *CommandHandlers) PermissionGet(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Error handling user command PermissionGet") +} + +func (c *CommandHandlers) PermissionClear(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Error handling user command PermissionClear") +} - return nil, errors.Wrap(fmt.Errorf("not implemented"), "permission") +func (c *CommandHandlers) PermissionList(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + id := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Options[0].Value) + ty, err := extractTypeFromString(s, types.GuildID(i.GuildID), id) + if err != nil { + return nil, errors.Wrap(err, "Error extracting type from string") + } + + var p permissions.Permissions + + switch ty.PermissionType { + case permissions.User: + p, err = c.dbs.PremissionDB.GetUser(types.UserID(ty.ID)) + case permissions.Role: + p, err = c.dbs.PremissionDB.GetRole(types.RoleID(ty.ID)) + case permissions.Channel: + p, err = c.dbs.PremissionDB.GetChannel(types.ChannelID(ty.ID)) + } + + if err != nil { + return nil, errors.Wrap(err, "Error getting permissions") + } + + ps, err := formatPermissions(p) + if err != nil { + return nil, errors.Wrap(err, "Error formatting permissions") + } + + resp := fmt.Sprintf("Permissions for %s %s:\n", ty.PermissionType, ty.ID) + resp += ps + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: resp, + }, + }, nil } diff --git a/EsefexApi/bot/commands/sound.go b/EsefexApi/bot/commands/sound.go index 5daccb8..b43d551 100644 --- a/EsefexApi/bot/commands/sound.go +++ b/EsefexApi/bot/commands/sound.go @@ -77,6 +77,8 @@ func (c *CommandHandlers) Sound(s *discordgo.Session, i *discordgo.InteractionCr return c.SoundList(s, i) case "play": return c.SoundPlay(s, i) + default: + return nil, errors.Wrap(fmt.Errorf("Unknown subcommand %s", i.ApplicationCommandData().Options[0].Name), "Error handling user command") } log.Println("User command called with options:") diff --git a/EsefexApi/bot/commands/user.go b/EsefexApi/bot/commands/user.go index f3c0f74..a9d223c 100644 --- a/EsefexApi/bot/commands/user.go +++ b/EsefexApi/bot/commands/user.go @@ -4,11 +4,9 @@ import ( "esefexapi/types" "esefexapi/userdb" "fmt" - "log" "time" "github.com/bwmarrin/discordgo" - "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" ) @@ -50,12 +48,9 @@ func (c *CommandHandlers) User(s *discordgo.Session, i *discordgo.InteractionCre return c.UserLink(s, i) case "unlink": return c.UserUnlink(s, i) + default: + return nil, errors.Wrap(fmt.Errorf("Unknown subcommand %s", i.ApplicationCommandData().Options[0].Name), "Error handling user command") } - - log.Println("User command called with options:") - spew.Dump(i.ApplicationCommandData().Options) - - return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Sound") } func (c *CommandHandlers) UserStats(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { diff --git a/EsefexApi/bot/commands/util.go b/EsefexApi/bot/commands/util.go index 8b33898..6bf7ed4 100644 --- a/EsefexApi/bot/commands/util.go +++ b/EsefexApi/bot/commands/util.go @@ -1,8 +1,15 @@ package commands import ( + "esefexapi/permissions" "esefexapi/sounddb" + "esefexapi/types" "fmt" + "log" + "regexp" + + "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" ) func fmtMetaList(metas []sounddb.SoundMeta) string { @@ -15,3 +22,122 @@ func fmtMetaList(metas []sounddb.SoundMeta) string { // log.Println(str) return str } + +// checks if its a user, role or channel +func extractTypeFromString(s *discordgo.Session, g types.GuildID, str string) (PermissionSet, error) { + regex, err := regexp.Compile(`^<(@|#|@&)(\d+)>$|^(@everyone)$|^(\d+)$`) + if err != nil { + return PermissionSet{}, errors.Wrap(err, "Error compiling regex") + } + + matches := regex.FindStringSubmatch(str) + log.Printf("%d matches: %#v", len(matches), matches) + + if len(matches) != 5 { + return PermissionSet{}, errors.Wrap(fmt.Errorf("Invalid id %s", str), "Error extracting type from string") + } + + switch matches[1] { + case "@": + return PermissionSet{ + PermissionType: permissions.User, + ID: matches[2], + }, nil + case "#": + return PermissionSet{ + PermissionType: permissions.Channel, + ID: matches[2], + }, nil + case "@&": + return PermissionSet{ + PermissionType: permissions.Role, + ID: matches[2], + }, nil + } + + if matches[3] == "@everyone" { + return PermissionSet{ + PermissionType: permissions.Role, + ID: "everyone", + }, nil + } + + id := matches[4] + + _, err = s.User(str) + if err == nil { + return PermissionSet{ + PermissionType: permissions.User, + ID: str, + }, nil + } + + roles, err := s.GuildRoles(g.String()) + if err != nil { + return PermissionSet{}, errors.Wrap(err, "Error getting guild roles") + } + + for _, r := range roles { + if r.ID == id { + return PermissionSet{ + PermissionType: permissions.Role, + ID: id, + }, nil + } + } + + channels, err := s.GuildChannels(g.String()) + if err != nil { + return PermissionSet{}, errors.Wrap(err, "Error getting guild channels") + } + + for _, c := range channels { + if c.ID == id { + return PermissionSet{ + PermissionType: permissions.Channel, + ID: id, + }, nil + } + } + + return PermissionSet{}, errors.Wrap(fmt.Errorf("Invalid id %s", str), "Error extracting type from string") +} + +func formatPermissions(p permissions.Permissions) (string, error) { + // b, err := toml.Marshal(p) + // if err != nil { + // return "", errors.Wrap(err, "Error marshalling permissions") + // } + + // resp := string(b) + // log.Println(resp) + + mdlang := "yaml" + + resp := "**Sound**\n" + resp += fmt.Sprintf("```%s\n", mdlang) + resp += fmt.Sprintf("Play: %s\n", p.Sound.Play.String()) + resp += fmt.Sprintf("Upload: %s\n", p.Sound.Upload.String()) + resp += fmt.Sprintf("Modify: %s\n", p.Sound.Modify.String()) + resp += fmt.Sprintf("Delete: %s\n", p.Sound.Delete.String()) + resp += "```" + + resp += "\n**Bot**\n" + resp += fmt.Sprintf("```%s\n", mdlang) + resp += fmt.Sprintf("Join: %s\n", p.Bot.Join.String()) + resp += fmt.Sprintf("Leave: %s\n", p.Bot.Leave.String()) + resp += "```" + + resp += "\n**Guild**\n" + resp += fmt.Sprintf("```%s\n", mdlang) + resp += fmt.Sprintf("ManageBot: %s\n", p.Guild.ManageBot.String()) + resp += fmt.Sprintf("ManageUser: %s\n", p.Guild.ManageUser.String()) + resp += "```" + + return resp, nil +} + +type PermissionSet struct { + PermissionType permissions.PermissionType + ID string +} diff --git a/EsefexApi/cmd/testing/config_testing/config_testing.go b/EsefexApi/cmd/testing/config_testing/config_testing.go index 42c664a..acb5490 100644 --- a/EsefexApi/cmd/testing/config_testing/config_testing.go +++ b/EsefexApi/cmd/testing/config_testing/config_testing.go @@ -20,8 +20,8 @@ func main() { Port: 8080, CustomProtocol: "esefex", }, - FileSoundDB: config.FileSoundDatabase{ - Location: "/tmp/esefexapi", + Database: config.Database{ + SounddbLocation: "/tmp/esefexapi/sounddb", }, Bot: config.Bot{}, } diff --git a/EsefexApi/config.toml b/EsefexApi/config.toml index 08998ce..36e1b5c 100644 --- a/EsefexApi/config.toml +++ b/EsefexApi/config.toml @@ -4,11 +4,10 @@ verification_expiry = 5.0 # minutes port = 8080 custom_protocol = "esefex" -[file_sound_database] -location = "./data/sounds" - -[file_user_database] -location = "./data/users/users.json" +[database] +sounddb_location = "./data/sounds" +userdb_location = "./data/users/users.json" +permissiondb_location = "./data/permissions/permissions.json" [bot] use_timeouts = false diff --git a/EsefexApi/config/config.go b/EsefexApi/config/config.go index eced46e..235e035 100644 --- a/EsefexApi/config/config.go +++ b/EsefexApi/config/config.go @@ -9,11 +9,10 @@ import ( ) type Config struct { - VerificationExpiry float32 `toml:"verification_expiry"` - HttpApi HttpApi `toml:"http_api"` - FileSoundDB FileSoundDatabase `toml:"file_sound_database"` - FileUserDB FileUserDatabase `toml:"file_user_database"` - Bot Bot `toml:"bot"` + VerificationExpiry float32 `toml:"verification_expiry"` + HttpApi HttpApi `toml:"http_api"` + Database Database `toml:"database"` + Bot Bot `toml:"bot"` } type HttpApi struct { @@ -21,12 +20,10 @@ type HttpApi struct { CustomProtocol string `toml:"custom_protocol"` } -type FileSoundDatabase struct { - Location string `toml:"location"` -} - -type FileUserDatabase struct { - Location string `toml:"location"` +type Database struct { + SounddbLocation string `toml:"sounddb_location"` + UserdbLocation string `toml:"userdb_location"` + Permissiondblocation string `toml:"permissiondb_location"` } type Bot struct { diff --git a/EsefexApi/db/db.go b/EsefexApi/db/db.go index f89fdb1..394c202 100644 --- a/EsefexApi/db/db.go +++ b/EsefexApi/db/db.go @@ -2,6 +2,7 @@ package db import ( "esefexapi/linktokenstore" + "esefexapi/permissiondb" "esefexapi/sounddb" "esefexapi/userdb" ) @@ -10,4 +11,5 @@ type Databases struct { SoundDB sounddb.ISoundDB UserDB userdb.IUserDB LinkTokenStore linktokenstore.ILinkTokenStore + PremissionDB permissiondb.PermissionDB } diff --git a/EsefexApi/main.go b/EsefexApi/main.go index e71ad12..e12ad6c 100644 --- a/EsefexApi/main.go +++ b/EsefexApi/main.go @@ -7,6 +7,7 @@ import ( "esefexapi/config" "esefexapi/db" "esefexapi/linktokenstore/memorylinktokenstore" + "esefexapi/permissiondb/filepermisssiondb" "esefexapi/sounddb/dbcache" "esefexapi/sounddb/filesounddb" "esefexapi/userdb/fileuserdb" @@ -36,15 +37,17 @@ func main() { ds, err := bot.CreateSession() Must(err) - sdb, err := filesounddb.NewFileDB(cfg.FileSoundDB.Location) + sdb, err := filesounddb.NewFileDB(cfg.Database.SounddbLocation) Must(err) sdbc, err := dbcache.NewSoundDBCache(sdb) Must(err) - udb, err := fileuserdb.NewFileUserDB(cfg.FileUserDB.Location) + udb, err := fileuserdb.NewFileUserDB(cfg.Database.UserdbLocation) Must(err) + fpdb, err := filepermisssiondb.NewFilePermissionDB(cfg.Database.Permissiondblocation) + verT := time.Duration(cfg.VerificationExpiry * float32(time.Minute)) ldb := memorylinktokenstore.NewMemoryLinkTokenStore(verT) @@ -52,6 +55,7 @@ func main() { SoundDB: sdbc, UserDB: udb, LinkTokenStore: ldb, + PremissionDB: fpdb, } botT := time.Duration(cfg.Bot.Timeout * float32(time.Minute)) diff --git a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go new file mode 100644 index 0000000..acee79c --- /dev/null +++ b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go @@ -0,0 +1,116 @@ +package filepermisssiondb + +import ( + "encoding/json" + "esefexapi/permissiondb" + "esefexapi/permissions" + "esefexapi/types" + "os" + "sync" + + "github.com/pkg/errors" +) + +var _ permissiondb.PermissionDB = &FilePermissionDB{} + +type FilePermissionDB struct { + filePath string + rw *sync.RWMutex + stack *permissions.PermissionStack +} + +func NewFilePermissionDB(path string) (*FilePermissionDB, error) { + fpdb := &FilePermissionDB{ + filePath: path, + rw: &sync.RWMutex{}, + stack: permissions.NewPermissionStack(), + } + err := fpdb.load() + return fpdb, err +} + +// GetChannel implements permissiondb.PermissionDB. +func (f *FilePermissionDB) GetChannel(channelID types.ChannelID) (permissions.Permissions, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + return f.stack.GetChannel(channelID), nil +} + +// GetRole implements permissiondb.PermissionDB. +func (f *FilePermissionDB) GetRole(roleID types.RoleID) (permissions.Permissions, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + return f.stack.GetRole(roleID), nil +} + +// GetUser implements permissiondb.PermissionDB. +func (f *FilePermissionDB) GetUser(userID types.UserID) (permissions.Permissions, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + return f.stack.GetUser(userID), nil +} + +// UpdateChannel implements permissiondb.PermissionDB. +func (f *FilePermissionDB) UpdateChannel(channelID types.ChannelID, p permissions.Permissions) error { + f.rw.Lock() + defer f.rw.Unlock() + + f.stack.UpdateChannel(channelID, p) + go f.save() + return nil +} + +// UpdateRole implements permissiondb.PermissionDB. +func (f *FilePermissionDB) UpdateRole(roleID types.RoleID, p permissions.Permissions) error { + f.rw.Lock() + defer f.rw.Unlock() + + f.stack.UpdateRole(roleID, p) + go f.save() + return nil +} + +// UpdateUser implements permissiondb.PermissionDB. +func (f *FilePermissionDB) UpdateUser(userID types.UserID, p permissions.Permissions) error { + f.rw.Lock() + defer f.rw.Unlock() + + f.stack.UpdateUser(userID, p) + go f.save() + return nil +} + +// load loads the permission stack from the file. +func (f *FilePermissionDB) load() error { + file, err := os.Open(f.filePath) + if err != nil { + return errors.Wrap(err, "Error opening file") + } + defer file.Close() + + err = json.NewDecoder(file).Decode(f.stack) + if err != nil { + return errors.Wrap(err, "Error decoding permission stack") + } + + return nil +} + +// save saves the permission stack to the file. +func (f *FilePermissionDB) save() error { + file, err := os.OpenFile(f.filePath, os.O_RDWR|os.O_CREATE, os.ModePerm) + if err != nil { + return errors.Wrap(err, "Error opening file") + } + defer file.Close() + + err = json.NewEncoder(file).Encode(f.stack) + if err != nil { + return errors.Wrap(err, "Error encoding permission stack") + } + + return nil +} diff --git a/EsefexApi/permissiondb/permissiondb.go b/EsefexApi/permissiondb/permissiondb.go new file mode 100644 index 0000000..44edc17 --- /dev/null +++ b/EsefexApi/permissiondb/permissiondb.go @@ -0,0 +1,15 @@ +package permissiondb + +import ( + "esefexapi/permissions" + "esefexapi/types" +) + +type PermissionDB interface { + GetUser(userID types.UserID) (permissions.Permissions, error) + GetRole(roleID types.RoleID) (permissions.Permissions, error) + GetChannel(channelID types.ChannelID) (permissions.Permissions, error) + UpdateUser(userID types.UserID, p permissions.Permissions) error + UpdateRole(roleID types.RoleID, p permissions.Permissions) error + UpdateChannel(channelID types.ChannelID, p permissions.Permissions) error +} diff --git a/EsefexApi/permissions/permissions.go b/EsefexApi/permissions/permissions.go index a3d6a7a..3e93026 100644 --- a/EsefexApi/permissions/permissions.go +++ b/EsefexApi/permissions/permissions.go @@ -1,5 +1,26 @@ package permissions +type PermissionType int + +const ( + User PermissionType = iota + Role + Channel +) + +func (pt PermissionType) String() string { + switch pt { + case User: + return "user" + case Role: + return "role" + case Channel: + return "channel" + default: + return "unknown" + } +} + type PermissionState int const ( diff --git a/EsefexApi/permissions/stack.go b/EsefexApi/permissions/stack.go index 58a5622..d83ca1e 100644 --- a/EsefexApi/permissions/stack.go +++ b/EsefexApi/permissions/stack.go @@ -7,86 +7,86 @@ import ( ) type PermissionStack struct { - user map[types.UserID]Permissions - role map[types.RoleID]Permissions - channel map[types.ChannelID]Permissions + User map[types.UserID]Permissions + Role map[types.RoleID]Permissions + Channel map[types.ChannelID]Permissions } func NewPermissionStack() *PermissionStack { return &PermissionStack{ - user: make(map[types.UserID]Permissions), - role: make(map[types.RoleID]Permissions), - channel: make(map[types.ChannelID]Permissions), + User: make(map[types.UserID]Permissions), + Role: make(map[types.RoleID]Permissions), + Channel: make(map[types.ChannelID]Permissions), } } func (ps *PermissionStack) GetUser(userID types.UserID) Permissions { - if u, ok := ps.user[userID]; ok { + if u, ok := ps.User[userID]; ok { return u } return NewUnset() } func (ps *PermissionStack) GetRole(roleID types.RoleID) Permissions { - if r, ok := ps.role[roleID]; ok { + if r, ok := ps.Role[roleID]; ok { return r } return NewUnset() } func (ps *PermissionStack) GetChannel(channelID types.ChannelID) Permissions { - if c, ok := ps.channel[channelID]; ok { + if c, ok := ps.Channel[channelID]; ok { return c } return NewUnset() } func (ps *PermissionStack) SetUser(user types.UserID, p Permissions) { - ps.user[user] = p + ps.User[user] = p } func (ps *PermissionStack) SetRole(role types.RoleID, p Permissions) { - ps.role[role] = p + ps.Role[role] = p } func (ps *PermissionStack) SetChannel(channel types.ChannelID, p Permissions) { - ps.channel[channel] = p + ps.Channel[channel] = p } func (ps *PermissionStack) UnsetUser(user types.UserID) { - delete(ps.user, user) + delete(ps.User, user) } func (ps *PermissionStack) UnsetRole(role types.RoleID) { - delete(ps.role, role) + delete(ps.Role, role) } func (ps *PermissionStack) UnsetChannel(channel types.ChannelID) { - delete(ps.channel, channel) + delete(ps.Channel, channel) } func (ps *PermissionStack) UpdateUser(user types.UserID, p Permissions) { - if _, ok := ps.user[user]; !ok { - ps.user[user] = NewUnset() + if _, ok := ps.User[user]; !ok { + ps.User[user] = NewUnset() } - ps.user[user] = ps.user[user].MergeParent(p) + ps.User[user] = ps.User[user].MergeParent(p) } func (ps *PermissionStack) UpdateRole(role types.RoleID, p Permissions) { - if _, ok := ps.role[role]; !ok { - ps.role[role] = NewUnset() + if _, ok := ps.Role[role]; !ok { + ps.Role[role] = NewUnset() } - ps.role[role] = ps.role[role].MergeParent(p) + ps.Role[role] = ps.Role[role].MergeParent(p) } func (ps *PermissionStack) UpdateChannel(channel types.ChannelID, p Permissions) { - if _, ok := ps.channel[channel]; !ok { - ps.channel[channel] = NewUnset() + if _, ok := ps.Channel[channel]; !ok { + ps.Channel[channel] = NewUnset() } - ps.channel[channel] = ps.channel[channel].MergeParent(p) + ps.Channel[channel] = ps.Channel[channel].MergeParent(p) } // Query returns the permission state for a given user, role, and channel by merging them together. @@ -95,18 +95,18 @@ func (ps *PermissionStack) UpdateChannel(channel types.ChannelID, p Permissions) // If a channel has a permission set, it will override the role permissions. // roles is a list of roles that the user has in order of precedence. func (ps *PermissionStack) Query(user types.UserID, roles []types.RoleID, channel opt.Option[types.ChannelID]) Permissions { - userPS := ps.user[user] + userPS := ps.User[user] slices.Reverse(roles) rolesPS := NewUnset() for _, role := range roles { - r := ps.role[role] + r := ps.Role[role] rolesPS = rolesPS.MergeParent(r) } var channelPS Permissions = NewUnset() if channel.IsSome() { - channelPS = ps.channel[channel.Unwrap()] + channelPS = ps.Channel[channel.Unwrap()] } return NewUnset().MergeParent(rolesPS).MergeParent(channelPS).MergeParent(userPS) From 7d86ff69c066c440eaefc80fbd001a05b18d03c6 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Sun, 14 Jan 2024 03:08:14 +0100 Subject: [PATCH 13/20] Permission List-All, Set, Get & Clear implementations, some goofy reflection stuff --- EsefexApi/bot/commands/permission.go | 259 ++++++++++++++++-- EsefexApi/bot/commands/sound.go | 6 - EsefexApi/bot/commands/util.go | 87 +++++- EsefexApi/db/db.go | 2 +- EsefexApi/main.go | 2 +- .../filepermisssiondb/filepermisssiondb.go | 56 +--- .../permissiondb/filepermisssiondb/impl.go | 96 +++++++ EsefexApi/permissiondb/permissiondb.go | 3 + EsefexApi/permissions/permissions.go | 46 +++- EsefexApi/permissions/stack.go | 27 ++ EsefexApi/util/iddisplay.go | 92 +++++++ EsefexApi/util/refl/err.go | 5 + EsefexApi/util/refl/get.go | 49 ++++ EsefexApi/util/refl/paths.go | 41 +++ EsefexApi/util/refl/set.go | 55 ++++ 15 files changed, 724 insertions(+), 102 deletions(-) create mode 100644 EsefexApi/permissiondb/filepermisssiondb/impl.go create mode 100644 EsefexApi/util/iddisplay.go create mode 100644 EsefexApi/util/refl/err.go create mode 100644 EsefexApi/util/refl/get.go create mode 100644 EsefexApi/util/refl/paths.go create mode 100644 EsefexApi/util/refl/set.go diff --git a/EsefexApi/bot/commands/permission.go b/EsefexApi/bot/commands/permission.go index 361fa65..19b520b 100644 --- a/EsefexApi/bot/commands/permission.go +++ b/EsefexApi/bot/commands/permission.go @@ -3,6 +3,8 @@ package commands import ( "esefexapi/permissions" "esefexapi/types" + "esefexapi/util" + "esefexapi/util/refl" "fmt" "github.com/bwmarrin/discordgo" @@ -29,8 +31,7 @@ var PermissionCommand = &discordgo.ApplicationCommand{ Description: "The permission to set.", Type: discordgo.ApplicationCommandOptionString, Required: true, - // TODO: Dynamically get the choices from the permission type on startup using reflection. - // Choices: []*discordgo.ApplicationCommandOptionChoice{} + Choices: getPathOptions(), }, { Name: "value", @@ -70,8 +71,7 @@ var PermissionCommand = &discordgo.ApplicationCommand{ Description: "The permission to get.", Type: discordgo.ApplicationCommandOptionString, Required: true, - // TODO: Dynamically get the choices from the permission type on startup using reflection. - // Choices: []*discordgo.ApplicationCommandOptionChoice{} + Choices: getPathOptions(), }, }, }, @@ -101,6 +101,11 @@ var PermissionCommand = &discordgo.ApplicationCommand{ }, }, }, + { + Name: "list-all", + Description: "List all permissions for all users, channels and roles.", + Type: discordgo.ApplicationCommandOptionSubCommand, + }, }, } @@ -114,52 +119,272 @@ func (c *CommandHandlers) Permission(s *discordgo.Session, i *discordgo.Interact return c.PermissionClear(s, i) case "list": return c.PermissionList(s, i) + case "list-all": + return c.PermissionListAll(s, i) default: return nil, errors.Wrap(fmt.Errorf("Unknown subcommand %s", i.ApplicationCommandData().Options[0].Name), "Error handling user command") } } +// TODO: Fix the race condition that might occur here func (c *CommandHandlers) PermissionSet(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Error handling user command PermissionSet") + id := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Options[0].Value) + ty, err := extractTypeFromString(s, types.GuildID(i.GuildID), id) + if err != nil { + return nil, errors.Wrap(err, "Error extracting type from string") + } + + p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), id) + if err != nil { + return nil, errors.Wrap(err, "Error getting permissions") + } + + ps := permissions.PSFromString(fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Options[2].Value)) + + ppath := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Options[1].Value) + + err = refl.SetNestedFieldValue(&p, ppath, ps) + if err != nil { + return nil, errors.Wrap(err, "Error setting nested field value") + } + + switch ty.PermissionType { + case permissions.User: + err = c.dbs.PermissionDB.UpdateUser(types.UserID(ty.ID), p) + case permissions.Role: + err = c.dbs.PermissionDB.UpdateRole(types.RoleID(ty.ID), p) + case permissions.Channel: + err = c.dbs.PermissionDB.UpdateChannel(types.ChannelID(ty.ID), p) + } + + if err != nil { + return nil, errors.Wrap(err, "Error setting permissions") + } + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Set %s for %s to %s", ppath, id, ps.String()), + }, + }, nil } -func (c *CommandHandlers) PermissionGet(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Error handling user command PermissionGet") +// TODO: Better alignment for the list all command (maybe use a table?) +func (c *CommandHandlers) PermissionListAll(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + resp := "Permissions for all users, channels and roles:\n" + + resp += "**Users**\n" + uids, err := c.dbs.PermissionDB.GetUsers() + if err != nil { + return nil, errors.Wrap(err, "Error getting users") + } + if len(uids) == 0 { + resp += "`No users found.`\n" + } + for _, uid := range uids { + p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), uid.String()) + if err != nil { + return nil, errors.Wrap(err, "Error getting permissions") + } + + pstr, err := formatPermissionsCompact(p) + if err != nil { + return nil, errors.Wrap(err, "Error formatting permissions") + } + + uname, err := util.UserIDName(s, uid) + if err != nil { + return nil, errors.Wrap(err, "Error getting user") + } + + resp += fmt.Sprintf("%s: ", uname) + resp += pstr + resp += "\n" + } + + resp += "**Roles**\n" + rids, err := c.dbs.PermissionDB.GetRoles() + if err != nil { + return nil, errors.Wrap(err, "Error getting roles") + } + if len(rids) == 0 { + resp += "`No roles found.`\n" + } + for _, rid := range rids { + p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), rid.String()) + if err != nil { + return nil, errors.Wrap(err, "Error getting permissions") + } + + pstr, err := formatPermissionsCompact(p) + if err != nil { + return nil, errors.Wrap(err, "Error formatting permissions") + } + + rmention, err := util.RoleIDName(s, types.GuildID(i.GuildID), rid) + if err != nil { + return nil, errors.Wrap(err, "Error getting role") + } + + resp += fmt.Sprintf("%s: ", rmention) + resp += pstr + resp += "\n" + } + + resp += "**Channels**\n" + cids, err := c.dbs.PermissionDB.GetChannels() + if err != nil { + return nil, errors.Wrap(err, "Error getting channels") + } + if len(cids) == 0 { + resp += "`No channels found.`\n" + } + for _, cid := range cids { + p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), cid.String()) + if err != nil { + return nil, errors.Wrap(err, "Error getting permissions") + } + + pstr, err := formatPermissionsCompact(p) + if err != nil { + return nil, errors.Wrap(err, "Error formatting permissions") + } + + cmention, err := util.ChannelIDMention(s, types.GuildID(i.GuildID), cid) + if err != nil { + return nil, errors.Wrap(err, "Error getting channel") + } + + resp += fmt.Sprintf("%s: ", cmention) + resp += pstr + resp += "\n" + } + + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: resp, + }, + }, nil } -func (c *CommandHandlers) PermissionClear(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Error handling user command PermissionClear") +func (c *CommandHandlers) PermissionGet(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + id := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Options[0].Value) + + p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), id) + if err != nil { + return nil, errors.Wrap(err, "Error getting permissions") + } + + ppath := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Options[1].Value) + + ps, err := getPermission(p, ppath) + if err != nil { + return nil, errors.Wrap(err, "Error getting permission") + } + + ty, err := extractTypeFromString(s, types.GuildID(i.GuildID), id) + if err != nil { + return nil, errors.Wrap(err, "Error extracting type from string") + } + + var name string + switch ty.PermissionType { + case permissions.User: + name, err = util.UserIDName(s, types.UserID(ty.ID)) + case permissions.Role: + name, err = util.RoleIDName(s, types.GuildID(i.GuildID), types.RoleID(ty.ID)) + case permissions.Channel: + name, err = util.ChannelIDMention(s, types.GuildID(i.GuildID), types.ChannelID(ty.ID)) + } + if err != nil { + return nil, errors.Wrap(err, "Error getting name") + } + + // TODO: add name display here + resp := fmt.Sprintf("%s for %s: %s", ppath, name, ps.String()) + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: resp, + }, + }, nil } -func (c *CommandHandlers) PermissionList(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { +// TODO: Fix this command (it is not clearing permissions) +func (c *CommandHandlers) PermissionClear(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { id := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Options[0].Value) ty, err := extractTypeFromString(s, types.GuildID(i.GuildID), id) if err != nil { return nil, errors.Wrap(err, "Error extracting type from string") } - var p permissions.Permissions + var name string + switch ty.PermissionType { + case permissions.User: + name, err = util.UserIDName(s, types.UserID(ty.ID)) + case permissions.Role: + name, err = util.RoleIDName(s, types.GuildID(i.GuildID), types.RoleID(ty.ID)) + case permissions.Channel: + name, err = util.ChannelIDMention(s, types.GuildID(i.GuildID), types.ChannelID(ty.ID)) + } + if err != nil { + return nil, errors.Wrap(err, "Error getting name") + } switch ty.PermissionType { case permissions.User: - p, err = c.dbs.PremissionDB.GetUser(types.UserID(ty.ID)) + err = c.dbs.PermissionDB.UpdateUser(types.UserID(ty.ID), permissions.NewUnset()) case permissions.Role: - p, err = c.dbs.PremissionDB.GetRole(types.RoleID(ty.ID)) + err = c.dbs.PermissionDB.UpdateRole(types.RoleID(ty.ID), permissions.NewUnset()) case permissions.Channel: - p, err = c.dbs.PremissionDB.GetChannel(types.ChannelID(ty.ID)) + err = c.dbs.PermissionDB.UpdateChannel(types.ChannelID(ty.ID), permissions.NewUnset()) + } + if err != nil { + return nil, errors.Wrap(err, "Error clearing permissions") } + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("Cleared permissions for %s %s", ty.PermissionType, name), + }, + }, nil +} + +func (c *CommandHandlers) PermissionList(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + id := fmt.Sprintf("%v", i.ApplicationCommandData().Options[0].Options[0].Value) + + p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), id) if err != nil { return nil, errors.Wrap(err, "Error getting permissions") } - ps, err := formatPermissions(p) + pstr, err := formatPermissions(p) if err != nil { return nil, errors.Wrap(err, "Error formatting permissions") } - resp := fmt.Sprintf("Permissions for %s %s:\n", ty.PermissionType, ty.ID) - resp += ps + ty, err := extractTypeFromString(s, types.GuildID(i.GuildID), id) + if err != nil { + return nil, errors.Wrap(err, "Error extracting type from string") + } + + var name string + switch ty.PermissionType { + case permissions.User: + name, err = util.UserIDName(s, types.UserID(ty.ID)) + case permissions.Role: + name, err = util.RoleIDName(s, types.GuildID(i.GuildID), types.RoleID(ty.ID)) + case permissions.Channel: + name, err = util.ChannelIDMention(s, types.GuildID(i.GuildID), types.ChannelID(ty.ID)) + } + if err != nil { + return nil, errors.Wrap(err, "Error getting name") + } + + resp := fmt.Sprintf("Permissions for %s:\n", name) + resp += pstr return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, diff --git a/EsefexApi/bot/commands/sound.go b/EsefexApi/bot/commands/sound.go index b43d551..257b27c 100644 --- a/EsefexApi/bot/commands/sound.go +++ b/EsefexApi/bot/commands/sound.go @@ -8,7 +8,6 @@ import ( "log" "github.com/bwmarrin/discordgo" - "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" ) @@ -80,11 +79,6 @@ func (c *CommandHandlers) Sound(s *discordgo.Session, i *discordgo.InteractionCr default: return nil, errors.Wrap(fmt.Errorf("Unknown subcommand %s", i.ApplicationCommandData().Options[0].Name), "Error handling user command") } - - log.Println("User command called with options:") - spew.Dump(i.ApplicationCommandData().Options) - - return nil, errors.Wrap(fmt.Errorf("Not implemented"), "Sound") } func (c *CommandHandlers) SoundUpload(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { diff --git a/EsefexApi/bot/commands/util.go b/EsefexApi/bot/commands/util.go index 6bf7ed4..8d2712e 100644 --- a/EsefexApi/bot/commands/util.go +++ b/EsefexApi/bot/commands/util.go @@ -1,11 +1,12 @@ package commands import ( + "esefexapi/db" "esefexapi/permissions" "esefexapi/sounddb" "esefexapi/types" + "esefexapi/util/refl" "fmt" - "log" "regexp" "github.com/bwmarrin/discordgo" @@ -25,13 +26,12 @@ func fmtMetaList(metas []sounddb.SoundMeta) string { // checks if its a user, role or channel func extractTypeFromString(s *discordgo.Session, g types.GuildID, str string) (PermissionSet, error) { - regex, err := regexp.Compile(`^<(@|#|@&)(\d+)>$|^(@everyone)$|^(\d+)$`) + regex, err := regexp.Compile(`^<(@|#|@&)(\d+)>$|^(@?everyone)$|^(\d+)$`) if err != nil { return PermissionSet{}, errors.Wrap(err, "Error compiling regex") } matches := regex.FindStringSubmatch(str) - log.Printf("%d matches: %#v", len(matches), matches) if len(matches) != 5 { return PermissionSet{}, errors.Wrap(fmt.Errorf("Invalid id %s", str), "Error extracting type from string") @@ -55,7 +55,7 @@ func extractTypeFromString(s *discordgo.Session, g types.GuildID, str string) (P }, nil } - if matches[3] == "@everyone" { + if matches[3] != "" { return PermissionSet{ PermissionType: permissions.Role, ID: "everyone", @@ -116,28 +116,91 @@ func formatPermissions(p permissions.Permissions) (string, error) { resp := "**Sound**\n" resp += fmt.Sprintf("```%s\n", mdlang) - resp += fmt.Sprintf("Play: %s\n", p.Sound.Play.String()) - resp += fmt.Sprintf("Upload: %s\n", p.Sound.Upload.String()) - resp += fmt.Sprintf("Modify: %s\n", p.Sound.Modify.String()) - resp += fmt.Sprintf("Delete: %s\n", p.Sound.Delete.String()) + resp += fmt.Sprintf("Sound.Play: %s\n", p.Sound.Play.String()) + resp += fmt.Sprintf("Sound.Upload: %s\n", p.Sound.Upload.String()) + resp += fmt.Sprintf("Sound.Modify: %s\n", p.Sound.Modify.String()) + resp += fmt.Sprintf("Sound.Delete: %s\n", p.Sound.Delete.String()) resp += "```" resp += "\n**Bot**\n" resp += fmt.Sprintf("```%s\n", mdlang) - resp += fmt.Sprintf("Join: %s\n", p.Bot.Join.String()) - resp += fmt.Sprintf("Leave: %s\n", p.Bot.Leave.String()) + resp += fmt.Sprintf("Bot.Join: %s\n", p.Bot.Join.String()) + resp += fmt.Sprintf("Bot.Leave: %s\n", p.Bot.Leave.String()) resp += "```" resp += "\n**Guild**\n" resp += fmt.Sprintf("```%s\n", mdlang) - resp += fmt.Sprintf("ManageBot: %s\n", p.Guild.ManageBot.String()) - resp += fmt.Sprintf("ManageUser: %s\n", p.Guild.ManageUser.String()) + resp += fmt.Sprintf("Guild.ManageBot: %s\n", p.Guild.ManageBot.String()) + resp += fmt.Sprintf("Guild.ManageUser: %s\n", p.Guild.ManageUser.String()) resp += "```" return resp, nil } +func formatPermissionsCompact(p permissions.Permissions) (string, error) { + ppaths := refl.FindAllPaths(p) + + resp := "" + for _, ppath := range ppaths { + ps, err := refl.GetNestedFieldValue(p, ppath) + if err != nil { + return "", errors.Wrap(err, "Error getting permission") + } + resp += ps.(permissions.PermissionState).Emoji() + } + + return resp, nil +} + type PermissionSet struct { PermissionType permissions.PermissionType ID string } + +func getPermissions(s *discordgo.Session, dbs *db.Databases, g types.GuildID, id string) (permissions.Permissions, error) { + ty, err := extractTypeFromString(s, g, id) + if err != nil { + return permissions.Permissions{}, errors.Wrap(err, "Error extracting type from string") + } + + var p permissions.Permissions + + switch ty.PermissionType { + case permissions.User: + p, err = dbs.PermissionDB.GetUser(types.UserID(ty.ID)) + case permissions.Role: + p, err = dbs.PermissionDB.GetRole(types.RoleID(ty.ID)) + case permissions.Channel: + p, err = dbs.PermissionDB.GetChannel(types.ChannelID(ty.ID)) + } + + if err != nil { + return permissions.Permissions{}, errors.Wrap(err, "Error getting permissions") + } + + return p, nil +} + +func getPermission(p permissions.Permissions, key string) (permissions.PermissionState, error) { + v, err := refl.GetNestedFieldValue(p, key) + if err != nil { + return permissions.Unset, errors.Wrap(err, "Error getting nested field value") + } + + return v.(permissions.PermissionState), nil +} + +func getPathOptions() []*discordgo.ApplicationCommandOptionChoice { + util := refl.FindAllPaths(permissions.NewUnset()) + + var options []*discordgo.ApplicationCommandOptionChoice + + for _, u := range util { + options = append(options, &discordgo.ApplicationCommandOptionChoice{ + Name: u, + Value: u, + }) + } + + return options +} diff --git a/EsefexApi/db/db.go b/EsefexApi/db/db.go index 394c202..56197e6 100644 --- a/EsefexApi/db/db.go +++ b/EsefexApi/db/db.go @@ -11,5 +11,5 @@ type Databases struct { SoundDB sounddb.ISoundDB UserDB userdb.IUserDB LinkTokenStore linktokenstore.ILinkTokenStore - PremissionDB permissiondb.PermissionDB + PermissionDB permissiondb.PermissionDB } diff --git a/EsefexApi/main.go b/EsefexApi/main.go index e12ad6c..494599a 100644 --- a/EsefexApi/main.go +++ b/EsefexApi/main.go @@ -55,7 +55,7 @@ func main() { SoundDB: sdbc, UserDB: udb, LinkTokenStore: ldb, - PremissionDB: fpdb, + PermissionDB: fpdb, } botT := time.Duration(cfg.Bot.Timeout * float32(time.Minute)) diff --git a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go index acee79c..6da8ba3 100644 --- a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go +++ b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go @@ -4,7 +4,6 @@ import ( "encoding/json" "esefexapi/permissiondb" "esefexapi/permissions" - "esefexapi/types" "os" "sync" @@ -29,60 +28,6 @@ func NewFilePermissionDB(path string) (*FilePermissionDB, error) { return fpdb, err } -// GetChannel implements permissiondb.PermissionDB. -func (f *FilePermissionDB) GetChannel(channelID types.ChannelID) (permissions.Permissions, error) { - f.rw.RLock() - defer f.rw.RUnlock() - - return f.stack.GetChannel(channelID), nil -} - -// GetRole implements permissiondb.PermissionDB. -func (f *FilePermissionDB) GetRole(roleID types.RoleID) (permissions.Permissions, error) { - f.rw.RLock() - defer f.rw.RUnlock() - - return f.stack.GetRole(roleID), nil -} - -// GetUser implements permissiondb.PermissionDB. -func (f *FilePermissionDB) GetUser(userID types.UserID) (permissions.Permissions, error) { - f.rw.RLock() - defer f.rw.RUnlock() - - return f.stack.GetUser(userID), nil -} - -// UpdateChannel implements permissiondb.PermissionDB. -func (f *FilePermissionDB) UpdateChannel(channelID types.ChannelID, p permissions.Permissions) error { - f.rw.Lock() - defer f.rw.Unlock() - - f.stack.UpdateChannel(channelID, p) - go f.save() - return nil -} - -// UpdateRole implements permissiondb.PermissionDB. -func (f *FilePermissionDB) UpdateRole(roleID types.RoleID, p permissions.Permissions) error { - f.rw.Lock() - defer f.rw.Unlock() - - f.stack.UpdateRole(roleID, p) - go f.save() - return nil -} - -// UpdateUser implements permissiondb.PermissionDB. -func (f *FilePermissionDB) UpdateUser(userID types.UserID, p permissions.Permissions) error { - f.rw.Lock() - defer f.rw.Unlock() - - f.stack.UpdateUser(userID, p) - go f.save() - return nil -} - // load loads the permission stack from the file. func (f *FilePermissionDB) load() error { file, err := os.Open(f.filePath) @@ -100,6 +45,7 @@ func (f *FilePermissionDB) load() error { } // save saves the permission stack to the file. +// TODO: Make this work func (f *FilePermissionDB) save() error { file, err := os.OpenFile(f.filePath, os.O_RDWR|os.O_CREATE, os.ModePerm) if err != nil { diff --git a/EsefexApi/permissiondb/filepermisssiondb/impl.go b/EsefexApi/permissiondb/filepermisssiondb/impl.go new file mode 100644 index 0000000..a31ce3d --- /dev/null +++ b/EsefexApi/permissiondb/filepermisssiondb/impl.go @@ -0,0 +1,96 @@ +package filepermisssiondb + +import ( + "esefexapi/permissions" + "esefexapi/types" +) + +// GetChannel implements permissiondb.PermissionDB. +func (f *FilePermissionDB) GetChannel(channelID types.ChannelID) (permissions.Permissions, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + return f.stack.GetChannel(channelID), nil +} + +// GetRole implements permissiondb.PermissionDB. +func (f *FilePermissionDB) GetRole(roleID types.RoleID) (permissions.Permissions, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + return f.stack.GetRole(roleID), nil +} + +// GetUser implements permissiondb.PermissionDB. +func (f *FilePermissionDB) GetUser(userID types.UserID) (permissions.Permissions, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + return f.stack.GetUser(userID), nil +} + +// UpdateChannel implements permissiondb.PermissionDB. +func (f *FilePermissionDB) UpdateChannel(channelID types.ChannelID, p permissions.Permissions) error { + f.rw.Lock() + defer f.rw.Unlock() + + f.stack.UpdateChannel(channelID, p) + go f.save() + return nil +} + +// UpdateRole implements permissiondb.PermissionDB. +func (f *FilePermissionDB) UpdateRole(roleID types.RoleID, p permissions.Permissions) error { + f.rw.Lock() + defer f.rw.Unlock() + + f.stack.UpdateRole(roleID, p) + go f.save() + return nil +} + +// UpdateUser implements permissiondb.PermissionDB. +func (f *FilePermissionDB) UpdateUser(userID types.UserID, p permissions.Permissions) error { + f.rw.Lock() + defer f.rw.Unlock() + + f.stack.UpdateUser(userID, p) + go f.save() + return nil +} + +func (f *FilePermissionDB) GetUsers() ([]types.UserID, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + var users []types.UserID + for k := range f.stack.User { + users = append(users, k) + } + + return users, nil +} + +func (f *FilePermissionDB) GetRoles() ([]types.RoleID, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + var roles []types.RoleID + for k := range f.stack.Role { + roles = append(roles, k) + } + + return roles, nil +} + +func (f *FilePermissionDB) GetChannels() ([]types.ChannelID, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + var channels []types.ChannelID + for k := range f.stack.Channel { + channels = append(channels, k) + } + + return channels, nil +} diff --git a/EsefexApi/permissiondb/permissiondb.go b/EsefexApi/permissiondb/permissiondb.go index 44edc17..70f4ffe 100644 --- a/EsefexApi/permissiondb/permissiondb.go +++ b/EsefexApi/permissiondb/permissiondb.go @@ -12,4 +12,7 @@ type PermissionDB interface { UpdateUser(userID types.UserID, p permissions.Permissions) error UpdateRole(roleID types.RoleID, p permissions.Permissions) error UpdateChannel(channelID types.ChannelID, p permissions.Permissions) error + GetUsers() ([]types.UserID, error) + GetRoles() ([]types.RoleID, error) + GetChannels() ([]types.ChannelID, error) } diff --git a/EsefexApi/permissions/permissions.go b/EsefexApi/permissions/permissions.go index 3e93026..d3714ad 100644 --- a/EsefexApi/permissions/permissions.go +++ b/EsefexApi/permissions/permissions.go @@ -11,34 +11,60 @@ const ( func (pt PermissionType) String() string { switch pt { case User: - return "user" + return "User" case Role: - return "role" + return "Role" case Channel: - return "channel" + return "Channel" default: - return "unknown" + return "Unknown" } } type PermissionState int const ( - Allow PermissionState = iota + Unset PermissionState = iota + Allow Deny - Unset ) +func PSFromString(str string) PermissionState { + switch str { + case "Allow": + return Allow + case "Deny": + return Deny + case "Unset": + return Unset + default: + return Unset + } +} + func (ps PermissionState) String() string { switch ps { case Allow: - return "allow" + return "Allow" + case Deny: + return "Deny" + case Unset: + return "Unset" + default: + return "Unknown" + } +} + +func (ps PermissionState) Emoji() string { + switch ps { + case Allow: + return "✅" case Deny: - return "deny" + return "❌" case Unset: - return "unset" + return " " default: - return "unknown" + return "❓" } } diff --git a/EsefexApi/permissions/stack.go b/EsefexApi/permissions/stack.go index d83ca1e..bed9ba7 100644 --- a/EsefexApi/permissions/stack.go +++ b/EsefexApi/permissions/stack.go @@ -71,6 +71,8 @@ func (ps *PermissionStack) UpdateUser(user types.UserID, p Permissions) { } ps.User[user] = ps.User[user].MergeParent(p) + + ps.clean() } func (ps *PermissionStack) UpdateRole(role types.RoleID, p Permissions) { @@ -79,6 +81,8 @@ func (ps *PermissionStack) UpdateRole(role types.RoleID, p Permissions) { } ps.Role[role] = ps.Role[role].MergeParent(p) + + ps.clean() } func (ps *PermissionStack) UpdateChannel(channel types.ChannelID, p Permissions) { @@ -87,6 +91,29 @@ func (ps *PermissionStack) UpdateChannel(channel types.ChannelID, p Permissions) } ps.Channel[channel] = ps.Channel[channel].MergeParent(p) + + ps.clean() +} + +// clean removes all permissions that are just unset. +func (ps *PermissionStack) clean() { + for user, p := range ps.User { + if p == NewUnset() { + delete(ps.User, user) + } + } + + for role, p := range ps.Role { + if p == NewUnset() { + delete(ps.Role, role) + } + } + + for channel, p := range ps.Channel { + if p == NewUnset() { + delete(ps.Channel, channel) + } + } } // Query returns the permission state for a given user, role, and channel by merging them together. diff --git a/EsefexApi/util/iddisplay.go b/EsefexApi/util/iddisplay.go new file mode 100644 index 0000000..4901d46 --- /dev/null +++ b/EsefexApi/util/iddisplay.go @@ -0,0 +1,92 @@ +package util + +import ( + "esefexapi/types" + + "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" +) + +func UserIDName(ds *discordgo.Session, u types.UserID) (string, error) { + user, err := ds.User(u.String()) + if err != nil { + return "", errors.Wrap(err, "Error getting user") + } + return user.Username, nil +} + +func RoleIDName(ds *discordgo.Session, g types.GuildID, r types.RoleID) (string, error) { + if r == "everyone" { + return "everyone", nil + } + + roles, err := ds.GuildRoles(g.String()) + if err != nil { + return "", errors.Wrap(err, "Error getting roles") + } + + for _, role := range roles { + if role.ID == r.String() { + return role.Name, nil + } + } + + return "", errors.Wrap(err, "Error getting role") +} + +func ChannelIDName(ds *discordgo.Session, g types.GuildID, c types.ChannelID) (string, error) { + channels, err := ds.GuildChannels(g.String()) + if err != nil { + return "", errors.Wrap(err, "Error getting channels") + } + + for _, channel := range channels { + if channel.ID == c.String() { + return channel.Name, nil + } + } + + return "", errors.Wrap(err, "Error getting channel") +} + +func UserIDMention(ds *discordgo.Session, u types.UserID) (string, error) { + user, err := ds.User(u.String()) + if err != nil { + return "", errors.Wrap(err, "Error getting user") + } + return user.Mention(), nil +} + +func RoleIDMention(ds *discordgo.Session, g types.GuildID, r types.RoleID) (string, error) { + if r == "everyone" { + return "@everyone", nil + } + + roles, err := ds.GuildRoles(g.String()) + if err != nil { + return "", errors.Wrap(err, "Error getting roles") + } + + for _, role := range roles { + if role.ID == r.String() { + return role.Mention(), nil + } + } + + return "", errors.Wrap(err, "Error getting role") +} + +func ChannelIDMention(ds *discordgo.Session, g types.GuildID, c types.ChannelID) (string, error) { + channels, err := ds.GuildChannels(g.String()) + if err != nil { + return "", errors.Wrap(err, "Error getting channels") + } + + for _, channel := range channels { + if channel.ID == c.String() { + return channel.Mention(), nil + } + } + + return "", errors.Wrap(err, "Error getting channel") +} diff --git a/EsefexApi/util/refl/err.go b/EsefexApi/util/refl/err.go new file mode 100644 index 0000000..4bd68fb --- /dev/null +++ b/EsefexApi/util/refl/err.go @@ -0,0 +1,5 @@ +package refl + +import "fmt" + +var ErrFieldNotFound = fmt.Errorf("field not found") diff --git a/EsefexApi/util/refl/get.go b/EsefexApi/util/refl/get.go new file mode 100644 index 0000000..4c122eb --- /dev/null +++ b/EsefexApi/util/refl/get.go @@ -0,0 +1,49 @@ +package refl + +import ( + "fmt" + "reflect" + "strings" + + "github.com/pkg/errors" +) + +func GetNestedFieldValue(obj interface{}, path string) (interface{}, error) { + val := reflect.ValueOf(obj) + + // Navigate through the nested fields + for val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface { + val = val.Elem() + } + + if val.Kind() != reflect.Struct { + return nil, fmt.Errorf("provided object is not a struct") + } + + // Split the path into individual field names + fieldNames := strings.Split(path, ".") + + // Call the recursive helper function + return GetNestedFieldValueRecursive(val, fieldNames) +} + +func GetNestedFieldValueRecursive(val reflect.Value, fieldNames []string) (interface{}, error) { + // Base case: no more field names to process + if len(fieldNames) == 0 { + return val.Interface(), nil + } + + // Iterate through the fields of the current struct + for i := 0; i < val.NumField(); i++ { + field := val.Type().Field(i) + + // Check if the field name matches the current path element + if field.Name == fieldNames[0] { + // Recursively call the function for the next level of nesting + return GetNestedFieldValueRecursive(val.Field(i), fieldNames[1:]) + } + } + + // If the field is not found, return an error + return nil, errors.Wrap(ErrFieldNotFound, fieldNames[0]) +} diff --git a/EsefexApi/util/refl/paths.go b/EsefexApi/util/refl/paths.go new file mode 100644 index 0000000..2cb7ddc --- /dev/null +++ b/EsefexApi/util/refl/paths.go @@ -0,0 +1,41 @@ +package refl + +import "reflect" + +func FindAllPaths(obj interface{}) []string { + paths := make([]string, 0) + FindAllPathsRecursive(reflect.ValueOf(obj), "", &paths) + return paths +} + +func FindAllPathsRecursive(val reflect.Value, currentPath string, paths *[]string) { + // Navigate through the nested fields + for val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface { + val = val.Elem() + } + + if val.Kind() != reflect.Struct { + return + } + + // Iterate through the fields of the current struct + for i := 0; i < val.NumField(); i++ { + field := val.Type().Field(i) + + // Construct the path for the current field + fieldPath := currentPath + field.Name + + // Add a dot separator if not the first field in the path + if currentPath != "" { + fieldPath = currentPath + "." + field.Name + } + + // Recursively call the function for the next level of nesting + FindAllPathsRecursive(val.Field(i), fieldPath, paths) + + // Add the path to the result if the field is not a struct + if val.Field(i).Kind() != reflect.Struct { + *paths = append(*paths, fieldPath) + } + } +} diff --git a/EsefexApi/util/refl/set.go b/EsefexApi/util/refl/set.go new file mode 100644 index 0000000..91326d3 --- /dev/null +++ b/EsefexApi/util/refl/set.go @@ -0,0 +1,55 @@ +package refl + +import ( + "fmt" + "reflect" + "strings" + + "github.com/pkg/errors" +) + +func SetNestedFieldValue(obj interface{}, path string, value interface{}) error { + val := reflect.ValueOf(obj) + + // Navigate through the nested fields + for val.Kind() == reflect.Ptr || val.Kind() == reflect.Interface { + val = val.Elem() + } + + if val.Kind() != reflect.Struct { + return fmt.Errorf("provided object is not a struct") + } + + // Split the path into individual field names + fieldNames := strings.Split(path, ".") + + // Call the recursive helper function + return SetNestedFieldValueRecursive(val, fieldNames, value) +} + +func SetNestedFieldValueRecursive(val reflect.Value, fieldNames []string, newValue interface{}) error { + // Base case: no more field names to process + if len(fieldNames) == 0 { + // Set the value at the final field + valSettable := reflect.ValueOf(newValue) + if val.CanSet() && valSettable.Type().AssignableTo(val.Type()) { + val.Set(valSettable) + return nil + } + return fmt.Errorf("cannot set value at path") + } + + // Iterate through the fields of the current struct + for i := 0; i < val.NumField(); i++ { + field := val.Type().Field(i) + + // Check if the field name matches the current path element + if field.Name == fieldNames[0] { + // Recursively call the function for the next level of nesting + return SetNestedFieldValueRecursive(val.Field(i), fieldNames[1:], newValue) + } + } + + // If the field is not found, return an error + return errors.Wrap(ErrFieldNotFound, fieldNames[0]) +} From 4777f2e8189f08b8bbb950e639fd5a0ed0c77269 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Sun, 14 Jan 2024 17:43:31 +0100 Subject: [PATCH 14/20] permission disk persistence --- EsefexApi/main.go | 2 + .../filepermisssiondb/filepermisssiondb.go | 76 ++++++++++++++----- EsefexApi/permissions/stack.go | 4 +- EsefexApi/userdb/fileuserdb/fileuserdb.go | 7 +- EsefexApi/userdb/fileuserdb/impluserdb.go | 6 +- EsefexApi/util/util.go | 16 ++++ 6 files changed, 81 insertions(+), 30 deletions(-) diff --git a/EsefexApi/main.go b/EsefexApi/main.go index 494599a..ca8741b 100644 --- a/EsefexApi/main.go +++ b/EsefexApi/main.go @@ -47,6 +47,7 @@ func main() { Must(err) fpdb, err := filepermisssiondb.NewFilePermissionDB(cfg.Database.Permissiondblocation) + Must(err) verT := time.Duration(cfg.VerificationExpiry * float32(time.Minute)) ldb := memorylinktokenstore.NewMemoryLinkTokenStore(verT) @@ -76,6 +77,7 @@ func main() { <-plr.Stop() udb.Close() + fpdb.Close() log.Println("All components stopped, exiting...") }() diff --git a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go index 6da8ba3..aabc0fb 100644 --- a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go +++ b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go @@ -4,6 +4,8 @@ import ( "encoding/json" "esefexapi/permissiondb" "esefexapi/permissions" + "esefexapi/util" + "log" "os" "sync" @@ -13,49 +15,83 @@ import ( var _ permissiondb.PermissionDB = &FilePermissionDB{} type FilePermissionDB struct { - filePath string - rw *sync.RWMutex - stack *permissions.PermissionStack + file *os.File + rw *sync.RWMutex + stack permissions.PermissionStack } func NewFilePermissionDB(path string) (*FilePermissionDB, error) { + log.Printf("Creating FileDB at %s", path) + file, err := util.EnsureFile(path) + if err != nil { + return nil, errors.Wrap(err, "Error ensuring file") + } + fpdb := &FilePermissionDB{ - filePath: path, - rw: &sync.RWMutex{}, - stack: permissions.NewPermissionStack(), + file: file, + rw: &sync.RWMutex{}, + stack: permissions.NewPermissionStack(), } - err := fpdb.load() - return fpdb, err + err = fpdb.load() + if err != nil { + return nil, errors.Wrap(err, "Error loading permission stack") + } + + return fpdb, nil } // load loads the permission stack from the file. func (f *FilePermissionDB) load() error { - file, err := os.Open(f.filePath) + f.rw.Lock() + defer f.rw.Unlock() + + // reset file + _, err := f.file.Seek(0, 0) if err != nil { - return errors.Wrap(err, "Error opening file") + return errors.Wrap(err, "Error seeking to start of file") } - defer file.Close() - err = json.NewDecoder(file).Decode(f.stack) + // read file + var perms permissions.PermissionStack + err = json.NewDecoder(f.file).Decode(&perms) if err != nil { - return errors.Wrap(err, "Error decoding permission stack") + log.Printf("Error decoding file, creating empty permission stack: (%v)", err) + f.stack = permissions.NewPermissionStack() + } else { + f.stack = perms } return nil } +func (f *FilePermissionDB) Close() error { + f.rw.Lock() + defer f.rw.Unlock() + log.Println("Closing file permissiondb") + + err := f.save() + if err != nil { + return errors.Wrap(err, "Error saving permissiondb") + } + return f.file.Close() +} + // save saves the permission stack to the file. -// TODO: Make this work -func (f *FilePermissionDB) save() error { - file, err := os.OpenFile(f.filePath, os.O_RDWR|os.O_CREATE, os.ModePerm) +// It assumes that the lock is already held. +func (f FilePermissionDB) save() error { + // reset file + _, err := f.file.Seek(0, 0) + if err != nil { + return errors.Wrap(err, "Error seeking to start of file") + } + err = f.file.Truncate(0) if err != nil { - return errors.Wrap(err, "Error opening file") + return errors.Wrap(err, "Error truncating file") } - defer file.Close() - err = json.NewEncoder(file).Encode(f.stack) + err = json.NewEncoder(f.file).Encode(f.stack) if err != nil { - return errors.Wrap(err, "Error encoding permission stack") + return errors.Wrap(err, "Error encoding file") } return nil diff --git a/EsefexApi/permissions/stack.go b/EsefexApi/permissions/stack.go index bed9ba7..54930af 100644 --- a/EsefexApi/permissions/stack.go +++ b/EsefexApi/permissions/stack.go @@ -12,8 +12,8 @@ type PermissionStack struct { Channel map[types.ChannelID]Permissions } -func NewPermissionStack() *PermissionStack { - return &PermissionStack{ +func NewPermissionStack() PermissionStack { + return PermissionStack{ User: make(map[types.UserID]Permissions), Role: make(map[types.RoleID]Permissions), Channel: make(map[types.ChannelID]Permissions), diff --git a/EsefexApi/userdb/fileuserdb/fileuserdb.go b/EsefexApi/userdb/fileuserdb/fileuserdb.go index 685666a..1ef27bc 100644 --- a/EsefexApi/userdb/fileuserdb/fileuserdb.go +++ b/EsefexApi/userdb/fileuserdb/fileuserdb.go @@ -64,17 +64,14 @@ func NewFileUserDB(filePath string) (*FileUserDB, error) { func (f *FileUserDB) Close() error { log.Println("Closing userdb") - err := f.Save() + err := f.save() if err != nil { return errors.Wrap(err, "Error saving userdb") } return f.file.Close() } -func (f *FileUserDB) Save() error { - f.fileLock.Lock() - defer f.fileLock.Unlock() - +func (f FileUserDB) save() error { // reset file _, err := f.file.Seek(0, 0) if err != nil { diff --git a/EsefexApi/userdb/fileuserdb/impluserdb.go b/EsefexApi/userdb/fileuserdb/impluserdb.go index e57733f..13458e7 100644 --- a/EsefexApi/userdb/fileuserdb/impluserdb.go +++ b/EsefexApi/userdb/fileuserdb/impluserdb.go @@ -26,7 +26,7 @@ func (f *FileUserDB) SetUser(user userdb.User) error { f.Users[user.ID] = user go func() { - err := f.Save() + err := f.save() if err != nil { log.Printf("Error saving userdb: %+v", err) } @@ -40,7 +40,7 @@ func (f *FileUserDB) DeleteUser(userID types.UserID) error { delete(f.Users, userID) go func() { - err := f.Save() + err := f.save() if err != nil { log.Printf("Error saving userdb: %+v", err) } @@ -87,7 +87,7 @@ func (f *FileUserDB) NewToken(userID types.UserID) (userdb.Token, error) { log.Printf("%v", f) go func() { - err := f.Save() + err := f.save() if err != nil { log.Printf("Error saving userdb: %+v", err) } diff --git a/EsefexApi/util/util.go b/EsefexApi/util/util.go index 7b58d55..5f007f8 100644 --- a/EsefexApi/util/util.go +++ b/EsefexApi/util/util.go @@ -4,10 +4,12 @@ import ( "fmt" "math/rand" "os" + "path" "regexp" "strings" "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" "github.com/samber/lo" ) @@ -58,3 +60,17 @@ func RandomString(charset []rune, length int) string { return string(str) } + +func EnsureFile(p string) (*os.File, error) { + err := os.MkdirAll(path.Dir(p), os.ModePerm) + if err != nil { + return nil, errors.Wrap(err, "Error creating directory") + } + + file, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE, os.ModePerm) + if err != nil { + return nil, errors.Wrap(err, "Error opening file") + } + + return file, nil +} From b64ad20970d8619ab233c30131e9a7555a131fd3 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Tue, 16 Jan 2024 22:34:44 +0100 Subject: [PATCH 15/20] permission checking, better middleware --- EsefexApi/api/api.go | 22 ++-- EsefexApi/api/middleware/auth.go | 8 +- EsefexApi/api/middleware/cors.go | 4 +- EsefexApi/api/middleware/middleware.go | 10 +- EsefexApi/api/middleware/permission.go | 54 +++++++++ EsefexApi/api/routes/getdump.go | 8 +- EsefexApi/api/routes/getguild.go | 37 ++++--- EsefexApi/api/routes/getguilds.go | 49 +++++---- EsefexApi/api/routes/getindex.go | 6 +- EsefexApi/api/routes/getjoinsession.go | 18 +-- EsefexApi/api/routes/getlinkdefer.go | 40 +++---- EsefexApi/api/routes/getlinkredirect.go | 104 +++++++++--------- EsefexApi/api/routes/getsounds.go | 70 ++++++------ EsefexApi/api/routes/postplaysound.go | 40 ++++--- EsefexApi/api/routes/postplaysoundinsecure.go | 36 +++--- .../bot/commands/cmdhandler/cmdhandler.go | 16 +++ EsefexApi/bot/commands/commands.go | 30 ++--- EsefexApi/bot/commands/middleware/chkperm.go | 58 ++++++++++ .../bot/commands/middleware/middleware.go | 13 +++ EsefexApi/bot/commands/permission.go | 2 +- EsefexApi/bot/commands/sound.go | 6 +- EsefexApi/main.go | 2 +- .../filepermisssiondb/filepermisssiondb.go | 5 +- .../permissiondb/filepermisssiondb/query.go | 38 +++++++ EsefexApi/permissiondb/permissiondb.go | 1 + EsefexApi/permissions/merge.go | 5 +- EsefexApi/permissions/permissions.go | 40 +++++-- EsefexApi/permissions/permissions_test.go | 5 +- EsefexApi/userdb/fileuserdb/impluserdb.go | 1 - EsefexApi/util/dcgoutil/dcgoutil.go | 14 +++ 30 files changed, 500 insertions(+), 242 deletions(-) create mode 100644 EsefexApi/api/middleware/permission.go create mode 100644 EsefexApi/bot/commands/cmdhandler/cmdhandler.go create mode 100644 EsefexApi/bot/commands/middleware/chkperm.go create mode 100644 EsefexApi/bot/commands/middleware/middleware.go create mode 100644 EsefexApi/permissiondb/filepermisssiondb/query.go diff --git a/EsefexApi/api/api.go b/EsefexApi/api/api.go index a175fd0..a0d1cff 100644 --- a/EsefexApi/api/api.go +++ b/EsefexApi/api/api.go @@ -31,7 +31,7 @@ type HttpApi struct { func NewHttpApi(dbs *db.Databases, plr audioplayer.IAudioPlayer, ds *discordgo.Session, apiPort int, cProto string) *HttpApi { return &HttpApi{ handlers: routes.NewRouteHandlers(dbs, plr, ds, cProto), - mw: middleware.NewMiddleware(dbs), + mw: middleware.NewMiddleware(dbs, ds), a: plr, apiPort: apiPort, cProto: cProto, @@ -50,20 +50,20 @@ func (api *HttpApi) run() { cors := api.mw.Cors h := api.handlers - router.HandleFunc("/api/sounds/{guild_id}", cors(h.GetSounds)).Methods("GET") + router.Handle("/api/sounds/{guild_id}", cors(h.GetSounds())).Methods("GET") - router.HandleFunc("/api/guild", cors(auth(h.GetGuild))).Methods("GET").Headers("Cookie", "") - router.HandleFunc("/api/guilds", cors(auth(h.GetGuilds))).Methods("GET").Headers("Cookie", "") + router.Handle("/api/guild", cors(auth(h.GetGuild()))).Methods("GET").Headers("Cookie", "") + router.Handle("/api/guilds", cors(auth(h.GetGuilds()))).Methods("GET").Headers("Cookie", "") - router.HandleFunc("/api/playsound/{user_id}/{guild_id}/{sound_id}", cors(h.PostPlaySoundInsecure)).Methods("POST") - router.HandleFunc("/api/playsound/{sound_id}", cors(auth(h.PostPlaySound))).Methods("POST").Headers("Cookie", "") + router.Handle("/api/playsound/{user_id}/{guild_id}/{sound_id}", cors(h.PostPlaySoundInsecure())).Methods("POST") + router.Handle("/api/playsound/{sound_id}", cors(auth(h.PostPlaySound()))).Methods("POST").Headers("Cookie", "") - router.HandleFunc("/joinsession/{guild_id}", cors(h.GetJoinSession)).Methods("GET") - router.HandleFunc("/link", cors(h.GetLinkDefer)).Methods("GET").Queries("t", "{t}") - router.HandleFunc("/api/link", cors(h.GetLinkRedirect)).Methods("GET").Queries("t", "{t}") + router.Handle("/joinsession/{guild_id}", cors(h.GetJoinSession())).Methods("GET") + router.Handle("/link", cors(h.GetLinkDefer())).Methods("GET").Queries("t", "{t}") + router.Handle("/api/link", cors(h.GetLinkRedirect())).Methods("GET").Queries("t", "{t}") - router.HandleFunc("/dump", cors(h.GetDump)) - router.HandleFunc("/", cors(h.GetIndex)).Methods("GET") + router.Handle("/dump", cors(h.GetDump())) + router.Handle("/", cors(h.GetIndex())).Methods("GET") router.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./api/public/")))) diff --git a/EsefexApi/api/middleware/auth.go b/EsefexApi/api/middleware/auth.go index a9d3f07..78302c4 100644 --- a/EsefexApi/api/middleware/auth.go +++ b/EsefexApi/api/middleware/auth.go @@ -1,7 +1,7 @@ package middleware import ( - "esefexapi/types" + "context" "esefexapi/userdb" "fmt" "log" @@ -9,7 +9,7 @@ import ( ) // Auth middleware checks if the user is authenticated and injects the user into the request context -func (m *Middleware) Auth(next func(w http.ResponseWriter, r *http.Request, userID types.UserID)) func(w http.ResponseWriter, r *http.Request) { +func (m *Middleware) Auth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user_token, err := r.Cookie("User-Token") if err != nil { @@ -29,6 +29,8 @@ func (m *Middleware) Auth(next func(w http.ResponseWriter, r *http.Request, user return } - next(w, r, Ouser.Unwrap().ID) + // Inject the user into the request context + ctx := context.WithValue(r.Context(), "user", Ouser) + next.ServeHTTP(w, r.WithContext(ctx)) }) } diff --git a/EsefexApi/api/middleware/cors.go b/EsefexApi/api/middleware/cors.go index 44722c3..2e6564d 100644 --- a/EsefexApi/api/middleware/cors.go +++ b/EsefexApi/api/middleware/cors.go @@ -2,10 +2,10 @@ package middleware import "net/http" -func (m *Middleware) Cors(next func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) { +func (m *Middleware) Cors(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Access-Control-Allow-Origin", "*") - next(w, r) + next.ServeHTTP(w, r) }) } diff --git a/EsefexApi/api/middleware/middleware.go b/EsefexApi/api/middleware/middleware.go index cbe8329..6ee4600 100644 --- a/EsefexApi/api/middleware/middleware.go +++ b/EsefexApi/api/middleware/middleware.go @@ -1,13 +1,19 @@ package middleware -import "esefexapi/db" +import ( + "esefexapi/db" + + "github.com/bwmarrin/discordgo" +) type Middleware struct { dbs *db.Databases + ds *discordgo.Session } -func NewMiddleware(dbs *db.Databases) *Middleware { +func NewMiddleware(dbs *db.Databases, ds *discordgo.Session) *Middleware { return &Middleware{ dbs: dbs, + ds: ds, } } diff --git a/EsefexApi/api/middleware/permission.go b/EsefexApi/api/middleware/permission.go new file mode 100644 index 0000000..b4f0baf --- /dev/null +++ b/EsefexApi/api/middleware/permission.go @@ -0,0 +1,54 @@ +package middleware + +import ( + "esefexapi/permissions" + "esefexapi/types" + "esefexapi/userdb" + "esefexapi/util/dcgoutil" + "esefexapi/util/refl" + "net/http" +) + +func (m *Middleware) Permission(next http.Handler, perms ...string) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Get the user from the request context + user := r.Context().Value("user").(userdb.User) + userChan, err := dcgoutil.UserVCAny(m.ds, user.ID) + if err != nil { + errorMsg := "Error getting user channel: " + err.Error() + http.Error(w, errorMsg, http.StatusInternalServerError) + return + } + + if userChan.IsNone() { + errorMsg := "User is not in a voice channel" + http.Error(w, errorMsg, http.StatusUnauthorized) + return + } + + p, err := m.dbs.PermissionDB.Query(user.ID, types.GuildID(userChan.Unwrap().ChannelID)) + if err != nil { + errorMsg := "Error querying permissions: " + err.Error() + http.Error(w, errorMsg, http.StatusInternalServerError) + return + } + + // Check if the user has any of the required permissions + for _, perm := range perms { + ps, err := refl.GetNestedFieldValue(p, perm) + if err != nil { + errorMsg := "Error getting nested field value: " + err.Error() + http.Error(w, errorMsg, http.StatusInternalServerError) + return + } + + if !ps.(permissions.PermissionState).Allowed() { + errorMsg := "User does not have the required permissions" + http.Error(w, errorMsg, http.StatusUnauthorized) + return + } + } + + next.ServeHTTP(w, r) + }) +} diff --git a/EsefexApi/api/routes/getdump.go b/EsefexApi/api/routes/getdump.go index f2ecf4d..506f294 100644 --- a/EsefexApi/api/routes/getdump.go +++ b/EsefexApi/api/routes/getdump.go @@ -7,7 +7,9 @@ import ( ) // /dump -func (h *RouteHandlers) GetDump(w http.ResponseWriter, r *http.Request) { - spew.Fdump(w, r) - spew.Dump(r) +func (h *RouteHandlers) GetDump() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + spew.Fdump(w, r) + spew.Dump(r) + }) } diff --git a/EsefexApi/api/routes/getguild.go b/EsefexApi/api/routes/getguild.go index f513caa..ce16009 100644 --- a/EsefexApi/api/routes/getguild.go +++ b/EsefexApi/api/routes/getguild.go @@ -9,22 +9,25 @@ import ( // api/guild // returns the guild a user is connected to -func (h *RouteHandlers) GetGuild(w http.ResponseWriter, r *http.Request, userID types.UserID) { - Ovs, err := dcgoutil.UserVCAny(h.ds, userID) - if err != nil { - errorMsg := "Error getting user Voice State" - http.Error(w, errorMsg, http.StatusInternalServerError) - return - } - if Ovs.IsNone() { - http.Error(w, "User not connected to guild channel", http.StatusForbidden) - return - } +func (h *RouteHandlers) GetGuild() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userID := r.Context().Value("user").(types.UserID) + Ovs, err := dcgoutil.UserVCAny(h.ds, userID) + if err != nil { + errorMsg := "Error getting user Voice State" + http.Error(w, errorMsg, http.StatusInternalServerError) + return + } + if Ovs.IsNone() { + http.Error(w, "User not connected to guild channel", http.StatusForbidden) + return + } - guildID := Ovs.Unwrap().GuildID - _, err = w.Write([]byte(guildID)) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - } + guildID := Ovs.Unwrap().GuildID + _, err = w.Write([]byte(guildID)) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) } diff --git a/EsefexApi/api/routes/getguilds.go b/EsefexApi/api/routes/getguilds.go index f0c798b..c300d72 100644 --- a/EsefexApi/api/routes/getguilds.go +++ b/EsefexApi/api/routes/getguilds.go @@ -13,31 +13,34 @@ type GuildInfo struct { GuildName string `json:"guildName"` } -func (h *RouteHandlers) GetGuilds(w http.ResponseWriter, r *http.Request, userID types.UserID) { - guilds, err := dcgoutil.UserGuilds(h.ds, userID) - if err != nil { - log.Println(err) - http.Error(w, "Error getting user guilds", http.StatusInternalServerError) - return - } +func (h *RouteHandlers) GetGuilds() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userID := r.Context().Value("user").(types.UserID) + guilds, err := dcgoutil.UserGuilds(h.ds, userID) + if err != nil { + log.Println(err) + http.Error(w, "Error getting user guilds", http.StatusInternalServerError) + return + } - si := []GuildInfo{} + si := []GuildInfo{} - for _, g := range guilds { - si = append(si, GuildInfo{GuildID: g.ID, GuildName: g.Name}) - } + for _, g := range guilds { + si = append(si, GuildInfo{GuildID: g.ID, GuildName: g.Name}) + } - js, err := json.Marshal(si) - if err != nil { - log.Println(err) - http.Error(w, "Error marshalling json", http.StatusInternalServerError) - return - } + js, err := json.Marshal(si) + if err != nil { + log.Println(err) + http.Error(w, "Error marshalling json", http.StatusInternalServerError) + return + } - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(js) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - } + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(js) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) } diff --git a/EsefexApi/api/routes/getindex.go b/EsefexApi/api/routes/getindex.go index 77be657..5af5511 100644 --- a/EsefexApi/api/routes/getindex.go +++ b/EsefexApi/api/routes/getindex.go @@ -6,6 +6,8 @@ import ( ) // / -func (h *RouteHandlers) GetIndex(w http.ResponseWriter, r *http.Request) { - _, _ = io.WriteString(w, "Index!\n") +func (h *RouteHandlers) GetIndex() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, _ = io.WriteString(w, "Index!\n") + }) } diff --git a/EsefexApi/api/routes/getjoinsession.go b/EsefexApi/api/routes/getjoinsession.go index e59067c..7d49385 100644 --- a/EsefexApi/api/routes/getjoinsession.go +++ b/EsefexApi/api/routes/getjoinsession.go @@ -9,15 +9,17 @@ import ( ) // joinsession/ -func (h *RouteHandlers) GetJoinSession(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - guild_id := vars["guild_id"] +func (h *RouteHandlers) GetJoinSession() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + guild_id := vars["guild_id"] - redirectUrl := fmt.Sprintf("%s://joinsession/%s", h.cProto, guild_id) - response := fmt.Sprintf(``, redirectUrl) + redirectUrl := fmt.Sprintf("%s://joinsession/%s", h.cProto, guild_id) + response := fmt.Sprintf(``, redirectUrl) - w.Header().Set("Content-Type", "text/html") - fmt.Fprint(w, response) + w.Header().Set("Content-Type", "text/html") + fmt.Fprint(w, response) - log.Printf("got /joinsession request\n") + log.Printf("got /joinsession request\n") + }) } diff --git a/EsefexApi/api/routes/getlinkdefer.go b/EsefexApi/api/routes/getlinkdefer.go index a521267..efe1c9e 100644 --- a/EsefexApi/api/routes/getlinkdefer.go +++ b/EsefexApi/api/routes/getlinkdefer.go @@ -10,27 +10,29 @@ type LinkDeferData struct { } // link? -func (h *RouteHandlers) GetLinkDefer(w http.ResponseWriter, r *http.Request) { - linkToken := r.URL.Query().Get("t") +func (h *RouteHandlers) GetLinkDefer() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + linkToken := r.URL.Query().Get("t") - if linkToken == "" { - http.Error(w, "Missing link token", http.StatusBadRequest) - return - } + if linkToken == "" { + http.Error(w, "Missing link token", http.StatusBadRequest) + return + } - data := LinkDeferData{ - LinkToken: linkToken, - } + data := LinkDeferData{ + LinkToken: linkToken, + } - tmpl, err := template.ParseFiles("./api/templates/link.html") - if err != nil { - http.Error(w, "Error parsing template", http.StatusInternalServerError) - return - } + tmpl, err := template.ParseFiles("./api/templates/link.html") + if err != nil { + http.Error(w, "Error parsing template", http.StatusInternalServerError) + return + } - err = tmpl.Execute(w, data) - if err != nil { - http.Error(w, "Error executing template", http.StatusInternalServerError) - return - } + err = tmpl.Execute(w, data) + if err != nil { + http.Error(w, "Error executing template", http.StatusInternalServerError) + return + } + }) } diff --git a/EsefexApi/api/routes/getlinkredirect.go b/EsefexApi/api/routes/getlinkredirect.go index 052dc14..2b7ad83 100644 --- a/EsefexApi/api/routes/getlinkredirect.go +++ b/EsefexApi/api/routes/getlinkredirect.go @@ -12,65 +12,67 @@ type LinkRedirect struct { } // api/link? -func (h *RouteHandlers) GetLinkRedirect(w http.ResponseWriter, r *http.Request) { - linkToken := r.URL.Query().Get("t") +func (h *RouteHandlers) GetLinkRedirect() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + linkToken := r.URL.Query().Get("t") - if linkToken == "" { - http.Error(w, "Missing link token", http.StatusBadRequest) - return - } + if linkToken == "" { + http.Error(w, "Missing link token", http.StatusBadRequest) + return + } - isValid, err := h.dbs.LinkTokenStore.ValidateToken(linkToken) - if err != nil || !isValid { - http.Error(w, fmt.Sprintf("Invalid link token, the token does not exist or is expired:\n%s", err), http.StatusBadRequest) - return - } + isValid, err := h.dbs.LinkTokenStore.ValidateToken(linkToken) + if err != nil || !isValid { + http.Error(w, fmt.Sprintf("Invalid link token, the token does not exist or is expired:\n%s", err), http.StatusBadRequest) + return + } - userID, err := h.dbs.LinkTokenStore.GetUser(linkToken) - if err != nil { - http.Error(w, fmt.Sprintf("Invalid link token, the token does not exist or is expired:\n%s", err), http.StatusBadRequest) - return - } + userID, err := h.dbs.LinkTokenStore.GetUser(linkToken) + if err != nil { + http.Error(w, fmt.Sprintf("Invalid link token, the token does not exist or is expired:\n%s", err), http.StatusBadRequest) + return + } - userToken, err := h.dbs.UserDB.NewToken(userID) - if err != nil { - http.Error(w, fmt.Sprintf("Error creating new user token:\n%s", err), http.StatusInternalServerError) - return - } + userToken, err := h.dbs.UserDB.NewToken(userID) + if err != nil { + http.Error(w, fmt.Sprintf("Error creating new user token:\n%s", err), http.StatusInternalServerError) + return + } - cookie := http.Cookie{ - Name: "User-Token", - Value: string(userToken), - Path: "/", - MaxAge: 0, - // enable this once we have https - Secure: false, - HttpOnly: true, - SameSite: http.SameSiteDefaultMode, - } - http.SetCookie(w, &cookie) + cookie := http.Cookie{ + Name: "User-Token", + Value: string(userToken), + Path: "/", + MaxAge: 0, + // enable this once we have https + Secure: false, + HttpOnly: true, + SameSite: http.SameSiteDefaultMode, + } + http.SetCookie(w, &cookie) - w.Header().Set("Content-Type", "text/html") + w.Header().Set("Content-Type", "text/html") - tmpl, err := template.ParseFiles("./api/templates/linkredirect.html") - if err != nil { - http.Error(w, "Error parsing template", http.StatusInternalServerError) - return - } + tmpl, err := template.ParseFiles("./api/templates/linkredirect.html") + if err != nil { + http.Error(w, "Error parsing template", http.StatusInternalServerError) + return + } - err = tmpl.Execute(w, LinkRedirect{ - RedirectUrl: fmt.Sprintf("%s://link/%s", h.cProto, userToken), - }) - if err != nil { - http.Error(w, "Error executing template", http.StatusInternalServerError) - return - } + err = tmpl.Execute(w, LinkRedirect{ + RedirectUrl: fmt.Sprintf("%s://link/%s", h.cProto, userToken), + }) + if err != nil { + http.Error(w, "Error executing template", http.StatusInternalServerError) + return + } - err = h.dbs.LinkTokenStore.DeleteToken(userID) - if err != nil { - log.Printf("Error deleting link token: %s", err) - http.Error(w, fmt.Sprintf("Error deleting link token: %s", err), http.StatusInternalServerError) - } + err = h.dbs.LinkTokenStore.DeleteToken(userID) + if err != nil { + log.Printf("Error deleting link token: %s", err) + http.Error(w, fmt.Sprintf("Error deleting link token: %s", err), http.StatusInternalServerError) + } - log.Printf("got /joinsession request\n") + log.Printf("got /joinsession request\n") + }) } diff --git a/EsefexApi/api/routes/getsounds.go b/EsefexApi/api/routes/getsounds.go index 2ffd754..52cbb62 100644 --- a/EsefexApi/api/routes/getsounds.go +++ b/EsefexApi/api/routes/getsounds.go @@ -12,48 +12,50 @@ import ( ) // api/sounds/ -func (h *RouteHandlers) GetSounds(w http.ResponseWriter, r *http.Request) { - vars := mux.Vars(r) - guild_id := types.GuildID(vars["guild_id"]) - - uids, err := h.dbs.SoundDB.GetSoundUIDs(guild_id) - if err != nil { - errorMsg := fmt.Sprintf("Error getting sound uids: %+v", err) - - log.Print(errorMsg) - http.Error(w, errorMsg, http.StatusInternalServerError) - return - } - - sounds := make([]sounddb.SoundMeta, len(uids)) - for i, uid := range uids { - m, err := h.dbs.SoundDB.GetSoundMeta(uid) +func (h *RouteHandlers) GetSounds() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + guild_id := types.GuildID(vars["guild_id"]) + + uids, err := h.dbs.SoundDB.GetSoundUIDs(guild_id) if err != nil { - errorMsg := fmt.Sprintf("Error getting sound meta: %+v", err) + errorMsg := fmt.Sprintf("Error getting sound uids: %+v", err) - log.Println(errorMsg) + log.Print(errorMsg) http.Error(w, errorMsg, http.StatusInternalServerError) return } - sounds[i] = m - } + sounds := make([]sounddb.SoundMeta, len(uids)) + for i, uid := range uids { + m, err := h.dbs.SoundDB.GetSoundMeta(uid) + if err != nil { + errorMsg := fmt.Sprintf("Error getting sound meta: %+v", err) + + log.Println(errorMsg) + http.Error(w, errorMsg, http.StatusInternalServerError) + return + } - jsonResponse, err := json.Marshal(sounds) - if err != nil { - errorMsg := fmt.Sprintf("Error marshalling json: %+v", err) + sounds[i] = m + } + + jsonResponse, err := json.Marshal(sounds) + if err != nil { + errorMsg := fmt.Sprintf("Error marshalling json: %+v", err) - log.Println(errorMsg) - http.Error(w, errorMsg, http.StatusInternalServerError) - return - } + log.Println(errorMsg) + http.Error(w, errorMsg, http.StatusInternalServerError) + return + } - w.Header().Set("Content-Type", "application/json") - _, err = w.Write(jsonResponse) - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - } + w.Header().Set("Content-Type", "application/json") + _, err = w.Write(jsonResponse) + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } - // log.Println("got /sounds request") + // log.Println("got /sounds request") + }) } diff --git a/EsefexApi/api/routes/postplaysound.go b/EsefexApi/api/routes/postplaysound.go index 5871a5c..77d39b0 100644 --- a/EsefexApi/api/routes/postplaysound.go +++ b/EsefexApi/api/routes/postplaysound.go @@ -12,27 +12,31 @@ import ( ) // api/playsound/ -func (h *RouteHandlers) PostPlaySound(w http.ResponseWriter, r *http.Request, userID types.UserID) { - // log.Printf("got /playsound request\n") - timer.SetStart() +func (h *RouteHandlers) PostPlaySound() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + userID := r.Context().Value("user").(types.UserID) - vars := mux.Vars(r) - sound_id := types.SoundID(vars["sound_id"]) + // log.Printf("got /playsound request\n") + timer.SetStart() - err := h.a.PlaySound(sound_id, userID) - if err != nil { - errorMsg := fmt.Sprintf("Error playing sound: \n%+v", err) + vars := mux.Vars(r) + sound_id := types.SoundID(vars["sound_id"]) - log.Println(errorMsg) - http.Error(w, errorMsg, http.StatusInternalServerError) - return - } + err := h.a.PlaySound(sound_id, userID) + if err != nil { + errorMsg := fmt.Sprintf("Error playing sound: \n%+v", err) - _, err = io.WriteString(w, "Play sound!\n") - if err != nil { - log.Println(err) - http.Error(w, err.Error(), http.StatusInternalServerError) - } + log.Println(errorMsg) + http.Error(w, errorMsg, http.StatusInternalServerError) + return + } - timer.MessageElapsed("Played sound") + _, err = io.WriteString(w, "Play sound!\n") + if err != nil { + log.Println(err) + http.Error(w, err.Error(), http.StatusInternalServerError) + } + + timer.MessageElapsed("Played sound") + }) } diff --git a/EsefexApi/api/routes/postplaysoundinsecure.go b/EsefexApi/api/routes/postplaysoundinsecure.go index 5b8bb7b..d6bc5a8 100644 --- a/EsefexApi/api/routes/postplaysoundinsecure.go +++ b/EsefexApi/api/routes/postplaysoundinsecure.go @@ -12,24 +12,26 @@ import ( ) // api/playsound/// -func (h *RouteHandlers) PostPlaySoundInsecure(w http.ResponseWriter, r *http.Request) { - log.Printf("got /playsound request\n") +func (h *RouteHandlers) PostPlaySoundInsecure() http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + log.Printf("got /playsound request\n") - vars := mux.Vars(r) - user_id := types.UserID(vars["user_id"]) - guild_id := types.GuildID(vars["guild_id"]) - sound_id := types.SoundID(vars["sound_id"]) + vars := mux.Vars(r) + user_id := types.UserID(vars["user_id"]) + guild_id := types.GuildID(vars["guild_id"]) + sound_id := types.SoundID(vars["sound_id"]) - err := h.a.PlaySoundInsecure(sounddb.New(guild_id, sound_id), guild_id, user_id) - if err != nil { - errorMsg := fmt.Sprintf("Error playing sound: %+v", err) - log.Println(errorMsg) - http.Error(w, errorMsg, http.StatusInternalServerError) - return - } + err := h.a.PlaySoundInsecure(sounddb.New(guild_id, sound_id), guild_id, user_id) + if err != nil { + errorMsg := fmt.Sprintf("Error playing sound: %+v", err) + log.Println(errorMsg) + http.Error(w, errorMsg, http.StatusInternalServerError) + return + } - _, err = io.WriteString(w, "Play sound!\n") - if err != nil { - log.Println(err) - } + _, err = io.WriteString(w, "Play sound!\n") + if err != nil { + log.Println(err) + } + }) } diff --git a/EsefexApi/bot/commands/cmdhandler/cmdhandler.go b/EsefexApi/bot/commands/cmdhandler/cmdhandler.go new file mode 100644 index 0000000..e390bae --- /dev/null +++ b/EsefexApi/bot/commands/cmdhandler/cmdhandler.go @@ -0,0 +1,16 @@ +package cmdhandler + +import ( + "fmt" + + "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" +) + +type CommandHandlerWithErr func(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) + +func ErrorHandler(msg string) CommandHandlerWithErr { + return func(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + return nil, errors.Wrap(fmt.Errorf(msg), "Error handling command") + } +} diff --git a/EsefexApi/bot/commands/commands.go b/EsefexApi/bot/commands/commands.go index c6590e6..e253f48 100644 --- a/EsefexApi/bot/commands/commands.go +++ b/EsefexApi/bot/commands/commands.go @@ -1,6 +1,8 @@ package commands import ( + "esefexapi/bot/commands/cmdhandler" + "esefexapi/bot/commands/middleware" "esefexapi/db" "fmt" "log" @@ -23,37 +25,39 @@ type SubcommandGroup struct { type CommandHandlers struct { dbs *db.Databases domain string + mw *middleware.CommandMiddleware Commands map[string]*discordgo.ApplicationCommand Handlers map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate) } func NewCommandHandlers(dbs *db.Databases, domain string) *CommandHandlers { - ch := &CommandHandlers{ + c := &CommandHandlers{ dbs: dbs, domain: domain, + mw: middleware.NewCommandMiddleware(dbs), Commands: map[string]*discordgo.ApplicationCommand{}, Handlers: map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate){}, } - ch.Commands["bot"] = BotCommand - ch.Handlers["bot"] = WithErrorHandling(ch.Bot) + c.Commands["bot"] = BotCommand + c.Handlers["bot"] = WithErrorHandling(c.mw.CheckPerms(c.Bot, "Guild.UseSlashCommands")) - ch.Commands["help"] = HelpCommand - ch.Handlers["help"] = WithErrorHandling(ch.Help) + c.Commands["help"] = HelpCommand + c.Handlers["help"] = WithErrorHandling(c.mw.CheckPerms(c.Help, "Guild.UseSlashCommands")) - ch.Commands["permission"] = PermissionCommand - ch.Handlers["permission"] = WithErrorHandling(ch.Permission) + c.Commands["permission"] = PermissionCommand + c.Handlers["permission"] = WithErrorHandling(c.mw.CheckPerms(c.Permission, "Guild.UseSlashCommands", "Guild.ManageUser")) - ch.Commands["sound"] = SoundCommand - ch.Handlers["sound"] = WithErrorHandling(ch.Sound) + c.Commands["sound"] = SoundCommand + c.Handlers["sound"] = WithErrorHandling(c.mw.CheckPerms(c.Sound, "Guild.UseSlashCommands")) - ch.Commands["user"] = UserCommand - ch.Handlers["user"] = WithErrorHandling(ch.User) + c.Commands["user"] = UserCommand + c.Handlers["user"] = WithErrorHandling(c.mw.CheckPerms(c.User, "Guild.UseSlashCommands")) - return ch + return c } -func WithErrorHandling(h func(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error)) func(s *discordgo.Session, i *discordgo.InteractionCreate) { +func WithErrorHandling(h cmdhandler.CommandHandlerWithErr) func(s *discordgo.Session, i *discordgo.InteractionCreate) { return func(s *discordgo.Session, i *discordgo.InteractionCreate) { r, err := h(s, i) if err != nil { diff --git a/EsefexApi/bot/commands/middleware/chkperm.go b/EsefexApi/bot/commands/middleware/chkperm.go new file mode 100644 index 0000000..64e3050 --- /dev/null +++ b/EsefexApi/bot/commands/middleware/chkperm.go @@ -0,0 +1,58 @@ +package middleware + +import ( + "esefexapi/bot/commands/cmdhandler" + "esefexapi/permissions" + "esefexapi/types" + "esefexapi/util/dcgoutil" + "esefexapi/util/refl" + "fmt" + "log" + + "github.com/bwmarrin/discordgo" + "github.com/pkg/errors" +) + +func (m *CommandMiddleware) CheckPerms(next cmdhandler.CommandHandlerWithErr, perms ...string) cmdhandler.CommandHandlerWithErr { + return func(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { + // check if user is admin or owner + hasPerms, err := dcgoutil.UserHasPermissions(i.Member, discordgo.PermissionAdministrator) + if err != nil { + return nil, errors.Wrap(err, "Error checking user permissions") + } + + isOwner, err := dcgoutil.UserIsOwner(s, types.GuildID(i.GuildID), types.UserID(i.Member.User.ID)) + if err != nil { + return nil, errors.Wrap(err, "Error checking user permissions") + } + log.Println("hasPerms:", hasPerms) + + if hasPerms || isOwner { + return next(s, i) + } + + // check if user has permissions + p, err := m.dbs.PermissionDB.Query(types.UserID(i.Member.User.ID), types.GuildID(i.GuildID)) + if err != nil { + return nil, errors.Wrap(err, "Error querying permissions") + } + + for _, permission := range perms { + ps, err := refl.GetNestedFieldValue(p, permission) + if err != nil { + return nil, errors.Wrap(err, "Error getting nested field value") + } + + if !ps.(permissions.PermissionState).Allowed() { + return &discordgo.InteractionResponse{ + Type: discordgo.InteractionResponseChannelMessageWithSource, + Data: &discordgo.InteractionResponseData{ + Content: fmt.Sprintf("You do not have the required permissions to use this command (missing `%s`)", permission), + }, + }, nil + } + } + + return next(s, i) + } +} diff --git a/EsefexApi/bot/commands/middleware/middleware.go b/EsefexApi/bot/commands/middleware/middleware.go new file mode 100644 index 0000000..5ca7155 --- /dev/null +++ b/EsefexApi/bot/commands/middleware/middleware.go @@ -0,0 +1,13 @@ +package middleware + +import "esefexapi/db" + +type CommandMiddleware struct { + dbs *db.Databases +} + +func NewCommandMiddleware(dbs *db.Databases) *CommandMiddleware { + return &CommandMiddleware{ + dbs: dbs, + } +} diff --git a/EsefexApi/bot/commands/permission.go b/EsefexApi/bot/commands/permission.go index 19b520b..f23c5a6 100644 --- a/EsefexApi/bot/commands/permission.go +++ b/EsefexApi/bot/commands/permission.go @@ -198,7 +198,7 @@ func (c *CommandHandlers) PermissionListAll(s *discordgo.Session, i *discordgo.I } resp += fmt.Sprintf("%s: ", uname) - resp += pstr + resp += fmt.Sprintf("`%s`", pstr) resp += "\n" } diff --git a/EsefexApi/bot/commands/sound.go b/EsefexApi/bot/commands/sound.go index 257b27c..475463a 100644 --- a/EsefexApi/bot/commands/sound.go +++ b/EsefexApi/bot/commands/sound.go @@ -69,13 +69,13 @@ var SoundCommand = &discordgo.ApplicationCommand{ func (c *CommandHandlers) Sound(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { switch i.ApplicationCommandData().Options[0].Name { case "upload": - return c.SoundUpload(s, i) + return c.mw.CheckPerms(c.SoundUpload, "Sound.Upload")(s, i) case "delete": - return c.SoundDelete(s, i) + return c.mw.CheckPerms(c.SoundDelete, "Sound.Delete")(s, i) case "list": return c.SoundList(s, i) case "play": - return c.SoundPlay(s, i) + return c.mw.CheckPerms(c.SoundPlay, "Sound.Play")(s, i) default: return nil, errors.Wrap(fmt.Errorf("Unknown subcommand %s", i.ApplicationCommandData().Options[0].Name), "Error handling user command") } diff --git a/EsefexApi/main.go b/EsefexApi/main.go index ca8741b..2b843b0 100644 --- a/EsefexApi/main.go +++ b/EsefexApi/main.go @@ -46,7 +46,7 @@ func main() { udb, err := fileuserdb.NewFileUserDB(cfg.Database.UserdbLocation) Must(err) - fpdb, err := filepermisssiondb.NewFilePermissionDB(cfg.Database.Permissiondblocation) + fpdb, err := filepermisssiondb.NewFilePermissionDB(cfg.Database.Permissiondblocation, ds) Must(err) verT := time.Duration(cfg.VerificationExpiry * float32(time.Minute)) diff --git a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go index aabc0fb..af8273a 100644 --- a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go +++ b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go @@ -9,6 +9,7 @@ import ( "os" "sync" + "github.com/bwmarrin/discordgo" "github.com/pkg/errors" ) @@ -18,9 +19,10 @@ type FilePermissionDB struct { file *os.File rw *sync.RWMutex stack permissions.PermissionStack + ds *discordgo.Session } -func NewFilePermissionDB(path string) (*FilePermissionDB, error) { +func NewFilePermissionDB(path string, ds *discordgo.Session) (*FilePermissionDB, error) { log.Printf("Creating FileDB at %s", path) file, err := util.EnsureFile(path) if err != nil { @@ -31,6 +33,7 @@ func NewFilePermissionDB(path string) (*FilePermissionDB, error) { file: file, rw: &sync.RWMutex{}, stack: permissions.NewPermissionStack(), + ds: ds, } err = fpdb.load() if err != nil { diff --git a/EsefexApi/permissiondb/filepermisssiondb/query.go b/EsefexApi/permissiondb/filepermisssiondb/query.go new file mode 100644 index 0000000..6b05bde --- /dev/null +++ b/EsefexApi/permissiondb/filepermisssiondb/query.go @@ -0,0 +1,38 @@ +package filepermisssiondb + +import ( + "esefexapi/opt" + "esefexapi/permissions" + "esefexapi/types" + "esefexapi/util/dcgoutil" + + "github.com/pkg/errors" +) + +// Query implements permissiondb.PermissionDB. +func (f *FilePermissionDB) Query(user types.UserID, guild types.GuildID) (permissions.Permissions, error) { + f.rw.RLock() + defer f.rw.RUnlock() + + m, err := f.ds.GuildMember(guild.String(), user.String()) + if err != nil { + return permissions.Permissions{}, errors.Wrap(err, "Error getting guild member") + } + + roles := make([]types.RoleID, len(m.Roles)) + for i, r := range m.Roles { + roles[i] = types.RoleID(r) + } + + userChannel, err := dcgoutil.UserGuildVC(f.ds, guild, user) + if err != nil { + return permissions.Permissions{}, errors.Wrap(err, "Error getting user channel") + } + + if userChannel.IsNone() { + return f.stack.Query(user, roles, opt.None[types.ChannelID]()), nil + } else { + chanID := types.ChannelID(userChannel.Unwrap().ChannelID) + return f.stack.Query(user, roles, opt.Some(chanID)), nil + } +} diff --git a/EsefexApi/permissiondb/permissiondb.go b/EsefexApi/permissiondb/permissiondb.go index 70f4ffe..b71d94c 100644 --- a/EsefexApi/permissiondb/permissiondb.go +++ b/EsefexApi/permissiondb/permissiondb.go @@ -15,4 +15,5 @@ type PermissionDB interface { GetUsers() ([]types.UserID, error) GetRoles() ([]types.RoleID, error) GetChannels() ([]types.ChannelID, error) + Query(user types.UserID, guild types.GuildID) (permissions.Permissions, error) } diff --git a/EsefexApi/permissions/merge.go b/EsefexApi/permissions/merge.go index ac51d0d..ecb8a61 100644 --- a/EsefexApi/permissions/merge.go +++ b/EsefexApi/permissions/merge.go @@ -38,7 +38,8 @@ func (p BotPermissions) MergeParent(otherP BotPermissions) BotPermissions { func (p GuildPermissions) MergeParent(otherP GuildPermissions) GuildPermissions { return GuildPermissions{ - ManageBot: p.ManageBot.MergeParent(otherP.ManageBot), - ManageUser: p.ManageUser.MergeParent(otherP.ManageUser), + ManageBot: p.ManageBot.MergeParent(otherP.ManageBot), + ManageUser: p.ManageUser.MergeParent(otherP.ManageUser), + UseSlashCommands: p.UseSlashCommands.MergeParent(otherP.UseSlashCommands), } } diff --git a/EsefexApi/permissions/permissions.go b/EsefexApi/permissions/permissions.go index d3714ad..93ee3af 100644 --- a/EsefexApi/permissions/permissions.go +++ b/EsefexApi/permissions/permissions.go @@ -91,8 +91,9 @@ type BotPermissions struct { } type GuildPermissions struct { - ManageBot PermissionState - ManageUser PermissionState + UseSlashCommands PermissionState + ManageBot PermissionState + ManageUser PermissionState } // Default returns a Permissions struct with all permissions set to Allow. @@ -109,8 +110,9 @@ func NewAllow() Permissions { Leave: Allow, }, Guild: GuildPermissions{ - ManageBot: Allow, - ManageUser: Allow, + UseSlashCommands: Allow, + ManageBot: Allow, + ManageUser: Allow, }, } } @@ -128,8 +130,9 @@ func NewUnset() Permissions { Leave: Unset, }, Guild: GuildPermissions{ - ManageBot: Unset, - ManageUser: Unset, + UseSlashCommands: Unset, + ManageBot: Unset, + ManageUser: Unset, }, } } @@ -147,8 +150,29 @@ func NewDeny() Permissions { Leave: Deny, }, Guild: GuildPermissions{ - ManageBot: Deny, - ManageUser: Deny, + UseSlashCommands: Deny, + ManageBot: Deny, + ManageUser: Deny, + }, + } +} + +func NewEveryoneDefault() Permissions { + return Permissions{ + Sound: SoundPermissions{ + Play: Allow, + Upload: Deny, + Modify: Deny, + Delete: Deny, + }, + Bot: BotPermissions{ + Join: Allow, + Leave: Deny, + }, + Guild: GuildPermissions{ + UseSlashCommands: Allow, + ManageBot: Deny, + ManageUser: Deny, }, } } diff --git a/EsefexApi/permissions/permissions_test.go b/EsefexApi/permissions/permissions_test.go index 8be83cd..f076ebb 100644 --- a/EsefexApi/permissions/permissions_test.go +++ b/EsefexApi/permissions/permissions_test.go @@ -65,8 +65,9 @@ func TestMerge(t *testing.T) { Leave: Unset, }, Guild: GuildPermissions{ - ManageBot: Deny, - ManageUser: Unset, + UseSlashCommands: Unset, + ManageBot: Deny, + ManageUser: Unset, }, } diff --git a/EsefexApi/userdb/fileuserdb/impluserdb.go b/EsefexApi/userdb/fileuserdb/impluserdb.go index 13458e7..c461004 100644 --- a/EsefexApi/userdb/fileuserdb/impluserdb.go +++ b/EsefexApi/userdb/fileuserdb/impluserdb.go @@ -84,7 +84,6 @@ func (f *FileUserDB) NewToken(userID types.UserID) (userdb.Token, error) { } log.Printf("New token for user %s: %s\n", userID, token) - log.Printf("%v", f) go func() { err := f.save() diff --git a/EsefexApi/util/dcgoutil/dcgoutil.go b/EsefexApi/util/dcgoutil/dcgoutil.go index 342e474..de03b7f 100644 --- a/EsefexApi/util/dcgoutil/dcgoutil.go +++ b/EsefexApi/util/dcgoutil/dcgoutil.go @@ -7,6 +7,7 @@ import ( "esefexapi/types" "github.com/bwmarrin/discordgo" + // "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" ) @@ -153,3 +154,16 @@ func UserGuilds(ds *discordgo.Session, userID types.UserID) ([]*discordgo.Guild, return guilds, nil } + +func UserHasPermissions(member *discordgo.Member, perms int64) (bool, error) { + return member.Permissions&perms == perms, nil +} + +func UserIsOwner(ds *discordgo.Session, guildID types.GuildID, userID types.UserID) (bool, error) { + guild, err := ds.Guild(guildID.String()) + if err != nil { + return false, errors.Wrap(err, "Error getting guild") + } + + return guild.OwnerID == userID.String(), nil +} From 6a1ce577cde1bb7b29df420ade061a9f5d539e09 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Fri, 26 Jan 2024 12:52:21 +0100 Subject: [PATCH 16/20] Per server permissions fix, cleaner display --- EsefexApi/api/middleware/permission.go | 2 +- EsefexApi/api/public/simpleui/index.js | 1 - EsefexApi/bot/commands/middleware/chkperm.go | 4 +- EsefexApi/bot/commands/permission.go | 33 ++++++++++------ EsefexApi/bot/commands/util.go | 18 ++++----- .../broken_commands_fix.go | 27 +++++++++++++ .../filepermisssiondb/filepermisssiondb.go | 39 +++++++++++++------ .../permissiondb/filepermisssiondb/impl.go | 36 ++++++++--------- .../permissiondb/filepermisssiondb/query.go | 6 +-- EsefexApi/permissiondb/permissiondb.go | 20 +++++----- EsefexApi/util/iddisplay.go | 4 ++ 11 files changed, 122 insertions(+), 68 deletions(-) create mode 100644 EsefexApi/cmd/testing/broken_commands_fix/broken_commands_fix.go diff --git a/EsefexApi/api/middleware/permission.go b/EsefexApi/api/middleware/permission.go index b4f0baf..9bb7aa1 100644 --- a/EsefexApi/api/middleware/permission.go +++ b/EsefexApi/api/middleware/permission.go @@ -26,7 +26,7 @@ func (m *Middleware) Permission(next http.Handler, perms ...string) http.Handler return } - p, err := m.dbs.PermissionDB.Query(user.ID, types.GuildID(userChan.Unwrap().ChannelID)) + p, err := m.dbs.PermissionDB.Query(types.GuildID(userChan.Unwrap().ChannelID), user.ID) if err != nil { errorMsg := "Error querying permissions: " + err.Error() http.Error(w, errorMsg, http.StatusInternalServerError) diff --git a/EsefexApi/api/public/simpleui/index.js b/EsefexApi/api/public/simpleui/index.js index 964cc16..f205124 100644 --- a/EsefexApi/api/public/simpleui/index.js +++ b/EsefexApi/api/public/simpleui/index.js @@ -1,6 +1,5 @@ async function init() { const soundsDiv = document.getElementById('sounds'); - const userTokenInput = document.getElementById('userTokenInput'); let guildRequest = await fetch('/api/guild', { method: 'GET', diff --git a/EsefexApi/bot/commands/middleware/chkperm.go b/EsefexApi/bot/commands/middleware/chkperm.go index 64e3050..4b293f4 100644 --- a/EsefexApi/bot/commands/middleware/chkperm.go +++ b/EsefexApi/bot/commands/middleware/chkperm.go @@ -7,7 +7,6 @@ import ( "esefexapi/util/dcgoutil" "esefexapi/util/refl" "fmt" - "log" "github.com/bwmarrin/discordgo" "github.com/pkg/errors" @@ -25,14 +24,13 @@ func (m *CommandMiddleware) CheckPerms(next cmdhandler.CommandHandlerWithErr, pe if err != nil { return nil, errors.Wrap(err, "Error checking user permissions") } - log.Println("hasPerms:", hasPerms) if hasPerms || isOwner { return next(s, i) } // check if user has permissions - p, err := m.dbs.PermissionDB.Query(types.UserID(i.Member.User.ID), types.GuildID(i.GuildID)) + p, err := m.dbs.PermissionDB.Query(types.GuildID(i.GuildID), types.UserID(i.Member.User.ID)) if err != nil { return nil, errors.Wrap(err, "Error querying permissions") } diff --git a/EsefexApi/bot/commands/permission.go b/EsefexApi/bot/commands/permission.go index f23c5a6..da6f91b 100644 --- a/EsefexApi/bot/commands/permission.go +++ b/EsefexApi/bot/commands/permission.go @@ -6,8 +6,10 @@ import ( "esefexapi/util" "esefexapi/util/refl" "fmt" + "log" "github.com/bwmarrin/discordgo" + "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" ) @@ -148,13 +150,15 @@ func (c *CommandHandlers) PermissionSet(s *discordgo.Session, i *discordgo.Inter return nil, errors.Wrap(err, "Error setting nested field value") } + guildID := types.GuildID(i.GuildID) + switch ty.PermissionType { case permissions.User: - err = c.dbs.PermissionDB.UpdateUser(types.UserID(ty.ID), p) + err = c.dbs.PermissionDB.UpdateUser(guildID, types.UserID(ty.ID), p) case permissions.Role: - err = c.dbs.PermissionDB.UpdateRole(types.RoleID(ty.ID), p) + err = c.dbs.PermissionDB.UpdateRole(guildID, types.RoleID(ty.ID), p) case permissions.Channel: - err = c.dbs.PermissionDB.UpdateChannel(types.ChannelID(ty.ID), p) + err = c.dbs.PermissionDB.UpdateChannel(guildID, types.ChannelID(ty.ID), p) } if err != nil { @@ -174,7 +178,8 @@ func (c *CommandHandlers) PermissionListAll(s *discordgo.Session, i *discordgo.I resp := "Permissions for all users, channels and roles:\n" resp += "**Users**\n" - uids, err := c.dbs.PermissionDB.GetUsers() + guildID := types.GuildID(i.GuildID) + uids, err := c.dbs.PermissionDB.GetUsers(guildID) if err != nil { return nil, errors.Wrap(err, "Error getting users") } @@ -203,7 +208,7 @@ func (c *CommandHandlers) PermissionListAll(s *discordgo.Session, i *discordgo.I } resp += "**Roles**\n" - rids, err := c.dbs.PermissionDB.GetRoles() + rids, err := c.dbs.PermissionDB.GetRoles(guildID) if err != nil { return nil, errors.Wrap(err, "Error getting roles") } @@ -227,24 +232,28 @@ func (c *CommandHandlers) PermissionListAll(s *discordgo.Session, i *discordgo.I } resp += fmt.Sprintf("%s: ", rmention) - resp += pstr + resp += fmt.Sprintf("`%s`", pstr) resp += "\n" } resp += "**Channels**\n" - cids, err := c.dbs.PermissionDB.GetChannels() + cids, err := c.dbs.PermissionDB.GetChannels(guildID) if err != nil { return nil, errors.Wrap(err, "Error getting channels") } if len(cids) == 0 { resp += "`No channels found.`\n" } + for _, cid := range cids { p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), cid.String()) if err != nil { return nil, errors.Wrap(err, "Error getting permissions") } + log.Printf("%v", cid) + spew.Dump(p) + pstr, err := formatPermissionsCompact(p) if err != nil { return nil, errors.Wrap(err, "Error formatting permissions") @@ -256,7 +265,7 @@ func (c *CommandHandlers) PermissionListAll(s *discordgo.Session, i *discordgo.I } resp += fmt.Sprintf("%s: ", cmention) - resp += pstr + resp += fmt.Sprintf("`%s`", pstr) resp += "\n" } @@ -332,13 +341,15 @@ func (c *CommandHandlers) PermissionClear(s *discordgo.Session, i *discordgo.Int return nil, errors.Wrap(err, "Error getting name") } + guildID := types.GuildID(i.GuildID) + switch ty.PermissionType { case permissions.User: - err = c.dbs.PermissionDB.UpdateUser(types.UserID(ty.ID), permissions.NewUnset()) + err = c.dbs.PermissionDB.UpdateUser(guildID, types.UserID(ty.ID), permissions.NewUnset()) case permissions.Role: - err = c.dbs.PermissionDB.UpdateRole(types.RoleID(ty.ID), permissions.NewUnset()) + err = c.dbs.PermissionDB.UpdateRole(guildID, types.RoleID(ty.ID), permissions.NewUnset()) case permissions.Channel: - err = c.dbs.PermissionDB.UpdateChannel(types.ChannelID(ty.ID), permissions.NewUnset()) + err = c.dbs.PermissionDB.UpdateChannel(guildID, types.ChannelID(ty.ID), permissions.NewUnset()) } if err != nil { return nil, errors.Wrap(err, "Error clearing permissions") diff --git a/EsefexApi/bot/commands/util.go b/EsefexApi/bot/commands/util.go index 8d2712e..3ce1783 100644 --- a/EsefexApi/bot/commands/util.go +++ b/EsefexApi/bot/commands/util.go @@ -8,6 +8,7 @@ import ( "esefexapi/util/refl" "fmt" "regexp" + "strings" "github.com/bwmarrin/discordgo" "github.com/pkg/errors" @@ -140,16 +141,16 @@ func formatPermissions(p permissions.Permissions) (string, error) { func formatPermissionsCompact(p permissions.Permissions) (string, error) { ppaths := refl.FindAllPaths(p) - resp := "" + parts := make([]string, 0, len(ppaths)) for _, ppath := range ppaths { ps, err := refl.GetNestedFieldValue(p, ppath) if err != nil { return "", errors.Wrap(err, "Error getting permission") } - resp += ps.(permissions.PermissionState).Emoji() + parts = append(parts, ps.(permissions.PermissionState).Emoji()) } - return resp, nil + return strings.Join(parts, "|"), nil } type PermissionSet struct { @@ -157,21 +158,20 @@ type PermissionSet struct { ID string } -func getPermissions(s *discordgo.Session, dbs *db.Databases, g types.GuildID, id string) (permissions.Permissions, error) { - ty, err := extractTypeFromString(s, g, id) +func getPermissions(s *discordgo.Session, dbs *db.Databases, guildID types.GuildID, id string) (permissions.Permissions, error) { + ty, err := extractTypeFromString(s, guildID, id) if err != nil { return permissions.Permissions{}, errors.Wrap(err, "Error extracting type from string") } - var p permissions.Permissions switch ty.PermissionType { case permissions.User: - p, err = dbs.PermissionDB.GetUser(types.UserID(ty.ID)) + p, err = dbs.PermissionDB.GetUser(guildID, types.UserID(ty.ID)) case permissions.Role: - p, err = dbs.PermissionDB.GetRole(types.RoleID(ty.ID)) + p, err = dbs.PermissionDB.GetRole(guildID, types.RoleID(ty.ID)) case permissions.Channel: - p, err = dbs.PermissionDB.GetChannel(types.ChannelID(ty.ID)) + p, err = dbs.PermissionDB.GetChannel(guildID, types.ChannelID(ty.ID)) } if err != nil { diff --git a/EsefexApi/cmd/testing/broken_commands_fix/broken_commands_fix.go b/EsefexApi/cmd/testing/broken_commands_fix/broken_commands_fix.go new file mode 100644 index 0000000..5f2b140 --- /dev/null +++ b/EsefexApi/cmd/testing/broken_commands_fix/broken_commands_fix.go @@ -0,0 +1,27 @@ +package main + +import ( + "esefexapi/bot" + . "esefexapi/util/must" + + "github.com/davecgh/go-spew/spew" + "github.com/joho/godotenv" +) + +func main() { + Must(godotenv.Load()) + + ds, err := bot.CreateSession() + Must(err) + + err = ds.Open() + Must(err) + defer ds.Close() + + guildID := "489017101894418444" + + cmds, err := ds.ApplicationCommands(ds.State.User.ID, guildID) + Must(err) + + spew.Dump(cmds) +} diff --git a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go index af8273a..74d021e 100644 --- a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go +++ b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go @@ -4,6 +4,7 @@ import ( "encoding/json" "esefexapi/permissiondb" "esefexapi/permissions" + "esefexapi/types" "esefexapi/util" "log" "os" @@ -16,10 +17,10 @@ import ( var _ permissiondb.PermissionDB = &FilePermissionDB{} type FilePermissionDB struct { - file *os.File - rw *sync.RWMutex - stack permissions.PermissionStack - ds *discordgo.Session + file *os.File + rw *sync.RWMutex + stacks map[types.GuildID]*permissions.PermissionStack + ds *discordgo.Session } func NewFilePermissionDB(path string, ds *discordgo.Session) (*FilePermissionDB, error) { @@ -30,10 +31,10 @@ func NewFilePermissionDB(path string, ds *discordgo.Session) (*FilePermissionDB, } fpdb := &FilePermissionDB{ - file: file, - rw: &sync.RWMutex{}, - stack: permissions.NewPermissionStack(), - ds: ds, + file: file, + rw: &sync.RWMutex{}, + stacks: make(map[types.GuildID]*permissions.PermissionStack), + ds: ds, } err = fpdb.load() if err != nil { @@ -55,13 +56,13 @@ func (f *FilePermissionDB) load() error { } // read file - var perms permissions.PermissionStack + var perms map[types.GuildID]*permissions.PermissionStack err = json.NewDecoder(f.file).Decode(&perms) if err != nil { log.Printf("Error decoding file, creating empty permission stack: (%v)", err) - f.stack = permissions.NewPermissionStack() + f.stacks = make(map[types.GuildID]*permissions.PermissionStack) } else { - f.stack = perms + f.stacks = perms } return nil @@ -92,10 +93,24 @@ func (f FilePermissionDB) save() error { return errors.Wrap(err, "Error truncating file") } - err = json.NewEncoder(f.file).Encode(f.stack) + err = json.NewEncoder(f.file).Encode(f.stacks) if err != nil { return errors.Wrap(err, "Error encoding file") } return nil } + +// ensureGuild ensures that the guild exists in the permission stack. +// It assumes that the lock is already held. +func (f *FilePermissionDB) ensureGuild(guild types.GuildID) *permissions.PermissionStack { + if _, ok := f.stacks[guild]; !ok { + ps := permissions.NewPermissionStack() + + f.stacks[guild] = &ps + ps.SetChannel(types.ChannelID("everyone"), permissions.NewEveryoneDefault()) + go f.save() + } + + return f.stacks[guild] +} diff --git a/EsefexApi/permissiondb/filepermisssiondb/impl.go b/EsefexApi/permissiondb/filepermisssiondb/impl.go index a31ce3d..6efe417 100644 --- a/EsefexApi/permissiondb/filepermisssiondb/impl.go +++ b/EsefexApi/permissiondb/filepermisssiondb/impl.go @@ -6,89 +6,89 @@ import ( ) // GetChannel implements permissiondb.PermissionDB. -func (f *FilePermissionDB) GetChannel(channelID types.ChannelID) (permissions.Permissions, error) { +func (f *FilePermissionDB) GetChannel(guildID types.GuildID, channelID types.ChannelID) (permissions.Permissions, error) { f.rw.RLock() defer f.rw.RUnlock() - return f.stack.GetChannel(channelID), nil + return f.ensureGuild(guildID).GetChannel(channelID), nil } // GetRole implements permissiondb.PermissionDB. -func (f *FilePermissionDB) GetRole(roleID types.RoleID) (permissions.Permissions, error) { +func (f *FilePermissionDB) GetRole(guildID types.GuildID, roleID types.RoleID) (permissions.Permissions, error) { f.rw.RLock() defer f.rw.RUnlock() - return f.stack.GetRole(roleID), nil + return f.ensureGuild(guildID).GetRole(roleID), nil } // GetUser implements permissiondb.PermissionDB. -func (f *FilePermissionDB) GetUser(userID types.UserID) (permissions.Permissions, error) { +func (f *FilePermissionDB) GetUser(guildID types.GuildID, userID types.UserID) (permissions.Permissions, error) { f.rw.RLock() defer f.rw.RUnlock() - return f.stack.GetUser(userID), nil + return f.ensureGuild(guildID).GetUser(userID), nil } // UpdateChannel implements permissiondb.PermissionDB. -func (f *FilePermissionDB) UpdateChannel(channelID types.ChannelID, p permissions.Permissions) error { +func (f *FilePermissionDB) UpdateChannel(guildID types.GuildID, channelID types.ChannelID, p permissions.Permissions) error { f.rw.Lock() defer f.rw.Unlock() - f.stack.UpdateChannel(channelID, p) + f.ensureGuild(guildID).UpdateChannel(channelID, p) go f.save() return nil } // UpdateRole implements permissiondb.PermissionDB. -func (f *FilePermissionDB) UpdateRole(roleID types.RoleID, p permissions.Permissions) error { +func (f *FilePermissionDB) UpdateRole(guildID types.GuildID, roleID types.RoleID, p permissions.Permissions) error { f.rw.Lock() defer f.rw.Unlock() - f.stack.UpdateRole(roleID, p) + f.ensureGuild(guildID).UpdateRole(roleID, p) go f.save() return nil } // UpdateUser implements permissiondb.PermissionDB. -func (f *FilePermissionDB) UpdateUser(userID types.UserID, p permissions.Permissions) error { +func (f *FilePermissionDB) UpdateUser(guildID types.GuildID, userID types.UserID, p permissions.Permissions) error { f.rw.Lock() defer f.rw.Unlock() - f.stack.UpdateUser(userID, p) + f.ensureGuild(guildID).UpdateUser(userID, p) go f.save() return nil } -func (f *FilePermissionDB) GetUsers() ([]types.UserID, error) { +func (f *FilePermissionDB) GetUsers(guildID types.GuildID) ([]types.UserID, error) { f.rw.RLock() defer f.rw.RUnlock() var users []types.UserID - for k := range f.stack.User { + for k := range f.ensureGuild(guildID).User { users = append(users, k) } return users, nil } -func (f *FilePermissionDB) GetRoles() ([]types.RoleID, error) { +func (f *FilePermissionDB) GetRoles(guildID types.GuildID) ([]types.RoleID, error) { f.rw.RLock() defer f.rw.RUnlock() var roles []types.RoleID - for k := range f.stack.Role { + for k := range f.ensureGuild(guildID).Role { roles = append(roles, k) } return roles, nil } -func (f *FilePermissionDB) GetChannels() ([]types.ChannelID, error) { +func (f *FilePermissionDB) GetChannels(guildID types.GuildID) ([]types.ChannelID, error) { f.rw.RLock() defer f.rw.RUnlock() var channels []types.ChannelID - for k := range f.stack.Channel { + for k := range f.ensureGuild(guildID).Channel { channels = append(channels, k) } diff --git a/EsefexApi/permissiondb/filepermisssiondb/query.go b/EsefexApi/permissiondb/filepermisssiondb/query.go index 6b05bde..c185e37 100644 --- a/EsefexApi/permissiondb/filepermisssiondb/query.go +++ b/EsefexApi/permissiondb/filepermisssiondb/query.go @@ -10,7 +10,7 @@ import ( ) // Query implements permissiondb.PermissionDB. -func (f *FilePermissionDB) Query(user types.UserID, guild types.GuildID) (permissions.Permissions, error) { +func (f *FilePermissionDB) Query(guild types.GuildID, user types.UserID) (permissions.Permissions, error) { f.rw.RLock() defer f.rw.RUnlock() @@ -30,9 +30,9 @@ func (f *FilePermissionDB) Query(user types.UserID, guild types.GuildID) (permis } if userChannel.IsNone() { - return f.stack.Query(user, roles, opt.None[types.ChannelID]()), nil + return f.ensureGuild(guild).Query(user, roles, opt.None[types.ChannelID]()), nil } else { chanID := types.ChannelID(userChannel.Unwrap().ChannelID) - return f.stack.Query(user, roles, opt.Some(chanID)), nil + return f.ensureGuild(guild).Query(user, roles, opt.Some(chanID)), nil } } diff --git a/EsefexApi/permissiondb/permissiondb.go b/EsefexApi/permissiondb/permissiondb.go index b71d94c..9709a0e 100644 --- a/EsefexApi/permissiondb/permissiondb.go +++ b/EsefexApi/permissiondb/permissiondb.go @@ -6,14 +6,14 @@ import ( ) type PermissionDB interface { - GetUser(userID types.UserID) (permissions.Permissions, error) - GetRole(roleID types.RoleID) (permissions.Permissions, error) - GetChannel(channelID types.ChannelID) (permissions.Permissions, error) - UpdateUser(userID types.UserID, p permissions.Permissions) error - UpdateRole(roleID types.RoleID, p permissions.Permissions) error - UpdateChannel(channelID types.ChannelID, p permissions.Permissions) error - GetUsers() ([]types.UserID, error) - GetRoles() ([]types.RoleID, error) - GetChannels() ([]types.ChannelID, error) - Query(user types.UserID, guild types.GuildID) (permissions.Permissions, error) + GetUser(guild types.GuildID, userID types.UserID) (permissions.Permissions, error) + GetRole(guild types.GuildID, roleID types.RoleID) (permissions.Permissions, error) + GetChannel(guild types.GuildID, channelID types.ChannelID) (permissions.Permissions, error) + UpdateUser(guild types.GuildID, userID types.UserID, p permissions.Permissions) error + UpdateRole(guild types.GuildID, roleID types.RoleID, p permissions.Permissions) error + UpdateChannel(guild types.GuildID, channelID types.ChannelID, p permissions.Permissions) error + GetUsers(guild types.GuildID) ([]types.UserID, error) + GetRoles(guild types.GuildID) ([]types.RoleID, error) + GetChannels(guild types.GuildID) ([]types.ChannelID, error) + Query(guild types.GuildID, user types.UserID) (permissions.Permissions, error) } diff --git a/EsefexApi/util/iddisplay.go b/EsefexApi/util/iddisplay.go index 4901d46..fda3aae 100644 --- a/EsefexApi/util/iddisplay.go +++ b/EsefexApi/util/iddisplay.go @@ -77,6 +77,10 @@ func RoleIDMention(ds *discordgo.Session, g types.GuildID, r types.RoleID) (stri } func ChannelIDMention(ds *discordgo.Session, g types.GuildID, c types.ChannelID) (string, error) { + if c == "everyone" { + return "`@everyone`", nil + } + channels, err := ds.GuildChannels(g.String()) if err != nil { return "", errors.Wrap(err, "Error getting channels") From ee6bb92f14736429513a8af722c9243b746fff7d Mon Sep 17 00:00:00 2001 From: jokil123 Date: Fri, 26 Jan 2024 18:23:39 +0100 Subject: [PATCH 17/20] check if commands check before updating (no rate limit, faster init time) --- EsefexApi/bot/bot.go | 36 ++++------ .../bot/commands/cmdhashstore/cmdhashstore.go | 52 ++++++++++++++ EsefexApi/bot/commands/commands.go | 19 ++++- EsefexApi/bot/commands/deleteallcommands.go | 44 ++++++++++++ EsefexApi/bot/commands/registerappcommands.go | 25 +++++++ EsefexApi/bot/commands/registercmdhandlers.go | 17 +++++ EsefexApi/bot/commands/updateappcommands.go | 51 ++++++++++++++ EsefexApi/bot/util.go | 70 +------------------ EsefexApi/config.toml | 1 + EsefexApi/config/config.go | 1 + EsefexApi/db/db.go | 37 ++++++++++ EsefexApi/main.go | 4 ++ 12 files changed, 265 insertions(+), 92 deletions(-) create mode 100644 EsefexApi/bot/commands/cmdhashstore/cmdhashstore.go create mode 100644 EsefexApi/bot/commands/deleteallcommands.go create mode 100644 EsefexApi/bot/commands/registerappcommands.go create mode 100644 EsefexApi/bot/commands/registercmdhandlers.go create mode 100644 EsefexApi/bot/commands/updateappcommands.go diff --git a/EsefexApi/bot/bot.go b/EsefexApi/bot/bot.go index d56cf0a..5dbef1a 100644 --- a/EsefexApi/bot/bot.go +++ b/EsefexApi/bot/bot.go @@ -14,18 +14,18 @@ var _ service.IService = &DiscordBot{} // DiscordBot implements Service type DiscordBot struct { - Session *discordgo.Session - cmdh *commands.CommandHandlers - stop chan struct{} - ready chan struct{} + ds *discordgo.Session + cmdh *commands.CommandHandlers + stop chan struct{} + ready chan struct{} } -func NewDiscordBot(s *discordgo.Session, dbs *db.Databases, domain string) *DiscordBot { +func NewDiscordBot(ds *discordgo.Session, dbs *db.Databases, domain string) *DiscordBot { return &DiscordBot{ - Session: s, - cmdh: commands.NewCommandHandlers(dbs, domain), - stop: make(chan struct{}, 1), - ready: make(chan struct{}), + ds: ds, + cmdh: commands.NewCommandHandlers(ds, dbs, domain), + stop: make(chan struct{}, 1), + ready: make(chan struct{}), } } @@ -34,12 +34,12 @@ func (b *DiscordBot) run() { log.Println("Starting bot...") defer log.Println("Bot stopped") - ds := b.Session + ds := b.ds ds.Identify.Intents = discordgo.IntentsAllWithoutPrivileged | discordgo.IntentsGuildMembers | discordgo.IntentsGuildPresences ready := b.WaitReady() - log.Println("Registering command handlers...") - b.RegisterComandHandlers() + + b.cmdh.RegisterComandHandlers() err := ds.Open() if err != nil { @@ -49,19 +49,11 @@ func (b *DiscordBot) run() { <-ready - log.Println("Registering commands...") - err = b.RegisterComands() + err = b.cmdh.UpdateApplicationCommands() if err != nil { - log.Printf("Cannot register commands: %+v", err) + log.Printf("Cannot update command handlers: %+v", err) } - defer func() { - err = b.DeleteAllCommands() - if err != nil { - log.Printf("Cannot delete commands: %+v", err) - } - }() - log.Println("Bot Ready.") close(b.ready) <-b.stop diff --git a/EsefexApi/bot/commands/cmdhashstore/cmdhashstore.go b/EsefexApi/bot/commands/cmdhashstore/cmdhashstore.go new file mode 100644 index 0000000..5afeb1b --- /dev/null +++ b/EsefexApi/bot/commands/cmdhashstore/cmdhashstore.go @@ -0,0 +1,52 @@ +package cmdhashstore + +import ( + "esefexapi/util" + "io" + "os" +) + +type CommandHashStore interface { + GetCommandHash() (string, error) + SetCommandHash(hash string) error +} + +type FileCmdHashStore struct { + FilePath string +} + +func NewFileCmdHashStore(filePath string) *FileCmdHashStore { + return &FileCmdHashStore{ + FilePath: filePath, + } +} + +func (f *FileCmdHashStore) GetCommandHash() (string, error) { + file, err := util.EnsureFile(f.FilePath) + if err != nil { + return "", err + } + defer file.Close() + + hash, err := io.ReadAll(file) + if err != nil { + return "", err + } + + return string(hash), nil +} + +func (f *FileCmdHashStore) SetCommandHash(hash string) error { + file, err := os.Create(f.FilePath) + if err != nil { + return err + } + defer file.Close() + + _, err = file.WriteString(hash) + if err != nil { + return err + } + + return nil +} diff --git a/EsefexApi/bot/commands/commands.go b/EsefexApi/bot/commands/commands.go index e253f48..630c845 100644 --- a/EsefexApi/bot/commands/commands.go +++ b/EsefexApi/bot/commands/commands.go @@ -1,6 +1,8 @@ package commands import ( + "crypto/sha256" + "encoding/json" "esefexapi/bot/commands/cmdhandler" "esefexapi/bot/commands/middleware" "esefexapi/db" @@ -23,6 +25,7 @@ type SubcommandGroup struct { } type CommandHandlers struct { + ds *discordgo.Session dbs *db.Databases domain string mw *middleware.CommandMiddleware @@ -30,8 +33,9 @@ type CommandHandlers struct { Handlers map[string]func(s *discordgo.Session, i *discordgo.InteractionCreate) } -func NewCommandHandlers(dbs *db.Databases, domain string) *CommandHandlers { +func NewCommandHandlers(ds *discordgo.Session, dbs *db.Databases, domain string) *CommandHandlers { c := &CommandHandlers{ + ds: ds, dbs: dbs, domain: domain, mw: middleware.NewCommandMiddleware(dbs), @@ -91,3 +95,16 @@ func OptionsMap(options []*discordgo.ApplicationCommandInteractionDataOption) ma return optionMap } + +// ApplicationCommandsHash returns a hash of the application commands +// This is used to determine if the commands need to be updated +// The hash is based on the CommandHandlers.Commands field +func (c *CommandHandlers) ApplicationCommandsHash() (string, error) { + data, err := json.Marshal(c.Commands) + if err != nil { + return "", err + } + + hash := sha256.Sum256(data) + return fmt.Sprintf("%x", hash), nil +} diff --git a/EsefexApi/bot/commands/deleteallcommands.go b/EsefexApi/bot/commands/deleteallcommands.go new file mode 100644 index 0000000..e21f3bc --- /dev/null +++ b/EsefexApi/bot/commands/deleteallcommands.go @@ -0,0 +1,44 @@ +package commands + +import ( + "log" + + "github.com/pkg/errors" +) + +func (c *CommandHandlers) DeleteGuildApplicationCommands(guildID string) error { + cmds, err := c.ds.ApplicationCommands(c.ds.State.User.ID, guildID) + if err != nil { + return errors.Wrapf(err, "Cannot get commands for guild '%v'", guildID) + } + + for _, v := range cmds { + err = c.ds.ApplicationCommandDelete(c.ds.State.User.ID, guildID, v.ID) + if err != nil { + return errors.Wrapf(err, "Cannot delete '%v' command", v.Name) + } + log.Printf("Deleted '%v' command", v.Name) + } + + return nil +} + +func (c *CommandHandlers) DeleteAllApplicationCommands() error { + log.Println("Deleting all commands...") + + for _, g := range c.ds.State.Guilds { + err := c.DeleteGuildApplicationCommands(g.ID) + if err != nil { + return errors.Wrapf(err, "Cannot delete commands for guild '%v'", g.ID) + } + } + + err := c.DeleteGuildApplicationCommands("") + if err != nil { + return errors.Wrap(err, "Cannot delete global commands") + } + + log.Println("Deleted all commands") + + return nil +} diff --git a/EsefexApi/bot/commands/registerappcommands.go b/EsefexApi/bot/commands/registerappcommands.go new file mode 100644 index 0000000..14ac3b7 --- /dev/null +++ b/EsefexApi/bot/commands/registerappcommands.go @@ -0,0 +1,25 @@ +package commands + +import ( + "log" + + "github.com/pkg/errors" +) + +// Call after opening the session +func (c *CommandHandlers) RegisterApplicationCommands() error { + log.Println("Registering application commands...") + + for _, v := range c.Commands { + _, err := c.ds.ApplicationCommandCreate(c.ds.State.User.ID, "", v) + if err != nil { + return errors.Wrapf(err, "Cannot create '%v' command", v.Name) + } + + log.Printf("Registered '%v' command", v.Name) + } + + log.Println("Registered all application commands") + + return nil +} diff --git a/EsefexApi/bot/commands/registercmdhandlers.go b/EsefexApi/bot/commands/registercmdhandlers.go new file mode 100644 index 0000000..2ab3bf4 --- /dev/null +++ b/EsefexApi/bot/commands/registercmdhandlers.go @@ -0,0 +1,17 @@ +package commands + +import ( + "log" + + "github.com/bwmarrin/discordgo" +) + +// call this before opening the session +func (c *CommandHandlers) RegisterComandHandlers() { + log.Println("Registering command handlers...") + c.ds.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { + if h, ok := c.Handlers[i.ApplicationCommandData().Name]; ok { + h(s, i) + } + }) +} diff --git a/EsefexApi/bot/commands/updateappcommands.go b/EsefexApi/bot/commands/updateappcommands.go new file mode 100644 index 0000000..5eb0e92 --- /dev/null +++ b/EsefexApi/bot/commands/updateappcommands.go @@ -0,0 +1,51 @@ +package commands + +import ( + "log" + + "github.com/pkg/errors" +) + +// get hash of current application commands and compare to the stored hash +// if the hashes are the same, return +// if the hashes are different, delete all application commands and register new ones +// update the stored hash +// This is done to avoid hitting the rate limit for registering application commands (200 per day) +// The rate limit is not usually a problem in production, but it can be if you are developing and testing a lot +func (c *CommandHandlers) UpdateApplicationCommands() error { + log.Println("Updating application commands...") + oldHash, err := c.dbs.CmdHashStore.GetCommandHash() + if err != nil { + return errors.Wrap(err, "Cannot get command hash") + } + + newHash, err := c.ApplicationCommandsHash() + if err != nil { + return errors.Wrap(err, "Cannot get application command hash") + } + + if oldHash == newHash { + log.Println("Command hashes are the same, skipping update") + return nil + } + log.Println("Command hashes are different, updating commands...") + + err = c.DeleteAllApplicationCommands() + if err != nil { + return errors.Wrap(err, "Cannot delete all commands") + } + + err = c.RegisterApplicationCommands() + if err != nil { + return errors.Wrap(err, "Cannot register commands") + } + + err = c.dbs.CmdHashStore.SetCommandHash(newHash) + if err != nil { + return errors.Wrap(err, "Cannot set command hash") + } + + log.Println("Finished updating application commands") + + return nil +} diff --git a/EsefexApi/bot/util.go b/EsefexApi/bot/util.go index c5931fc..a86a0d7 100644 --- a/EsefexApi/bot/util.go +++ b/EsefexApi/bot/util.go @@ -10,74 +10,6 @@ import ( "github.com/bwmarrin/discordgo" ) -// call this before opening the session -func (b *DiscordBot) RegisterComandHandlers() { - ds := b.Session - - ds.AddHandler(func(s *discordgo.Session, i *discordgo.InteractionCreate) { - if h, ok := b.cmdh.Handlers[i.ApplicationCommandData().Name]; ok { - h(s, i) - } - }) -} - -// Call after opening the session -func (b *DiscordBot) RegisterComands() error { - ds := b.Session - - for _, v := range b.cmdh.Commands { - _, err := ds.ApplicationCommandCreate(ds.State.User.ID, "", v) - if err != nil { - return errors.Wrapf(err, "Cannot create '%v' command", v.Name) - } - - log.Printf("Registered '%v' command", v.Name) - } - - return nil -} - -func (b *DiscordBot) DeleteAllCommands() error { - ds := b.Session - - log.Println("Deleting all commands...") - - for _, g := range ds.State.Guilds { - err := b.DeleteGuildCommands(g.ID) - if err != nil { - return errors.Wrapf(err, "Cannot delete commands for guild '%v'", g.ID) - } - } - - err := b.DeleteGuildCommands("") - if err != nil { - return errors.Wrap(err, "Cannot delete global commands") - } - - log.Println("Deleted all commands") - - return nil -} - -func (b *DiscordBot) DeleteGuildCommands(guildID string) error { - ds := b.Session - - cmds, err := ds.ApplicationCommands(ds.State.User.ID, guildID) - if err != nil { - return errors.Wrapf(err, "Cannot get commands for guild '%v'", guildID) - } - - for _, v := range cmds { - err = ds.ApplicationCommandDelete(ds.State.User.ID, guildID, v.ID) - if err != nil { - return errors.Wrapf(err, "Cannot delete '%v' command", v.Name) - } - log.Printf("Deleted '%v' command", v.Name) - } - - return nil -} - var BotTokenNotSet = fmt.Errorf("BOT_TOKEN is not set") func CreateSession() (*discordgo.Session, error) { @@ -98,7 +30,7 @@ func CreateSession() (*discordgo.Session, error) { func (ds *DiscordBot) WaitReady() chan struct{} { ready := make(chan struct{}, 1) - ds.Session.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { + ds.ds.AddHandler(func(s *discordgo.Session, r *discordgo.Ready) { log.Printf("Logged in as: %v#%v", s.State.User.Username, s.State.User.Discriminator) close(ready) }) diff --git a/EsefexApi/config.toml b/EsefexApi/config.toml index 36e1b5c..cdde243 100644 --- a/EsefexApi/config.toml +++ b/EsefexApi/config.toml @@ -8,6 +8,7 @@ custom_protocol = "esefex" sounddb_location = "./data/sounds" userdb_location = "./data/users/users.json" permissiondb_location = "./data/permissions/permissions.json" +cmd_hash_store_location = "./data/commands/cmd_hash.json" [bot] use_timeouts = false diff --git a/EsefexApi/config/config.go b/EsefexApi/config/config.go index 235e035..c150e7a 100644 --- a/EsefexApi/config/config.go +++ b/EsefexApi/config/config.go @@ -24,6 +24,7 @@ type Database struct { SounddbLocation string `toml:"sounddb_location"` UserdbLocation string `toml:"userdb_location"` Permissiondblocation string `toml:"permissiondb_location"` + CmdHashStoreLocation string `toml:"cmd_hash_store_location"` } type Bot struct { diff --git a/EsefexApi/db/db.go b/EsefexApi/db/db.go index 56197e6..ed0b518 100644 --- a/EsefexApi/db/db.go +++ b/EsefexApi/db/db.go @@ -1,6 +1,7 @@ package db import ( + "esefexapi/bot/commands/cmdhashstore" "esefexapi/linktokenstore" "esefexapi/permissiondb" "esefexapi/sounddb" @@ -12,4 +13,40 @@ type Databases struct { UserDB userdb.IUserDB LinkTokenStore linktokenstore.ILinkTokenStore PermissionDB permissiondb.PermissionDB + CmdHashStore cmdhashstore.CommandHashStore } + +// func CreateDatabases(cfg *config.Config, ds *discordgo.Session) (*Databases, error) { +// dbs := &Databases{} + +// sdb, err := filesounddb.NewFileDB(cfg.Database.SounddbLocation) +// if err != nil { +// return nil, err +// } +// sdbc, err := dbcache.NewSoundDBCache(sdb) +// if err != nil { +// return nil, err +// } +// dbs.SoundDB = sdbc + +// udb, err := fileuserdb.NewFileUserDB(cfg.Database.UserdbLocation) +// if err != nil { +// return nil, err +// } +// dbs.UserDB = udb + +// pdb, err := filepermisssiondb.NewFilePermissionDB(cfg.Database.Permissiondblocation, ds) +// if err != nil { +// return nil, err +// } +// dbs.PermissionDB = pdb + +// verT := time.Duration(cfg.VerificationExpiry * float32(time.Minute)) +// ldb := memorylinktokenstore.NewMemoryLinkTokenStore(verT) +// dbs.LinkTokenStore = ldb + +// hdb := cmdhashstore.NewFileCmdHashStore(cfg.Database.CmdHashStoreLocation) +// dbs.CmdHashStore = hdb + +// return dbs, nil +// } diff --git a/EsefexApi/main.go b/EsefexApi/main.go index 2b843b0..8552db1 100644 --- a/EsefexApi/main.go +++ b/EsefexApi/main.go @@ -4,6 +4,7 @@ import ( "esefexapi/api" "esefexapi/audioplayer/discordplayer" "esefexapi/bot" + "esefexapi/bot/commands/cmdhashstore" "esefexapi/config" "esefexapi/db" "esefexapi/linktokenstore/memorylinktokenstore" @@ -52,11 +53,14 @@ func main() { verT := time.Duration(cfg.VerificationExpiry * float32(time.Minute)) ldb := memorylinktokenstore.NewMemoryLinkTokenStore(verT) + fcmhs := cmdhashstore.NewFileCmdHashStore(cfg.Database.CmdHashStoreLocation) + dbs := &db.Databases{ SoundDB: sdbc, UserDB: udb, LinkTokenStore: ldb, PermissionDB: fpdb, + CmdHashStore: fcmhs, } botT := time.Duration(cfg.Bot.Timeout * float32(time.Minute)) From 9a869e321b4b30091f1713d216413c8d74a46889 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Fri, 26 Jan 2024 19:24:58 +0100 Subject: [PATCH 18/20] renamed fields, fixed default perms --- .../filepermisssiondb/filepermisssiondb.go | 2 +- .../permissiondb/filepermisssiondb/impl.go | 6 +- EsefexApi/permissions/stack.go | 66 +++++++++---------- 3 files changed, 37 insertions(+), 37 deletions(-) diff --git a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go index 74d021e..3d7ca8a 100644 --- a/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go +++ b/EsefexApi/permissiondb/filepermisssiondb/filepermisssiondb.go @@ -108,7 +108,7 @@ func (f *FilePermissionDB) ensureGuild(guild types.GuildID) *permissions.Permiss ps := permissions.NewPermissionStack() f.stacks[guild] = &ps - ps.SetChannel(types.ChannelID("everyone"), permissions.NewEveryoneDefault()) + ps.SetRole(types.RoleID("everyone"), permissions.NewEveryoneDefault()) go f.save() } diff --git a/EsefexApi/permissiondb/filepermisssiondb/impl.go b/EsefexApi/permissiondb/filepermisssiondb/impl.go index 6efe417..6f5abe1 100644 --- a/EsefexApi/permissiondb/filepermisssiondb/impl.go +++ b/EsefexApi/permissiondb/filepermisssiondb/impl.go @@ -64,7 +64,7 @@ func (f *FilePermissionDB) GetUsers(guildID types.GuildID) ([]types.UserID, erro defer f.rw.RUnlock() var users []types.UserID - for k := range f.ensureGuild(guildID).User { + for k := range f.ensureGuild(guildID).Users { users = append(users, k) } @@ -76,7 +76,7 @@ func (f *FilePermissionDB) GetRoles(guildID types.GuildID) ([]types.RoleID, erro defer f.rw.RUnlock() var roles []types.RoleID - for k := range f.ensureGuild(guildID).Role { + for k := range f.ensureGuild(guildID).Roles { roles = append(roles, k) } @@ -88,7 +88,7 @@ func (f *FilePermissionDB) GetChannels(guildID types.GuildID) ([]types.ChannelID defer f.rw.RUnlock() var channels []types.ChannelID - for k := range f.ensureGuild(guildID).Channel { + for k := range f.ensureGuild(guildID).Channels { channels = append(channels, k) } diff --git a/EsefexApi/permissions/stack.go b/EsefexApi/permissions/stack.go index 54930af..7efff2f 100644 --- a/EsefexApi/permissions/stack.go +++ b/EsefexApi/permissions/stack.go @@ -7,111 +7,111 @@ import ( ) type PermissionStack struct { - User map[types.UserID]Permissions - Role map[types.RoleID]Permissions - Channel map[types.ChannelID]Permissions + Users map[types.UserID]Permissions + Roles map[types.RoleID]Permissions + Channels map[types.ChannelID]Permissions } func NewPermissionStack() PermissionStack { return PermissionStack{ - User: make(map[types.UserID]Permissions), - Role: make(map[types.RoleID]Permissions), - Channel: make(map[types.ChannelID]Permissions), + Users: make(map[types.UserID]Permissions), + Roles: make(map[types.RoleID]Permissions), + Channels: make(map[types.ChannelID]Permissions), } } func (ps *PermissionStack) GetUser(userID types.UserID) Permissions { - if u, ok := ps.User[userID]; ok { + if u, ok := ps.Users[userID]; ok { return u } return NewUnset() } func (ps *PermissionStack) GetRole(roleID types.RoleID) Permissions { - if r, ok := ps.Role[roleID]; ok { + if r, ok := ps.Roles[roleID]; ok { return r } return NewUnset() } func (ps *PermissionStack) GetChannel(channelID types.ChannelID) Permissions { - if c, ok := ps.Channel[channelID]; ok { + if c, ok := ps.Channels[channelID]; ok { return c } return NewUnset() } func (ps *PermissionStack) SetUser(user types.UserID, p Permissions) { - ps.User[user] = p + ps.Users[user] = p } func (ps *PermissionStack) SetRole(role types.RoleID, p Permissions) { - ps.Role[role] = p + ps.Roles[role] = p } func (ps *PermissionStack) SetChannel(channel types.ChannelID, p Permissions) { - ps.Channel[channel] = p + ps.Channels[channel] = p } func (ps *PermissionStack) UnsetUser(user types.UserID) { - delete(ps.User, user) + delete(ps.Users, user) } func (ps *PermissionStack) UnsetRole(role types.RoleID) { - delete(ps.Role, role) + delete(ps.Roles, role) } func (ps *PermissionStack) UnsetChannel(channel types.ChannelID) { - delete(ps.Channel, channel) + delete(ps.Channels, channel) } func (ps *PermissionStack) UpdateUser(user types.UserID, p Permissions) { - if _, ok := ps.User[user]; !ok { - ps.User[user] = NewUnset() + if _, ok := ps.Users[user]; !ok { + ps.Users[user] = NewUnset() } - ps.User[user] = ps.User[user].MergeParent(p) + ps.Users[user] = ps.Users[user].MergeParent(p) ps.clean() } func (ps *PermissionStack) UpdateRole(role types.RoleID, p Permissions) { - if _, ok := ps.Role[role]; !ok { - ps.Role[role] = NewUnset() + if _, ok := ps.Roles[role]; !ok { + ps.Roles[role] = NewUnset() } - ps.Role[role] = ps.Role[role].MergeParent(p) + ps.Roles[role] = ps.Roles[role].MergeParent(p) ps.clean() } func (ps *PermissionStack) UpdateChannel(channel types.ChannelID, p Permissions) { - if _, ok := ps.Channel[channel]; !ok { - ps.Channel[channel] = NewUnset() + if _, ok := ps.Channels[channel]; !ok { + ps.Channels[channel] = NewUnset() } - ps.Channel[channel] = ps.Channel[channel].MergeParent(p) + ps.Channels[channel] = ps.Channels[channel].MergeParent(p) ps.clean() } // clean removes all permissions that are just unset. func (ps *PermissionStack) clean() { - for user, p := range ps.User { + for user, p := range ps.Users { if p == NewUnset() { - delete(ps.User, user) + delete(ps.Users, user) } } - for role, p := range ps.Role { + for role, p := range ps.Roles { if p == NewUnset() { - delete(ps.Role, role) + delete(ps.Roles, role) } } - for channel, p := range ps.Channel { + for channel, p := range ps.Channels { if p == NewUnset() { - delete(ps.Channel, channel) + delete(ps.Channels, channel) } } } @@ -122,18 +122,18 @@ func (ps *PermissionStack) clean() { // If a channel has a permission set, it will override the role permissions. // roles is a list of roles that the user has in order of precedence. func (ps *PermissionStack) Query(user types.UserID, roles []types.RoleID, channel opt.Option[types.ChannelID]) Permissions { - userPS := ps.User[user] + userPS := ps.Users[user] slices.Reverse(roles) rolesPS := NewUnset() for _, role := range roles { - r := ps.Role[role] + r := ps.Roles[role] rolesPS = rolesPS.MergeParent(r) } var channelPS Permissions = NewUnset() if channel.IsSome() { - channelPS = ps.Channel[channel.Unwrap()] + channelPS = ps.Channels[channel.Unwrap()] } return NewUnset().MergeParent(rolesPS).MergeParent(channelPS).MergeParent(userPS) From c26ffc97790197a660718db100e3897b47575427 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Fri, 26 Jan 2024 23:48:52 +0100 Subject: [PATCH 19/20] permission list all table formatting --- EsefexApi/.vscode/launch.json | 15 +++ EsefexApi/bot/commands/permission.go | 114 +++--------------- EsefexApi/bot/commands/util.go | 6 +- EsefexApi/cmd/testing/fmtstack/fmtstack.go | 31 +++++ EsefexApi/go.mod | 11 ++ EsefexApi/go.sum | 56 +++++++++ .../permissiondb/filepermisssiondb/impl.go | 7 ++ EsefexApi/permissiondb/permissiondb.go | 1 + EsefexApi/permissions/fmtstack.go | 112 +++++++++++++++++ EsefexApi/permissions/merge.go | 6 +- EsefexApi/permissions/permissions.go | 32 ++--- EsefexApi/permissions/permissions_test.go | 8 +- EsefexApi/util/{ => dcgoutil}/iddisplay.go | 2 +- EsefexApi/util/util.go | 15 +++ 14 files changed, 290 insertions(+), 126 deletions(-) create mode 100644 EsefexApi/.vscode/launch.json create mode 100644 EsefexApi/cmd/testing/fmtstack/fmtstack.go create mode 100644 EsefexApi/permissions/fmtstack.go rename EsefexApi/util/{ => dcgoutil}/iddisplay.go (99%) diff --git a/EsefexApi/.vscode/launch.json b/EsefexApi/.vscode/launch.json new file mode 100644 index 0000000..d6de130 --- /dev/null +++ b/EsefexApi/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch file", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "main.go", + } + ] +} \ No newline at end of file diff --git a/EsefexApi/bot/commands/permission.go b/EsefexApi/bot/commands/permission.go index da6f91b..33f080c 100644 --- a/EsefexApi/bot/commands/permission.go +++ b/EsefexApi/bot/commands/permission.go @@ -3,13 +3,12 @@ package commands import ( "esefexapi/permissions" "esefexapi/types" - "esefexapi/util" + "esefexapi/util/dcgoutil" "esefexapi/util/refl" "fmt" "log" "github.com/bwmarrin/discordgo" - "github.com/davecgh/go-spew/spew" "github.com/pkg/errors" ) @@ -152,6 +151,8 @@ func (c *CommandHandlers) PermissionSet(s *discordgo.Session, i *discordgo.Inter guildID := types.GuildID(i.GuildID) + log.Printf("Permissiondb is nul? %v", c.dbs.PermissionDB == nil) + switch ty.PermissionType { case permissions.User: err = c.dbs.PermissionDB.UpdateUser(guildID, types.UserID(ty.ID), p) @@ -175,99 +176,14 @@ func (c *CommandHandlers) PermissionSet(s *discordgo.Session, i *discordgo.Inter // TODO: Better alignment for the list all command (maybe use a table?) func (c *CommandHandlers) PermissionListAll(s *discordgo.Session, i *discordgo.InteractionCreate) (*discordgo.InteractionResponse, error) { - resp := "Permissions for all users, channels and roles:\n" - resp += "**Users**\n" guildID := types.GuildID(i.GuildID) - uids, err := c.dbs.PermissionDB.GetUsers(guildID) - if err != nil { - return nil, errors.Wrap(err, "Error getting users") - } - if len(uids) == 0 { - resp += "`No users found.`\n" - } - for _, uid := range uids { - p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), uid.String()) - if err != nil { - return nil, errors.Wrap(err, "Error getting permissions") - } - - pstr, err := formatPermissionsCompact(p) - if err != nil { - return nil, errors.Wrap(err, "Error formatting permissions") - } - - uname, err := util.UserIDName(s, uid) - if err != nil { - return nil, errors.Wrap(err, "Error getting user") - } - - resp += fmt.Sprintf("%s: ", uname) - resp += fmt.Sprintf("`%s`", pstr) - resp += "\n" - } - - resp += "**Roles**\n" - rids, err := c.dbs.PermissionDB.GetRoles(guildID) - if err != nil { - return nil, errors.Wrap(err, "Error getting roles") - } - if len(rids) == 0 { - resp += "`No roles found.`\n" - } - for _, rid := range rids { - p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), rid.String()) - if err != nil { - return nil, errors.Wrap(err, "Error getting permissions") - } - - pstr, err := formatPermissionsCompact(p) - if err != nil { - return nil, errors.Wrap(err, "Error formatting permissions") - } - - rmention, err := util.RoleIDName(s, types.GuildID(i.GuildID), rid) - if err != nil { - return nil, errors.Wrap(err, "Error getting role") - } - - resp += fmt.Sprintf("%s: ", rmention) - resp += fmt.Sprintf("`%s`", pstr) - resp += "\n" - } - - resp += "**Channels**\n" - cids, err := c.dbs.PermissionDB.GetChannels(guildID) + tabl, err := c.dbs.PermissionDB.GetGuild(guildID).FmtStack(s, guildID) if err != nil { - return nil, errors.Wrap(err, "Error getting channels") - } - if len(cids) == 0 { - resp += "`No channels found.`\n" + return nil, errors.Wrap(err, "Error formatting permissions") } - for _, cid := range cids { - p, err := getPermissions(s, c.dbs, types.GuildID(i.GuildID), cid.String()) - if err != nil { - return nil, errors.Wrap(err, "Error getting permissions") - } - - log.Printf("%v", cid) - spew.Dump(p) - - pstr, err := formatPermissionsCompact(p) - if err != nil { - return nil, errors.Wrap(err, "Error formatting permissions") - } - - cmention, err := util.ChannelIDMention(s, types.GuildID(i.GuildID), cid) - if err != nil { - return nil, errors.Wrap(err, "Error getting channel") - } - - resp += fmt.Sprintf("%s: ", cmention) - resp += fmt.Sprintf("`%s`", pstr) - resp += "\n" - } + resp := fmt.Sprintf("Permissions for all users, channels and roles:```%s```", tabl) return &discordgo.InteractionResponse{ Type: discordgo.InteractionResponseChannelMessageWithSource, @@ -300,11 +216,11 @@ func (c *CommandHandlers) PermissionGet(s *discordgo.Session, i *discordgo.Inter var name string switch ty.PermissionType { case permissions.User: - name, err = util.UserIDName(s, types.UserID(ty.ID)) + name, err = dcgoutil.UserIDName(s, types.UserID(ty.ID)) case permissions.Role: - name, err = util.RoleIDName(s, types.GuildID(i.GuildID), types.RoleID(ty.ID)) + name, err = dcgoutil.RoleIDName(s, types.GuildID(i.GuildID), types.RoleID(ty.ID)) case permissions.Channel: - name, err = util.ChannelIDMention(s, types.GuildID(i.GuildID), types.ChannelID(ty.ID)) + name, err = dcgoutil.ChannelIDMention(s, types.GuildID(i.GuildID), types.ChannelID(ty.ID)) } if err != nil { return nil, errors.Wrap(err, "Error getting name") @@ -331,11 +247,11 @@ func (c *CommandHandlers) PermissionClear(s *discordgo.Session, i *discordgo.Int var name string switch ty.PermissionType { case permissions.User: - name, err = util.UserIDName(s, types.UserID(ty.ID)) + name, err = dcgoutil.UserIDName(s, types.UserID(ty.ID)) case permissions.Role: - name, err = util.RoleIDName(s, types.GuildID(i.GuildID), types.RoleID(ty.ID)) + name, err = dcgoutil.RoleIDName(s, types.GuildID(i.GuildID), types.RoleID(ty.ID)) case permissions.Channel: - name, err = util.ChannelIDMention(s, types.GuildID(i.GuildID), types.ChannelID(ty.ID)) + name, err = dcgoutil.ChannelIDMention(s, types.GuildID(i.GuildID), types.ChannelID(ty.ID)) } if err != nil { return nil, errors.Wrap(err, "Error getting name") @@ -384,11 +300,11 @@ func (c *CommandHandlers) PermissionList(s *discordgo.Session, i *discordgo.Inte var name string switch ty.PermissionType { case permissions.User: - name, err = util.UserIDName(s, types.UserID(ty.ID)) + name, err = dcgoutil.UserIDName(s, types.UserID(ty.ID)) case permissions.Role: - name, err = util.RoleIDName(s, types.GuildID(i.GuildID), types.RoleID(ty.ID)) + name, err = dcgoutil.RoleIDName(s, types.GuildID(i.GuildID), types.RoleID(ty.ID)) case permissions.Channel: - name, err = util.ChannelIDMention(s, types.GuildID(i.GuildID), types.ChannelID(ty.ID)) + name, err = dcgoutil.ChannelIDMention(s, types.GuildID(i.GuildID), types.ChannelID(ty.ID)) } if err != nil { return nil, errors.Wrap(err, "Error getting name") diff --git a/EsefexApi/bot/commands/util.go b/EsefexApi/bot/commands/util.go index 3ce1783..e56da33 100644 --- a/EsefexApi/bot/commands/util.go +++ b/EsefexApi/bot/commands/util.go @@ -131,8 +131,8 @@ func formatPermissions(p permissions.Permissions) (string, error) { resp += "\n**Guild**\n" resp += fmt.Sprintf("```%s\n", mdlang) - resp += fmt.Sprintf("Guild.ManageBot: %s\n", p.Guild.ManageBot.String()) - resp += fmt.Sprintf("Guild.ManageUser: %s\n", p.Guild.ManageUser.String()) + resp += fmt.Sprintf("Guild.BotManage: %s\n", p.Guild.BotManage.String()) + resp += fmt.Sprintf("Guild.UserManage: %s\n", p.Guild.UserManage.String()) resp += "```" return resp, nil @@ -147,7 +147,7 @@ func formatPermissionsCompact(p permissions.Permissions) (string, error) { if err != nil { return "", errors.Wrap(err, "Error getting permission") } - parts = append(parts, ps.(permissions.PermissionState).Emoji()) + parts = append(parts, ps.(permissions.PermissionState).String()) } return strings.Join(parts, "|"), nil diff --git a/EsefexApi/cmd/testing/fmtstack/fmtstack.go b/EsefexApi/cmd/testing/fmtstack/fmtstack.go new file mode 100644 index 0000000..75d8ec9 --- /dev/null +++ b/EsefexApi/cmd/testing/fmtstack/fmtstack.go @@ -0,0 +1,31 @@ +package main + +import ( + "esefexapi/bot" + "esefexapi/permissions" + "esefexapi/types" + . "esefexapi/util/must" + "fmt" + + "github.com/joho/godotenv" +) + +func main() { + godotenv.Load() + + guild := types.GuildID("777344211246120991") + + ps := permissions.NewPermissionStack() + ps.SetChannel(types.ChannelID("777344211828604950"), permissions.NewEveryoneDefault()) + ps.SetRole(types.RoleID("1177022771260297278"), permissions.NewAllow()) + ps.SetUser(types.UserID("246941577904128000"), permissions.NewDeny()) + ps.SetUser(types.UserID("247763762298355712"), permissions.NewUnset()) + + ds, err := bot.CreateSession() + Must(err) + + ds.Open() + defer ds.Close() + + fmt.Println(ps.FmtStack(ds, guild)) +} diff --git a/EsefexApi/go.mod b/EsefexApi/go.mod index 5b87150..62625c6 100644 --- a/EsefexApi/go.mod +++ b/EsefexApi/go.mod @@ -14,9 +14,18 @@ require ( ) require ( + github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect + github.com/go-openapi/errors v0.21.0 // indirect + github.com/go-openapi/strfmt v0.22.0 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/oklog/ulid v1.3.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect + go.mongodb.org/mongo-driver v1.13.1 // indirect golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect @@ -24,6 +33,8 @@ require ( require ( github.com/gorilla/websocket v1.4.2 // indirect + github.com/jedib0t/go-pretty v4.3.0+incompatible + github.com/jedib0t/go-pretty/v6 v6.5.3 github.com/pkg/errors v0.9.1 github.com/samber/lo v1.39.0 github.com/stretchr/testify v1.8.4 diff --git a/EsefexApi/go.sum b/EsefexApi/go.sum index 92bedf2..32564ad 100644 --- a/EsefexApi/go.sum +++ b/EsefexApi/go.sum @@ -1,14 +1,30 @@ +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= +github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/bwmarrin/discordgo v0.27.1 h1:ib9AIc/dom1E/fSIulrBwnez0CToJE113ZGt4HoliGY= github.com/bwmarrin/discordgo v0.27.1/go.mod h1:NJZpH+1AfhIcyQsPeuBKsUtYrRnjkyu0kIVMCHkZtRY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-openapi/errors v0.21.0 h1:FhChC/duCnfoLj1gZ0BgaBmzhJC2SL/sJr8a2vAobSY= +github.com/go-openapi/errors v0.21.0/go.mod h1:jxNTMUxRCKj65yb/okJGEtahVd7uvWnuWfj53bse4ho= +github.com/go-openapi/strfmt v0.22.0 h1:Ew9PnEYc246TwrEspvBdDHS4BVKXy/AOVsfqGDgAcaI= +github.com/go-openapi/strfmt v0.22.0/go.mod h1:HzJ9kokGIju3/K6ap8jL+OlGAbjpSv27135Yr9OivU4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= +github.com/jedib0t/go-pretty v4.3.0+incompatible/go.mod h1:XemHduiw8R651AF9Pt4FwCTKeG3oo7hrHJAoznj9nag= +github.com/jedib0t/go-pretty/v6 v6.5.3 h1:GIXn6Er/anHTkVUoufs7ptEvxdD6KIhR7Axa2wYCPF0= +github.com/jedib0t/go-pretty/v6 v6.5.3/go.mod h1:5LQIxa52oJ/DlDSLv0HEkWOFMDGoWkJb9ss5KqPpJBg= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= @@ -16,6 +32,13 @@ 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/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -23,6 +46,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= @@ -30,18 +55,49 @@ github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc h1:ao2WRsKSzW6KuUY9IWPwWahcHCgR0s52IfwutMfEbdM= golang.org/x/exp v0.0.0-20240103183307-be819d1f06fc/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= diff --git a/EsefexApi/permissiondb/filepermisssiondb/impl.go b/EsefexApi/permissiondb/filepermisssiondb/impl.go index 6f5abe1..6d21022 100644 --- a/EsefexApi/permissiondb/filepermisssiondb/impl.go +++ b/EsefexApi/permissiondb/filepermisssiondb/impl.go @@ -94,3 +94,10 @@ func (f *FilePermissionDB) GetChannels(guildID types.GuildID) ([]types.ChannelID return channels, nil } + +func (f *FilePermissionDB) GetGuild(guildID types.GuildID) *permissions.PermissionStack { + f.rw.RLock() + defer f.rw.RUnlock() + + return f.ensureGuild(guildID) +} diff --git a/EsefexApi/permissiondb/permissiondb.go b/EsefexApi/permissiondb/permissiondb.go index 9709a0e..2204e5c 100644 --- a/EsefexApi/permissiondb/permissiondb.go +++ b/EsefexApi/permissiondb/permissiondb.go @@ -16,4 +16,5 @@ type PermissionDB interface { GetRoles(guild types.GuildID) ([]types.RoleID, error) GetChannels(guild types.GuildID) ([]types.ChannelID, error) Query(guild types.GuildID, user types.UserID) (permissions.Permissions, error) + GetGuild(types.GuildID) *permissions.PermissionStack } diff --git a/EsefexApi/permissions/fmtstack.go b/EsefexApi/permissions/fmtstack.go new file mode 100644 index 0000000..1e3fc31 --- /dev/null +++ b/EsefexApi/permissions/fmtstack.go @@ -0,0 +1,112 @@ +package permissions + +import ( + "bytes" + "esefexapi/types" + "esefexapi/util" + "esefexapi/util/dcgoutil" + "esefexapi/util/refl" + "strings" + + "github.com/bwmarrin/discordgo" + "github.com/jedib0t/go-pretty/v6/table" +) + +func (ps *PermissionStack) FmtStack(ds *discordgo.Session, guildID types.GuildID) (string, error) { + t := table.NewWriter() + buf := new(bytes.Buffer) + t.SetOutputMirror(buf) + + paths := refl.FindAllPaths(Permissions{}) + + pps := []interface{}{} + for _, p := range shortenPaths(paths) { + pps = append(pps, p) + } + + header := append(table.Row{"#"}, pps...) + t.AppendHeader(header) + + t.AppendSeparator() + t.AppendRow(table.Row{"USERS"}) + for k, v := range ps.Users { + uname, err := dcgoutil.UserIDName(ds, k) + if err != nil { + return "", err + } + row := table.Row{uname} + for _, p := range paths { + ps, err := refl.GetNestedFieldValue(v, p) + if err != nil { + return "", err + } + + row = append(row, ps.(PermissionState).String()) + } + t.AppendRow(row) + } + + t.AppendSeparator() + t.AppendRow(table.Row{"ROLES"}) + for k, v := range ps.Roles { + rname, err := dcgoutil.RoleIDName(ds, guildID, k) + if err != nil { + return "", err + } + row := table.Row{rname} + for _, p := range paths { + ps, err := refl.GetNestedFieldValue(v, p) + if err != nil { + return "", err + } + + row = append(row, ps.(PermissionState).String()) + } + t.AppendRow(row) + } + + t.AppendSeparator() + t.AppendRow(table.Row{"CHANNELS"}) + for k, v := range ps.Channels { + cname, err := dcgoutil.ChannelIDName(ds, guildID, k) + if err != nil { + return "", err + } + row := table.Row{cname} + for _, p := range paths { + ps, err := refl.GetNestedFieldValue(v, p) + if err != nil { + return "", err + } + + row = append(row, ps.(PermissionState).String()) + } + t.AppendRow(row) + } + + t.SetStyle(table.StyleRounded) + t.Render() + return buf.String(), nil +} + +var segLens []int = []int{1, 15} +var segLenDefault = 3 + +func shortenPaths(paths []string) []string { + shortened := []string{} + for _, p := range paths { + pathSegs := []string{} + + for i, spl := range strings.Split(p, ".") { + if i >= len(segLens) { + pathSegs = append(pathSegs, util.FirstNRunes(spl, segLenDefault)) + continue + } + + pathSegs = append(pathSegs, util.FirstNRunes(spl, segLens[i])) + } + + shortened = append(shortened, strings.Join(pathSegs, ".")) + } + return shortened +} diff --git a/EsefexApi/permissions/merge.go b/EsefexApi/permissions/merge.go index ecb8a61..ad0e986 100644 --- a/EsefexApi/permissions/merge.go +++ b/EsefexApi/permissions/merge.go @@ -38,8 +38,8 @@ func (p BotPermissions) MergeParent(otherP BotPermissions) BotPermissions { func (p GuildPermissions) MergeParent(otherP GuildPermissions) GuildPermissions { return GuildPermissions{ - ManageBot: p.ManageBot.MergeParent(otherP.ManageBot), - ManageUser: p.ManageUser.MergeParent(otherP.ManageUser), - UseSlashCommands: p.UseSlashCommands.MergeParent(otherP.UseSlashCommands), + BotManage: p.BotManage.MergeParent(otherP.BotManage), + UserManage: p.UserManage.MergeParent(otherP.UserManage), + UseCmds: p.UseCmds.MergeParent(otherP.UseCmds), } } diff --git a/EsefexApi/permissions/permissions.go b/EsefexApi/permissions/permissions.go index 93ee3af..5d4498c 100644 --- a/EsefexApi/permissions/permissions.go +++ b/EsefexApi/permissions/permissions.go @@ -62,7 +62,7 @@ func (ps PermissionState) Emoji() string { case Deny: return "❌" case Unset: - return " " + return "//" default: return "❓" } @@ -91,9 +91,9 @@ type BotPermissions struct { } type GuildPermissions struct { - UseSlashCommands PermissionState - ManageBot PermissionState - ManageUser PermissionState + UseCmds PermissionState + BotManage PermissionState + UserManage PermissionState } // Default returns a Permissions struct with all permissions set to Allow. @@ -110,9 +110,9 @@ func NewAllow() Permissions { Leave: Allow, }, Guild: GuildPermissions{ - UseSlashCommands: Allow, - ManageBot: Allow, - ManageUser: Allow, + UseCmds: Allow, + BotManage: Allow, + UserManage: Allow, }, } } @@ -130,9 +130,9 @@ func NewUnset() Permissions { Leave: Unset, }, Guild: GuildPermissions{ - UseSlashCommands: Unset, - ManageBot: Unset, - ManageUser: Unset, + UseCmds: Unset, + BotManage: Unset, + UserManage: Unset, }, } } @@ -150,9 +150,9 @@ func NewDeny() Permissions { Leave: Deny, }, Guild: GuildPermissions{ - UseSlashCommands: Deny, - ManageBot: Deny, - ManageUser: Deny, + UseCmds: Deny, + BotManage: Deny, + UserManage: Deny, }, } } @@ -170,9 +170,9 @@ func NewEveryoneDefault() Permissions { Leave: Deny, }, Guild: GuildPermissions{ - UseSlashCommands: Allow, - ManageBot: Deny, - ManageUser: Deny, + UseCmds: Allow, + BotManage: Deny, + UserManage: Deny, }, } } diff --git a/EsefexApi/permissions/permissions_test.go b/EsefexApi/permissions/permissions_test.go index f076ebb..e9a577e 100644 --- a/EsefexApi/permissions/permissions_test.go +++ b/EsefexApi/permissions/permissions_test.go @@ -49,7 +49,7 @@ func TestMerge(t *testing.T) { p2 := NewUnset() p1.Sound.Play = Deny - p1.Guild.ManageBot = Deny + p1.Guild.BotManage = Deny p2.Sound.Play = Allow @@ -65,9 +65,9 @@ func TestMerge(t *testing.T) { Leave: Unset, }, Guild: GuildPermissions{ - UseSlashCommands: Unset, - ManageBot: Deny, - ManageUser: Unset, + UseCmds: Unset, + BotManage: Deny, + UserManage: Unset, }, } diff --git a/EsefexApi/util/iddisplay.go b/EsefexApi/util/dcgoutil/iddisplay.go similarity index 99% rename from EsefexApi/util/iddisplay.go rename to EsefexApi/util/dcgoutil/iddisplay.go index fda3aae..421df35 100644 --- a/EsefexApi/util/iddisplay.go +++ b/EsefexApi/util/dcgoutil/iddisplay.go @@ -1,4 +1,4 @@ -package util +package dcgoutil import ( "esefexapi/types" diff --git a/EsefexApi/util/util.go b/EsefexApi/util/util.go index 5f007f8..53bc018 100644 --- a/EsefexApi/util/util.go +++ b/EsefexApi/util/util.go @@ -74,3 +74,18 @@ func EnsureFile(p string) (*os.File, error) { return file, nil } + +func ToGenericArray(arr ...interface{}) []interface{} { + return arr +} + +func FirstNRunes(s string, n int) string { + i := 0 + for j := range s { + if i == n { + return s[:j] + } + i++ + } + return s +} From 801128f975955032184121c6b5ace300f5b4e922 Mon Sep 17 00:00:00 2001 From: jokil123 Date: Sat, 27 Jan 2024 00:30:28 +0100 Subject: [PATCH 20/20] fixed test bc I am dumb --- EsefexApi/permissions/stack.go | 1 - EsefexApi/permissions/stack_test.go | 10 +++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/EsefexApi/permissions/stack.go b/EsefexApi/permissions/stack.go index 7efff2f..e2f1a72 100644 --- a/EsefexApi/permissions/stack.go +++ b/EsefexApi/permissions/stack.go @@ -137,5 +137,4 @@ func (ps *PermissionStack) Query(user types.UserID, roles []types.RoleID, channe } return NewUnset().MergeParent(rolesPS).MergeParent(channelPS).MergeParent(userPS) - } diff --git a/EsefexApi/permissions/stack_test.go b/EsefexApi/permissions/stack_test.go index af84653..c3a92dd 100644 --- a/EsefexApi/permissions/stack_test.go +++ b/EsefexApi/permissions/stack_test.go @@ -11,12 +11,12 @@ import ( func TestStack(t *testing.T) { s := NewPermissionStack() - u1 := types.UserID(rune(1)) + u1 := types.UserID("user1") s.SetUser(u1, NewAllow()) assert.Equal(t, NewAllow(), s.GetUser(u1)) - u2 := types.UserID(rune(2)) + u2 := types.UserID("user2") up2 := Permissions{ Sound: SoundPermissions{ Play: Allow, @@ -26,7 +26,7 @@ func TestStack(t *testing.T) { assert.Equal(t, up2, s.GetUser(u2)) - r1 := types.RoleID(rune(1)) + r1 := types.RoleID("role1") rp1 := Permissions{ Sound: SoundPermissions{ Upload: Allow, @@ -34,8 +34,8 @@ func TestStack(t *testing.T) { }, } - s.SetRole(r1, NewAllow()) - assert.Equal(t, NewAllow(), s.GetRole(r1)) + s.SetRole(r1, rp1) + assert.Equal(t, rp1, s.GetRole(r1)) assert.Equal(t, NewUnset().MergeParent(rp1).MergeParent(up2), s.Query(u2, []types.RoleID{r1}, opt.None[types.ChannelID]())) }