feat: notifications - i18n (#112)
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details

Reviewed-on: #112
This commit is contained in:
Dawid Wysokiński 2023-06-30 05:09:52 +00:00
parent 6415b162de
commit 4012bd0a2f
18 changed files with 510 additions and 131 deletions

View File

@ -31,6 +31,7 @@ func (g *Group) Create(ctx context.Context, params domain.CreateGroupParams) (do
ServerKey: params.ServerKey(), ServerKey: params.ServerKey(),
Barbarians: params.Barbarians(), Barbarians: params.Barbarians(),
Internals: params.Internals(), Internals: params.Internals(),
LanguageTag: params.LanguageTag(),
} }
if _, err := g.db.NewInsert(). if _, err := g.db.NewInsert().
@ -232,6 +233,14 @@ func (u updateGroupsParamsApplier) apply(q *bun.UpdateQuery) *bun.UpdateQuery {
} }
} }
if u.params.LanguageTag.Valid {
if u.params.LanguageTag.String != "" {
q = q.Set("language_tag = ?", u.params.LanguageTag.String)
} else {
q = q.Set("language_tag = NULL")
}
}
if u.params.Barbarians.Valid { if u.params.Barbarians.Valid {
q = q.Set("barbarians = ?", u.params.Barbarians.Bool) q = q.Set("barbarians = ?", u.params.Barbarians.Bool)
} }

View File

@ -10,6 +10,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/text/language"
) )
func TestGroup_Create(t *testing.T) { func TestGroup_Create(t *testing.T) {
@ -28,6 +29,7 @@ func TestGroup_Create(t *testing.T) {
"592292203234328587", "592292203234328587",
"en", "en",
"en113", "en113",
language.Polish.String(),
"1234", "1234",
"1235", "1235",
true, true,
@ -45,6 +47,7 @@ func TestGroup_Create(t *testing.T) {
assert.Equal(t, params.ChannelLosses(), group.ChannelLosses) assert.Equal(t, params.ChannelLosses(), group.ChannelLosses)
assert.Equal(t, params.Barbarians(), group.Barbarians) assert.Equal(t, params.Barbarians(), group.Barbarians)
assert.Equal(t, params.Internals(), group.Internals) assert.Equal(t, params.Internals(), group.Internals)
assert.Equal(t, params.LanguageTag(), group.LanguageTag)
assert.WithinDuration(t, time.Now(), group.CreatedAt, 1*time.Second) assert.WithinDuration(t, time.Now(), group.CreatedAt, 1*time.Second)
}) })
} }
@ -73,6 +76,10 @@ func TestGroup_Update(t *testing.T) {
String: group.ChannelLosses + "update", String: group.ChannelLosses + "update",
Valid: true, Valid: true,
}, },
LanguageTag: domain.NullString{
String: language.AmericanEnglish.String(),
Valid: true,
},
Barbarians: domain.NullBool{ Barbarians: domain.NullBool{
Bool: !group.Barbarians, Bool: !group.Barbarians,
Valid: true, Valid: true,
@ -87,6 +94,7 @@ func TestGroup_Update(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, params.ChannelGains.String, updatedGroup.ChannelGains) assert.Equal(t, params.ChannelGains.String, updatedGroup.ChannelGains)
assert.Equal(t, params.ChannelLosses.String, updatedGroup.ChannelLosses) assert.Equal(t, params.ChannelLosses.String, updatedGroup.ChannelLosses)
assert.Equal(t, params.LanguageTag.String, updatedGroup.LanguageTag)
assert.Equal(t, params.Barbarians.Bool, updatedGroup.Barbarians) assert.Equal(t, params.Barbarians.Bool, updatedGroup.Barbarians)
assert.Equal(t, params.Internals.Bool, updatedGroup.Internals) assert.Equal(t, params.Internals.Bool, updatedGroup.Internals)
}) })

View File

@ -19,6 +19,7 @@ type Group struct {
Barbarians bool `bun:"barbarians"` Barbarians bool `bun:"barbarians"`
ServerKey string `bun:"server_key,nullzero"` ServerKey string `bun:"server_key,nullzero"`
VersionCode string `bun:"version_code,nullzero"` VersionCode string `bun:"version_code,nullzero"`
LanguageTag string `bun:"language_tag,nullzero"`
CreatedAt time.Time `bun:"created_at,nullzero"` CreatedAt time.Time `bun:"created_at,nullzero"`
Monitors []Monitor `bun:"monitors,rel:has-many,join:id=group_id"` Monitors []Monitor `bun:"monitors,rel:has-many,join:id=group_id"`
} }
@ -38,6 +39,7 @@ func (g Group) ToDomain() domain.GroupWithMonitors {
Barbarians: g.Barbarians, Barbarians: g.Barbarians,
ServerKey: g.ServerKey, ServerKey: g.ServerKey,
VersionCode: g.VersionCode, VersionCode: g.VersionCode,
LanguageTag: g.LanguageTag,
CreatedAt: g.CreatedAt, CreatedAt: g.CreatedAt,
}, },
Monitors: monitors, Monitors: monitors,

View File

@ -6,6 +6,7 @@
server_key: pl181 server_key: pl181
version_code: pl version_code: pl
channel_losses: 125 channel_losses: 125
language_tag: pl
created_at: 2022-03-15T15:00:10.000Z created_at: 2022-03-15T15:00:10.000Z
- _id: group-2-server-1 - _id: group-2-server-1
id: 429b790e-7186-4106-b531-4cc4931ce2ba id: 429b790e-7186-4106-b531-4cc4931ce2ba
@ -13,12 +14,14 @@
server_key: pl181 server_key: pl181
version_code: pl version_code: pl
channel_gains: 125 channel_gains: 125
language_tag: pl
created_at: 2022-03-15T15:03:10.000Z created_at: 2022-03-15T15:03:10.000Z
- _id: group-1-server-2 - _id: group-1-server-2
id: abeb6c8e-70b6-445c-989f-890cd2a1f87a id: abeb6c8e-70b6-445c-989f-890cd2a1f87a
server_id: server-2 server_id: server-2
server_key: pl181 server_key: pl181
version_code: pl version_code: pl
language_tag: pl
created_at: 2022-03-18T15:03:10.000Z created_at: 2022-03-18T15:03:10.000Z
- _id: group-2-server-2 - _id: group-2-server-2
id: 0be82203-4ca3-4b4c-a0c8-3a70099d88f7 id: 0be82203-4ca3-4b4c-a0c8-3a70099d88f7
@ -27,6 +30,7 @@
version_code: pl version_code: pl
channel_gains: 1555 channel_gains: 1555
channel_losses: 1235 channel_losses: 1235
language_tag: pl
created_at: 2022-03-19T15:03:10.000Z created_at: 2022-03-19T15:03:10.000Z
- _id: group-3-server-1 - _id: group-3-server-1
id: 982a9765-471c-43e8-9abb-1e4ee4f03738 id: 982a9765-471c-43e8-9abb-1e4ee4f03738
@ -35,6 +39,7 @@
version_code: pl version_code: pl
channel_gains: 1555 channel_gains: 1555
channel_losses: 1235 channel_losses: 1235
language_tag: pl
created_at: 2022-03-23T18:03:10.000Z created_at: 2022-03-23T18:03:10.000Z
- _id: group-3-server-2 - _id: group-3-server-2
id: f3a0a5d9-07fe-4770-8b43-79cbe10bb310 id: f3a0a5d9-07fe-4770-8b43-79cbe10bb310
@ -43,6 +48,7 @@
version_code: en version_code: en
channel_gains: 1555 channel_gains: 1555
channel_losses: 1235 channel_losses: 1235
language_tag: en
created_at: 2022-04-23T18:03:10.000Z created_at: 2022-04-23T18:03:10.000Z
- model: Monitor - model: Monitor
rows: rows:
@ -60,4 +66,4 @@
id: c7d63c3d-55f6-432e-b9d8-006f8c5ab407 id: c7d63c3d-55f6-432e-b9d8-006f8c5ab407
group_id: 0be82203-4ca3-4b4c-a0c8-3a70099d88f7 group_id: 0be82203-4ca3-4b4c-a0c8-3a70099d88f7
tribe_id: 121 tribe_id: 121
created_at: 2022-03-19T15:10:10.000Z created_at: 2022-03-19T15:10:10.000Z

