refactor: rewrite, a lot
The last implementation was sketchy, and since I have been weeks without interacting with the codebase, I don't have the pacience to know what the fuck my mind was thinking before
This commit is contained in:
46
bot/bot.go
46
bot/bot.go
@@ -1,61 +1,49 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"log/slog"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/db"
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot/gconf"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
"github.com/bwmarrin/discordgo"
|
||||
)
|
||||
|
||||
type Bot struct {
|
||||
token string
|
||||
db gconf.DB
|
||||
translator translator.Translator
|
||||
session *dgo.Session
|
||||
db *db.Queries
|
||||
logger *slog.Logger
|
||||
session *discordgo.Session
|
||||
}
|
||||
|
||||
func NewBot(
|
||||
token string,
|
||||
db gconf.DB,
|
||||
database *sql.DB,
|
||||
translator translator.Translator,
|
||||
logger *slog.Logger,
|
||||
log *slog.Logger,
|
||||
) (*Bot, error) {
|
||||
discord, err := dgo.New("Bot " + token)
|
||||
s, err := discordgo.New(token)
|
||||
if err != nil {
|
||||
return &Bot{}, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := db.Prepare(database)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Bot{
|
||||
token: token,
|
||||
session: s,
|
||||
db: db,
|
||||
translator: translator,
|
||||
session: discord,
|
||||
logger: logger,
|
||||
logger: log,
|
||||
}, 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
|
||||
return b.session.Open()
|
||||
}
|
||||
|
||||
func (b *Bot) Stop() error {
|
||||
if err := b.removeCommands(); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.session.Close()
|
||||
}
|
||||
|
||||
179
bot/commands.go
179
bot/commands.go
@@ -1,180 +1 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot/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
|
||||
}
|
||||
|
||||
61
botv1/bot.go
Normal file
61
botv1/bot.go
Normal file
@@ -0,0 +1,61 @@
|
||||
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()
|
||||
}
|
||||
180
botv1/commands.go
Normal file
180
botv1/commands.go
Normal file
@@ -0,0 +1,180 @@
|
||||
package bot
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot/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
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"forge.capytal.company/capytal/dislate/bot/gconf"
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
|
||||
gdb "dislate/internals/guilddb"
|
||||
gdb "forge.capytal.company/capytal/dislate/guilddb"
|
||||
|
||||
dgo "github.com/bwmarrin/discordgo"
|
||||
)
|
||||
123
db/channel.go
Normal file
123
db/channel.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
)
|
||||
|
||||
type Channel struct {
|
||||
GuildID string
|
||||
ID string
|
||||
Language translator.Language
|
||||
LinkedChannels []string
|
||||
}
|
||||
|
||||
const channelCreate = `
|
||||
CREATE IF NOT EXISTS channels (
|
||||
GuildID text NOT NULL,
|
||||
ID text NOT NULL,
|
||||
Language text NOT NULL,
|
||||
LinkedChannels text NOT NULL,
|
||||
PRIMARY KEY(GuildID, ID),
|
||||
FOREIGN KEY(GuildID) REFERENCES guilds(ID)
|
||||
);
|
||||
`
|
||||
|
||||
const createChannel = `
|
||||
INSERT INTO channels (
|
||||
GuildID,
|
||||
ID,
|
||||
Language,
|
||||
LinkedChannels,
|
||||
) VALUES (?, ?, ?, ?);
|
||||
`
|
||||
|
||||
func (q *Queries) CreateChannel(
|
||||
GuildID, ID string,
|
||||
Language translator.Translator,
|
||||
LinkedChannels []string,
|
||||
) error {
|
||||
j, err := json.Marshal(LinkedChannels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = q.exec(createChannel, GuildID, ID, Language, string(j))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
const updateChannel = `
|
||||
UPDATE channels
|
||||
SET GuildID = ?, ID = ?, Language = ?, LinkedChannels = json(?)
|
||||
WHERE GuildID = ? AND ID = ?;
|
||||
`
|
||||
|
||||
func (q *Queries) UpdateChannel(
|
||||
GuildID, ID string,
|
||||
Language translator.Translator,
|
||||
LinkedChannels []string,
|
||||
) error {
|
||||
j, err := json.Marshal(LinkedChannels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = q.exec(updateChannel, GuildID, ID, Language, string(j), GuildID, ID)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
const getChannel = `
|
||||
SELECT (GuildID, ID, Language, LinkedChannels) FROM channels
|
||||
WHERE GuildID = ? AND ID = ?;
|
||||
`
|
||||
|
||||
func (q *Queries) GetChannel(GuildID, ID string) (Channel, error) {
|
||||
row := q.queryRow(getChannel, GuildID, ID)
|
||||
|
||||
var c Channel
|
||||
var lc string
|
||||
|
||||
if err := row.Scan(&c.GuildID, &c.ID, &c.Language, &lc); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(lc), &c.LinkedChannels); err != nil {
|
||||
return c, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
const listGuildChannels = `
|
||||
SELECT (GuildID, ID, Language, LinkedChannels) FROM channels
|
||||
WHERE GuildID = ?;
|
||||
`
|
||||
|
||||
func (q *Queries) ListGuildChannels(GuildID string) ([]Channel, error) {
|
||||
rows, err := q.query(listGuildChannels, GuildID)
|
||||
if err != nil {
|
||||
return []Channel{}, err
|
||||
}
|
||||
|
||||
cs := []Channel{}
|
||||
|
||||
for rows.Next() {
|
||||
var m Channel
|
||||
var lc string
|
||||
|
||||
if err := rows.Scan(&m.GuildID, &m.ID, &m.Language, &lc); err != nil {
|
||||
return cs, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(lc), &m.LinkedChannels); err != nil {
|
||||
return cs, err
|
||||
}
|
||||
|
||||
cs = append(cs, m)
|
||||
}
|
||||
|
||||
return cs, err
|
||||
}
|
||||
64
db/db.go
Normal file
64
db/db.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
ExecContext(context.Context, string, ...any) (sql.Result, error)
|
||||
QueryContext(context.Context, string, ...any) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...any) *sql.Row
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
func New(db DBTX, ctx ...context.Context) *Queries {
|
||||
var c context.Context
|
||||
if len(ctx) > 0 {
|
||||
c = ctx[0]
|
||||
} else {
|
||||
c = context.Background()
|
||||
}
|
||||
|
||||
return &Queries{db, c}
|
||||
}
|
||||
|
||||
func Prepare(db DBTX, ctx ...context.Context) (*Queries, error) {
|
||||
q := New(db, ctx...)
|
||||
|
||||
if _, err := q.exec(guildCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := q.exec(channelCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := q.exec(messageCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return q, nil
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx *sql.Tx, ctx ...context.Context) *Queries {
|
||||
return New(tx, ctx...)
|
||||
}
|
||||
|
||||
func (q *Queries) WithContext(ctx context.Context) *Queries {
|
||||
return New(q.db, ctx)
|
||||
}
|
||||
|
||||
func (q *Queries) exec(query string, args ...any) (sql.Result, error) {
|
||||
return q.db.ExecContext(q.ctx, query, args...)
|
||||
}
|
||||
|
||||
func (q *Queries) query(query string, args ...any) (*sql.Rows, error) {
|
||||
return q.db.QueryContext(q.ctx, query, args...)
|
||||
}
|
||||
|
||||
func (q *Queries) queryRow(query string, args ...any) *sql.Row {
|
||||
return q.db.QueryRowContext(q.ctx, query, args...)
|
||||
}
|
||||
45
db/guild.go
Normal file
45
db/guild.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package db
|
||||
|
||||
type Guild struct {
|
||||
ID string
|
||||
}
|
||||
|
||||
const guildCreate = `
|
||||
CREATE IF NOT EXISTS guilds (
|
||||
ID text NOT NULL,
|
||||
PRIMARY KEY(ID)
|
||||
);
|
||||
`
|
||||
|
||||
const createGuild = `
|
||||
INSERT INTO guilds (ID) VALUES (?);
|
||||
`
|
||||
|
||||
func (q *Queries) CreateGuild(id string) error {
|
||||
_, err := q.exec(createGuild, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateGuild = `
|
||||
UPDATE guilds SET ID = ? WHERE ID = ?;
|
||||
`
|
||||
|
||||
func (q *Queries) UpdateGuild(id string) error {
|
||||
_, err := q.exec(updateGuild, id, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const getGuild = `
|
||||
SELECT (ID) FROM guilds WHERE ID = ?;
|
||||
`
|
||||
|
||||
func (q *Queries) GetGuild(id string) (Guild, error) {
|
||||
row := q.queryRow(getGuild, id)
|
||||
|
||||
var g Guild
|
||||
if err := row.Scan(&g.ID); err != nil {
|
||||
return Guild{}, err
|
||||
}
|
||||
|
||||
return g, nil
|
||||
}
|
||||
137
db/message.go
Normal file
137
db/message.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
)
|
||||
|
||||
type Message struct {
|
||||
GuildID string
|
||||
ChannelID string
|
||||
ID string
|
||||
Language translator.Language
|
||||
LinkedMessages []string
|
||||
}
|
||||
|
||||
const messageCreate = `
|
||||
CREATE IF NOT EXISTS messages (
|
||||
GuildID text NOT NULL,
|
||||
ChannelID text NOT NULL,
|
||||
ID text NOT NULL,
|
||||
Language text NOT NULL,
|
||||
LinkedMessages text NOT NULL,
|
||||
PRIMARY KEY(GuildID, ChannelID, ID),
|
||||
FOREIGN KEY(GuildID) REFERENCES guilds(ID),
|
||||
FOREIGN KEY(ChannelID) REFERENCES channels(ID)
|
||||
);
|
||||
`
|
||||
|
||||
const createMessage = `
|
||||
INSERT INTO messages (
|
||||
GuildID,
|
||||
ChannelID,
|
||||
ID,
|
||||
Language,
|
||||
LinkedMessages,
|
||||
) VALUES (?, ?, ?, ?, ?);
|
||||
`
|
||||
|
||||
func (q *Queries) CreateMessage(
|
||||
GuildID, ChannelID, ID string,
|
||||
Language translator.Translator,
|
||||
LinkedMessages []string,
|
||||
) error {
|
||||
j, err := json.Marshal(LinkedMessages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = q.exec(createMessage, GuildID, ChannelID, ID, Language, string(j))
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
const updateMessage = `
|
||||
UPDATE messages
|
||||
SET GuildID = ?, ChannelID = ?, ID = ?, Language = ?, LinkedMessages = json(?)
|
||||
WHERE GuildID = ? AND ChannelID = ? AND ID = ?;
|
||||
`
|
||||
|
||||
func (q *Queries) UpdateMessage(
|
||||
GuildID, ChannelID, ID string,
|
||||
Language translator.Translator,
|
||||
LinkedMessages []string,
|
||||
) error {
|
||||
j, err := json.Marshal(LinkedMessages)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = q.exec(
|
||||
updateMessage,
|
||||
GuildID,
|
||||
ChannelID,
|
||||
ID,
|
||||
Language,
|
||||
string(j),
|
||||
GuildID,
|
||||
ChannelID,
|
||||
ID,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
const getMessage = `
|
||||
SELECT (GuildID, ChannelID, ID, Language, LinkedMessages) FROM messages
|
||||
WHERE GuildID = ? AND ChannelID = ? AND ID = ?;
|
||||
`
|
||||
|
||||
func (q *Queries) GetMessage(GuildID, ChannelID, ID string) (Message, error) {
|
||||
row := q.queryRow(getMessage, GuildID, ChannelID, ID)
|
||||
|
||||
var m Message
|
||||
var lm string
|
||||
|
||||
if err := row.Scan(&m.GuildID, &m.ChannelID, &m.ID, &m.Language, &lm); err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(lm), &m.LinkedMessages); err != nil {
|
||||
return m, err
|
||||
}
|
||||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
const listChannelMessages = `
|
||||
SELECT (GuildID, ChannelID, ID, Language, LinkedMessages) FROM messages
|
||||
WHERE GuildID = ? AND ChannelID = ?;
|
||||
`
|
||||
|
||||
func (q *Queries) ListChannelMessages(GuildID, ChannelID string) ([]Message, error) {
|
||||
rows, err := q.query(listChannelMessages, GuildID, ChannelID)
|
||||
if err != nil {
|
||||
return []Message{}, err
|
||||
}
|
||||
|
||||
ms := []Message{}
|
||||
|
||||
for rows.Next() {
|
||||
var m Message
|
||||
var lm string
|
||||
|
||||
if err := rows.Scan(&m.GuildID, &m.ChannelID, &m.ID, &m.Language, &lm); err != nil {
|
||||
return ms, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(lm), &m.LinkedMessages); err != nil {
|
||||
return ms, err
|
||||
}
|
||||
|
||||
ms = append(ms, m)
|
||||
}
|
||||
|
||||
return ms, err
|
||||
}
|
||||
19
main.go
19
main.go
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"flag"
|
||||
"log/slog"
|
||||
"os"
|
||||
@@ -9,10 +10,10 @@ import (
|
||||
"time"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot"
|
||||
"forge.capytal.company/capytal/dislate/bot/gconf"
|
||||
"forge.capytal.company/capytal/dislate/guilddb"
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
|
||||
_ "github.com/tursodatabase/go-libsql"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
@@ -43,12 +44,14 @@ func main() {
|
||||
ReportCaller: true,
|
||||
}))
|
||||
|
||||
db, err := guilddb.NewSQLiteDB[gconf.ConfigString](*database_file + "?_busy_timeout=5000")
|
||||
db, err := sql.Open("libsql", "file://sqlite.db")
|
||||
if err != nil {
|
||||
logger.Error("Failed to open database connection", slog.String("err", err.Error()))
|
||||
logger.Error("Failed to start SQLite database", slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Connection to database started", slog.String("file", *database_file))
|
||||
|
||||
defer func() {
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
@@ -58,12 +61,6 @@ func main() {
|
||||
logger.Info("Connection to database closed", slog.String("file", *database_file))
|
||||
}()
|
||||
|
||||
if err := db.Prepare(); err != nil {
|
||||
logger.Error("Failed to prepare database", slog.String("err", err.Error()))
|
||||
return
|
||||
}
|
||||
logger.Info("Database ready to be used")
|
||||
|
||||
bot, err := bot.NewBot(*discord_token, db, translator.NewMockTranslator(), logger)
|
||||
if err != nil {
|
||||
logger.Error("Failed to create discord bot", slog.String("err", err.Error()))
|
||||
@@ -73,7 +70,9 @@ func main() {
|
||||
logger.Error("Failed to start discord bot", slog.String("err", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Discord bot started")
|
||||
|
||||
defer func() {
|
||||
if err := bot.Stop(); err != nil {
|
||||
logger.Error("Failed to stop discord bot", slog.String("err", err.Error()))
|
||||
|
||||
88
v1/mainv1.go
Normal file
88
v1/mainv1.go
Normal file
@@ -0,0 +1,88 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"forge.capytal.company/capytal/dislate/bot"
|
||||
"forge.capytal.company/capytal/dislate/botv1/gconf"
|
||||
"forge.capytal.company/capytal/dislate/guilddb"
|
||||
"forge.capytal.company/capytal/dislate/translator"
|
||||
|
||||
"github.com/charmbracelet/log"
|
||||
)
|
||||
|
||||
type TranslationProvider string
|
||||
|
||||
const (
|
||||
GOOGLE_TRANSLATE TranslationProvider = "google-translate"
|
||||
)
|
||||
|
||||
// var translation_provider = flag.String("tprovider", string(GOOGLE_TRANSLATE), "Translation provider")
|
||||
var (
|
||||
database_file = flag.String("db", "file:./guild.db", "SQLite database file/location")
|
||||
discord_token = flag.String(
|
||||
"token",
|
||||
os.Getenv("DISCORD_TOKEN"),
|
||||
"Discord bot authentication token",
|
||||
)
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func main() {
|
||||
logger := slog.New(log.NewWithOptions(os.Stderr, log.Options{
|
||||
TimeFormat: time.DateTime,
|
||||
ReportTimestamp: true,
|
||||
ReportCaller: true,
|
||||
}))
|
||||
|
||||
db, err := guilddb.NewSQLiteDB[gconf.ConfigString](*database_file + "?_busy_timeout=5000")
|
||||
if err != nil {
|
||||
logger.Error("Failed to open database connection", slog.String("err", err.Error()))
|
||||
return
|
||||
}
|
||||
logger.Info("Connection to database started", slog.String("file", *database_file))
|
||||
defer func() {
|
||||
err := db.Close()
|
||||
if err != nil {
|
||||
logger.Error("Failed to close database connection", slog.String("err", err.Error()))
|
||||
return
|
||||
}
|
||||
logger.Info("Connection to database closed", slog.String("file", *database_file))
|
||||
}()
|
||||
|
||||
if err := db.Prepare(); err != nil {
|
||||
logger.Error("Failed to prepare database", slog.String("err", err.Error()))
|
||||
return
|
||||
}
|
||||
logger.Info("Database ready to be used")
|
||||
|
||||
bot, err := bot.NewBot(*discord_token, db, translator.NewMockTranslator(), logger)
|
||||
if err != nil {
|
||||
logger.Error("Failed to create discord bot", slog.String("err", err.Error()))
|
||||
return
|
||||
}
|
||||
if err := bot.Start(); err != nil {
|
||||
logger.Error("Failed to start discord bot", slog.String("err", err.Error()))
|
||||
return
|
||||
}
|
||||
logger.Info("Discord bot started")
|
||||
defer func() {
|
||||
if err := bot.Stop(); err != nil {
|
||||
logger.Error("Failed to stop discord bot", slog.String("err", err.Error()))
|
||||
return
|
||||
}
|
||||
logger.Info("Discord bot stopped")
|
||||
}()
|
||||
|
||||
sig := make(chan os.Signal, 1)
|
||||
signal.Notify(sig, os.Interrupt, syscall.SIGINT)
|
||||
<-sig
|
||||
}
|
||||
Reference in New Issue
Block a user