dcbot/internal/discord/bot.go

199 lines
5.1 KiB
Go

package discord
import (
"context"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/discord/internal/discordi18n"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/bwmarrin/discordgo"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
"golang.org/x/text/language"
)
type GroupService interface {
Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error)
AddTribe(ctx context.Context, id, serverID, tribeTag string) (domain.GroupWithMonitors, error)
RemoveTribe(ctx context.Context, id, serverID, tribeTag string) (domain.GroupWithMonitors, error)
SetLanguageTag(ctx context.Context, id, serverID, languageTag string) (domain.GroupWithMonitors, error)
SetChannelGains(ctx context.Context, id, serverID, channel string) (domain.GroupWithMonitors, error)
SetChannelLosses(ctx context.Context, id, serverID, channel string) (domain.GroupWithMonitors, error)
SetInternals(ctx context.Context, id, serverID string, internals bool) (domain.GroupWithMonitors, error)
SetBarbarians(ctx context.Context, id, serverID string, barbarians bool) (domain.GroupWithMonitors, error)
Execute(ctx context.Context) ([]domain.EnnoblementNotification, error)
CleanUp(ctx context.Context) error
ListServer(ctx context.Context, serverID string) ([]domain.GroupWithMonitors, error)
GetWithTribes(ctx context.Context, id, serverID string) (domain.GroupWithMonitorsAndTribes, error)
Delete(ctx context.Context, id, serverID string) error
}
type ChoiceService interface {
Versions(ctx context.Context) ([]domain.Choice, error)
}
type VillageService interface {
TranslateCoords(
ctx context.Context,
params domain.TranslateVillageCoordsParams,
page int32,
) (domain.TranslateVillageCoordsResult, error)
TranslateCoordsFromHash(ctx context.Context, paramsSHA256Hash string, page int32) (domain.TranslateVillageCoordsResult, error)
}
type Bot struct {
session *discordgo.Session
cron *cron.Cron
groupSvc GroupService
choiceSvc *choiceService
villageSvc VillageService
logger *zap.Logger
localizer *discordi18n.Localizer
}
func NewBot(
token string,
groupSvc GroupService,
choiceSvc ChoiceService,
villageSvc VillageService,
logger *zap.Logger,
) (*Bot, error) {
localizer, err := discordi18n.NewLocalizer(language.English)
if err != nil {
return nil, err
}
s, err := discordgo.New("Bot " + token)
if err != nil {
return nil, err
}
s.Identify.Intents = discordgo.IntentsNone
b := &Bot{
session: s,
cron: cron.New(
cron.WithLocation(time.UTC),
cron.WithChain(
cron.SkipIfStillRunning(cron.DiscardLogger),
),
),
groupSvc: groupSvc,
choiceSvc: &choiceService{
choiceSvc: choiceSvc,
localizer: localizer,
},
villageSvc: villageSvc,
logger: logger,
localizer: localizer,
}
b.session.AddHandler(b.handleSessionReady)
b.session.AddHandler(b.logCommands)
return b, nil
}
func (b *Bot) Run() error {
if err := b.registerCronJobs(); err != nil {
return err
}
if err := b.session.Open(); err != nil {
return err
}
if err := b.registerCommands(); err != nil {
_ = b.session.Close()
return err
}
b.cron.Run()
return nil
}
type command interface {
name() string
register(s *discordgo.Session) error
}
func (b *Bot) registerCommands() error {
commands := []command{
&groupCommand{localizer: b.localizer, logger: b.logger, groupSvc: b.groupSvc, choiceSvc: b.choiceSvc},
&coordsCommand{localizer: b.localizer, logger: b.logger, villageSvc: b.villageSvc, choiceSvc: b.choiceSvc},
}
for _, cmd := range commands {
if err := cmd.register(b.session); err != nil {
return fmt.Errorf("couldn't register command '%s': %w", cmd.name(), err)
}
}
return nil
}
func (b *Bot) registerCronJobs() error {
jobs := []struct {
spec string
job cron.Job
}{
{
spec: "@every 1m",
job: &executeMonitorsJob{svc: b.groupSvc, s: b.session, localizer: b.localizer, logger: b.logger},
},
{
spec: "0 */8 * * *",
job: &cleanUpGroupsJob{svc: b.groupSvc, logger: b.logger},
},
}
for _, j := range jobs {
if _, err := b.cron.AddJob(j.spec, j.job); err != nil {
return err
}
}
return nil
}
func (b *Bot) handleSessionReady(s *discordgo.Session, _ *discordgo.Ready) {
_ = s.UpdateGameStatus(0, "Tribal Wars")
b.logger.Info("Bot is up and running")
}
func (b *Bot) logCommands(_ *discordgo.Session, i *discordgo.InteractionCreate) {
if i.Type != discordgo.InteractionApplicationCommand {
return
}
cmdData := i.ApplicationCommandData()
cmd := cmdData.Name
options := cmdData.Options
for len(options) > 0 &&
(options[0].Type == discordgo.ApplicationCommandOptionSubCommand || options[0].Type == discordgo.ApplicationCommandOptionSubCommandGroup) {
cmd += " " + options[0].Name
options = options[0].Options
}
userID := ""
if i.User != nil {
userID = i.User.ID
} else if i.Member != nil {
userID = i.Member.User.ID
}
b.logger.Info(
"executing command",
zap.String("command", cmd),
zap.String("user", userID),
zap.String("serverID", i.GuildID),
zap.String("channelID", i.ChannelID),
)
}
func (b *Bot) Close() error {
<-b.cron.Stop().Done()
return b.session.Close()
}