chore(v1): delete old code from version 1
This commit is contained in:
61
botv1/bot.go
61
botv1/bot.go
@@ -1,61 +0,0 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot/gconf"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type Bot struct {
|
||||
token string
|
||||
db gconf.DB
|
||||
translator translator.Translator
|
||||
session *dgo.Session
|
||||
logger *slog.Logger
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return &Bot{
|
||||
token: token,
|
||||
db: db,
|
||||
translator: translator,
|
||||
session: discord,
|
||||
logger: logger,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Bot) Start() error {
|
||||
b.registerEventHandlers()
|
||||
|
||||
b.session.Identify.Intents = dgo.MakeIntent(dgo.IntentsAllWithoutPrivileged)
|
||||
|
||||
if err := b.session.Open(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := b.registerCommands(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bot) Stop() error {
|
||||
if err := b.removeCommands(); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.session.Close()
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/botv1/commands"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
func (b *Bot) registerCommands() error {
|
||||
cs := []commands.Command{
|
||||
commands.NewMagageConfig(b.db),
|
||||
commands.NewManageChannel(b.db),
|
||||
}
|
||||
|
||||
handlers := make(map[string]func(*dgo.Session, *dgo.InteractionCreate), len(cs))
|
||||
componentsHandlers := make(map[string]func(*dgo.Session, *dgo.InteractionCreate))
|
||||
|
||||
for _, v := range cs {
|
||||
var cmd *dgo.ApplicationCommand
|
||||
var err error
|
||||
subCmds := make(map[string]commands.Command)
|
||||
|
||||
sb := v.Subcommands()
|
||||
|
||||
if len(sb) == 0 {
|
||||
cmd, err = b.session.ApplicationCommandCreate(b.session.State.User.ID, "", v.Info())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
subCmdsOpts := make([]*dgo.ApplicationCommandOption, len(sb))
|
||||
for i, sb := range sb {
|
||||
subCmds[sb.Info().Name] = sb
|
||||
subCmdsOpts[i] = &dgo.ApplicationCommandOption{
|
||||
Type: dgo.ApplicationCommandOptionSubCommand,
|
||||
Name: sb.Info().Name,
|
||||
Description: sb.Info().Description,
|
||||
Options: sb.Info().Options,
|
||||
}
|
||||
}
|
||||
info := v.Info()
|
||||
info.Options = subCmdsOpts
|
||||
|
||||
cmd, err = b.session.ApplicationCommandCreate(b.session.State.User.ID, "", info)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
for _, c := range v.Components() {
|
||||
cj, err := c.Info().MarshalJSON()
|
||||
if err != nil {
|
||||
return errors.Join(fmt.Errorf("Failed to marshal command"), err)
|
||||
}
|
||||
|
||||
var v struct {
|
||||
CustomID string `json:"custom_id"`
|
||||
}
|
||||
if err := json.Unmarshal(cj, &v); err != nil {
|
||||
return errors.Join(fmt.Errorf("Failed to unmarshal command"), err)
|
||||
}
|
||||
|
||||
componentsHandlers[v.CustomID] = func(s *dgo.Session, ic *dgo.InteractionCreate) {
|
||||
b.logger.Debug("Handling message component",
|
||||
slog.String("id", ic.Interaction.ID),
|
||||
slog.String("custom_id", ic.Interaction.MessageComponentData().CustomID),
|
||||
)
|
||||
err := c.Handle(s, ic)
|
||||
if err != nil {
|
||||
b.logger.Error("Failed to handle message component",
|
||||
slog.String("custom_id", ic.Interaction.MessageComponentData().CustomID),
|
||||
slog.String("err", err.Error()),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handlers[cmd.Name] = func(s *dgo.Session, ic *dgo.InteractionCreate) {
|
||||
b.logger.Debug("Handling command",
|
||||
slog.String("id", ic.Interaction.ID),
|
||||
slog.String("name", ic.Interaction.ApplicationCommandData().Name),
|
||||
)
|
||||
|
||||
opts := ic.Interaction.ApplicationCommandData().Options
|
||||
isSub := slices.IndexFunc(
|
||||
opts,
|
||||
func(o *dgo.ApplicationCommandInteractionDataOption) bool {
|
||||
return o.Type == dgo.ApplicationCommandOptionSubCommand
|
||||
},
|
||||
)
|
||||
if isSub != -1 {
|
||||
sc := opts[isSub]
|
||||
|
||||
err := subCmds[sc.Name].Handle(s, ic)
|
||||
if err != nil {
|
||||
_ = s.InteractionRespond(ic.Interaction, &dgo.InteractionResponse{
|
||||
Type: dgo.InteractionResponseDeferredChannelMessageWithSource,
|
||||
Data: &dgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf(
|
||||
"Error while trying to handle sub command: %s",
|
||||
err.Error(),
|
||||
),
|
||||
Flags: dgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
b.logger.Error("Failed to handle sub command",
|
||||
slog.String("name", sc.Name),
|
||||
slog.String("err", err.Error()),
|
||||
)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
err := v.Handle(s, ic)
|
||||
if err != nil {
|
||||
_ = s.InteractionRespond(ic.Interaction, &dgo.InteractionResponse{
|
||||
Type: dgo.InteractionResponseDeferredChannelMessageWithSource,
|
||||
Data: &dgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf(
|
||||
"Error while trying to handle command: %s",
|
||||
err.Error(),
|
||||
),
|
||||
Flags: dgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
b.logger.Error("Failed to handle command",
|
||||
slog.String("name", cmd.Name),
|
||||
slog.String("id", cmd.ID),
|
||||
slog.String("err", err.Error()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
b.logger.Info("Registered command",
|
||||
slog.String("name", cmd.Name),
|
||||
slog.String("id", cmd.ID),
|
||||
)
|
||||
}
|
||||
|
||||
b.session.AddHandler(func(s *dgo.Session, i *dgo.InteractionCreate) {
|
||||
switch i.Interaction.Type {
|
||||
case dgo.InteractionApplicationCommand:
|
||||
if h, ok := handlers[i.ApplicationCommandData().Name]; ok {
|
||||
h(s, i)
|
||||
}
|
||||
case dgo.InteractionMessageComponent:
|
||||
if h, ok := componentsHandlers[i.MessageComponentData().CustomID]; ok {
|
||||
h(s, i)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Bot) removeCommands() error {
|
||||
cmds, err := b.session.ApplicationCommands(b.session.State.Application.ID, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, v := range cmds {
|
||||
err := b.session.ApplicationCommandDelete(b.session.State.User.ID, "", v.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.logger.Info("Removed command",
|
||||
slog.String("name", v.Name),
|
||||
slog.String("id", v.ID),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot/gconf"
|
||||
"forge.capytal.company/capytal/dislate/guilddb"
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
|
||||
gdb "forge.capytal.company/capytal/dislate/guilddb"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type ManageChannel struct {
|
||||
db gconf.DB
|
||||
}
|
||||
|
||||
func NewManageChannel(db gconf.DB) ManageChannel {
|
||||
return ManageChannel{db}
|
||||
}
|
||||
|
||||
func (c ManageChannel) Info() *dgo.ApplicationCommand {
|
||||
var permissions int64 = dgo.PermissionManageChannels
|
||||
|
||||
return &dgo.ApplicationCommand{
|
||||
Name: "channel",
|
||||
Description: "Manages a channel options",
|
||||
DefaultMemberPermissions: &permissions,
|
||||
}
|
||||
}
|
||||
|
||||
func (c ManageChannel) Subcommands() []Command {
|
||||
return []Command{
|
||||
channelsInfo(c),
|
||||
channelsLink(c),
|
||||
channelsSetLang(c),
|
||||
}
|
||||
}
|
||||
|
||||
func (c ManageChannel) Handle(s *dgo.Session, i *dgo.InteractionCreate) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c ManageChannel) Components() []Component {
|
||||
return []Component{}
|
||||
}
|
||||
|
||||
type channelsInfo struct {
|
||||
db gconf.DB
|
||||
}
|
||||
|
||||
func (c channelsInfo) Info() *dgo.ApplicationCommand {
|
||||
var permissions int64 = dgo.PermissionManageChannels
|
||||
|
||||
return &dgo.ApplicationCommand{
|
||||
Name: "info",
|
||||
Description: "Get information about a channel",
|
||||
DefaultMemberPermissions: &permissions,
|
||||
Options: []*dgo.ApplicationCommandOption{{
|
||||
Type: dgo.ApplicationCommandOptionChannel,
|
||||
Name: "channel",
|
||||
Description: "The channel to manage",
|
||||
ChannelTypes: []dgo.ChannelType{
|
||||
dgo.ChannelTypeGuildText,
|
||||
dgo.ChannelTypeGuildForum,
|
||||
dgo.ChannelTypeGuildPublicThread,
|
||||
dgo.ChannelTypeGuildPrivateThread,
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func (c channelsInfo) Handle(s *dgo.Session, ic *dgo.InteractionCreate) error {
|
||||
opts := getOptions(ic.ApplicationCommandData().Options)
|
||||
|
||||
var err error
|
||||
|
||||
var dch *dgo.Channel
|
||||
if c, ok := opts["channel"]; ok {
|
||||
dch = c.ChannelValue(s)
|
||||
} else {
|
||||
dch, err = s.Channel(ic.ChannelID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ch, err := getChannel(c.db, dch.GuildID, dch.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := getChannelInfo(c.db, ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.InteractionRespond(ic.Interaction, &dgo.InteractionResponse{
|
||||
Type: dgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &dgo.InteractionResponseData{
|
||||
Embeds: []*dgo.MessageEmbed{info},
|
||||
Flags: dgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c channelsInfo) Components() []Component {
|
||||
return []Component{}
|
||||
}
|
||||
|
||||
func (c channelsInfo) Subcommands() []Command {
|
||||
return []Command{}
|
||||
}
|
||||
|
||||
type channelsLink struct {
|
||||
db gconf.DB
|
||||
}
|
||||
|
||||
func (c channelsLink) Info() *dgo.ApplicationCommand {
|
||||
var permissions int64 = dgo.PermissionManageChannels
|
||||
|
||||
return &dgo.ApplicationCommand{
|
||||
Name: "link",
|
||||
Description: "Link two channels together",
|
||||
DefaultMemberPermissions: &permissions,
|
||||
Options: []*dgo.ApplicationCommandOption{{
|
||||
Type: dgo.ApplicationCommandOptionChannel,
|
||||
Name: "channel_one",
|
||||
Description: "The channel to link",
|
||||
Required: true,
|
||||
ChannelTypes: []dgo.ChannelType{
|
||||
dgo.ChannelTypeGuildText,
|
||||
dgo.ChannelTypeGuildForum,
|
||||
dgo.ChannelTypeGuildPublicThread,
|
||||
dgo.ChannelTypeGuildPrivateThread,
|
||||
},
|
||||
}, {
|
||||
Type: dgo.ApplicationCommandOptionChannel,
|
||||
Name: "channel_two",
|
||||
Description: "The channel to link",
|
||||
ChannelTypes: []dgo.ChannelType{
|
||||
dgo.ChannelTypeGuildText,
|
||||
dgo.ChannelTypeGuildForum,
|
||||
dgo.ChannelTypeGuildPublicThread,
|
||||
dgo.ChannelTypeGuildPrivateThread,
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func (c channelsLink) Handle(s *dgo.Session, ic *dgo.InteractionCreate) error {
|
||||
opts := getOptions(ic.ApplicationCommandData().Options)
|
||||
|
||||
var err error
|
||||
var dch1, dch2 *dgo.Channel
|
||||
if c, ok := opts["channel_one"]; ok {
|
||||
dch1 = c.ChannelValue(s)
|
||||
} else {
|
||||
return errors.New("channel_one is required")
|
||||
}
|
||||
|
||||
if c, ok := opts["channel_two"]; ok {
|
||||
dch2 = c.ChannelValue(s)
|
||||
} else {
|
||||
dch2, err = s.Channel(ic.ChannelID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if dch1.ID == dch2.ID {
|
||||
return errors.New("channel_one and channel_two must be different values")
|
||||
} else if dch1.Type != dch2.Type {
|
||||
return errors.New("channel_one and channel_two must be the same channel types")
|
||||
}
|
||||
|
||||
ch1, err := getChannel(c.db, dch1.GuildID, dch1.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ch2, err := getChannel(c.db, dch2.GuildID, dch2.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var cb1, cb2 guilddb.ChannelGroup
|
||||
|
||||
cb1, err = c.db.ChannelGroup(ch1.GuildID, ch1.ID)
|
||||
if err != nil && !errors.Is(err, guilddb.ErrNotFound) {
|
||||
return err
|
||||
}
|
||||
cb2, err = c.db.ChannelGroup(ch2.GuildID, ch2.ID)
|
||||
if err != nil && !errors.Is(err, guilddb.ErrNotFound) {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(cb1) > 0 && len(cb2) > 0 {
|
||||
return errors.New("both channels are already in a group")
|
||||
} else if len(cb1) > 0 {
|
||||
cb1 = append(cb1, ch2)
|
||||
err = c.db.ChannelGroupUpdate(cb1)
|
||||
} else if len(cb2) > 0 {
|
||||
cb2 = append(cb2, ch1)
|
||||
err = c.db.ChannelGroupUpdate(cb2)
|
||||
} else {
|
||||
err = c.db.ChannelGroupInsert(guilddb.ChannelGroup{ch1, ch2})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.InteractionRespond(ic.Interaction, &dgo.InteractionResponse{
|
||||
Type: dgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &dgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf(
|
||||
"Linked channel %s (%s) and %s (%s)",
|
||||
dch1.Name, dch1.ID, dch2.Name, dch2.ID,
|
||||
),
|
||||
Flags: dgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c channelsLink) Components() []Component {
|
||||
return []Component{}
|
||||
}
|
||||
|
||||
func (c channelsLink) Subcommands() []Command {
|
||||
return []Command{}
|
||||
}
|
||||
|
||||
type channelsSetLang struct {
|
||||
db gconf.DB
|
||||
}
|
||||
|
||||
func (c channelsSetLang) Info() *dgo.ApplicationCommand {
|
||||
var permissions int64 = dgo.PermissionManageChannels
|
||||
|
||||
return &dgo.ApplicationCommand{
|
||||
Name: "set-lang",
|
||||
Description: "Link two channels together",
|
||||
DefaultMemberPermissions: &permissions,
|
||||
Options: []*dgo.ApplicationCommandOption{{
|
||||
Type: dgo.ApplicationCommandOptionString,
|
||||
Required: true,
|
||||
Name: "language",
|
||||
Description: "The new language",
|
||||
Choices: []*dgo.ApplicationCommandOptionChoice{
|
||||
{Name: "English (EN)", Value: translator.EN},
|
||||
{Name: "Portuguese (PT)", Value: translator.PT},
|
||||
},
|
||||
}, {
|
||||
Type: dgo.ApplicationCommandOptionChannel,
|
||||
Name: "channel",
|
||||
Description: "The channel to change the language",
|
||||
ChannelTypes: []dgo.ChannelType{
|
||||
dgo.ChannelTypeGuildText,
|
||||
dgo.ChannelTypeGuildForum,
|
||||
dgo.ChannelTypeGuildPublicThread,
|
||||
dgo.ChannelTypeGuildPrivateThread,
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func (c channelsSetLang) Handle(s *dgo.Session, ic *dgo.InteractionCreate) error {
|
||||
opts := getOptions(ic.ApplicationCommandData().Options)
|
||||
|
||||
var err error
|
||||
var dch *dgo.Channel
|
||||
var l translator.Language
|
||||
|
||||
if c, ok := opts["language"]; ok {
|
||||
switch c.StringValue() {
|
||||
case string(translator.PT):
|
||||
l = translator.PT
|
||||
default:
|
||||
l = translator.EN
|
||||
}
|
||||
} else {
|
||||
return errors.New("language is a required option")
|
||||
}
|
||||
|
||||
if c, ok := opts["channel"]; ok {
|
||||
dch = c.ChannelValue(s)
|
||||
} else {
|
||||
dch, err = s.Channel(ic.ChannelID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
ch, err := getChannel(c.db, dch.GuildID, dch.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ch.Language = l
|
||||
|
||||
err = c.db.ChannelUpdate(ch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.InteractionRespond(ic.Interaction, &dgo.InteractionResponse{
|
||||
Type: dgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &dgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf(
|
||||
"Changed language of channel %s (%s) to %s",
|
||||
dch.Name, dch.ID, l,
|
||||
),
|
||||
Flags: dgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c channelsSetLang) Components() []Component {
|
||||
return []Component{}
|
||||
}
|
||||
|
||||
func (c channelsSetLang) Subcommands() []Command {
|
||||
return []Command{}
|
||||
}
|
||||
|
||||
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, translator.EN)); err != nil {
|
||||
return gdb.Channel{}, err
|
||||
}
|
||||
ch, err = db.Channel(guildID, channelID)
|
||||
if err != nil {
|
||||
return gdb.Channel{}, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return gdb.Channel{}, err
|
||||
}
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func getChannelInfo(db gconf.DB, ch gdb.Channel) (*dgo.MessageEmbed, error) {
|
||||
group, err := db.ChannelGroup(ch.GuildID, ch.ID)
|
||||
if err != nil && !errors.Is(err, gdb.ErrNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g := make([]string, len(group))
|
||||
for i, gi := range group {
|
||||
g[i] = "<#" + gi.ID + ">"
|
||||
}
|
||||
|
||||
return &dgo.MessageEmbed{
|
||||
Title: "Channel Information",
|
||||
Fields: []*dgo.MessageEmbedField{
|
||||
{Name: "ID", Value: ch.ID, Inline: true},
|
||||
{Name: "Language", Value: string(ch.Language), Inline: true},
|
||||
{Name: "Linked Channels", Value: strings.Join(g, ", "), Inline: true},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type Command interface {
|
||||
Info() *dgo.ApplicationCommand
|
||||
Handle(s *dgo.Session, i *dgo.InteractionCreate) error
|
||||
Subcommands() []Command
|
||||
Components() []Component
|
||||
}
|
||||
|
||||
type Component interface {
|
||||
Info() dgo.MessageComponent
|
||||
Handle(s *dgo.Session, i *dgo.InteractionCreate) error
|
||||
}
|
||||
|
||||
func getOptions(
|
||||
opts []*dgo.ApplicationCommandInteractionDataOption,
|
||||
) map[string]*dgo.ApplicationCommandInteractionDataOption {
|
||||
m := make(map[string]*dgo.ApplicationCommandInteractionDataOption, len(opts))
|
||||
|
||||
for _, opt := range opts {
|
||||
if opt.Type == dgo.ApplicationCommandOptionSubCommand {
|
||||
return getOptions(opt.Options)
|
||||
} else {
|
||||
m[opt.Name] = opt
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
e "errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot/gconf"
|
||||
|
||||
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),
|
||||
loggerConfigLevel(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
|
||||
}
|
||||
|
||||
err = s.InteractionRespond(ic.Interaction, &dgo.InteractionResponse{
|
||||
Type: dgo.InteractionResponseChannelMessageWithSource,
|
||||
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{}
|
||||
}
|
||||
|
||||
type loggerConfigLevel struct {
|
||||
db gconf.DB
|
||||
}
|
||||
|
||||
func (c loggerConfigLevel) Info() *dgo.ApplicationCommand {
|
||||
var permissions int64 = dgo.PermissionAdministrator
|
||||
return &dgo.ApplicationCommand{
|
||||
Name: "log-level",
|
||||
Description: "Change logging channel",
|
||||
DefaultMemberPermissions: &permissions,
|
||||
Options: []*dgo.ApplicationCommandOption{{
|
||||
Type: dgo.ApplicationCommandOptionString,
|
||||
Required: true,
|
||||
Name: "log-level",
|
||||
Description: "The logging level of messages and errors",
|
||||
Choices: []*dgo.ApplicationCommandOptionChoice{
|
||||
{Name: "Debug", Value: slog.LevelDebug.String()},
|
||||
{Name: "Info", Value: slog.LevelInfo.String()},
|
||||
{Name: "Warn", Value: slog.LevelWarn.String()},
|
||||
{Name: "Error", Value: slog.LevelError.String()},
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func (c loggerConfigLevel) Handle(s *dgo.Session, ic *dgo.InteractionCreate) error {
|
||||
opts := getOptions(ic.ApplicationCommandData().Options)
|
||||
|
||||
var err error
|
||||
|
||||
opt, ok := opts["log-level"]
|
||||
if !ok {
|
||||
return e.New("Parameter log-level is required")
|
||||
}
|
||||
|
||||
var l slog.Level
|
||||
err = l.UnmarshalText([]byte(opt.StringValue()))
|
||||
if err != nil {
|
||||
return e.Join(e.New("Parameter log-level is not a valid value"), err)
|
||||
}
|
||||
|
||||
guild, err := c.db.Guild(ic.GuildID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
conf := guild.Config
|
||||
conf.LoggingLevel = &l
|
||||
guild.Config = conf
|
||||
|
||||
err = c.db.GuildUpdate(guild)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.InteractionRespond(ic.Interaction, &dgo.InteractionResponse{
|
||||
Type: dgo.InteractionResponseChannelMessageWithSource,
|
||||
Data: &dgo.InteractionResponseData{
|
||||
Content: fmt.Sprintf("Logging level changed to %s", l),
|
||||
Flags: dgo.MessageFlagsEphemeral,
|
||||
},
|
||||
})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (c loggerConfigLevel) Components() []Component {
|
||||
return []Component{}
|
||||
}
|
||||
|
||||
func (c loggerConfigLevel) Subcommands() []Command {
|
||||
return []Command{}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"forge.capytal.company/capytal/dislate/bot/events"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
func w[E any](h events.EventHandler[E]) interface{} {
|
||||
return func(s *dgo.Session, ev E) {
|
||||
err := h.Serve(s, ev)
|
||||
if err != nil {
|
||||
err.Log()
|
||||
err.Send()
|
||||
err.Reply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *Bot) registerEventHandlers() {
|
||||
ehs := []any{
|
||||
w(events.NewGuildCreate(b.logger, b.db)),
|
||||
w(events.NewMessageCreate(b.db, b.translator)),
|
||||
w(events.NewMessageUpdate(b.db, b.translator)),
|
||||
w(events.NewMessageDelete(b.db)),
|
||||
w(events.NewReady(b.logger, b.db)),
|
||||
w(events.NewThreadCreate(b.db, b.translator)),
|
||||
}
|
||||
for _, h := range ehs {
|
||||
b.session.AddHandler(h)
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type defaultEventErr[E any] struct {
|
||||
message string
|
||||
data map[string]any
|
||||
session *dgo.Session
|
||||
channelID string
|
||||
messageReference *dgo.MessageReference
|
||||
logger *slog.Logger
|
||||
errs []error
|
||||
}
|
||||
|
||||
func (d *defaultEventErr[E]) Join(errs ...error) EventErr {
|
||||
n := 0
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
n++
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return nil
|
||||
}
|
||||
e := &defaultEventErr[E]{
|
||||
message: d.message,
|
||||
data: d.data,
|
||||
session: d.session,
|
||||
channelID: d.channelID,
|
||||
messageReference: d.messageReference,
|
||||
logger: d.logger,
|
||||
errs: make([]error, 0, n),
|
||||
}
|
||||
for _, err := range errs {
|
||||
if err != nil {
|
||||
e.errs = append(e.errs, err)
|
||||
}
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (d *defaultEventErr[E]) Error() string {
|
||||
var data []string
|
||||
for k, v := range d.data {
|
||||
data = append(data, slog.Any(k, v).String())
|
||||
}
|
||||
|
||||
var e string
|
||||
if d.message != "" {
|
||||
e = fmt.Sprintf("%s-ERRO: %s %s", d.message, d.Event(), strings.Join(data, " "))
|
||||
} else {
|
||||
e = fmt.Sprintf("%s-ERRO: %s", d.Event(), strings.Join(data, " "))
|
||||
}
|
||||
|
||||
var s strings.Builder
|
||||
_, berr := s.WriteString(e)
|
||||
if berr != nil {
|
||||
return "Failed to write error string"
|
||||
}
|
||||
for _, err := range d.errs {
|
||||
_, berr := s.WriteString("\n" + err.Error())
|
||||
if berr != nil {
|
||||
return "Failed to write error string"
|
||||
}
|
||||
}
|
||||
return s.String()
|
||||
}
|
||||
|
||||
func (d *defaultEventErr[E]) Event() string {
|
||||
var e E
|
||||
t := reflect.TypeOf(e)
|
||||
if t.Kind() == reflect.Pointer {
|
||||
t = t.Elem()
|
||||
}
|
||||
|
||||
return strings.ToUpper(t.Name())
|
||||
}
|
||||
|
||||
func (d *defaultEventErr[E]) Reply() error {
|
||||
if d.channelID == "" || d.messageReference == nil || d.session == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := d.session.ChannelMessageSendReply(d.channelID, d.Error(), d.messageReference)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *defaultEventErr[E]) Send() error {
|
||||
if d.channelID == "" || d.session == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
_, err := d.session.ChannelMessageSend(d.channelID, d.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *defaultEventErr[E]) Log() {
|
||||
var args []any
|
||||
for k, v := range d.data {
|
||||
args = append(args, slog.Any(k, v))
|
||||
}
|
||||
|
||||
d.logger.Error(d.Error(), args...)
|
||||
}
|
||||
|
||||
func (d *defaultEventErr[E]) AddData(key string, v any) {
|
||||
d.data[key] = v
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package errors
|
||||
|
||||
type EventErr interface {
|
||||
Error() string
|
||||
Event() string
|
||||
Reply() error
|
||||
Send() error
|
||||
Log()
|
||||
Join(...error) EventErr
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type GuildErr[E any] struct {
|
||||
*defaultEventErr[E]
|
||||
}
|
||||
|
||||
func NewGuildErr[E any](
|
||||
g *dgo.Guild,
|
||||
log *slog.Logger,
|
||||
) GuildErr[E] {
|
||||
return GuildErr[E]{&defaultEventErr[E]{
|
||||
data: map[string]any{
|
||||
"GuildID": g.ID,
|
||||
},
|
||||
logger: log,
|
||||
}}
|
||||
}
|
||||
|
||||
type ReadyErr struct {
|
||||
*defaultEventErr[*dgo.Ready]
|
||||
}
|
||||
|
||||
func NewReadyErr(
|
||||
ev *dgo.Ready,
|
||||
log *slog.Logger,
|
||||
) ReadyErr {
|
||||
return ReadyErr{&defaultEventErr[*dgo.Ready]{
|
||||
data: map[string]any{
|
||||
"SessionID": ev.SessionID,
|
||||
"BotUserID": ev.User.ID,
|
||||
"BotUserName": ev.User.Username,
|
||||
"Guilds": ev.Guilds,
|
||||
},
|
||||
logger: log,
|
||||
}}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type MessageErr[E any] struct {
|
||||
*defaultEventErr[E]
|
||||
}
|
||||
|
||||
func NewMessageErr[E any](
|
||||
s *dgo.Session,
|
||||
msg *dgo.Message,
|
||||
log *slog.Logger,
|
||||
) MessageErr[E] {
|
||||
var authorID string
|
||||
if msg.Author != nil {
|
||||
authorID = msg.Author.ID
|
||||
}
|
||||
|
||||
return MessageErr[E]{&defaultEventErr[E]{
|
||||
data: map[string]any{
|
||||
"MessageID": msg.ID,
|
||||
"ChannelID": msg.ChannelID,
|
||||
"GuildID": msg.GuildID,
|
||||
"AuthorID": authorID,
|
||||
},
|
||||
session: s,
|
||||
channelID: msg.ChannelID,
|
||||
messageReference: msg.Reference(),
|
||||
logger: log,
|
||||
}}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package errors
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type ThreadCreateErr struct {
|
||||
*defaultEventErr[*dgo.ThreadCreate]
|
||||
}
|
||||
|
||||
func NewThreadCreateErr(s *dgo.Session, ev *dgo.ThreadCreate, log *slog.Logger) ThreadCreateErr {
|
||||
return ThreadCreateErr{&defaultEventErr[*dgo.ThreadCreate]{
|
||||
data: map[string]any{
|
||||
"ThreadID": ev.ID,
|
||||
"ParentID": ev.ParentID,
|
||||
"GuildID": ev.GuildID,
|
||||
},
|
||||
session: s,
|
||||
channelID: ev.ID,
|
||||
logger: log,
|
||||
}}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"forge.capytal.company/capytal/dislate/bot/events/errors"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type EventHandler[E any] interface {
|
||||
Serve(*dgo.Session, E) errors.EventErr
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
e "errors"
|
||||
"log/slog"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot/events/errors"
|
||||
"forge.capytal.company/capytal/dislate/bot/gconf"
|
||||
|
||||
gdb "forge.capytal.company/capytal/dislate/guilddb"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type GuildCreate struct {
|
||||
log *slog.Logger
|
||||
db gconf.DB
|
||||
}
|
||||
|
||||
func NewGuildCreate(log *slog.Logger, db gconf.DB) GuildCreate {
|
||||
return GuildCreate{log, db}
|
||||
}
|
||||
|
||||
func (h GuildCreate) Serve(s *dgo.Session, ev *dgo.GuildCreate) errors.EventErr {
|
||||
err := h.db.GuildInsert(gdb.Guild[gconf.ConfigString]{ID: ev.Guild.ID})
|
||||
|
||||
everr := errors.NewGuildErr[*dgo.GuildCreate](ev.Guild, h.log)
|
||||
|
||||
if err != nil && !e.Is(err, gdb.ErrNoAffect) {
|
||||
return everr.Join(e.New("Failed to add guild to database"), err)
|
||||
} else if err != nil {
|
||||
h.log.Info("Guild already in database", slog.String("id", ev.Guild.ID))
|
||||
} else {
|
||||
h.log.Info("Added guild", slog.String("id", ev.Guild.ID))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Ready struct {
|
||||
log *slog.Logger
|
||||
db gconf.DB
|
||||
}
|
||||
|
||||
func NewReady(log *slog.Logger, db gconf.DB) EventHandler[*dgo.Ready] {
|
||||
return Ready{log, db}
|
||||
}
|
||||
|
||||
func (h Ready) Serve(s *dgo.Session, ev *dgo.Ready) errors.EventErr {
|
||||
everr := errors.NewReadyErr(ev, h.log)
|
||||
|
||||
for _, g := range ev.Guilds {
|
||||
err := h.db.GuildInsert(gdb.Guild[gconf.ConfigString]{ID: g.ID})
|
||||
|
||||
if err != nil && !e.Is(err, gdb.ErrNoAffect) {
|
||||
return everr.Join(err)
|
||||
} else if err != nil {
|
||||
h.log.Info("Guild already in database", slog.String("id", g.ID))
|
||||
} else {
|
||||
h.log.Info("Added guild", slog.String("id", g.ID))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,440 +0,0 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
e "errors"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot/events/errors"
|
||||
"forge.capytal.company/capytal/dislate/bot/gconf"
|
||||
"forge.capytal.company/capytal/dislate/guilddb"
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type MessageCreate struct {
|
||||
db gconf.DB
|
||||
translator translator.Translator
|
||||
}
|
||||
|
||||
func NewMessageCreate(db gconf.DB, t translator.Translator) MessageCreate {
|
||||
return MessageCreate{db, t}
|
||||
}
|
||||
|
||||
func (h MessageCreate) Serve(
|
||||
s *dgo.Session,
|
||||
ev *dgo.MessageCreate,
|
||||
) errors.EventErr {
|
||||
if ev.Message.Author.Bot || ev.Type != dgo.MessageTypeDefault {
|
||||
return nil
|
||||
}
|
||||
|
||||
log := gconf.GetLogger(ev.Message.GuildID, s, h.db)
|
||||
return h.sendMessage(log, s, ev.Message)
|
||||
}
|
||||
|
||||
func (h MessageCreate) sendMessage(
|
||||
log *slog.Logger,
|
||||
s *dgo.Session,
|
||||
msg *dgo.Message,
|
||||
) errors.EventErr {
|
||||
everr := errors.NewMessageErr[*dgo.MessageCreate](s, msg, log)
|
||||
|
||||
ch, err := h.db.Channel(msg.GuildID, msg.ChannelID)
|
||||
if e.Is(err, guilddb.ErrNotFound) {
|
||||
log.Debug("Channel is not in database, ignoring.",
|
||||
slog.String("guild", msg.GuildID),
|
||||
slog.String("channel", msg.ChannelID),
|
||||
slog.String("message", msg.ID),
|
||||
)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get channel from database"), err)
|
||||
}
|
||||
|
||||
gc, err := h.db.ChannelGroup(ch.GuildID, ch.ID)
|
||||
if e.Is(err, guilddb.ErrNotFound) {
|
||||
log.Debug("Channel is not in a group, ignoring.",
|
||||
slog.String("guild", msg.GuildID),
|
||||
slog.String("channel", msg.ChannelID),
|
||||
slog.String("message", msg.ID),
|
||||
)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get channel group from database"), err)
|
||||
}
|
||||
|
||||
_, err = getMessage(h.db, msg, ch.Language)
|
||||
if err != nil {
|
||||
return everr.Join(e.New("Failed to get/add message to database"), err)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errs := make(chan errors.EventErr)
|
||||
|
||||
for _, c := range gc {
|
||||
if c.ID == ch.ID && c.GuildID == ch.GuildID {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(c guilddb.Channel, errs chan<- errors.EventErr) {
|
||||
defer wg.Done()
|
||||
|
||||
everr := errors.NewMessageErr[*dgo.MessageCreate](s, msg, log)
|
||||
everr.AddData("TranslatedChannelID", c.ID)
|
||||
|
||||
dch, err := s.Channel(c.ID)
|
||||
|
||||
var channelID string
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to get information about translated channel"), err)
|
||||
return
|
||||
} else if dch.IsThread() {
|
||||
channelID = dch.ParentID
|
||||
} else {
|
||||
channelID = dch.ID
|
||||
}
|
||||
|
||||
uw, err := getUserWebhook(s, channelID, msg.Author)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to get/set user webhook for translated channel"), err)
|
||||
return
|
||||
}
|
||||
|
||||
t, err := h.translator.Translate(ch.Language, c.Language, msg.Content)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Error while trying to translate message"), err)
|
||||
return
|
||||
}
|
||||
|
||||
var tdm *dgo.Message
|
||||
if dch.IsThread() {
|
||||
tdm, err = s.WebhookThreadExecute(uw.ID, uw.Token, true, dch.ID, &dgo.WebhookParams{
|
||||
AvatarURL: msg.Author.AvatarURL(""),
|
||||
Username: msg.Author.GlobalName,
|
||||
Content: t,
|
||||
})
|
||||
} else {
|
||||
tdm, err = s.WebhookExecute(uw.ID, uw.Token, true, &dgo.WebhookParams{
|
||||
AvatarURL: msg.Author.AvatarURL(""),
|
||||
Username: msg.Author.GlobalName,
|
||||
Content: t,
|
||||
})
|
||||
}
|
||||
if err != nil {
|
||||
everr.AddData("WebhookID", uw.ID)
|
||||
errs <- everr.Join(e.New("Error while trying to execute user webhook"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if tdm.GuildID == "" {
|
||||
tdm.GuildID = msg.GuildID
|
||||
}
|
||||
|
||||
_, err = getTranslatedMessage(h.db, tdm, msg, c.Language)
|
||||
if err != nil {
|
||||
everr.AddData("WebhookID", uw.ID)
|
||||
everr.AddData("TranslatedMessageID", uw.ID)
|
||||
errs <- everr.Join(e.New("Error while trying to add translated message to dabase"), err)
|
||||
return
|
||||
}
|
||||
}(c, errs)
|
||||
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
for err := range errs {
|
||||
everr.Join(err)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return everr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type MessageUpdate struct {
|
||||
db gconf.DB
|
||||
translator translator.Translator
|
||||
}
|
||||
|
||||
func NewMessageUpdate(db gconf.DB, t translator.Translator) MessageUpdate {
|
||||
return MessageUpdate{db, t}
|
||||
}
|
||||
|
||||
func (h MessageUpdate) Serve(s *dgo.Session, ev *dgo.MessageUpdate) errors.EventErr {
|
||||
if ev.Message.Author.Bot || ev.Type != dgo.MessageTypeDefault {
|
||||
return nil
|
||||
}
|
||||
|
||||
log := gconf.GetLogger(ev.Message.GuildID, s, h.db)
|
||||
everr := errors.NewMessageErr[*dgo.MessageUpdate](s, ev.Message, log)
|
||||
|
||||
msg, err := h.db.Message(ev.Message.GuildID, ev.Message.ChannelID, ev.Message.ID)
|
||||
if e.Is(err, guilddb.ErrNotFound) {
|
||||
log.Debug("Message is not in database, ignoring.",
|
||||
slog.String("guild", ev.Message.GuildID),
|
||||
slog.String("channel", ev.Message.ChannelID),
|
||||
)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get message from database"), err)
|
||||
}
|
||||
|
||||
tmsgs, err := h.db.MessagesWithOrigin(msg.GuildID, msg.ChannelID, msg.ID)
|
||||
if e.Is(err, guilddb.ErrNotFound) {
|
||||
log.Debug("No translated message found, ignoring.",
|
||||
slog.String("guild", ev.GuildID),
|
||||
slog.String("channel", ev.ChannelID),
|
||||
)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get translated messages from database"), err)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errs := make(chan errors.EventErr)
|
||||
|
||||
for _, m := range tmsgs {
|
||||
if m.ID == msg.ID && m.GuildID == msg.GuildID {
|
||||
continue
|
||||
}
|
||||
wg.Add(1)
|
||||
go func(m guilddb.Message, errs chan<- errors.EventErr) {
|
||||
defer wg.Done()
|
||||
|
||||
everr := errors.NewMessageErr[*dgo.MessageUpdate](s, ev.Message, log)
|
||||
everr.AddData("TranslatedMessageID", m.ID)
|
||||
everr.AddData("TranslatedChannelID", m.ChannelID)
|
||||
|
||||
var channelID string
|
||||
if dch, err := s.Channel(m.ChannelID); err != nil {
|
||||
errs <- everr.Join(e.New("Failed to get information about translated channel"), err)
|
||||
return
|
||||
} else if dch.IsThread() {
|
||||
channelID = dch.ParentID
|
||||
} else {
|
||||
channelID = dch.ID
|
||||
}
|
||||
|
||||
uw, err := getUserWebhook(s, channelID, ev.Message.Author)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to get/set user webhook for translated channel"), err)
|
||||
return
|
||||
}
|
||||
|
||||
t, err := h.translator.Translate(msg.Language, m.Language, ev.Message.Content)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Error while trying to translate message"), err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = s.WebhookMessageEdit(uw.ID, uw.Token, m.ID, &dgo.WebhookEdit{
|
||||
Content: &t,
|
||||
})
|
||||
if err != nil {
|
||||
everr.AddData("WebhookID", uw.ID)
|
||||
errs <- everr.Join(e.New("Error while trying to execute user webhook"), err)
|
||||
return
|
||||
}
|
||||
}(m, errs)
|
||||
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
for err := range errs {
|
||||
everr.Join(err)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return everr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type MessageDelete struct {
|
||||
db gconf.DB
|
||||
}
|
||||
|
||||
func NewMessageDelete(db gconf.DB) MessageDelete {
|
||||
return MessageDelete{db}
|
||||
}
|
||||
|
||||
func (h MessageDelete) Serve(s *dgo.Session, ev *dgo.MessageDelete) errors.EventErr {
|
||||
if ev.Type != dgo.MessageTypeDefault {
|
||||
return nil
|
||||
}
|
||||
|
||||
log := gconf.GetLogger(ev.Message.GuildID, s, h.db)
|
||||
everr := errors.NewMessageErr[*dgo.MessageUpdate](s, ev.Message, log)
|
||||
|
||||
msg, err := h.db.Message(ev.Message.GuildID, ev.Message.ChannelID, ev.Message.ID)
|
||||
if e.Is(err, guilddb.ErrNotFound) {
|
||||
log.Debug("Message is not in database, ignoring.",
|
||||
slog.String("guild", ev.Message.GuildID),
|
||||
slog.String("channel", ev.Message.ChannelID),
|
||||
)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get message from database"), err)
|
||||
}
|
||||
|
||||
var originChannelID, originID string
|
||||
if msg.OriginID != nil && msg.OriginChannelID != nil {
|
||||
oMsg, err := h.db.Message(ev.Message.GuildID, *msg.OriginChannelID, *msg.OriginID)
|
||||
if err != nil {
|
||||
originChannelID = *msg.OriginChannelID
|
||||
originID = *msg.OriginID
|
||||
} else {
|
||||
msg = oMsg
|
||||
originChannelID = oMsg.ChannelID
|
||||
originID = oMsg.ID
|
||||
}
|
||||
} else {
|
||||
originChannelID = msg.ChannelID
|
||||
originID = msg.ID
|
||||
}
|
||||
|
||||
tmsgs, err := h.db.MessagesWithOrigin(msg.GuildID, originChannelID, originID)
|
||||
if e.Is(err, guilddb.ErrNotFound) {
|
||||
log.Debug("No translated message found, ignoring.",
|
||||
slog.String("guild", ev.GuildID),
|
||||
slog.String("channel", ev.ChannelID),
|
||||
)
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get translated messages from database"), err)
|
||||
}
|
||||
|
||||
for _, m := range tmsgs {
|
||||
if m.ID == msg.ID && m.ChannelID == msg.ChannelID && m.GuildID == msg.GuildID {
|
||||
continue
|
||||
}
|
||||
go func(m guilddb.Message) {
|
||||
if err := s.ChannelMessageDelete(m.ChannelID, m.ID); err != nil {
|
||||
log.Warn("Failed to delete message",
|
||||
slog.String("channel", m.ChannelID),
|
||||
slog.String("message", m.ID),
|
||||
slog.String("err", err.Error()),
|
||||
)
|
||||
}
|
||||
}(m)
|
||||
}
|
||||
|
||||
if err := s.ChannelMessageDelete(msg.ChannelID, msg.ID); err != nil {
|
||||
log.Warn("Failed to delete message",
|
||||
slog.String("channel", msg.ChannelID),
|
||||
slog.String("message", msg.ID),
|
||||
slog.String("err", err.Error()),
|
||||
)
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
errs := make(chan errors.EventErr)
|
||||
|
||||
for _, m := range append(tmsgs, msg) {
|
||||
go func(m guilddb.Message, errs chan<- errors.EventErr) {
|
||||
everr := errors.NewMessageErr[*dgo.MessageUpdate](s, ev.Message, log)
|
||||
everr.AddData("TranslatedMessageID", m.ID)
|
||||
everr.AddData("TranslatedChannelID", m.ChannelID)
|
||||
|
||||
err := h.db.MessageDeleteFromChannel(guilddb.NewChannel(m.GuildID, m.ID, translator.EN))
|
||||
if err != nil && !e.Is(err, guilddb.ErrNoAffect) {
|
||||
errs <- everr.Join(e.New("Failed to delete message from channel"), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.db.ChannelDelete(guilddb.NewChannel(m.GuildID, m.ID, translator.EN))
|
||||
if err != nil && !e.Is(err, guilddb.ErrNoAffect) {
|
||||
errs <- everr.Join(e.New("Failed to delete message thread from channel"), err)
|
||||
return
|
||||
}
|
||||
}(m, errs)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
everrs := make([]error, 0, len(errs))
|
||||
for err := range errs {
|
||||
everrs = append(everrs, err)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return everr.Join(everrs...)
|
||||
}
|
||||
|
||||
if err := h.db.MessageDelete(guilddb.NewMessage(msg.GuildID, msg.ChannelID, msg.ID, translator.EN)); err != nil {
|
||||
return everr.Join(e.New("Failed to delete message from database"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getUserWebhook(s *dgo.Session, channelID string, user *dgo.User) (*dgo.Webhook, error) {
|
||||
whName := "DISLATE_USER_WEBHOOK_" + user.ID
|
||||
|
||||
ws, err := s.ChannelWebhooks(channelID)
|
||||
if err != nil {
|
||||
return &dgo.Webhook{}, err
|
||||
}
|
||||
wi := slices.IndexFunc(ws, func(w *dgo.Webhook) bool {
|
||||
return w.Name == whName
|
||||
})
|
||||
|
||||
if wi > -1 {
|
||||
return ws[wi], nil
|
||||
}
|
||||
|
||||
w, err := s.WebhookCreate(channelID, whName, user.AvatarURL(""))
|
||||
if err != nil {
|
||||
return &dgo.Webhook{}, err
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
func getMessage(db gconf.DB, m *dgo.Message, lang translator.Language) (guilddb.Message, error) {
|
||||
msg, err := db.Message(m.GuildID, m.ChannelID, m.ID)
|
||||
|
||||
if e.Is(err, guilddb.ErrNotFound) {
|
||||
if err := db.MessageInsert(guilddb.NewMessage(m.GuildID, m.ChannelID, m.ID, lang)); err != nil {
|
||||
return guilddb.Message{}, err
|
||||
}
|
||||
msg, err = db.Message(m.GuildID, m.ChannelID, m.ID)
|
||||
if err != nil {
|
||||
return guilddb.Message{}, err
|
||||
}
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
|
||||
func getTranslatedMessage(
|
||||
db gconf.DB,
|
||||
m, original *dgo.Message,
|
||||
lang translator.Language,
|
||||
) (guilddb.Message, error) {
|
||||
msg, err := db.Message(m.GuildID, m.ChannelID, m.ID)
|
||||
|
||||
if e.Is(err, guilddb.ErrNotFound) {
|
||||
if err := db.MessageInsert(guilddb.NewTranslatedMessage(
|
||||
m.GuildID,
|
||||
m.ChannelID,
|
||||
m.ID,
|
||||
lang,
|
||||
original.ChannelID,
|
||||
original.ID,
|
||||
)); err != nil {
|
||||
return guilddb.Message{}, err
|
||||
}
|
||||
msg, err = db.Message(m.GuildID, m.ChannelID, m.ID)
|
||||
if err != nil {
|
||||
return guilddb.Message{}, err
|
||||
}
|
||||
} else if err != nil {
|
||||
return guilddb.Message{}, err
|
||||
}
|
||||
|
||||
return msg, nil
|
||||
}
|
||||
@@ -1,497 +0,0 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
e "errors"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot/events/errors"
|
||||
"forge.capytal.company/capytal/dislate/bot/gconf"
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
|
||||
gdb "forge.capytal.company/capytal/dislate/guilddb"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type EThreadCreate struct {
|
||||
db gconf.DB
|
||||
translator translator.Translator
|
||||
}
|
||||
|
||||
func NewEThreadCreate(db gconf.DB, t translator.Translator) EThreadCreate {
|
||||
return EThreadCreate{db, t}
|
||||
}
|
||||
|
||||
func (h EThreadCreate) Serve(s *dgo.Session, ev *dgo.ThreadCreate) errors.EventErr {
|
||||
log := gconf.GetLogger(ev.GuildID, s, h.db)
|
||||
everr := errors.NewThreadCreateErr(s, ev, log)
|
||||
|
||||
parentCh, err := h.db.Channel(ev.GuildID, ev.ParentID)
|
||||
if e.Is(err, gdb.ErrNotFound) {
|
||||
log.Debug("Parent channel of thread not in database, ignoring",
|
||||
slog.String("thread", ev.ID),
|
||||
slog.String("parent", ev.ParentID),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
// INFO: Threads have the same ID as the origin message of them
|
||||
threadMsg, err := h.db.Message(ev.GuildID, ev.ParentID, ev.ID)
|
||||
|
||||
var startMsg *dgo.Message
|
||||
|
||||
// If no thread message is found in database, it is probably a thread started without
|
||||
// a source message or a forum post.
|
||||
if e.Is(err, gdb.ErrNotFound) {
|
||||
ms, err := s.ChannelMessages(ev.ID, 10, "", "", "")
|
||||
if err != nil {
|
||||
return everr.Join(e.New("Failed to get messages of thread"), err)
|
||||
} else if len(ms) == 0 {
|
||||
log.Debug("Failed to get messages of thread, empty slice returned, probably created by bot, ignoring",
|
||||
slog.String("thread", ev.ID),
|
||||
slog.String("parent", ev.ParentID),
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
threadMsg = gdb.NewMessage(ev.GuildID, ev.ParentID, ev.ID, parentCh.Language)
|
||||
startMsg = ms[0]
|
||||
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get thread starter message from database"), err)
|
||||
}
|
||||
|
||||
var originMsg gdb.Message
|
||||
if threadMsg.OriginID != nil && threadMsg.OriginChannelID != nil {
|
||||
oMsg, err := h.db.Message(ev.GuildID, *threadMsg.OriginChannelID, *threadMsg.OriginID)
|
||||
if err != nil {
|
||||
originMsg = threadMsg
|
||||
} else {
|
||||
originMsg = oMsg
|
||||
}
|
||||
} else {
|
||||
originMsg = threadMsg
|
||||
}
|
||||
|
||||
dth, err := s.Channel(ev.ID)
|
||||
if err != nil {
|
||||
return everr.Join(e.New("Failed to get discord thread"), err)
|
||||
} else if !dth.IsThread() {
|
||||
return everr.Join(e.New("Channel is not a thread"))
|
||||
}
|
||||
|
||||
th := gdb.NewChannel(dth.GuildID, dth.ID, threadMsg.Language)
|
||||
if err := h.db.ChannelInsert(th); e.Is(err, gdb.ErrNoAffect) {
|
||||
if err = h.db.MessageInsert(threadMsg); err != nil && !e.Is(err, gdb.ErrNoAffect) {
|
||||
return everr.Join(e.New("Failed to add thread started message to database"), err)
|
||||
}
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to add thread channel to database"), err)
|
||||
}
|
||||
|
||||
parentChannelGroup, err := h.db.ChannelGroup(parentCh.GuildID, parentCh.ID)
|
||||
if e.Is(err, gdb.ErrNotFound) {
|
||||
parentChannelGroup = gdb.ChannelGroup{parentCh}
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get parent channel group"))
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
|
||||
tg := make(chan gdb.Channel, len(parentChannelGroup))
|
||||
errs := make(chan errors.EventErr)
|
||||
|
||||
for _, pc := range parentChannelGroup {
|
||||
if pc.ID == dth.ParentID {
|
||||
continue
|
||||
}
|
||||
|
||||
m, err := h.db.MessageWithOriginByLang(pc.GuildID, pc.ID, originMsg.ID, pc.Language)
|
||||
if e.Is(err, gdb.ErrNotFound) && startMsg != nil {
|
||||
|
||||
wg.Add(1)
|
||||
go func(pc gdb.Channel, tg chan<- gdb.Channel, errs chan<- errors.EventErr) {
|
||||
defer wg.Done()
|
||||
|
||||
everr := errors.NewThreadCreateErr(s, ev, log)
|
||||
everr.AddData("TranslatedParentID", pc.ID)
|
||||
|
||||
parentDCh, err := s.Channel(pc.ID)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to get translated parent channel object"), err)
|
||||
return
|
||||
}
|
||||
|
||||
content, err := h.translator.Translate(
|
||||
parentCh.Language,
|
||||
pc.Language,
|
||||
startMsg.Content,
|
||||
)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to translate forum post of thread"), err)
|
||||
return
|
||||
}
|
||||
|
||||
var dtth *dgo.Channel
|
||||
var msg *dgo.Message
|
||||
|
||||
if parentDCh.Type == dgo.ChannelTypeGuildForum && startMsg != nil {
|
||||
tags := slices.DeleteFunc(dth.AppliedTags, func(t string) bool {
|
||||
return !slices.ContainsFunc(
|
||||
parentDCh.AvailableTags,
|
||||
func(pt dgo.ForumTag) bool {
|
||||
return pt.Name == t
|
||||
},
|
||||
)
|
||||
})
|
||||
|
||||
dtth, err = s.ForumThreadStartComplex(pc.ID, &dgo.ThreadStart{
|
||||
Name: dth.Name,
|
||||
AutoArchiveDuration: dth.ThreadMetadata.AutoArchiveDuration,
|
||||
Type: dth.Type,
|
||||
Invitable: dth.ThreadMetadata.Invitable,
|
||||
RateLimitPerUser: dth.RateLimitPerUser,
|
||||
AppliedTags: tags,
|
||||
}, &dgo.MessageSend{
|
||||
Content: content,
|
||||
Embeds: startMsg.Embeds,
|
||||
TTS: startMsg.TTS,
|
||||
Components: startMsg.Components,
|
||||
})
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to translate forum post of thread"), err)
|
||||
return
|
||||
}
|
||||
|
||||
msg, err = s.ChannelMessage(dtth.ID, dtth.ID)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to get translated thread starter message"), err)
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
dtth, err = s.ThreadStartComplex(pc.ID, &dgo.ThreadStart{
|
||||
Name: dth.Name,
|
||||
AutoArchiveDuration: dth.ThreadMetadata.AutoArchiveDuration,
|
||||
Type: dth.Type,
|
||||
Invitable: dth.ThreadMetadata.Invitable,
|
||||
RateLimitPerUser: dth.RateLimitPerUser,
|
||||
})
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to create thread"), err)
|
||||
return
|
||||
}
|
||||
|
||||
uw, err := getUserWebhook(s, pc.ID, startMsg.Author)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to get/set user webhook for parent channel of translated thread"), err)
|
||||
return
|
||||
}
|
||||
|
||||
msg, err = s.WebhookThreadExecute(uw.ID, uw.Token, true, dtth.ID, &dgo.WebhookParams{
|
||||
AvatarURL: startMsg.Author.AvatarURL(""),
|
||||
Username: startMsg.Author.GlobalName,
|
||||
Content: content,
|
||||
})
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Error while trying to execute user webhook"), err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err := h.db.ChannelInsert(gdb.NewChannel(dtth.GuildID, dtth.ID, pc.Language)); err != nil &&
|
||||
!e.Is(err, gdb.ErrNoAffect) {
|
||||
everr.AddData("TranslatedThreadID", dtth.ID)
|
||||
errs <- everr.Join(e.New("Failed to add translated thread to database"), err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.db.MessageInsert(
|
||||
gdb.NewTranslatedMessage(
|
||||
dtth.GuildID,
|
||||
dtth.ID,
|
||||
msg.ID,
|
||||
pc.Language,
|
||||
startMsg.ChannelID,
|
||||
startMsg.ID,
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to add translated thread starter message to database"), err)
|
||||
return
|
||||
}
|
||||
|
||||
tg <- gdb.NewChannel(dtth.GuildID, dtth.ID, pc.Language)
|
||||
}(
|
||||
pc,
|
||||
tg,
|
||||
errs,
|
||||
)
|
||||
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get thread translated start message"), err)
|
||||
} else {
|
||||
|
||||
wg.Add(1)
|
||||
go func(m gdb.Message, tg chan<- gdb.Channel, errs chan<- errors.EventErr) {
|
||||
defer wg.Done()
|
||||
everr := errors.NewThreadCreateErr(s, ev, log)
|
||||
everr.AddData("TranslatedParentID", m.ChannelID)
|
||||
|
||||
dtth, err := s.MessageThreadStartComplex(
|
||||
m.ChannelID,
|
||||
m.ID,
|
||||
&dgo.ThreadStart{
|
||||
Name: dth.Name,
|
||||
AutoArchiveDuration: dth.ThreadMetadata.AutoArchiveDuration,
|
||||
Type: dth.Type,
|
||||
Invitable: dth.ThreadMetadata.Invitable,
|
||||
RateLimitPerUser: dth.RateLimitPerUser,
|
||||
AppliedTags: dth.AppliedTags,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
errs <- everr.Join(e.New("Failed to create translated thread"), err)
|
||||
return
|
||||
}
|
||||
everr.AddData("TranslatedThreadID", pc.ID)
|
||||
|
||||
if err := h.db.ChannelInsert(gdb.NewChannel(dtth.GuildID, dtth.ID, m.Language)); err != nil &&
|
||||
!e.Is(err, gdb.ErrNoAffect) {
|
||||
errs <- everr.Join(e.New("Failed to add translated thread to database"), err)
|
||||
return
|
||||
}
|
||||
|
||||
tg <- gdb.NewChannel(dtth.GuildID, dtth.ID, m.Language)
|
||||
}(m, tg, errs)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
everrs := make([]error, 0, len(errs))
|
||||
for err := range errs {
|
||||
everrs = append(everrs, err)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return everr.Join(everrs...)
|
||||
}
|
||||
|
||||
var threadGroup gdb.ChannelGroup
|
||||
for t := range tg {
|
||||
threadGroup = append(threadGroup, t)
|
||||
}
|
||||
|
||||
if err := h.db.ChannelGroupInsert(threadGroup); err != nil {
|
||||
return everr.Join(e.New("Failed to add group of threads to database"), err)
|
||||
}
|
||||
|
||||
thMsgs, err := s.ChannelMessages(th.ID, 10, "", "", "")
|
||||
if err != nil {
|
||||
return everr.Join(e.New("Failed to get thread messages"), err)
|
||||
}
|
||||
|
||||
for _, m := range thMsgs {
|
||||
if startMsg != nil && m.ID == startMsg.ID {
|
||||
continue
|
||||
}
|
||||
if m.Content != "" {
|
||||
m.GuildID = th.GuildID
|
||||
NewMessageCreate(h.db, h.translator).sendMessage(log, s, m)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type ThreadCreate struct {
|
||||
db gconf.DB
|
||||
translator translator.Translator
|
||||
session *dgo.Session
|
||||
thread *dgo.Channel
|
||||
originLang translator.Language
|
||||
}
|
||||
|
||||
func NewThreadCreate(db gconf.DB, t translator.Translator) ThreadCreate {
|
||||
return ThreadCreate{db, t, nil, nil, translator.EN}
|
||||
}
|
||||
|
||||
func (h ThreadCreate) Serve(s *dgo.Session, ev *dgo.ThreadCreate) errors.EventErr {
|
||||
log := gconf.GetLogger(ev.GuildID, s, h.db)
|
||||
everr := errors.NewThreadCreateErr(s, ev, log)
|
||||
|
||||
parentCh, err := h.db.Channel(ev.GuildID, ev.ParentID)
|
||||
if e.Is(err, gdb.ErrNotFound) {
|
||||
log.Debug("Parent channel of thread not in database, ignoring",
|
||||
slog.String("ThreadID", ev.ID),
|
||||
slog.String("ParentID", ev.ParentID))
|
||||
return nil
|
||||
}
|
||||
|
||||
ms, err := s.ChannelMessages(ev.ID, 10, "", "", "")
|
||||
if err != nil {
|
||||
return everr.Join(e.New("Failed to get messages of thread"), err)
|
||||
} else if len(ms) == 0 || (len(ms) == 1 && ms[0].Type == dgo.MessageTypeThreadStarterMessage) {
|
||||
log.Debug("No messages found in thread, probably created by bot, ignoring",
|
||||
slog.String("ThreadID", ev.ID),
|
||||
slog.String("ParentID", ev.ParentID))
|
||||
return nil
|
||||
}
|
||||
|
||||
// INFO: Threads have the same ID as their starter messages
|
||||
starterMsg, err := h.db.Message(parentCh.GuildID, parentCh.ID, ev.ID)
|
||||
if e.Is(err, gdb.ErrNotFound) {
|
||||
starterMsg = gdb.NewMessage(parentCh.GuildID, ev.ID, ev.ID, parentCh.Language)
|
||||
err = h.db.MessageInsert(starterMsg)
|
||||
if err != nil {
|
||||
return everr.Join(e.New("Failed to add starter message to database"), err)
|
||||
}
|
||||
}
|
||||
|
||||
thread, err := s.Channel(starterMsg.ID)
|
||||
if err != nil {
|
||||
return everr.Join(e.New("Failed to get thread from discord"), err)
|
||||
} else if !thread.IsThread() {
|
||||
return everr.Join(e.New("Failed to get thread from discord, thread is not a thread somehow"), err)
|
||||
}
|
||||
|
||||
parentChannelGroup, err := h.db.ChannelGroup(parentCh.GuildID, parentCh.ID)
|
||||
if e.Is(err, gdb.ErrNotFound) {
|
||||
log.Debug("Parent channel not in a group, ignoring",
|
||||
slog.String("ThreadID", ev.ID),
|
||||
slog.String("ParentID", ev.ParentID))
|
||||
return nil
|
||||
} else if err != nil {
|
||||
return everr.Join(e.New("Failed to get parent channel group"))
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
tg := make(chan gdb.Channel)
|
||||
errs := make(chan error)
|
||||
|
||||
h.session = s
|
||||
h.originLang = parentCh.Language
|
||||
h.thread = thread
|
||||
|
||||
for _, pc := range parentChannelGroup {
|
||||
if pc.ID == ev.ParentID {
|
||||
continue
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(tg chan<- gdb.Channel, errs chan<- error) {
|
||||
defer wg.Done()
|
||||
t, err := h.startTranslatedThread(pc, starterMsg)
|
||||
tg <- t
|
||||
if err != nil {
|
||||
errs <- err
|
||||
}
|
||||
log.Debug("FINISHED")
|
||||
}(tg, errs)
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
everrs := make([]error, 0, len(errs))
|
||||
for err := range errs {
|
||||
log.Debug("ERR")
|
||||
everrs = append(everrs, err)
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
log.Debug("ERR RETURN")
|
||||
return everr.Join(everrs...)
|
||||
}
|
||||
|
||||
log.Debug("FUNCTION 1")
|
||||
|
||||
if err := h.db.ChannelInsert(gdb.NewChannel(thread.GuildID, thread.ID, parentCh.Language)); err != nil {
|
||||
return everr.Join(e.New("Failed to add thread channel to database"), err)
|
||||
}
|
||||
|
||||
threadGroup := make(gdb.ChannelGroup, 0, len(tg))
|
||||
for t := range tg {
|
||||
threadGroup = append(threadGroup, t)
|
||||
}
|
||||
|
||||
log.Debug("FUNCTION 2")
|
||||
|
||||
if err := h.db.ChannelGroupInsert(threadGroup); err != nil {
|
||||
return everr.Join(e.New("Failed to add group of thread to database"), err)
|
||||
}
|
||||
|
||||
thMsgs, err := s.ChannelMessages(thread.ID, 10, "", "", "")
|
||||
if err != nil {
|
||||
return everr.Join(e.New("Failed to get thread messages"), err)
|
||||
}
|
||||
|
||||
log.Debug("FUNCTION 3")
|
||||
|
||||
for _, m := range thMsgs {
|
||||
m.GuildID = thread.GuildID
|
||||
err := NewMessageCreate(h.db, h.translator).sendMessage(log, s, m)
|
||||
if err != nil {
|
||||
return everr.Join(e.New("Failed to translate thread messages"), err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h ThreadCreate) startTranslatedThread(
|
||||
pc gdb.Channel,
|
||||
sm gdb.Message,
|
||||
) (gdb.Channel, error) {
|
||||
if sm.OriginChannelID != nil && *sm.OriginChannelID == pc.ID {
|
||||
m, err := h.db.Message(sm.GuildID, *sm.OriginChannelID, *sm.OriginID)
|
||||
if err != nil {
|
||||
return gdb.Channel{}, e.Join(
|
||||
e.New("Failed to get origin message of starter message"),
|
||||
err,
|
||||
)
|
||||
}
|
||||
return h.startTranslatedMessageThread(m)
|
||||
}
|
||||
|
||||
m, err := h.db.MessageWithOriginByLang(sm.GuildID, sm.ChannelID, sm.ID, pc.Language)
|
||||
if e.Is(err, gdb.ErrNotFound) {
|
||||
} else if err != nil {
|
||||
return gdb.Channel{}, e.Join(
|
||||
e.New("Failed to get translated message of starter message"),
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
return h.startTranslatedMessageThread(m)
|
||||
}
|
||||
|
||||
func (h ThreadCreate) startTranslatedMessageThread(
|
||||
m gdb.Message,
|
||||
) (gdb.Channel, error) {
|
||||
name, err := h.translator.Translate(h.originLang, m.Language, h.thread.Name)
|
||||
if err != nil {
|
||||
return gdb.Channel{}, e.Join(e.New("Failed to translate thread name"), err)
|
||||
}
|
||||
|
||||
th, err := h.session.MessageThreadStartComplex(m.ChannelID, m.ID, &dgo.ThreadStart{
|
||||
Name: name,
|
||||
AutoArchiveDuration: h.thread.ThreadMetadata.AutoArchiveDuration,
|
||||
Type: h.thread.Type,
|
||||
Invitable: h.thread.ThreadMetadata.Invitable,
|
||||
RateLimitPerUser: h.thread.RateLimitPerUser,
|
||||
AppliedTags: h.thread.AppliedTags,
|
||||
})
|
||||
if err != nil {
|
||||
return gdb.Channel{}, e.Join(e.New("Failed to create thread"), err)
|
||||
}
|
||||
|
||||
c := gdb.NewChannel(th.GuildID, th.ID, m.Language)
|
||||
if err := h.db.ChannelInsert(c); err != nil {
|
||||
return c, e.Join(e.New("Failed to insert thread on database"), err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
package gconf
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
|
||||
gdb "forge.capytal.company/capytal/dislate/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]
|
||||
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
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
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
|
||||
}
|
||||
@@ -1,163 +0,0 @@
|
||||
package guilddb
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
)
|
||||
|
||||
type Guild[C any] struct {
|
||||
ID string
|
||||
Config C
|
||||
}
|
||||
|
||||
func NewGuild[C any](ID string, config C) Guild[C] {
|
||||
return Guild[C]{ID, config}
|
||||
}
|
||||
|
||||
type Channel struct {
|
||||
GuildID string
|
||||
ID string
|
||||
Language translator.Language
|
||||
}
|
||||
|
||||
func NewChannel(GuildID, ID string, lang translator.Language) Channel {
|
||||
return Channel{GuildID, ID, lang}
|
||||
}
|
||||
|
||||
type ChannelGroup []Channel
|
||||
|
||||
type Message struct {
|
||||
GuildID string
|
||||
ChannelID string
|
||||
ID string
|
||||
Language translator.Language
|
||||
OriginChannelID *string
|
||||
OriginID *string
|
||||
}
|
||||
|
||||
func NewMessage(GuildID, ChannelID, ID string, lang translator.Language) Message {
|
||||
return Message{GuildID, ChannelID, ID, lang, nil, nil}
|
||||
}
|
||||
|
||||
func NewTranslatedMessage(
|
||||
GuildID, ChannelID, ID string,
|
||||
lang translator.Language,
|
||||
OriginChannelID, OriginID string,
|
||||
) Message {
|
||||
return Message{GuildID, ChannelID, ID, lang, &OriginChannelID, &OriginID}
|
||||
}
|
||||
|
||||
type GuildDB[C any] interface {
|
||||
// Selects and returns a Message from the database, based on the
|
||||
// key pair of Channel's ID and Message's ID.
|
||||
//
|
||||
// Will return ErrNotFound if no message is found or ErrInternal.
|
||||
Message(guildID, channelID, ID string) (Message, error)
|
||||
// Returns a slice of Messages with the provided Message.OriginChannelID and Message.OriginID.
|
||||
//
|
||||
// Will return ErrNotFound if no message is found (slice's length == 0) or ErrInternal.
|
||||
MessagesWithOrigin(guildID, originChannelID, originID string) ([]Message, error)
|
||||
// Returns a Messages with the provided Message.OriginChannelID, Message.OriginID
|
||||
// and Message.Language.
|
||||
//
|
||||
// Will return ErrNotFound if no message is found or ErrInternal.
|
||||
MessageWithOriginByLang(
|
||||
guildID, originChannelId, originId string,
|
||||
language translator.Language,
|
||||
) (Message, error)
|
||||
// Inserts a new Message object in the database.
|
||||
//
|
||||
// Message.ChannelID and Message.ID must be a unique pair and not already
|
||||
// in the database. Implementations of this function may require Message.ChannelID
|
||||
// to be an already stored Channel object, in the case that it isn't stored,
|
||||
// ErrPreconditionFailed may be returned.
|
||||
//
|
||||
// Message.OriginID and Message.OriginChannelID should not be nil if the message
|
||||
// is a translated one.
|
||||
//
|
||||
// Will return ErrNoAffect if the object already exists or ErrInternal.
|
||||
MessageInsert(m Message) error
|
||||
// Updates the Message object in the database. Message.ID and Message.ChannelID
|
||||
// are used to find the correct message.
|
||||
//
|
||||
// Will return ErrNoAffect if no object was updated or ErrInternal.
|
||||
MessageUpdate(m Message) error
|
||||
// Deletes the Message object in the database. Message.ID and Message.ChannelID
|
||||
// are used to find the correct message.
|
||||
//
|
||||
// Will return ErrNoAffect if no object was deleted or ErrInternal.
|
||||
MessageDelete(m Message) error
|
||||
// Deletes all messages in a Channel in the database. Channel.ID is used to find
|
||||
// the correct messages.
|
||||
//
|
||||
// Will return ErrNoAffect if no object was deleted or ErrInternal.
|
||||
MessageDeleteFromChannel(c Channel) error
|
||||
// Selects and returns a Channel from the database, based on the
|
||||
// ID provided.
|
||||
//
|
||||
// Will return ErrNotFound if no channel is found or ErrInternal.
|
||||
Channel(guildID, ID string) (Channel, error)
|
||||
// Inserts a new Channel object in the database.
|
||||
//
|
||||
// Channel.ID must be unique and not already in the database.
|
||||
//
|
||||
// Will return ErrNoAffect if the object already exists or ErrInternal.
|
||||
ChannelInsert(c Channel) error
|
||||
// Updates the Channel object in the database. Channel.ID is used to find the
|
||||
// correct Channel.
|
||||
//
|
||||
// Will return ErrNoAffect if no object was updated or ErrInternal.
|
||||
ChannelUpdate(c Channel) error
|
||||
// Deletes the Channel object in the database. Channel.ID is used to find the
|
||||
// correct Channel.
|
||||
//
|
||||
// Will return ErrNoAffect if no object was deleted or ErrInternal.
|
||||
ChannelDelete(c Channel) error
|
||||
// Selects and returns a ChannelGroup from the database. Finds a ChannelGroup
|
||||
// that has a Channel if the provided ID.
|
||||
//
|
||||
// Channels cannot be in two ChannelGroup at the same time.
|
||||
//
|
||||
// Will return ErrNotFound if no channel is found or ErrInternal.
|
||||
ChannelGroup(guildID, ID string) (ChannelGroup, error)
|
||||
// Inserts a new ChannelGroup object in the database. ChannelGroup must be unique
|
||||
// and not have Channels that are already in other groups.
|
||||
//
|
||||
// Will return ErrNoAffect if the object already exists or ErrInternal.
|
||||
ChannelGroupInsert(g ChannelGroup) error
|
||||
// Updates the ChannelGroup object in the database.
|
||||
//
|
||||
// Will return ErrNoAffect if no object was updated or ErrInternal.
|
||||
ChannelGroupUpdate(g ChannelGroup) error
|
||||
// Deletes the ChannelGroup object in the database.
|
||||
//
|
||||
// Will return ErrNoAffect if no object was deleted or ErrInternal.
|
||||
ChannelGroupDelete(g ChannelGroup) error
|
||||
// Selects and returns a Guild from the database.
|
||||
//
|
||||
// Will return ErrNotFound if no Guild is found or ErrInternal.
|
||||
Guild(ID string) (Guild[C], error)
|
||||
// Inserts a new Guild object in the database. Guild.ID must be unique and
|
||||
// not already in the database.
|
||||
//
|
||||
// Will return ErrNoAffect if the object already exists or ErrInternal.
|
||||
GuildInsert(g Guild[C]) error
|
||||
// Delete a Guild from the database. Guild.ID is used to find the object.
|
||||
//
|
||||
// Will return ErrNoAffect if no object was deleted or ErrInternal.
|
||||
GuildDelete(g Guild[C]) error
|
||||
// Updates the Guild object in the database.
|
||||
//
|
||||
// Will return ErrNoAffect if no object was updated or ErrInternal.
|
||||
GuildUpdate(g Guild[C]) error
|
||||
}
|
||||
|
||||
var (
|
||||
ErrNoAffect = errors.New("Not able to affect anything in the database")
|
||||
ErrNotFound = errors.New("Object not found in the database")
|
||||
ErrPreconditionFailed = errors.New("Precondition failed")
|
||||
ErrInvalidObject = errors.New("Invalid object")
|
||||
ErrInternal = errors.New("Internal error while trying to use database")
|
||||
ErrConfigParsing = errors.New("Error while parsing Guild's config")
|
||||
)
|
||||
@@ -1,564 +0,0 @@
|
||||
package guilddb
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
|
||||
_ "github.com/tursodatabase/go-libsql"
|
||||
)
|
||||
|
||||
type SQLiteDB[C any] struct {
|
||||
sql *sql.DB
|
||||
}
|
||||
|
||||
func NewSQLiteDB[C any](file string) (*SQLiteDB[C], error) {
|
||||
db, err := sql.Open("libsql", file)
|
||||
if err != nil {
|
||||
return &SQLiteDB[C]{}, err
|
||||
}
|
||||
db.SetMaxOpenConns(1)
|
||||
return &SQLiteDB[C]{db}, nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) Close() error {
|
||||
return db.sql.Close()
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) Prepare() error {
|
||||
if _, err := db.sql.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS guilds (
|
||||
ID text NOT NULL,
|
||||
Config text NOT NULL,
|
||||
PRIMARY KEY(ID)
|
||||
);
|
||||
`); err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
if _, err := db.sql.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS channels (
|
||||
GuildID text NOT NULL,
|
||||
ID text NOT NULL,
|
||||
Language text NOT NULL,
|
||||
PRIMARY KEY(ID, GuildID),
|
||||
FOREIGN KEY(GuildID) REFERENCES guilds(ID)
|
||||
);
|
||||
`); err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
if _, err := db.sql.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS channelGroups (
|
||||
GuildID text NOT NULL,
|
||||
Channels text NOT NULL,
|
||||
PRIMARY KEY(Channels, GuildID),
|
||||
FOREIGN KEY(GuildID) REFERENCES guilds(ID)
|
||||
);
|
||||
`); err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
if _, err := db.sql.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS messages (
|
||||
GuildID text NOT NULL,
|
||||
ChannelID text NOT NULL,
|
||||
ID text NOT NULL,
|
||||
Language text NOT NULL,
|
||||
OriginChannelID text,
|
||||
OriginID text,
|
||||
PRIMARY KEY(ID, ChannelID, GuildID),
|
||||
FOREIGN KEY(GuildID, ChannelID) REFERENCES channels(GuildID, ID),
|
||||
FOREIGN KEY(GuildID, OriginChannelID, OriginID) REFERENCES messages(GuildID, ChannelID, ID)
|
||||
);
|
||||
`); err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) Message(guildID, channelID, messageID string) (Message, error) {
|
||||
return db.selectMessage(`
|
||||
WHERE "GuildID" = $1 AND "ChannelID" = $2 AND "ID" = $3
|
||||
`, guildID, channelID, messageID)
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) MessagesWithOrigin(
|
||||
guildID, originChannelID, originID string,
|
||||
) ([]Message, error) {
|
||||
return db.selectMessages(`
|
||||
WHERE "GuildID" = $1 AND "OriginChannelID" = $2 AND "OriginID" = $3
|
||||
`, guildID, originChannelID, originID)
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) MessageWithOriginByLang(
|
||||
guildID, originChannelID, originID string,
|
||||
language translator.Language,
|
||||
) (Message, error) {
|
||||
return db.selectMessage(`
|
||||
WHERE "GuildID" = $1 AND "OriginChannelID" = $2 AND "OriginID" = $3 AND "Language" = $4
|
||||
`, guildID, originChannelID, originID, language)
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) MessageInsert(m Message) error {
|
||||
_, err := db.Channel(m.GuildID, m.ChannelID)
|
||||
if errors.Is(err, ErrNotFound) {
|
||||
return errors.Join(
|
||||
ErrPreconditionFailed,
|
||||
fmt.Errorf("Channel %s doesn't exists in the database", m.ChannelID),
|
||||
)
|
||||
} else if err != nil {
|
||||
return errors.Join(
|
||||
ErrInternal,
|
||||
errors.New("Failed to check if Channel exists in the database"),
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
r, err := db.sql.Exec(`
|
||||
INSERT OR IGNORE INTO messages (GuildID, ChannelID, ID, Language, OriginChannelID, OriginID)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
`, m.GuildID, m.ChannelID, m.ID, m.Language, m.OriginChannelID, m.OriginID)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) MessageUpdate(m Message) error {
|
||||
r, err := db.sql.Exec(`
|
||||
UPDATE messages
|
||||
SET Language = $1, OriginChannelID = $2, OriginID = $3
|
||||
WHERE "GuildID" = $4 AND "ChannelID" = $5 AND "ID" = $6
|
||||
`, m.Language,
|
||||
m.OriginChannelID,
|
||||
m.OriginID,
|
||||
m.GuildID,
|
||||
m.ChannelID,
|
||||
m.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) MessageDelete(m Message) error {
|
||||
_, err := db.sql.Exec(`
|
||||
DELETE FROM messages
|
||||
WHERE "GuildID" = $1 AND "OriginChannelID" = $2 AND "OriginID" = $3
|
||||
`, m.GuildID, m.ChannelID, m.ID)
|
||||
if err != nil && !errors.Is(err, ErrNoAffect) {
|
||||
return errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
r, err := db.sql.Exec(`
|
||||
DELETE FROM messages
|
||||
WHERE "GuildID" = $1 AND "ChannelID" = $2 AND "ID" = $3
|
||||
`, m.GuildID, m.ChannelID, m.ID)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) MessageDeleteFromChannel(c Channel) error {
|
||||
r, err := db.sql.Exec(`
|
||||
DELETE FROM messages
|
||||
WHERE "GuildID" = $1 AND "ChannelID" = $2
|
||||
`, c.GuildID, c.ID)
|
||||
if err != nil && !errors.Is(err, ErrNoAffect) {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) selectMessage(query string, args ...any) (Message, error) {
|
||||
var m Message
|
||||
err := db.sql.QueryRow(fmt.Sprintf(`
|
||||
SELECT GuildID, ChannelID, ID, Language, OriginChannelID, OriginID FROM messages
|
||||
%s
|
||||
`, query), args...).
|
||||
Scan(&m.GuildID, &m.ChannelID, &m.ID, &m.Language, &m.OriginChannelID, &m.OriginID)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return m, errors.Join(ErrNotFound, err)
|
||||
} else if err != nil {
|
||||
return m, errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) selectMessages(query string, args ...any) ([]Message, error) {
|
||||
r, err := db.sql.Query(fmt.Sprintf(`
|
||||
SELECT GuildID, ChannelID, ID, Language, OriginChannelID, OriginID FROM messages
|
||||
%s
|
||||
`, query), args...)
|
||||
defer r.Close()
|
||||
|
||||
if err != nil {
|
||||
return []Message{}, errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
var ms []Message
|
||||
for r.Next() {
|
||||
var m Message
|
||||
|
||||
err = r.Scan(&m.GuildID, &m.ChannelID, &m.ID, &m.Language, &m.OriginChannelID, &m.OriginID)
|
||||
if err != nil {
|
||||
return ms, errors.Join(
|
||||
ErrInternal,
|
||||
fmt.Errorf("Query: %s\nArguments: %v", query, args),
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
ms = append(ms, m)
|
||||
}
|
||||
|
||||
if len(ms) == 0 {
|
||||
return ms, errors.Join(
|
||||
ErrNotFound,
|
||||
fmt.Errorf("Query: %s\nArguments: %v", query, args),
|
||||
)
|
||||
}
|
||||
return ms, err
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) Channel(guildID, ID string) (Channel, error) {
|
||||
return db.selectChannel(`
|
||||
WHERE "GuildID" = $1 AND "ID" = $2
|
||||
`, guildID, ID)
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) ChannelInsert(c Channel) error {
|
||||
r, err := db.sql.Exec(`
|
||||
INSERT OR IGNORE INTO channels (GuildID, ID, Language)
|
||||
VALUES ($1, $2, $3)
|
||||
`, c.GuildID, c.ID, c.Language)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) ChannelUpdate(c Channel) error {
|
||||
r, err := db.sql.Exec(`
|
||||
UPDATE channels
|
||||
SET Language = $1
|
||||
WHERE "GuildID" = $2 AND "ID" = $3
|
||||
`, c.Language, c.GuildID, c.ID)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) ChannelDelete(c Channel) error {
|
||||
r, err := db.sql.Exec(`
|
||||
DELETE FROM channels
|
||||
WHERE "GuildID" = $1 AND "ID" = $2
|
||||
`, c.ID, c.ID)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) ChannelGroup(guildID, channelID string) (ChannelGroup, error) {
|
||||
var j string
|
||||
err := db.sql.QueryRow(fmt.Sprintf(`
|
||||
SELECT Channels FROM channelGroups, json_each(Channels)
|
||||
WHERE "GuildID" = $1 AND json_each.value='%s';
|
||||
`, channelID), guildID).Scan(&j)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return ChannelGroup{}, errors.Join(ErrNotFound, err)
|
||||
} else if err != nil {
|
||||
return ChannelGroup{}, errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
var ids []string
|
||||
err = json.Unmarshal([]byte(j), &ids)
|
||||
if err != nil {
|
||||
return ChannelGroup{}, errors.Join(ErrInternal, err)
|
||||
}
|
||||
for i, v := range ids {
|
||||
ids[i] = fmt.Sprintf("\"ID\" = %s", v)
|
||||
}
|
||||
|
||||
cs, err := db.selectChannels(fmt.Sprintf(`
|
||||
WHERE %s AND "GuildID" = $1
|
||||
`, strings.Join(ids, " OR ")), guildID)
|
||||
|
||||
if errors.Is(err, ErrNotFound) || len(cs) != len(ids) {
|
||||
return ChannelGroup{}, errors.Join(
|
||||
ErrPreconditionFailed,
|
||||
fmt.Errorf(
|
||||
"ChannelGroup has Channels that doesn't exist in the database, group: %s",
|
||||
ids,
|
||||
),
|
||||
err,
|
||||
)
|
||||
} else if err != nil {
|
||||
return ChannelGroup{}, errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
return cs, nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) ChannelGroupInsert(g ChannelGroup) error {
|
||||
if len(g) == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
var ids []string
|
||||
for _, c := range g {
|
||||
ids = append(ids, c.ID)
|
||||
}
|
||||
slices.Sort(ids)
|
||||
|
||||
j, err := json.Marshal(ids)
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
r, err := db.sql.Exec(fmt.Sprintf(`
|
||||
INSERT OR IGNORE INTO channelGroups (GuildID, Channels)
|
||||
VALUES ($1, json('%s'))
|
||||
`, string(j)), g[0].GuildID)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) ChannelGroupUpdate(g ChannelGroup) error {
|
||||
if len(g) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ids, idsq []string
|
||||
for _, c := range g {
|
||||
ids = append(ids, c.ID)
|
||||
idsq = append(idsq, "json_each.value='"+c.ID+"'")
|
||||
}
|
||||
slices.Sort(ids)
|
||||
|
||||
r, err := db.sql.Exec(
|
||||
fmt.Sprintf(`
|
||||
UPDATE channelGroups, json_each(Channels)
|
||||
SET Channels = $1
|
||||
WHERE %s AND "GuildID" = $2
|
||||
`, strings.Join(idsq, " OR ")),
|
||||
strings.Join(ids, ","),
|
||||
g[0].GuildID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) ChannelGroupDelete(g ChannelGroup) error {
|
||||
if len(g) != 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var ids, idsq []string
|
||||
for _, c := range g {
|
||||
ids = append(ids, c.ID)
|
||||
idsq = append(idsq, "json_each.value='"+c.ID+"'")
|
||||
}
|
||||
slices.Sort(ids)
|
||||
|
||||
r, err := db.sql.Exec(
|
||||
fmt.Sprintf(`
|
||||
DELETE FROM channelGroups, json_each(Channels)
|
||||
WHERE %s AND "GuildID" = $2
|
||||
`, strings.Join(idsq, " OR ")),
|
||||
g[0].GuildID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) selectChannel(query string, args ...any) (Channel, error) {
|
||||
var c Channel
|
||||
err := db.sql.QueryRow(fmt.Sprintf(`
|
||||
SELECT GuildID, ID, Language FROM channels
|
||||
%s
|
||||
`, query), args...).Scan(&c.GuildID, &c.ID, &c.Language)
|
||||
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return c, errors.Join(ErrNotFound, err)
|
||||
} else if err != nil {
|
||||
return c, errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) selectChannels(query string, args ...any) ([]Channel, error) {
|
||||
r, err := db.sql.Query(fmt.Sprintf(`
|
||||
SELECT GuildID, ID, Language FROM channels
|
||||
%s
|
||||
`, query), args...)
|
||||
defer r.Close()
|
||||
|
||||
if err != nil {
|
||||
return []Channel{}, errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
var cs []Channel
|
||||
for r.Next() {
|
||||
var c Channel
|
||||
|
||||
err = r.Scan(&c.GuildID, &c.ID, &c.Language)
|
||||
if err != nil {
|
||||
return cs, errors.Join(
|
||||
ErrInternal,
|
||||
fmt.Errorf("Query: %s\nArguments: %v", query, args),
|
||||
err,
|
||||
)
|
||||
}
|
||||
|
||||
cs = append(cs, c)
|
||||
}
|
||||
|
||||
if len(cs) == 0 {
|
||||
return cs, errors.Join(
|
||||
ErrNotFound,
|
||||
fmt.Errorf("Query: %s\nArguments: %v", query, args),
|
||||
)
|
||||
}
|
||||
return cs, err
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) Guild(ID string) (Guild[C], error) {
|
||||
var g struct {
|
||||
ID string
|
||||
Config string
|
||||
}
|
||||
|
||||
if err := db.sql.QueryRow(`
|
||||
SELECT "ID", "Config" FROM guilds
|
||||
WHERE "ID" = $1
|
||||
`, ID).Scan(&g.ID, &g.Config); errors.Is(err, sql.ErrNoRows) {
|
||||
return Guild[C]{}, errors.Join(ErrNotFound, err)
|
||||
} else if err != nil {
|
||||
return Guild[C]{}, errors.Join(ErrInternal, err)
|
||||
}
|
||||
|
||||
var c C
|
||||
err := json.Unmarshal([]byte(g.Config), &c)
|
||||
if err != nil {
|
||||
return Guild[C]{}, errors.Join(ErrConfigParsing, err)
|
||||
}
|
||||
|
||||
return Guild[C]{g.ID, c}, nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) GuildInsert(g Guild[C]) error {
|
||||
j, err := json.Marshal(g.Config)
|
||||
if err != nil {
|
||||
return errors.Join(ErrConfigParsing, err)
|
||||
}
|
||||
|
||||
r, err := db.sql.Exec(`
|
||||
INSERT OR IGNORE INTO guilds (ID, Config)
|
||||
VALUES ($1, $2)
|
||||
`, g.ID, string(j))
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) GuildUpdate(g Guild[C]) error {
|
||||
j, err := json.Marshal(g.Config)
|
||||
if err != nil {
|
||||
return errors.Join(ErrConfigParsing, err)
|
||||
}
|
||||
|
||||
r, err := db.sql.Exec(fmt.Sprintf(`
|
||||
UPDATE guilds
|
||||
SET "Config" = '%s'
|
||||
WHERE "ID" = '%s'
|
||||
`, string(j), g.ID))
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *SQLiteDB[C]) GuildDelete(g Guild[C]) error {
|
||||
r, err := db.sql.Exec(`
|
||||
DELETE FROM guilds
|
||||
WHERE "ID" = $1
|
||||
`, g.ID)
|
||||
|
||||
if err != nil {
|
||||
return errors.Join(ErrInternal, err)
|
||||
} else if rows, _ := r.RowsAffected(); rows == 0 {
|
||||
return ErrNoAffect
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user