View File

@ -17,6 +17,7 @@ type GroupService interface {
Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error) Create(ctx context.Context, params domain.CreateGroupParams) (domain.GroupWithMonitors, error)
AddTribe(ctx context.Context, id, serverID, tribeTag string) (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) 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) SetChannelGains(ctx context.Context, id, serverID, channel string) (domain.GroupWithMonitors, error)
SetChannelLosses(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) SetInternals(ctx context.Context, id, serverID string, internals bool) (domain.GroupWithMonitors, error)
@ -54,7 +55,7 @@ func NewBot(
s, err := discordgo.New("Bot " + token) s, err := discordgo.New("Bot " + token)
if err != nil { if err != nil {
return nil, fmt.Errorf("discordgo.New: %w", err) return nil, err
} }
s.Identify.Intents = discordgo.IntentsNone s.Identify.Intents = discordgo.IntentsNone
@ -80,16 +81,16 @@ func NewBot(
func (b *Bot) Run() error { func (b *Bot) Run() error {
if err := b.registerCronJobs(); err != nil { if err := b.registerCronJobs(); err != nil {
return fmt.Errorf("initCron: %w", err) return err
} }
if err := b.session.Open(); err != nil { if err := b.session.Open(); err != nil {
return fmt.Errorf("s.Open: %w", err) return err
} }
if err := b.registerCommands(); err != nil { if err := b.registerCommands(); err != nil {
_ = b.session.Close() _ = b.session.Close()
return fmt.Errorf("couldn't register commands: %w", err) return err
} }
b.cron.Run() b.cron.Run()
@ -123,7 +124,7 @@ func (b *Bot) registerCronJobs() error {
}{ }{
{ {
spec: "@every 1m", spec: "@every 1m",
job: &executeMonitorsJob{svc: b.groupSvc, s: b.session, logger: b.logger}, job: &executeMonitorsJob{svc: b.groupSvc, s: b.session, localizer: b.localizer, logger: b.logger},
}, },
{ {
spec: "0 */8 * * *", spec: "0 */8 * * *",

View File

@ -80,7 +80,7 @@ func (c *groupCommand) create(s *discordgo.Session) error {
} }
func (c *groupCommand) buildSubcommandCreate() (*discordgo.ApplicationCommandOption, error) { func (c *groupCommand) buildSubcommandCreate() (*discordgo.ApplicationCommandOption, error) {
versionChoices, err := c.getVersionChoices() options, err := c.buildSubcommandCreateOptions()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -90,6 +90,26 @@ func (c *groupCommand) buildSubcommandCreate() (*discordgo.ApplicationCommandOpt
return nil, err return nil, err
} }
return &discordgo.ApplicationCommandOption{
Name: "create",
Description: cmdDescriptionDefault,
DescriptionLocalizations: cmdDescriptionLocalizations,
Type: discordgo.ApplicationCommandOptionSubCommand,
Options: options,
}, nil
}
func (c *groupCommand) buildSubcommandCreateOptions() ([]*discordgo.ApplicationCommandOption, error) {
versionChoices, err := c.getVersionChoices()
if err != nil {
return nil, err
}
languageChoices, err := c.getLanguageChoices()
if err != nil {
return nil, err
}
optionVersionDefault, optionVersionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.option.version.description") optionVersionDefault, optionVersionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.option.version.description")
if err != nil { if err != nil {
return nil, err return nil, err
@ -115,6 +135,11 @@ func (c *groupCommand) buildSubcommandCreate() (*discordgo.ApplicationCommandOpt
return nil, err return nil, err
} }
optionLanguageDefault, optionLanguageLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.create.option.language.description")
if err != nil {
return nil, err
}
optionChannelLossesDefault, optionChannelLossesLocalizations, err := c.localizer.LocalizeDiscord( optionChannelLossesDefault, optionChannelLossesLocalizations, err := c.localizer.LocalizeDiscord(
"cmd.group.create.option.channel-losses.description", "cmd.group.create.option.channel-losses.description",
) )
@ -122,65 +147,101 @@ func (c *groupCommand) buildSubcommandCreate() (*discordgo.ApplicationCommandOpt
return nil, err return nil, err
} }
return &discordgo.ApplicationCommandOption{ return []*discordgo.ApplicationCommandOption{
Name: "create", {
Description: cmdDescriptionDefault, Name: "version",
DescriptionLocalizations: cmdDescriptionLocalizations, Description: optionVersionDefault,
Type: discordgo.ApplicationCommandOptionSubCommand, DescriptionLocalizations: optionVersionLocalizations,
Options: []*discordgo.ApplicationCommandOption{ Type: discordgo.ApplicationCommandOptionString,
{ Choices: versionChoices,
Name: "version", Required: true,
Description: optionVersionDefault, },
DescriptionLocalizations: optionVersionLocalizations, {
Type: discordgo.ApplicationCommandOptionString, Name: "server",
Choices: versionChoices, Description: optionServerDefault,
Required: true, DescriptionLocalizations: optionServerLocalizations,
Type: discordgo.ApplicationCommandOptionString,
Required: true,
},
{
Name: "language",
Description: optionLanguageDefault,
DescriptionLocalizations: optionLanguageLocalizations,
Type: discordgo.ApplicationCommandOptionString,
Choices: languageChoices,
Required: true,
},
{
Name: "internals",
Description: optionInternalsDefault,
DescriptionLocalizations: optionInternalsLocalizations,
Type: discordgo.ApplicationCommandOptionBoolean,
Required: true,
},
{
Name: "barbarians",
Description: optionBarbariansDefault,
DescriptionLocalizations: optionBarbariansLocalizations,
Type: discordgo.ApplicationCommandOptionBoolean,
Required: true,
},
{
Name: "channel-gains",
Description: optionChannelGainsDefault,
DescriptionLocalizations: optionChannelGainsLocalizations,
Type: discordgo.ApplicationCommandOptionChannel,
ChannelTypes: []discordgo.ChannelType{
discordgo.ChannelTypeGuildText,
}, },
{ Required: false,
Name: "server", },
Description: optionServerDefault, {
DescriptionLocalizations: optionServerLocalizations, Name: "channel-losses",
Type: discordgo.ApplicationCommandOptionString, Description: optionChannelLossesDefault,
Required: true, DescriptionLocalizations: optionChannelLossesLocalizations,
}, Type: discordgo.ApplicationCommandOptionChannel,
{ ChannelTypes: []discordgo.ChannelType{
Name: "internals", discordgo.ChannelTypeGuildText,
Description: optionInternalsDefault,
DescriptionLocalizations: optionInternalsLocalizations,
Type: discordgo.ApplicationCommandOptionBoolean,
Required: true,
},
{
Name: "barbarians",
Description: optionBarbariansDefault,
DescriptionLocalizations: optionBarbariansLocalizations,
Type: discordgo.ApplicationCommandOptionBoolean,
Required: true,
},
{
Name: "channel-gains",
Description: optionChannelGainsDefault,
DescriptionLocalizations: optionChannelGainsLocalizations,
Type: discordgo.ApplicationCommandOptionChannel,
ChannelTypes: []discordgo.ChannelType{
discordgo.ChannelTypeGuildText,
},
Required: false,
},
{
Name: "channel-losses",
Description: optionChannelLossesDefault,
DescriptionLocalizations: optionChannelLossesLocalizations,
Type: discordgo.ApplicationCommandOptionChannel,
ChannelTypes: []discordgo.ChannelType{
discordgo.ChannelTypeGuildText,
},
Required: false,
}, },
Required: false,
}, },
}, nil }, nil
} }
func (c *groupCommand) getVersionChoices() ([]*discordgo.ApplicationCommandOptionChoice, error) {
choices, err := c.choiceSvc.Versions(context.Background())
if err != nil {
return nil, fmt.Errorf("ChoiceService.Versions: %w", err)
}
dcChoices := make([]*discordgo.ApplicationCommandOptionChoice, 0, len(choices))
for _, v := range choices {
dcChoices = append(dcChoices, &discordgo.ApplicationCommandOptionChoice{
Name: v.Name,
Value: v.Value,
})
}
return dcChoices, nil
}
func (c *groupCommand) getLanguageChoices() ([]*discordgo.ApplicationCommandOptionChoice, error) {
tags := c.localizer.LanguageTags()
choices := make([]*discordgo.ApplicationCommandOptionChoice, 0, len(tags))
for _, tag := range tags {
lang, langLocalizations, err := c.localizer.LocalizeDiscord(buildLangMessageID(tag.String()))
if err != nil {
return nil, err
}
choices = append(choices, &discordgo.ApplicationCommandOptionChoice{
Name: lang,
NameLocalizations: langLocalizations,
Value: tag.String(),
})
}
return choices, nil
}
func (c *groupCommand) buildSubcommandList() (*discordgo.ApplicationCommandOption, error) { func (c *groupCommand) buildSubcommandList() (*discordgo.ApplicationCommandOption, error) {
cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.list.description") cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.list.description")
if err != nil { if err != nil {
@ -329,6 +390,7 @@ func (c *groupCommand) buildSubcommandMonitorRemove() (*discordgo.ApplicationCom
func (c *groupCommand) buildSubcommandSet() (*discordgo.ApplicationCommandOption, error) { func (c *groupCommand) buildSubcommandSet() (*discordgo.ApplicationCommandOption, error) {
subcommandBuilders := []func() (*discordgo.ApplicationCommandOption, error){ subcommandBuilders := []func() (*discordgo.ApplicationCommandOption, error){
c.buildSubcommandSetLanguage,
c.buildSubcommandSetChannelGains, c.buildSubcommandSetChannelGains,
c.buildSubcommandSetChannelLosses, c.buildSubcommandSetChannelLosses,
c.buildSubcommandSetInternals, c.buildSubcommandSetInternals,
@ -357,6 +419,52 @@ func (c *groupCommand) buildSubcommandSet() (*discordgo.ApplicationCommandOption
}, nil }, nil
} }
func (c *groupCommand) buildSubcommandSetLanguage() (*discordgo.ApplicationCommandOption, error) {
languageChoices, err := c.getLanguageChoices()
if err != nil {
return nil, err
}
cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.language.description")
if err != nil {
return nil, err
}
optionGroupDefault, optionGroupLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.language.option.group.description")
if err != nil {
return nil, err
}
optionLanguageDefault, optionLanguageLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.language.option.language.description")
if err != nil {
return nil, err
}
return &discordgo.ApplicationCommandOption{
Name: "language",
Description: cmdDescriptionDefault,
DescriptionLocalizations: cmdDescriptionLocalizations,
Type: discordgo.ApplicationCommandOptionSubCommand,
Options: []*discordgo.ApplicationCommandOption{
{
Name: "group",
Description: optionGroupDefault,
DescriptionLocalizations: optionGroupLocalizations,
Type: discordgo.ApplicationCommandOptionString,
Required: true,
},
{
Name: "language",
Description: optionLanguageDefault,
DescriptionLocalizations: optionLanguageLocalizations,
Type: discordgo.ApplicationCommandOptionString,
Choices: languageChoices,
Required: true,
},
},
}, nil
}
func (c *groupCommand) buildSubcommandSetChannelGains() (*discordgo.ApplicationCommandOption, error) { func (c *groupCommand) buildSubcommandSetChannelGains() (*discordgo.ApplicationCommandOption, error) {
cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.channel-gains.description") cmdDescriptionDefault, cmdDescriptionLocalizations, err := c.localizer.LocalizeDiscord("cmd.group.set.channel-gains.description")
if err != nil { if err != nil {
@ -549,23 +657,6 @@ func (c *groupCommand) buildSubcommandDelete() (*discordgo.ApplicationCommandOpt
}, nil }, nil
} }
func (c *groupCommand) getVersionChoices() ([]*discordgo.ApplicationCommandOptionChoice, error) {
choices, err := c.choiceSvc.Versions(context.Background())
if err != nil {
return nil, fmt.Errorf("ChoiceService.Versions: %w", err)
}
dcChoices := make([]*discordgo.ApplicationCommandOptionChoice, 0, len(choices))
for _, v := range choices {
dcChoices = append(dcChoices, &discordgo.ApplicationCommandOptionChoice{
Name: v.Name,
Value: v.Value,
})
}
return dcChoices, nil
}
func (c *groupCommand) handle(s *discordgo.Session, i *discordgo.InteractionCreate) { func (c *groupCommand) handle(s *discordgo.Session, i *discordgo.InteractionCreate) {
cmdData := i.ApplicationCommandData() cmdData := i.ApplicationCommandData()
@ -575,7 +666,8 @@ func (c *groupCommand) handle(s *discordgo.Session, i *discordgo.InteractionCrea
ctx := context.Background() ctx := context.Background()
switch cmdData.Options[0].Name { name := cmdData.Options[0].Name
switch name {
case "create": case "create":
c.handleCreate(ctx, s, i) c.handleCreate(ctx, s, i)
case "list": case "list":
@ -588,6 +680,8 @@ func (c *groupCommand) handle(s *discordgo.Session, i *discordgo.InteractionCrea
c.handleTribe(ctx, s, i) c.handleTribe(ctx, s, i)
case "delete": case "delete":
c.handleDelete(ctx, s, i) c.handleDelete(ctx, s, i)
default:
c.logger.Error("unknown subcommand", zap.String("command", "group"), zap.String("subcommand", name))
} }
} }
@ -647,12 +741,15 @@ func (c *groupCommand) optionsToCreateGroupParams(
channelLosses := "" channelLosses := ""
internals := false internals := false
barbarians := false barbarians := false
languageTag := ""
for _, opt := range options { for _, opt := range options {
switch opt.Name { switch opt.Name {
case "version": case "version":
version = opt.StringValue() version = opt.StringValue()
case "server": case "server":
server = opt.StringValue() server = opt.StringValue()
case "language":
languageTag = opt.StringValue()
case "channel-gains": case "channel-gains":
channelGains = opt.ChannelValue(s).ID channelGains = opt.ChannelValue(s).ID
case "channel-losses": case "channel-losses":
@ -663,8 +760,16 @@ func (c *groupCommand) optionsToCreateGroupParams(
barbarians = opt.BoolValue() barbarians = opt.BoolValue()
} }
} }
return domain.NewCreateGroupParams(
return domain.NewCreateGroupParams(guildID, version, server, channelGains, channelLosses, barbarians, internals) guildID,
version,
server,
languageTag,
channelGains,
channelLosses,
barbarians,
internals,
)
} }
func (c *groupCommand) handleList(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { func (c *groupCommand) handleList(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
@ -688,10 +793,21 @@ func (c *groupCommand) handleList(ctx context.Context, s *discordgo.Session, i *
if g.ChannelGains != "" { if g.ChannelGains != "" {
channelGains = buildChannelMention(g.ChannelGains) channelGains = buildChannelMention(g.ChannelGains)
} }
channelLosses := "-" channelLosses := "-"
if g.ChannelLosses != "" { if g.ChannelLosses != "" {
channelLosses = buildChannelMention(g.ChannelLosses) channelLosses = buildChannelMention(g.ChannelLosses)
} }
langMessageID := buildLangMessageID(g.LanguageTag)
lang, localizeErr := c.localizer.Localize(locale, &i18n.LocalizeConfig{
MessageID: langMessageID,
})
if localizeErr != nil {
c.logger.Error("no message with the specified identifier", zap.String("id", langMessageID), zap.Error(localizeErr))
return
}
fieldValue, localizeErr := c.localizer.Localize(locale, &i18n.LocalizeConfig{ fieldValue, localizeErr := c.localizer.Localize(locale, &i18n.LocalizeConfig{
MessageID: fieldValueMessageID, MessageID: fieldValueMessageID,
TemplateData: map[string]any{ TemplateData: map[string]any{
@ -701,12 +817,14 @@ func (c *groupCommand) handleList(ctx context.Context, s *discordgo.Session, i *
"Internals": boolToEmoji(g.Internals), "Internals": boolToEmoji(g.Internals),
"Barbarians": boolToEmoji(g.Barbarians), "Barbarians": boolToEmoji(g.Barbarians),
"NumTribes": len(g.Monitors), "NumTribes": len(g.Monitors),
"Language": lang,
}, },
}) })
if localizeErr != nil { if localizeErr != nil {
c.logger.Error("no message with the specified identifier", zap.String("id", fieldValueMessageID), zap.Error(localizeErr)) c.logger.Error("no message with the specified identifier", zap.String("id", fieldValueMessageID), zap.Error(localizeErr))
return return
} }
fields = append(fields, &discordgo.MessageEmbedField{ fields = append(fields, &discordgo.MessageEmbedField{
Name: g.ID, Name: g.ID,
Value: fieldValue, Value: fieldValue,
@ -770,6 +888,15 @@ func (c *groupCommand) handleDetails(ctx context.Context, s *discordgo.Session,
channelLosses = buildChannelMention(g.ChannelLosses) channelLosses = buildChannelMention(g.ChannelLosses)
} }
langMessageID := buildLangMessageID(g.LanguageTag)
lang, localizeErr := c.localizer.Localize(locale, &i18n.LocalizeConfig{
MessageID: langMessageID,
})
if localizeErr != nil {
c.logger.Error("no message with the specified identifier", zap.String("id", langMessageID), zap.Error(localizeErr))
return
}
descriptionMessageID := "cmd.group.details.embed.description" descriptionMessageID := "cmd.group.details.embed.description"
description, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{ description, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{
MessageID: descriptionMessageID, MessageID: descriptionMessageID,
@ -780,6 +907,7 @@ func (c *groupCommand) handleDetails(ctx context.Context, s *discordgo.Session,
"Internals": boolToEmoji(g.Internals), "Internals": boolToEmoji(g.Internals),
"Barbarians": boolToEmoji(g.Barbarians), "Barbarians": boolToEmoji(g.Barbarians),
"Tribes": builderTribes.String(), "Tribes": builderTribes.String(),
"Language": lang,
}, },
}) })
if err != nil { if err != nil {
@ -802,7 +930,10 @@ func (c *groupCommand) handleDetails(ctx context.Context, s *discordgo.Session,
} }
func (c *groupCommand) handleSet(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { func (c *groupCommand) handleSet(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
switch i.ApplicationCommandData().Options[0].Options[0].Name { name := i.ApplicationCommandData().Options[0].Options[0].Name
switch name {
case "language":
c.handleSetLanguage(ctx, s, i)
case "channel-gains": case "channel-gains":
c.handleSetChannelGains(ctx, s, i) c.handleSetChannelGains(ctx, s, i)
case "channel-losses": case "channel-losses":
@ -811,9 +942,54 @@ func (c *groupCommand) handleSet(ctx context.Context, s *discordgo.Session, i *d
c.handleSetInternals(ctx, s, i) c.handleSetInternals(ctx, s, i)
case "barbarians": case "barbarians":
c.handleSetBarbarians(ctx, s, i) c.handleSetBarbarians(ctx, s, i)
default:
c.logger.Error("unknown subcommand", zap.String("command", "group set"), zap.String("subcommand", name))
} }
} }
func (c *groupCommand) handleSetLanguage(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
locale := string(i.Locale)
group := ""
languageTag := ""
for _, opt := range i.ApplicationCommandData().Options[0].Options[0].Options {
if opt == nil {
continue
}
switch opt.Name {
case "group":
group = opt.StringValue()
case "language":
languageTag = opt.StringValue()
}
}
_, err := c.groupSvc.SetLanguageTag(ctx, group, i.GuildID, languageTag)
if err != nil {
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: c.localizer.LocalizeError(locale, err),
},
})
return
}
successMessageID := "cmd.group.set.language.success"
successMsg, err := c.localizer.Localize(locale, &i18n.LocalizeConfig{MessageID: successMessageID})
if err != nil {
c.logger.Error("no message with the specified identifier", zap.String("id", successMessageID), zap.Error(err))
return
}
_ = s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: successMsg,
},
})
}
func (c *groupCommand) handleSetChannelGains(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { func (c *groupCommand) handleSetChannelGains(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
locale := string(i.Locale) locale := string(i.Locale)
@ -987,11 +1163,14 @@ func (c *groupCommand) handleSetBarbarians(ctx context.Context, s *discordgo.Ses
} }
func (c *groupCommand) handleTribe(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) { func (c *groupCommand) handleTribe(ctx context.Context, s *discordgo.Session, i *discordgo.InteractionCreate) {
switch i.ApplicationCommandData().Options[0].Options[0].Name { name := i.ApplicationCommandData().Options[0].Options[0].Name
switch name {
case "add": case "add":
c.handleTribeAdd(ctx, s, i) c.handleTribeAdd(ctx, s, i)
case "remove": case "remove":
c.handleTribeRemove(ctx, s, i) c.handleTribeRemove(ctx, s, i)
default:
c.logger.Error("unknown subcommand", zap.String("command", "group tribe"), zap.String("subcommand", name))
} }
} }

View File

@ -30,3 +30,7 @@ func boolToEmoji(val bool) string {
} }
return ":x:" return ":x:"
} }
func buildLangMessageID(languageTag string) string {
return "lang." + languageTag
}

View File

@ -1,4 +1,7 @@
{ {
"lang.pl": "Polish",
"lang.en": "English",
"err.default": "Something went wrong. Try again later.", "err.default": "Something went wrong. Try again later.",
"err.required": "{{ .Field }} can't be blank.", "err.required": "{{ .Field }} can't be blank.",
"err.group-limit-reached": "The group limit has been reached ({{ .Current }}/{{ .Limit }}).", "err.group-limit-reached": "The group limit has been reached ({{ .Current }}/{{ .Limit }}).",
@ -12,11 +15,14 @@
"err.tribe-not-found": "Tribe (tag={{ .Tag }}) not found.", "err.tribe-not-found": "Tribe (tag={{ .Tag }}) not found.",
"err.tribe-does-not-exist": "The tribe (tag={{ .Tag }}) doesn't exist.", "err.tribe-does-not-exist": "The tribe (tag={{ .Tag }}) doesn't exist.",
"job.execute-monitors.embed.description": "{{ buildPlayerMarkdown .Ennoblement.NewOwner }} has taken {{ buildLink .Ennoblement.Village.FullName .Ennoblement.Village.ProfileURL }} (Old owner: {{ buildPlayerMarkdown .Ennoblement.Village.Player }}).",
"cmd.group.description": "Manages groups on this server", "cmd.group.description": "Manages groups on this server",
"cmd.group.create.description": "Creates a new monitor group", "cmd.group.create.description": "Creates a new monitor group",
"cmd.group.create.option.version.description": "e.g. www.tribalwars.net, www.plemiona.pl", "cmd.group.create.option.version.description": "e.g. www.tribalwars.net, www.plemiona.pl",
"cmd.group.create.option.server.description": "Tribal Wars server (e.g. en115, pl170)", "cmd.group.create.option.server.description": "Tribal Wars server (e.g. en115, pl170)",
"cmd.group.create.option.language.description": "Language in which you wish to receive notifications",
"cmd.group.create.option.internals.description": "Show conquers in the same group", "cmd.group.create.option.internals.description": "Show conquers in the same group",
"cmd.group.create.option.barbarians.description": "Show barbarian conquers", "cmd.group.create.option.barbarians.description": "Show barbarian conquers",
"cmd.group.create.option.channel-gains.description": "Channel where notifications of gained villages will appear", "cmd.group.create.option.channel-gains.description": "Channel where notifications of gained villages will appear",
@ -25,10 +31,10 @@
"cmd.group.list.description": "Lists all created groups on this server", "cmd.group.list.description": "Lists all created groups on this server",
"cmd.group.list.embed.title": "Group list", "cmd.group.list.embed.title": "Group list",
"cmd.group.list.embed.field.value": "**Server**: {{ .ServerKey }}\n**Channel gains**: {{ .ChannelGains }}\n**Channel losses**: {{ .ChannelLosses }}\n**Internals**: {{ .Internals }}\n **Barbarians**: {{ .Barbarians }}\n **Number of monitored tribes**: {{ .NumTribes }}", "cmd.group.list.embed.field.value": "**Server**: {{ .ServerKey }}\n**Language**: {{ .Language }}\n**Channel gains**: {{ .ChannelGains }}\n**Channel losses**: {{ .ChannelLosses }}\n**Internals**: {{ .Internals }}\n **Barbarians**: {{ .Barbarians }}\n **Number of monitored tribes**: {{ .NumTribes }}",
"cmd.group.details.description": "Displays group details (including added tribes)", "cmd.group.details.description": "Displays group details (including added tribes)",
"cmd.group.details.embed.description": "**Server**: {{ .ServerKey }}\n**Channel gains**: {{ .ChannelGains }}\n**Channel losses**: {{ .ChannelLosses }}\n**Internals**: {{ .Internals }}\n **Barbarians**: {{ .Barbarians }}\n **Tribes**: {{ .Tribes }}", "cmd.group.details.embed.description": "**Server**: {{ .ServerKey }}\n**Language**: {{ .Language }}\n**Channel gains**: {{ .ChannelGains }}\n**Channel losses**: {{ .ChannelLosses }}\n**Internals**: {{ .Internals }}\n **Barbarians**: {{ .Barbarians }}\n **Tribes**: {{ .Tribes }}",
"cmd.group.tribe.description": "Manages tribes in a group", "cmd.group.tribe.description": "Manages tribes in a group",
@ -44,6 +50,11 @@
"cmd.group.set.description": "Sets various properties in group configuration", "cmd.group.set.description": "Sets various properties in group configuration",
"cmd.group.set.language.description": "Sets the language in which you will receive notifications",
"cmd.group.set.language.option.group.description": "Group ID",
"cmd.group.set.language.option.language.description": "Language in which you wish to receive notifications",
"cmd.group.set.language.success": "The group has been successfully updated.",
"cmd.group.set.channel-gains.description": "Enables/disables notifications of gained villages", "cmd.group.set.channel-gains.description": "Enables/disables notifications of gained villages",
"cmd.group.set.channel-gains.option.group.description": "Group ID", "cmd.group.set.channel-gains.option.group.description": "Group ID",
"cmd.group.set.channel-gains.option.channel.description": "Channel where notifications of gained villages will appear", "cmd.group.set.channel-gains.option.channel.description": "Channel where notifications of gained villages will appear",

View File

@ -1,4 +1,7 @@
{ {
"lang.pl": "Polski",
"lang.en": "Angielski",
"err.default": "Coś poszło nie tak. Spróbuj ponownie później.", "err.default": "Coś poszło nie tak. Spróbuj ponownie później.",
"err.required": "{{ .Field }} nie może być pusty.", "err.required": "{{ .Field }} nie może być pusty.",
"err.group-limit-reached": "Limit grup został osiągnięty ({{ .Current }}/{{ .Limit }}).", "err.group-limit-reached": "Limit grup został osiągnięty ({{ .Current }}/{{ .Limit }}).",
@ -12,11 +15,14 @@
"err.tribe-not-found": "Plemię (skrót={{ .Tag }}) nie zostało znalezione.", "err.tribe-not-found": "Plemię (skrót={{ .Tag }}) nie zostało znalezione.",
"err.tribe-does-not-exist": "Plemię (skrót={{ .Tag }}) nie istnieje.", "err.tribe-does-not-exist": "Plemię (skrót={{ .Tag }}) nie istnieje.",
"job.execute-monitors.embed.description": "{{ buildPlayerMarkdown .Ennoblement.NewOwner }} przejął {{ buildLink .Ennoblement.Village.FullName .Ennoblement.Village.ProfileURL }} (Poprzedni właściciel: {{ buildPlayerMarkdown .Ennoblement.Village.Player }}).",
"cmd.group.description": "Umożliwia zarządzanie grupami na tym serwerze", "cmd.group.description": "Umożliwia zarządzanie grupami na tym serwerze",
"cmd.group.create.description": "Tworzy nową grupę", "cmd.group.create.description": "Tworzy nową grupę",
"cmd.group.create.option.version.description": "np. www.tribalwars.net, www.plemiona.pl", "cmd.group.create.option.version.description": "np. www.tribalwars.net, www.plemiona.pl",
"cmd.group.create.option.server.description": "Serwer (np. en115, pl170)", "cmd.group.create.option.server.description": "Serwer (np. en115, pl170)",
"cmd.group.create.option.language.description": "Język, w którym mają być wysyłane powiadomienia",
"cmd.group.create.option.internals.description": "Informuj o przejęciach wewnętrznych", "cmd.group.create.option.internals.description": "Informuj o przejęciach wewnętrznych",
"cmd.group.create.option.barbarians.description": "Informuj o przejęciach wiosek barbarzyńskich", "cmd.group.create.option.barbarians.description": "Informuj o przejęciach wiosek barbarzyńskich",
"cmd.group.create.option.channel-gains.description": "Kanał na którym będą pojawiać się powiadomienia o zdobytych wioskach", "cmd.group.create.option.channel-gains.description": "Kanał na którym będą pojawiać się powiadomienia o zdobytych wioskach",
@ -25,10 +31,10 @@
"cmd.group.list.description": "Wyświetla wszystkie utworzone grupy na tym serwerze", "cmd.group.list.description": "Wyświetla wszystkie utworzone grupy na tym serwerze",
"cmd.group.list.embed.title": "Grupy", "cmd.group.list.embed.title": "Grupy",
"cmd.group.list.embed.field.value": "**Serwer**: {{ .ServerKey }}\n**Kanał zdobyte wioski**: {{ .ChannelGains }}\n**Kanał stracone wioski**: {{ .ChannelLosses }}\n**Przejęcia wewnętrzne**: {{ .Internals }}\n **Przejęcia barbarek**: {{ .Barbarians }}\n **Liczba monitorowanych plemion**: {{ .NumTribes }}", "cmd.group.list.embed.field.value": "**Serwer**: {{ .ServerKey }}\n**Język**: {{ .Language }}\n**Kanał zdobyte wioski**: {{ .ChannelGains }}\n**Kanał stracone wioski**: {{ .ChannelLosses }}\n**Przejęcia wewnętrzne**: {{ .Internals }}\n **Przejęcia barbarek**: {{ .Barbarians }}\n **Liczba monitorowanych plemion**: {{ .NumTribes }}",
"cmd.group.details.description": "Wyświetla szczegóły grupy (w tym dodane plemiona)", "cmd.group.details.description": "Wyświetla szczegóły grupy (w tym dodane plemiona)",
"cmd.group.details.embed.description": "**Serwer**: {{ .ServerKey }}\n**Kanał zdobyte wioski**: {{ .ChannelGains }}\n**Kanał stracone wioski**: {{ .ChannelLosses }}\n**Przejęcia wewnętrzne**: {{ .Internals }}\n **Przejęcia barbarek**: {{ .Barbarians }}\n **Plemiona**: {{ .Tribes }}", "cmd.group.details.embed.description": "**Serwer**: {{ .ServerKey }}\n**Język**: {{ .Language }}\n**Kanał zdobyte wioski**: {{ .ChannelGains }}\n**Kanał stracone wioski**: {{ .ChannelLosses }}\n**Przejęcia wewnętrzne**: {{ .Internals }}\n **Przejęcia barbarek**: {{ .Barbarians }}\n **Plemiona**: {{ .Tribes }}",
"cmd.group.tribe.description": "Zarządza plemionami w grupie", "cmd.group.tribe.description": "Zarządza plemionami w grupie",
@ -44,6 +50,11 @@
"cmd.group.set.description": "Ustawia różne właściwości w konfiguracji grupy", "cmd.group.set.description": "Ustawia różne właściwości w konfiguracji grupy",
"cmd.group.set.language.description": "Ustawia język w którym będziesz otrzymywać powiadomienia",
"cmd.group.set.language.option.group.description": "ID grupy",
"cmd.group.set.language.option.language.description": "Język, w którym mają być wysyłane powiadomienia",
"cmd.group.set.language.success": "Grupa została pomyślnie zaaktualizowana.",
"cmd.group.set.channel-gains.description": "Włącza/wyłącza powiadomienia o podbitych wioskach", "cmd.group.set.channel-gains.description": "Włącza/wyłącza powiadomienia o podbitych wioskach",
"cmd.group.set.channel-gains.option.group.description": "ID grupy", "cmd.group.set.channel-gains.option.group.description": "ID grupy",
"cmd.group.set.channel-gains.option.channel.description": "Kanał na którym będą pojawiać się powiadomienia o zdobytych wioskach", "cmd.group.set.channel-gains.option.channel.description": "Kanał na którym będą pojawiać się powiadomienia o zdobytych wioskach",

View File

@ -112,3 +112,7 @@ func (l *Localizer) LocalizeError(lang string, errToLocalize error) string {
} }
return msg return msg
} }
func (l *Localizer) LanguageTags() []language.Tag {
return l.bundle.LanguageTags()
}

View File

@ -2,12 +2,14 @@ package discord
import ( import (
"context" "context"
"fmt"
"sync" "sync"
"text/template"
"time" "time"
"gitea.dwysokinski.me/twhelp/dcbot/internal/discord/internal/discordi18n"
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain" "gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/bwmarrin/discordgo" "github.com/bwmarrin/discordgo"
"github.com/nicksnyder/go-i18n/v2/i18n"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -18,9 +20,10 @@ const (
) )
type executeMonitorsJob struct { type executeMonitorsJob struct {
s *discordgo.Session s *discordgo.Session
svc GroupService svc GroupService
logger *zap.Logger localizer *discordi18n.Localizer
logger *zap.Logger
} }
func (e *executeMonitorsJob) Run() { func (e *executeMonitorsJob) Run() {
@ -30,19 +33,32 @@ func (e *executeMonitorsJob) Run() {
start := time.Now() start := time.Now()
notifications, err := e.svc.Execute(ctx) notifications, err := e.svc.Execute(ctx)
if err != nil { if err != nil {
e.logger.Error("something went wrong while executing monitors", zap.Error(err)) e.logger.Error(
"something went wrong while executing monitors",
zap.Error(err),
zap.Duration("duration", time.Since(start)),
)
return
}
convertedNotifications, err := (ennoblementNotificationToMessageEmbedConverter{notifications, e.localizer}).convert()
if err != nil {
e.logger.Error("couldn't convert []domain.EnnoblementNotification to []*discordgo.MessageEmbed", zap.Error(err))
return return
} }
var wg sync.WaitGroup var wg sync.WaitGroup
for ch, embeds := range (ennoblementNotificationToMessageEmbedConverter{notifications}).convert() { for ch, embeds := range convertedNotifications {
wg.Add(1) wg.Add(1)
go func(ch string, embeds []*discordgo.MessageEmbed) { go func(ch string, embeds []*discordgo.MessageEmbed) {
defer wg.Done() defer wg.Done()
for _, embed := range embeds { for _, embed := range embeds {
_, _ = e.s.ChannelMessageSendEmbed(ch, embed) _, sendErr := e.s.ChannelMessageSendEmbed(ch, embed)
if sendErr != nil {
e.logger.Warn("couldn't send notification", zap.String("channel", ch), zap.Error(sendErr))
}
} }
}(ch, embeds) }(ch, embeds)
} }
@ -57,10 +73,11 @@ func (e *executeMonitorsJob) Run() {
} }
type ennoblementNotificationToMessageEmbedConverter struct { type ennoblementNotificationToMessageEmbedConverter struct {
ns []domain.EnnoblementNotification ns []domain.EnnoblementNotification
localizer *discordi18n.Localizer
} }
func (c ennoblementNotificationToMessageEmbedConverter) convert() map[string][]*discordgo.MessageEmbed { func (c ennoblementNotificationToMessageEmbedConverter) convert() (map[string][]*discordgo.MessageEmbed, error) {
m := make(map[string]map[domain.EnnoblementNotificationType][]*discordgo.MessageEmbed) m := make(map[string]map[domain.EnnoblementNotificationType][]*discordgo.MessageEmbed)
timestamp := formatTimestamp(time.Now()) timestamp := formatTimestamp(time.Now())
for _, n := range c.ns { for _, n := range c.ns {
@ -70,8 +87,11 @@ func (c ennoblementNotificationToMessageEmbedConverter) convert() map[string][]*
embeds := m[n.ChannelID][n.Type] embeds := m[n.ChannelID][n.Type]
str := c.buildDescription(n) description, err := c.buildDescription(n)
if l := len(embeds); l == 0 || len(embeds[l-1].Description)+len(str) > embedDescriptionCharLimit { if err != nil {
return nil, err
}
if l := len(embeds); l == 0 || len(embeds[l-1].Description)+len(description) > embedDescriptionCharLimit {
embeds = append(embeds, &discordgo.MessageEmbed{ embeds = append(embeds, &discordgo.MessageEmbed{
Color: c.mapNotificationTypeToColor(n.Type), Color: c.mapNotificationTypeToColor(n.Type),
Type: discordgo.EmbedTypeRich, Type: discordgo.EmbedTypeRich,
@ -79,9 +99,9 @@ func (c ennoblementNotificationToMessageEmbedConverter) convert() map[string][]*
}) })
} }
if len(embeds[len(embeds)-1].Description) > 0 { if len(embeds[len(embeds)-1].Description) > 0 {
str = "\n" + str description = "\n" + description
} }
embeds[len(embeds)-1].Description += str embeds[len(embeds)-1].Description += description
m[n.ChannelID][n.Type] = embeds m[n.ChannelID][n.Type] = embeds
} }
@ -99,16 +119,20 @@ func (c ennoblementNotificationToMessageEmbedConverter) convert() map[string][]*
} }
} }
return res return res, nil
} }
func (c ennoblementNotificationToMessageEmbedConverter) buildDescription(n domain.EnnoblementNotification) string { func (c ennoblementNotificationToMessageEmbedConverter) buildDescription(n domain.EnnoblementNotification) (string, error) {
return fmt.Sprintf( return c.localizer.Localize(n.LanguageTag, &i18n.LocalizeConfig{
"%s has taken %s (Old owner: %s)", MessageID: "job.execute-monitors.embed.description",
c.buildPlayerMarkdown(n.Ennoblement.NewOwner), TemplateData: map[string]any{
buildLink(n.Ennoblement.Village.FullName, n.Ennoblement.Village.ProfileURL), "Ennoblement": n.Ennoblement,
c.buildPlayerMarkdown(n.Ennoblement.Village.Player), },
) Funcs: template.FuncMap{
"buildPlayerMarkdown": c.buildPlayerMarkdown,
"buildLink": buildLink,
},
})
} }
func (c ennoblementNotificationToMessageEmbedConverter) buildPlayerMarkdown(p domain.NullPlayerMeta) string { func (c ennoblementNotificationToMessageEmbedConverter) buildPlayerMarkdown(p domain.NullPlayerMeta) string {

View File

@ -11,5 +11,6 @@ type EnnoblementNotification struct {
Type EnnoblementNotificationType Type EnnoblementNotificationType
ServerID string ServerID string
ChannelID string ChannelID string
LanguageTag string
Ennoblement Ennoblement Ennoblement Ennoblement
} }

View File

@ -14,6 +14,7 @@ type Group struct {
Barbarians bool // Show barbarian conquers Barbarians bool // Show barbarian conquers
ServerKey string // TW server key (e.g. en130) ServerKey string // TW server key (e.g. en130)
VersionCode string VersionCode string
LanguageTag string // BCP 47 language tag
CreatedAt time.Time CreatedAt time.Time
} }
@ -37,10 +38,11 @@ type CreateGroupParams struct {
channelLosses string channelLosses string
barbarians bool barbarians bool
internals bool internals bool
languageTag string
} }
func NewCreateGroupParams( func NewCreateGroupParams(
serverID, versionCode, serverKey, channelGains, channelLosses string, serverID, versionCode, serverKey, languageTag, channelGains, channelLosses string,
barbarians, internals bool, barbarians, internals bool,
) (CreateGroupParams, error) { ) (CreateGroupParams, error) {
if serverID == "" { if serverID == "" {
@ -55,6 +57,10 @@ func NewCreateGroupParams(
return CreateGroupParams{}, RequiredError{Field: "ServerKey"} return CreateGroupParams{}, RequiredError{Field: "ServerKey"}
} }
if languageTag == "" {
return CreateGroupParams{}, RequiredError{Field: "LanguageTag"}
}
return CreateGroupParams{ return CreateGroupParams{
serverID: serverID, serverID: serverID,
serverKey: serverKey, serverKey: serverKey,
@ -63,6 +69,7 @@ func NewCreateGroupParams(
channelLosses: channelLosses, channelLosses: channelLosses,
barbarians: barbarians, barbarians: barbarians,
internals: internals, internals: internals,
languageTag: languageTag,
}, nil }, nil
} }
@ -74,6 +81,10 @@ func (c CreateGroupParams) VersionCode() string {
return c.versionCode return c.versionCode
} }
func (c CreateGroupParams) LanguageTag() string {
return c.languageTag
}
func (c CreateGroupParams) ServerKey() string { func (c CreateGroupParams) ServerKey() string {
return c.serverKey return c.serverKey
} }
@ -97,6 +108,7 @@ func (c CreateGroupParams) Internals() bool {
type UpdateGroupParams struct { type UpdateGroupParams struct {
ChannelGains NullString ChannelGains NullString
ChannelLosses NullString ChannelLosses NullString
LanguageTag NullString
Internals NullBool Internals NullBool
Barbarians NullBool Barbarians NullBool
} }
@ -105,7 +117,8 @@ func (u UpdateGroupParams) IsZero() bool {
return !u.ChannelGains.Valid && return !u.ChannelGains.Valid &&
!u.ChannelLosses.Valid && !u.ChannelLosses.Valid &&
!u.Internals.Valid && !u.Internals.Valid &&
!u.Barbarians.Valid !u.Barbarians.Valid &&
!u.LanguageTag.Valid
} }
type ListGroupsParams struct { type ListGroupsParams struct {

View File

@ -5,6 +5,7 @@ import (
"gitea.dwysokinski.me/twhelp/dcbot/internal/domain" "gitea.dwysokinski.me/twhelp/dcbot/internal/domain"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"golang.org/x/text/language"
) )
func TestNewCreateGroupParams(t *testing.T) { func TestNewCreateGroupParams(t *testing.T) {
@ -15,6 +16,7 @@ func TestNewCreateGroupParams(t *testing.T) {
serverID string serverID string
versionCode string versionCode string
serverKey string serverKey string
languageTag string
channelGains string channelGains string
channelLosses string channelLosses string
barbarians bool barbarians bool
@ -26,6 +28,7 @@ func TestNewCreateGroupParams(t *testing.T) {
serverID: "123441", serverID: "123441",
versionCode: "en", versionCode: "en",
serverKey: "en113", serverKey: "en113",
languageTag: language.English.String(),
channelGains: "1234", channelGains: "1234",
channelLosses: "1234", channelLosses: "1234",
barbarians: true, barbarians: true,
@ -51,6 +54,14 @@ func TestNewCreateGroupParams(t *testing.T) {
serverKey: "", serverKey: "",
err: domain.RequiredError{Field: "ServerKey"}, err: domain.RequiredError{Field: "ServerKey"},
}, },
{
name: "ERR: LanguageTag cannot be blank",
serverID: "1234",
versionCode: "en",
serverKey: "en113",
languageTag: "",
err: domain.RequiredError{Field: "LanguageTag"},
},
} }
for _, tt := range tests { for _, tt := range tests {
@ -63,6 +74,7 @@ func TestNewCreateGroupParams(t *testing.T) {
tt.serverID, tt.serverID,
tt.versionCode, tt.versionCode,
tt.serverKey, tt.serverKey,
tt.languageTag,
tt.channelGains, tt.channelGains,
tt.channelLosses, tt.channelLosses,
tt.barbarians, tt.barbarians,
@ -93,28 +105,6 @@ func TestUpdateGroupParams_IsZero(t *testing.T) {
params domain.UpdateGroupParams params domain.UpdateGroupParams
output bool output bool
}{ }{
{
name: "OK: all",
params: domain.UpdateGroupParams{
ChannelGains: domain.NullString{
String: "123",
Valid: true,
},
ChannelLosses: domain.NullString{
String: "123",
Valid: true,
},
Barbarians: domain.NullBool{
Bool: false,
Valid: true,
},
Internals: domain.NullBool{
Bool: false,
Valid: true,
},
},
output: false,
},
{ {
name: "OK: ChannelGains", name: "OK: ChannelGains",
params: domain.UpdateGroupParams{ params: domain.UpdateGroupParams{
@ -155,6 +145,16 @@ func TestUpdateGroupParams_IsZero(t *testing.T) {
}, },
output: false, output: false,
}, },
{
name: "OK: LanguageTag",
params: domain.UpdateGroupParams{
LanguageTag: domain.NullString{
String: language.English.String(),
Valid: true,
},
},
output: false,
},
{ {
name: "OK: empty struct", name: "OK: empty struct",
params: domain.UpdateGroupParams{}, params: domain.UpdateGroupParams{},

View File

@ -27,6 +27,7 @@ func (b ennoblementNotificationBuilder) buildGroup(g domain.GroupWithMonitors) [
Type: domain.EnnoblementNotificationTypeGain, Type: domain.EnnoblementNotificationTypeGain,
ServerID: g.ServerID, ServerID: g.ServerID,
ChannelID: g.ChannelGains, ChannelID: g.ChannelGains,
LanguageTag: g.LanguageTag,
Ennoblement: b.ennoblementToDomainModel(e), Ennoblement: b.ennoblementToDomainModel(e),
}) })
} }
@ -36,6 +37,7 @@ func (b ennoblementNotificationBuilder) buildGroup(g domain.GroupWithMonitors) [
Type: domain.EnnoblementNotificationTypeLoss, Type: domain.EnnoblementNotificationTypeLoss,
ServerID: g.ServerID, ServerID: g.ServerID,
ChannelID: g.ChannelLosses, ChannelID: g.ChannelLosses,
LanguageTag: g.LanguageTag,
Ennoblement: b.ennoblementToDomainModel(e), Ennoblement: b.ennoblementToDomainModel(e),
}) })
} }

View File

@ -209,6 +209,15 @@ func (g *Group) SetBarbarians(ctx context.Context, id, serverID string, barbaria
}) })
} }
func (g *Group) SetLanguageTag(ctx context.Context, id, serverID, languageTag string) (domain.GroupWithMonitors, error) {
return g.update(ctx, id, serverID, domain.UpdateGroupParams{
LanguageTag: domain.NullString{
String: languageTag,
Valid: true,
},
})
}
func (g *Group) update(ctx context.Context, id, serverID string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) { func (g *Group) update(ctx context.Context, id, serverID string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
// check if group exists // check if group exists
if _, err := g.Get(ctx, id, serverID); err != nil { if _, err := g.Get(ctx, id, serverID); err != nil {

View File

@ -15,6 +15,7 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/text/language"
) )
func TestGroup_Create(t *testing.T) { func TestGroup_Create(t *testing.T) {
@ -26,6 +27,7 @@ func TestGroup_Create(t *testing.T) {
"592292203234328587", "592292203234328587",
"en", "en",
"en113", "en113",
language.English.String(),
"1234", "1234",
"1235", "1235",
true, true,
@ -457,6 +459,90 @@ func TestGroup_RemoveTribe(t *testing.T) {
}) })
} }
func TestGroup_SetLanguageTag(t *testing.T) {
t.Parallel()
t.Run("OK", func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
repo.GetReturns(group, nil)
repo.UpdateCalls(func(ctx context.Context, id string, params domain.UpdateGroupParams) (domain.GroupWithMonitors, error) {
return domain.GroupWithMonitors{
Group: domain.Group{
ID: id,
LanguageTag: params.LanguageTag.String,
},
}, nil
})
languageTag := uuid.NewString()
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetLanguageTag(context.Background(), group.ID, group.ServerID, languageTag)
assert.NoError(t, err)
assert.Equal(t, group.ID, g.ID)
assert.Equal(t, languageTag, g.LanguageTag)
})
t.Run("ERR: group not found", func(t *testing.T) {
t.Parallel()
group := domain.GroupWithMonitors{
Group: domain.Group{
ID: uuid.NewString(),
ServerID: uuid.NewString(),
},
}
tests := []struct {
name string
group domain.GroupWithMonitors
serverID string
err error
}{
{
name: "repository returned error",
serverID: group.ServerID,
err: domain.GroupNotFoundError{
ID: group.ID,
},
},
{
name: "incorrect server id",
group: group,
serverID: uuid.NewString(),
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
repo := &mock.FakeGroupRepository{}
repo.GetReturns(tt.group, tt.err)
g, err := service.
NewGroup(repo, nil, zap.NewNop(), 1, 1).
SetLanguageTag(context.Background(), group.ID, tt.serverID, uuid.NewString())
assert.ErrorIs(t, err, domain.GroupNotFoundError{
ID: group.ID,
})
assert.Zero(t, g)
})
}
})
}
func TestGroup_SetChannelGains(t *testing.T) { func TestGroup_SetChannelGains(t *testing.T) {
t.Parallel() t.Parallel()

View File

@ -0,0 +1,9 @@
-- +goose Up
-- +goose StatementBegin
ALTER TABLE groups ADD COLUMN language_tag varchar(10) NOT NULL DEFAULT 'en'; -- BCP 47 language tag
-- +goose StatementEnd
-- +goose Down
-- +goose StatementBegin
ALTER TABLE groups DROP COLUMN IF EXISTS language_tag;
-- +goose StatementEnd