From b37934ded1d8bd5ee37563fb7c53594945f56b72 Mon Sep 17 00:00:00 2001 From: "Gustavo \"Guz\" L. de Mello" Date: Mon, 26 Aug 2024 11:16:26 -0300 Subject: [PATCH] feat(bot,events,commands): guild configuration and logging --- .gitignore | 1 + flake.nix | 56 +++--- internals/discord/bot/bot.go | 9 +- internals/discord/bot/commands.go | 1 + internals/discord/bot/commands/channels.go | 15 +- internals/discord/bot/commands/config.go | 102 +++++++++++ internals/discord/bot/errors/errors.go | 86 ++++++++++ internals/discord/bot/events.go | 2 +- internals/discord/bot/events/events.go | 9 + internals/discord/bot/events/guild.go | 19 +- internals/discord/bot/events/messages.go | 191 +++++++-------------- internals/discord/bot/gconf/config.go | 61 +++++++ internals/discord/bot/gconf/logger.go | 45 +++++ main.go | 14 +- 14 files changed, 429 insertions(+), 182 deletions(-) create mode 100644 internals/discord/bot/commands/config.go create mode 100644 internals/discord/bot/errors/errors.go create mode 100644 internals/discord/bot/events/events.go create mode 100644 internals/discord/bot/gconf/config.go create mode 100644 internals/discord/bot/gconf/logger.go diff --git a/.gitignore b/.gitignore index 900ff7b..0a6d489 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ dist tmp bin static/uno.css +guild.db diff --git a/flake.nix b/flake.nix index 6ad64be..55e7fb0 100644 --- a/flake.nix +++ b/flake.nix @@ -4,33 +4,33 @@ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; templ.url = "github:a-h/templ?ref=v0.2.707"; }; - outputs = { nixpkgs, ... } @ inputs: - let - systems = [ - "x86_64-linux" - "aarch64-linux" - "x86_64-darwin" - "aarch64-darwin" - ]; - forAllSystems = f: nixpkgs.lib.genAttrs systems (system: - let - pkgs = import nixpkgs { inherit system; }; - in + outputs = {nixpkgs, ...} @ inputs: let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + forAllSystems = f: + nixpkgs.lib.genAttrs systems (system: let + pkgs = import nixpkgs {inherit system;}; + in f system pkgs); - templ = system: inputs.templ.packages.${system}.templ; - in - { - devShells = forAllSystems (system: pkgs: { - default = pkgs.mkShell { - buildInputs = with pkgs; [ - sqlite - sqlitebrowser - go - golangci-lint - docker-compose - (templ system) - ]; - }; - }); - }; + templ = system: inputs.templ.packages.${system}.templ; + in { + devShells = forAllSystems (system: pkgs: { + default = pkgs.mkShell { + hardeningDisable = ["fortify"]; + buildInputs = with pkgs; [ + sqlite + sqlitebrowser + go + golangci-lint + delve + docker-compose + (templ system) + ]; + }; + }); + }; } diff --git a/internals/discord/bot/bot.go b/internals/discord/bot/bot.go index b00da9a..35c277a 100644 --- a/internals/discord/bot/bot.go +++ b/internals/discord/bot/bot.go @@ -1,22 +1,23 @@ package bot import ( - "dislate/internals/guilddb" - "dislate/internals/translator" "log/slog" + "dislate/internals/discord/bot/gconf" + "dislate/internals/translator" + dgo "github.com/bwmarrin/discordgo" ) type Bot struct { token string - db guilddb.GuildDB + db gconf.DB translator translator.Translator session *dgo.Session logger *slog.Logger } -func NewBot(token string, db guilddb.GuildDB, translator translator.Translator, logger *slog.Logger) (*Bot, error) { +func NewBot(token string, db gconf.DB, translator translator.Translator, logger *slog.Logger) (*Bot, error) { discord, err := dgo.New("Bot " + token) if err != nil { return &Bot{}, err diff --git a/internals/discord/bot/commands.go b/internals/discord/bot/commands.go index 610fcd5..b3ea367 100644 --- a/internals/discord/bot/commands.go +++ b/internals/discord/bot/commands.go @@ -13,6 +13,7 @@ import ( func (b *Bot) registerCommands() error { cs := []commands.Command{ + commands.NewMagageConfig(b.db), commands.NewManageChannel(b.db), } diff --git a/internals/discord/bot/commands/channels.go b/internals/discord/bot/commands/channels.go index 5e657a3..a1b623c 100644 --- a/internals/discord/bot/commands/channels.go +++ b/internals/discord/bot/commands/channels.go @@ -5,6 +5,7 @@ import ( "fmt" "strings" + "dislate/internals/discord/bot/gconf" "dislate/internals/guilddb" gdb "dislate/internals/guilddb" "dislate/internals/translator/lang" @@ -13,10 +14,10 @@ import ( ) type ManageChannel struct { - db gdb.GuildDB + db gconf.DB } -func NewManageChannel(db gdb.GuildDB) ManageChannel { +func NewManageChannel(db gconf.DB) ManageChannel { return ManageChannel{db} } func (c ManageChannel) Info() *dgo.ApplicationCommand { @@ -43,7 +44,7 @@ func (c ManageChannel) Components() []Component { } type ChannelsInfo struct { - db gdb.GuildDB + db gconf.DB } func (c ChannelsInfo) Info() *dgo.ApplicationCommand { @@ -110,7 +111,7 @@ func (c ChannelsInfo) Subcommands() []Command { } type ChannelsLink struct { - db guilddb.GuildDB + db gconf.DB } func (c ChannelsLink) Info() *dgo.ApplicationCommand { @@ -223,7 +224,7 @@ func (c ChannelsLink) Subcommands() []Command { } type ChannelsSetLang struct { - db guilddb.GuildDB + db gconf.DB } func (c ChannelsSetLang) Info() *dgo.ApplicationCommand { @@ -315,7 +316,7 @@ func (c ChannelsSetLang) Subcommands() []Command { return []Command{} } -func getChannel(db guilddb.GuildDB, guildID, channelID string) (gdb.Channel, error) { +func getChannel(db gconf.DB, guildID, channelID string) (gdb.Channel, error) { ch, err := db.Channel(guildID, channelID) if errors.Is(err, gdb.ErrNotFound) { if err := db.ChannelInsert(gdb.NewChannel(guildID, channelID, lang.EN)); err != nil { @@ -332,7 +333,7 @@ func getChannel(db guilddb.GuildDB, guildID, channelID string) (gdb.Channel, err return ch, nil } -func getChannelInfo(db guilddb.GuildDB, ch gdb.Channel) (*dgo.MessageEmbed, error) { +func getChannelInfo(db gconf.DB, ch gdb.Channel) (*dgo.MessageEmbed, error) { group, err := db.ChannelGroup(ch.GuildID, ch.ID) if !errors.Is(err, gdb.ErrNotFound) { return nil, err diff --git a/internals/discord/bot/commands/config.go b/internals/discord/bot/commands/config.go new file mode 100644 index 0000000..8ac385f --- /dev/null +++ b/internals/discord/bot/commands/config.go @@ -0,0 +1,102 @@ +package commands + +import ( + "dislate/internals/discord/bot/gconf" + "fmt" + + dgo "github.com/bwmarrin/discordgo" +) + +type ManageConfig struct { + db gconf.DB +} + +func NewMagageConfig(db gconf.DB) ManageConfig { + return ManageConfig{db} +} +func (c ManageConfig) Info() *dgo.ApplicationCommand { + var permissions int64 = dgo.PermissionAdministrator + + return &dgo.ApplicationCommand{ + Name: "config", + Description: "Manages the guild's configuration", + DefaultMemberPermissions: &permissions, + } +} +func (c ManageConfig) Handle(s *dgo.Session, ic *dgo.InteractionCreate) error { + return nil +} +func (c ManageConfig) Components() []Component { + return []Component{} +} +func (c ManageConfig) Subcommands() []Command { + return []Command{ + loggerConfigChannel(c), + } +} + +type loggerConfigChannel struct { + db gconf.DB +} +func (c loggerConfigChannel) Info() *dgo.ApplicationCommand { + var permissions int64 = dgo.PermissionAdministrator + return &dgo.ApplicationCommand{ + Name: "log-channel", + Description: "Change logging channel", + DefaultMemberPermissions: &permissions, + Options: []*dgo.ApplicationCommandOption{{ + Type: dgo.ApplicationCommandOptionChannel, + Required: true, + Name: "log-channel", + Description: "The channel to send log messages and errors to", + ChannelTypes: []dgo.ChannelType{ + dgo.ChannelTypeGuildText, + }, + }}, + } +} +func (c loggerConfigChannel) Handle(s *dgo.Session, ic *dgo.InteractionCreate) error { + opts := getOptions(ic.ApplicationCommandData().Options) + + var err error + var dch *dgo.Channel + if c, ok := opts["log-channel"]; ok { + dch = c.ChannelValue(s) + } else { + dch, err = s.Channel(ic.ChannelID) + if err != nil { + return err + } + } + + guild, err := c.db.Guild(ic.GuildID) + if err != nil { + return err + } + + conf := guild.Config + conf.LoggingChannel = &dch.ID + guild.Config = conf + + err = c.db.GuildUpdate(guild) + if err != nil { + return err + } + + // FIXME: response message continuously on "thinking..." + err = s.InteractionRespond(ic.Interaction, &dgo.InteractionResponse{ + Type: dgo.InteractionResponseDeferredChannelMessageWithSource, + Data: &dgo.InteractionResponseData{ + Content: fmt.Sprintf("Logging channel changed to %s", *guild.Config.LoggingChannel), + Flags: dgo.MessageFlagsEphemeral, + }, + }) + + return err +} +func (c loggerConfigChannel) Components() []Component { + return []Component{} +} +func (c loggerConfigChannel) Subcommands() []Command { + return []Command{} +} diff --git a/internals/discord/bot/errors/errors.go b/internals/discord/bot/errors/errors.go new file mode 100644 index 0000000..29dad39 --- /dev/null +++ b/internals/discord/bot/errors/errors.go @@ -0,0 +1,86 @@ +package errors + +import ( + "fmt" + "log/slog" + "strings" + + dgo "github.com/bwmarrin/discordgo" +) + +type Error interface { + Log(*slog.Logger) + Reply(*dgo.Session, *dgo.Message) + LogReply(*slog.Logger, *dgo.Session, *dgo.Message) + Error() string +} + +type defaultError struct { + err string + args []slog.Attr +} + +func NewError(err string, args ...slog.Attr) defaultError { + return defaultError{err, args} +} +func New(err string, args ...slog.Attr) defaultError { + return NewError(err, args...) +} + +func (err defaultError) Log(l *slog.Logger) { + args := make([]any, len(err.args)) + for i, a := range err.args { + args[i] = any(a) + } + l.Error(err.err, args...) +} + +func (err defaultError) Reply(s *dgo.Session, m *dgo.Message) { + _, erro := s.ChannelMessageSendReply( + m.ChannelID, + fmt.Sprintf("Error: %s\nSee logs for more details", err.err), + m.Reference(), + ) + if erro != nil { + _, _ = s.ChannelMessageSendReply( + m.ChannelID, + fmt.Sprintf("Failed to send error message (somehow), due to:\n%s", erro.Error()), + m.Reference(), + ) + } +} + +func (err defaultError) LogReply(l *slog.Logger, s *dgo.Session, m *dgo.Message) { + err.Reply(s,m) + err.Log(l) +} + +func (err defaultError) Error() string { + s := make([]string, len(err.args)) + for i, a := range err.args { + s[i] = fmt.Sprintf("%s=%s", a.Key, a.Value) + } + return fmt.Sprintf("%s\n%s", err.err, strings.Join(s, " ")) +} + +type ErrDatabase struct { + defaultError +} + +func NewErrDatabase(args ...slog.Attr) ErrDatabase { + return ErrDatabase{defaultError{ + "Error while trying to talk to the database.", + args, + }} +} + +type ErrUserWebhook struct { + defaultError +} + +func NewErrUserWebhook(args ...slog.Attr) ErrUserWebhook { + return ErrUserWebhook{defaultError{ + "Error while trying to access/execute the user webhook", + args, + }} +} diff --git a/internals/discord/bot/events.go b/internals/discord/bot/events.go index 87b8f69..e9be082 100644 --- a/internals/discord/bot/events.go +++ b/internals/discord/bot/events.go @@ -5,7 +5,7 @@ import "dislate/internals/discord/bot/events" func (b *Bot) registerEventHandlers() { ehs := []any{ events.NewGuildCreate(b.logger, b.db).Serve, - events.NewMessageCreate(b.logger, b.db, b.translator).Serve, + events.NewMessageCreate(b.db, b.translator).Serve, events.NewReady(b.logger, b.db).Serve, } for _, h := range ehs { diff --git a/internals/discord/bot/events/events.go b/internals/discord/bot/events/events.go new file mode 100644 index 0000000..099c71d --- /dev/null +++ b/internals/discord/bot/events/events.go @@ -0,0 +1,9 @@ +package events + +import ( + dgo "github.com/bwmarrin/discordgo" +) + +type EventHandler[E any] interface { + Serve(*dgo.Session, E) +} diff --git a/internals/discord/bot/events/guild.go b/internals/discord/bot/events/guild.go index 9b322c4..3ccd275 100644 --- a/internals/discord/bot/events/guild.go +++ b/internals/discord/bot/events/guild.go @@ -4,23 +4,24 @@ import ( "errors" "log/slog" - "dislate/internals/guilddb" + "dislate/internals/discord/bot/gconf" + gdb "dislate/internals/guilddb" dgo "github.com/bwmarrin/discordgo" ) type GuildCreate struct { log *slog.Logger - db guilddb.GuildDB + db gconf.DB } -func NewGuildCreate(log *slog.Logger, db guilddb.GuildDB) GuildCreate { +func NewGuildCreate(log *slog.Logger, db gconf.DB) GuildCreate { return GuildCreate{log, db} } func (h GuildCreate) Serve(s *dgo.Session, e *dgo.GuildCreate) { - err := h.db.GuildInsert(guilddb.Guild{ID: e.Guild.ID}) + err := h.db.GuildInsert(gdb.Guild[gconf.ConfigString]{ID: e.Guild.ID}) - if err != nil && !errors.Is(err, guilddb.ErrNoAffect) { + if err != nil && !errors.Is(err, gdb.ErrNoAffect) { h.log.Error("Failed to add guild to database", slog.String("id", e.Guild.ID), slog.String("err", err.Error()), @@ -34,17 +35,17 @@ func (h GuildCreate) Serve(s *dgo.Session, e *dgo.GuildCreate) { type Ready struct { log *slog.Logger - db guilddb.GuildDB + db gconf.DB } -func NewReady(log *slog.Logger, db guilddb.GuildDB) EventHandler[*dgo.Ready] { +func NewReady(log *slog.Logger, db gconf.DB) EventHandler[*dgo.Ready] { return Ready{log, db} } func (h Ready) Serve(s *dgo.Session, e *dgo.Ready) { for _, g := range e.Guilds { - err := h.db.GuildInsert(guilddb.Guild{ID: g.ID}) + err := h.db.GuildInsert(gdb.Guild[gconf.ConfigString]{ID: g.ID}) - if err != nil && !errors.Is(err, guilddb.ErrNoAffect) { + if err != nil && !errors.Is(err, gdb.ErrNoAffect) { h.log.Error("Failed to add guild to database", slog.String("id", g.ID), slog.String("err", err.Error()), diff --git a/internals/discord/bot/events/messages.go b/internals/discord/bot/events/messages.go index f9c9899..96021b9 100644 --- a/internals/discord/bot/events/messages.go +++ b/internals/discord/bot/events/messages.go @@ -1,11 +1,12 @@ package events import ( - "errors" - "fmt" + e "errors" "log/slog" "slices" + "dislate/internals/discord/bot/errors" + "dislate/internals/discord/bot/gconf" "dislate/internals/guilddb" "dislate/internals/translator" "dislate/internals/translator/lang" @@ -13,71 +14,60 @@ import ( dgo "github.com/bwmarrin/discordgo" ) -type EventHandler[E any] interface { - Serve(*dgo.Session, E) -} - type MessageCreate struct { - log *slog.Logger - db guilddb.GuildDB + db gconf.DB translator translator.Translator } -func NewMessageCreate(log *slog.Logger, db guilddb.GuildDB, t translator.Translator) MessageCreate { - return MessageCreate{log, db, t} +func NewMessageCreate(db gconf.DB, t translator.Translator) MessageCreate { + return MessageCreate{db, t} } -func (h MessageCreate) Serve(s *dgo.Session, e *dgo.MessageCreate) { - if e.Message.Author.Bot { +func (h MessageCreate) Serve(s *dgo.Session, ev *dgo.MessageCreate) { + log := gconf.GetLogger(ev.GuildID, s, h.db) + if ev.Message.Author.Bot { return } - ch, err := h.db.Channel(e.GuildID, e.ChannelID) - if errors.Is(err, guilddb.ErrNotFound) { - h.log.Debug("Channel is not in database, ignoring.", slog.String("guild", e.GuildID), slog.String("channel", e.ChannelID)) + ch, err := h.db.Channel(ev.GuildID, ev.ChannelID) + if e.Is(err, guilddb.ErrNotFound) { + log.Debug("Channel is not in database, ignoring.", + slog.String("guild", ev.GuildID), + slog.String("channel", ev.ChannelID), + ) return } else if err != nil { - h.log.Error("Error while trying to get channel from database", - slog.String("guild", e.GuildID), - slog.String("channel", e.ChannelID), + errors.NewErrDatabase( + slog.String("guild", ev.GuildID), + slog.String("channel", ev.ChannelID), slog.String("err", err.Error()), - ) + ).LogReply(log, s, ev.Message) return } gc, err := h.db.ChannelGroup(ch.GuildID, ch.ID) - if errors.Is(err, guilddb.ErrNotFound) { - h.log.Debug("Channel is not in a group, ignoring.", slog.String("guild", e.GuildID), slog.String("channel", e.ChannelID)) + if e.Is(err, guilddb.ErrNotFound) { + log.Debug("Channel is not in a group, ignoring.", + slog.String("guild", ev.GuildID), + slog.String("channel", ev.ChannelID), + ) return } else if err != nil { - h.log.Error("Error while trying to get channel group from database", - slog.String("guild", e.GuildID), - slog.String("channel", e.ChannelID), + errors.NewErrDatabase( + slog.String("guild", ev.GuildID), + slog.String("channel", ev.ChannelID), slog.String("err", err.Error()), - ) + ).LogReply(log, s, ev.Message) return } - _, err = h.getMessage(e.Message, ch.Language) + _, err = h.getMessage(ev.Message, ch.Language) if err != nil { - h.log.Error("Error while trying to get/set message to database", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.String("message", e.Message.ID), + errors.NewErrDatabase( + slog.String("guild", ev.Message.GuildID), + slog.String("channel", ev.Message.ChannelID), + slog.String("message", ev.Message.ID), slog.String("err", err.Error()), - ) - _, err := s.ChannelMessageSendReply( - e.Message.ChannelID, - fmt.Sprintf("Error while trying to send message to database. %s", err.Error()), - e.Message.Reference(), - ) - if err != nil { - h.log.Error("Error while trying to send error message", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.String("message", e.Message.ID), - slog.String("err", err.Error()), - ) - } + ).LogReply(log, s, ev.Message) return } @@ -86,106 +76,53 @@ func (h MessageCreate) Serve(s *dgo.Session, e *dgo.MessageCreate) { continue } go func(c guilddb.Channel) { - uw, err := h.getUserWebhook(s, c.ID, e.Message.Author) + uw, err := h.getUserWebhook(s, c.ID, ev.Message.Author) if err != nil { - h.log.Error("Error while trying to create user webhook", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.Any("user", e.Message.Author), - ) - _, err := s.ChannelMessageSendReply( - e.Message.ChannelID, - fmt.Sprintf("Error while trying to create user webhook %s", err.Error()), - e.Message.Reference(), - ) - if err != nil { - h.log.Error("Error while trying to send error message", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.String("message", e.Message.ID), - slog.String("err", err.Error()), - ) - } + errors.NewErrUserWebhook( + slog.String("guild", ev.Message.GuildID), + slog.String("channel", ev.Message.ChannelID), + slog.Any("user", ev.Message.Author), + ).LogReply(log, s, ev.Message) } - t, err := h.translator.Translate(ch.Language, c.Language, e.Message.Content) + t, err := h.translator.Translate(ch.Language, c.Language, ev.Message.Content) if err != nil { - h.log.Error("Error while trying to translate message", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.String("message", e.Message.ID), - slog.String("content", e.Message.Content), + errors.New("Error while trying to translate message", + slog.String("guild", ev.Message.GuildID), + slog.String("channel", ev.Message.ChannelID), + slog.String("message", ev.Message.ID), + slog.String("content", ev.Message.Content), slog.String("err", err.Error()), - ) - _, err := s.ChannelMessageSendReply( - e.Message.ChannelID, - fmt.Sprintf("Error while trying to translate message. %s", err.Error()), - e.Message.Reference(), - ) - if err != nil { - h.log.Error("Error while trying to send error message", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.String("message", e.Message.ID), - slog.String("err", err.Error()), - ) - } + ).LogReply(log, s, ev.Message) } tdm, err := s.WebhookExecute(uw.ID, uw.Token, true, &dgo.WebhookParams{ - AvatarURL: e.Message.Author.AvatarURL(""), - Username: e.Message.Author.GlobalName, + AvatarURL: ev.Message.Author.AvatarURL(""), + Username: ev.Message.Author.GlobalName, Content: t, }) - // tdm, err := s.ChannelMessageSend(c.ID, t) if err != nil { - h.log.Error("Error while trying to send translated message", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.String("message", e.Message.ID), - slog.String("content", e.Message.Content), + errors.NewErrUserWebhook( + slog.String("guild", ev.Message.GuildID), + slog.String("channel", ev.Message.ChannelID), + slog.String("message", ev.Message.ID), + slog.String("content", ev.Message.Content), slog.String("err", err.Error()), - ) - _, err := s.ChannelMessageSendReply( - e.Message.ChannelID, - fmt.Sprintf("Error while trying to send translated message. %s", err.Error()), - e.Message.Reference(), - ) - if err != nil { - h.log.Error("Error while trying to send error message", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.String("message", e.Message.ID), - slog.String("err", err.Error()), - ) - } + ).LogReply(log, s, ev.Message) } if tdm.GuildID == "" { - tdm.GuildID = e.Message.GuildID + tdm.GuildID = ev.Message.GuildID } - _, err = h.getTranslatedMessage(tdm, e.Message, c.Language) + _, err = h.getTranslatedMessage(tdm, ev.Message, c.Language) if err != nil { - h.log.Error("Error while trying to get/set translated message to database", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.String("message", e.Message.ID), + errors.NewErrDatabase( + slog.String("guild", ev.Message.GuildID), + slog.String("channel", ev.Message.ChannelID), + slog.String("message", ev.Message.ID), slog.String("err", err.Error()), - ) - _, err := s.ChannelMessageSendReply( - e.Message.ChannelID, - fmt.Sprintf("Error while trying to send translated message to database. %s", err.Error()), - e.Message.Reference(), - ) - if err != nil { - h.log.Error("Error while trying to send error message", - slog.String("guild", e.Message.GuildID), - slog.String("channel", e.Message.ChannelID), - slog.String("message", e.Message.ID), - slog.String("err", err.Error()), - ) - } + ).LogReply(log, s, ev.Message) } }(c) @@ -219,7 +156,7 @@ func (h MessageCreate) getUserWebhook(s *dgo.Session, channelID string, user *dg func (h MessageCreate) getMessage(m *dgo.Message, lang lang.Language) (guilddb.Message, error) { msg, err := h.db.Message(m.GuildID, m.ChannelID, m.ID) - if errors.Is(err, guilddb.ErrNotFound) { + if e.Is(err, guilddb.ErrNotFound) { if err := h.db.MessageInsert(guilddb.NewMessage(m.GuildID, m.ChannelID, m.ID, lang)); err != nil { return guilddb.Message{}, err } @@ -236,7 +173,7 @@ func (h MessageCreate) getMessage(m *dgo.Message, lang lang.Language) (guilddb.M func (h MessageCreate) getTranslatedMessage(m, original *dgo.Message, lang lang.Language) (guilddb.Message, error) { msg, err := h.db.Message(m.GuildID, m.ChannelID, m.ID) - if errors.Is(err, guilddb.ErrNotFound) { + if e.Is(err, guilddb.ErrNotFound) { if err := h.db.MessageInsert(guilddb.NewTranslatedMessage( m.GuildID, m.ChannelID, diff --git a/internals/discord/bot/gconf/config.go b/internals/discord/bot/gconf/config.go new file mode 100644 index 0000000..6a299c6 --- /dev/null +++ b/internals/discord/bot/gconf/config.go @@ -0,0 +1,61 @@ +package gconf + +import ( + "log/slog" + + gdb "dislate/internals/guilddb" + + dgo "github.com/bwmarrin/discordgo" +) + +type Config struct { + Logger *slog.Logger +} + +type ConfigString struct { + LoggingChannel *string `json:"logging_channel"` + LoggingLevel *slog.Level `json:"logging_level"` +} + +type Guild gdb.Guild[ConfigString] +type DB gdb.GuildDB[ConfigString] + +func (g Guild) GetConfig(s *dgo.Session) (*Config, error) { + var l *slog.Logger + var err error + + if g.Config.LoggingChannel != nil { + c, err := s.Channel(*g.Config.LoggingChannel) + if err != nil { + return nil, err + } + + var lv slog.Level + if g.Config.LoggingLevel != nil { + lv = *g.Config.LoggingLevel + } else { + lv = slog.LevelInfo + } + l = slog.New(NewGuildHandler(s,c, &slog.HandlerOptions{ + Level: lv, + })) + } else { + l = slog.New(disabledHandler{}) + } + + return &Config{l}, err +} + +func GetLogger(guildID string, s *dgo.Session, db DB) *slog.Logger { + g, err := db.Guild(guildID) + if err != nil { + return slog.New(disabledHandler{}) + } + + c, err := Guild(g).GetConfig(s) + if err != nil { + return slog.New(disabledHandler{}) + } + + return c.Logger +} diff --git a/internals/discord/bot/gconf/logger.go b/internals/discord/bot/gconf/logger.go new file mode 100644 index 0000000..92eb7e8 --- /dev/null +++ b/internals/discord/bot/gconf/logger.go @@ -0,0 +1,45 @@ +package gconf + +import ( + "context" + "log/slog" + + dgo "github.com/bwmarrin/discordgo" +) + +type guildHandler struct { + *slog.TextHandler +} + +func NewGuildHandler(s *dgo.Session, c *dgo.Channel, opts *slog.HandlerOptions) guildHandler { + w := NewChannelWriter(s, c) + h := slog.NewTextHandler(w, opts) + return guildHandler{h} +} + +type disabledHandler struct { + *slog.TextHandler +} +func (_ disabledHandler) Enabled(_ context.Context,_ slog.Level) bool { + return false +} + +type channelWriter struct { + session *dgo.Session + channel *dgo.Channel +} + +func NewChannelWriter(s *dgo.Session, c *dgo.Channel) channelWriter { + w := channelWriter{s, c} + + return w +} + +func (w channelWriter) Write(p []byte) (int, error) { + m, err := w.session.ChannelMessageSend(w.channel.ID, string(p)) + if err != nil { + return 0, err + } + + return len(m.Content), nil +} diff --git a/main.go b/main.go index 0076724..ba74d4c 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,6 @@ package main import ( - "dislate/internals/discord/bot" - "dislate/internals/guilddb" - "dislate/internals/translator" "flag" "log/slog" "os" @@ -11,6 +8,11 @@ import ( "syscall" "time" + "dislate/internals/discord/bot" + "dislate/internals/discord/bot/gconf" + "dislate/internals/guilddb" + "dislate/internals/translator" + "github.com/charmbracelet/log" ) @@ -30,12 +32,12 @@ func init() { func main() { logger := slog.New(log.NewWithOptions(os.Stderr, log.Options{ - TimeFormat: time.DateTime, + TimeFormat: time.DateTime, ReportTimestamp: true, - ReportCaller: true, + ReportCaller: true, })) - db, err := guilddb.NewSQLiteDB(*database_file) + db, err := guilddb.NewSQLiteDB[gconf.ConfigString](*database_file) if err != nil { logger.Error("Failed to open database connection", slog.String("err", err.Error())) return