dcbot/internal/discord/bot.go
Dawid Wysokiński 5e99f68a91
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
feat: auto clean up old groups (#38)
Reviewed-on: #38
2022-10-31 05:52:20 +00:00

187 lines
4.2 KiB
Go

package discord
import (
"context"
"fmt"
"time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/bwmarrin/discordgo"
"github.com/robfig/cron/v3"
"go.uber.org/zap"
)
type GroupService interface {
Create(ctx context.Context, params domain.CreateGroupParams) (domain.Group, error)
SetChannelGains(ctx context.Context, id, serverID, channel string) (domain.Group, error)
SetChannelLosses(ctx context.Context, id, serverID, channel string) (domain.Group, error)
SetInternals(ctx context.Context, id, serverID string, internals bool) (domain.Group, error)
SetBarbarians(ctx context.Context, id, serverID string, barbarians bool) (domain.Group, error)
CleanUp(ctx context.Context) error
List(ctx context.Context, params domain.ListGroupsParams) ([]domain.Group, error)
Delete(ctx context.Context, id, serverID string) error
}
type MonitorService interface {
Create(ctx context.Context, groupID, serverID, tribeTag string) (domain.Monitor, error)
List(ctx context.Context, groupID, serverID string) ([]domain.MonitorWithTribe, error)
Execute(ctx context.Context) ([]domain.EnnoblementNotification, error)
Delete(ctx context.Context, id, serverID string) error
}
type ChoiceService interface {
Versions(ctx context.Context) ([]domain.Choice, error)
}
type Bot struct {
s *discordgo.Session
c *cron.Cron
groupSvc GroupService
monitorSvc MonitorService
choiceSvc ChoiceService
logger *zap.Logger
}
func NewBot(
token string,
groupSvc GroupService,
monitorSvc MonitorService,
client ChoiceService,
logger *zap.Logger,
) (*Bot, error) {
s, err := discordgo.New("Bot " + token)
if err != nil {
return nil, fmt.Errorf("discordgo.New: %w", err)
}
s.Identify.Intents = discordgo.IntentsNone
b := &Bot{
s: s,
c: cron.New(
cron.WithLocation(time.UTC),
cron.WithChain(
cron.SkipIfStillRunning(cron.DiscardLogger),
),
),
groupSvc: groupSvc,
monitorSvc: monitorSvc,
choiceSvc: client,
logger: logger,
}
b.s.AddHandler(b.handleSessionReady)
b.s.AddHandler(b.logCommands)
return b, nil
}
func (b *Bot) Run() error {
if err := b.initCron(); err != nil {
return fmt.Errorf("initCron: %w", err)
}
if err := b.s.Open(); err != nil {
return fmt.Errorf("s.Open: %w", err)
}
if err := b.registerCommands(); err != nil {
_ = b.s.Close()
return fmt.Errorf("couldn't register commands: %w", err)
}
b.c.Run()
return nil
}
type command interface {
name() string
register(s *discordgo.Session) error
}
func (b *Bot) registerCommands() error {
commands := []command{
&groupCommand{groupSvc: b.groupSvc, choiceSvc: b.choiceSvc},
&monitorCommand{svc: b.monitorSvc},
}
for _, c := range commands {
if err := b.registerCommand(c); err != nil {
return err
}
}
return nil
}
func (b *Bot) registerCommand(cmd command) error {
if err := cmd.register(b.s); err != nil {
return fmt.Errorf("couldn't register command '%s': %w", cmd.name(), err)
}
return nil
}
func (b *Bot) initCron() error {
jobs := []struct {
spec string
job cron.Job
}{
{
spec: "@every 1m",
job: &executeMonitorsJob{svc: b.monitorSvc, s: b.s, logger: b.logger},
},
{
spec: "0 */8 * * *",
job: &cleanUpGroupsJob{svc: b.groupSvc, logger: b.logger},
},
}
for _, j := range jobs {
_, err := b.c.AddJob(j.spec, j.job)
if err != nil {
return err
}
}
return nil
}
func (b *Bot) handleSessionReady(s *discordgo.Session, _ *discordgo.Ready) {
_ = s.UpdateGameStatus(0, "Tribal Wars")
}
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 {
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.c.Stop().Done()
return b.s.Close()
}