diff --git a/app/configurator/connection.go b/app/configurator/connection.go index 04006f6..70f02d1 100644 --- a/app/configurator/connection.go +++ b/app/configurator/connection.go @@ -45,6 +45,7 @@ func (cg *Configurator) AutoMigrateSchema() *Configurator { &models.TagRegion{}, &models.TagForumPostTrack{}, &models.TagForumPostIgnore{}, + &models.TagPlayerEvent{}, &models.AlertNeutralPlayersEqualOrGreater{}, &models.AlertEnemiesEqualOrGreater{}, &models.AlertFriendsEqualOrGreater{}, diff --git a/app/configurator/models/models.go b/app/configurator/models/models.go index c321fed..3ac72d9 100644 --- a/app/configurator/models/models.go +++ b/app/configurator/models/models.go @@ -25,6 +25,10 @@ type TagPlayerEnemy struct { TagTemplate } +type TagPlayerEvent struct { + TagTemplate +} + type TagSystem struct { TagTemplate } diff --git a/app/configurator/tags.go b/app/configurator/tags.go index 0541dab..997ef34 100644 --- a/app/configurator/tags.go +++ b/app/configurator/tags.go @@ -19,7 +19,8 @@ type taggable interface { models.TagSystem | models.TagRegion | models.TagPlayerFriend | - models.TagPlayerEnemy + models.TagPlayerEnemy | + models.TagPlayerEvent GetTag() types.Tag } @@ -52,6 +53,10 @@ type ConfiguratorPlayerEnemy = ConfiguratorTags[models.TagPlayerEnemy] var NewConfiguratorPlayerEnemy = NewConfiguratorTags[models.TagPlayerEnemy] +type ConfiguratorPlayerEvent = ConfiguratorTags[models.TagPlayerEvent] + +var NewConfiguratorPlayerEvent = NewConfiguratorTags[models.TagPlayerEvent] + func (c ConfiguratorTags[T]) TagsAdd(channelID types.DiscordChannelID, tags ...types.Tag) error { objs := []T{} for _, tag := range tags { @@ -80,11 +85,18 @@ func (c ConfiguratorTags[T]) TagsAdd(channelID types.DiscordChannelID, tags ...t func (c ConfiguratorTags[T]) TagsRemove(channelID types.DiscordChannelID, tags ...types.Tag) error { errors := NewErrorAggregator() + TotalRowsAffected := 0 for _, tag := range tags { result := c.db.Where("channel_id = ? AND tag = ?", channelID, tag).Delete(&T{}) logus.CheckWarn(result.Error, "unsuccesful result of c.db.Delete") errors.Append(result.Error) + TotalRowsAffected += int(result.RowsAffected) } + + if TotalRowsAffected == 0 { + return ErrorZeroAffectedRows{} + } + return errors.TryToGetError() } diff --git a/app/consoler/commands/root.go b/app/consoler/commands/root.go index bc64df9..63a1faf 100644 --- a/app/consoler/commands/root.go +++ b/app/consoler/commands/root.go @@ -95,6 +95,15 @@ func CreateConsoler( configurator.NewConfiguratorPlayerEnemy(configurator.NewConfigurator(channelInfo.GetDbpath())), ) + NewTagCommands( + playerGroup.GetChild( + playerGroup.CurrentCmd, + cmdgroup.Command("event"), + cmdgroup.ShortDesc("Player event commands"), + ), + configurator.NewConfiguratorPlayerEvent(configurator.NewConfigurator(channelInfo.GetDbpath())), + ) + alertGroup := root.GetChild( root.CurrentCmd, cmdgroup.Command("alert"), diff --git a/app/consoler/commands/tags.go b/app/consoler/commands/tags.go index 9155ba1..97cbb1f 100644 --- a/app/consoler/commands/tags.go +++ b/app/consoler/commands/tags.go @@ -61,14 +61,20 @@ func (t *tagCommands) CreateTagRemove() { err := t.cfgTags.TagsRemove(t.GetChannelID(), types.Tag(strings.Join(args, " "))) if err != nil { - printer.Println(cmd, "ERR msg="+err.Error()) + if _, ok := err.(configurator.ErrorZeroAffectedRows); ok { + printer.Println(cmd, "ERR removed nothing, because inserted value did not match anything present in the list") + } else { + printer.Println(cmd, "ERR ="+err.Error()) + } return } - printer.Println(cmd, "OK tags are removed"+strings.Join(args, " ")) + printer.Println(cmd, "OK tags are removed: "+strings.Join(args, " ")) + logus.Debug("executed Create Tag Remove with args", logus.Args(args)) }, } t.CurrentCmd.AddCommand(command) + } func (t *tagCommands) CreateTagClear() { diff --git a/app/consoler/consoler.go b/app/consoler/consoler.go index f221010..3dca393 100644 --- a/app/consoler/consoler.go +++ b/app/consoler/consoler.go @@ -26,16 +26,12 @@ func NewConsoler( dbpath types.Dbpath, ) *Consoler { c := &Consoler{} - c.buffStdout = NewWriter() - c.buffStderr = NewWriter() + c.dbpath = dbpath c.params = consoler_types.NewChannelParams("", dbpath) configur := configurator.NewConfigurator(dbpath) c.rootCmd = commands.CreateConsoler(c.params, configur) - c.rootCmd.SetOut(c.buffStdout) - c.rootCmd.SetErr(c.buffStderr) - return c } @@ -48,6 +44,11 @@ func (c *Consoler) Execute( return c } + c.buffStdout = NewWriter() + c.buffStderr = NewWriter() + c.rootCmd.SetOut(c.buffStdout) + c.rootCmd.SetErr(c.buffStderr) + c.params.SetChannelID(channelID) c.rootCmd.SetArgs(strings.Split(cmd, " ")) c.rootCmd.Execute() diff --git a/app/consoler/printer/printer.go b/app/consoler/printer/printer.go index b0dced9..111212b 100644 --- a/app/consoler/printer/printer.go +++ b/app/consoler/printer/printer.go @@ -5,6 +5,7 @@ Those functions are capable to print back to user to Discord via Cobra */ import ( + "darkbot/app/settings/logus" "fmt" "github.com/spf13/cobra" @@ -15,5 +16,6 @@ func Print(Cmd *cobra.Command, msg string) { } func Println(Cmd *cobra.Command, msg string) { + logus.Debug(fmt.Sprintf("printer.Println msg=%s", msg)) Cmd.OutOrStdout().Write([]byte(fmt.Sprintf("%s\n", msg))) } diff --git a/app/scrappy/fixtures.go b/app/scrappy/fixtures.go index 7731335..7c42de7 100644 --- a/app/scrappy/fixtures.go +++ b/app/scrappy/fixtures.go @@ -6,14 +6,19 @@ import ( "darkbot/app/scrappy/player" ) -func FixtureNewStorageWithPlayers(players *player.PlayerStorage) *ScrappyStorage { - return &ScrappyStorage{playerStorage: players} -} - -func FixtureMockedStorage() *ScrappyStorage { +func FixtureMockedStorage(opts ...storageParam) *ScrappyStorage { return NewScrapyStorage( base.FixtureBaseApiMock(), player.FixturePlayerAPIMock(), baseattack.FixtureBaseAttackAPIMock(), + opts..., ) } + +type storageParam func(storage *ScrappyStorage) + +func WithPlayerStorage(playerStorage *player.PlayerStorage) storageParam { + return func(storage *ScrappyStorage) { + storage.playerStorage = playerStorage + } +} diff --git a/app/scrappy/main.go b/app/scrappy/main.go index 39703f0..2735bad 100644 --- a/app/scrappy/main.go +++ b/app/scrappy/main.go @@ -15,11 +15,15 @@ type ScrappyStorage struct { baseAttackStorage *baseattack.BaseAttackStorage } -func NewScrapyStorage(base_api base.IbaseAPI, player_api player.IPlayerAPI, base_attack baseattack.IbaseAttackAPI) *ScrappyStorage { +func NewScrapyStorage(base_api base.IbaseAPI, player_api player.IPlayerAPI, base_attack baseattack.IbaseAttackAPI, opts ...storageParam) *ScrappyStorage { s := &ScrappyStorage{} s.baseStorage = base.NewBaseStorage(base_api) s.playerStorage = player.NewPlayerStorage(player_api) s.baseAttackStorage = baseattack.NewBaseAttackStorage(base_attack) + + for _, opt := range opts { + opt(s) + } return s } diff --git a/app/settings/logus/params.go b/app/settings/logus/params.go index c984deb..500d5d2 100644 --- a/app/settings/logus/params.go +++ b/app/settings/logus/params.go @@ -173,3 +173,9 @@ func GormResult(result *gorm.DB) slogParam { c.params["result.error_type"] = fmt.Sprintf("%T", result.Error) } } + +func DiscordMessageID(value types.DiscordMessageID) slogParam { + return func(c *slogGroup) { + c.params["discord_msg_id"] = string(value) + } +} diff --git a/app/viewer/apis/apis.go b/app/viewer/apis/apis.go index e94c825..019ece9 100644 --- a/app/viewer/apis/apis.go +++ b/app/viewer/apis/apis.go @@ -12,6 +12,7 @@ type Players struct { Regions configurator.ConfiguratorRegion Enemies configurator.ConfiguratorPlayerEnemy Friends configurator.ConfiguratorPlayerFriend + Events configurator.ConfiguratorPlayerEvent } type Alerts struct { NeutralsGreaterThan configurator.CfgAlertNeutralPlayersGreaterThan @@ -59,6 +60,7 @@ func NewAPI(ChannelID types.DiscordChannelID, dbpath types.Dbpath, opts ...apiPa Regions: configurator.NewConfiguratorRegion(configur), Enemies: configurator.NewConfiguratorPlayerEnemy(configur), Friends: configurator.NewConfiguratorPlayerFriend(configur), + Events: configurator.NewConfiguratorPlayerEvent(configur), }, Alerts: Alerts{ NeutralsGreaterThan: configurator.NewCfgAlertNeutralPlayersGreaterThan(configur), diff --git a/app/viewer/channel.go b/app/viewer/channel.go index 9a93971..14e0f2f 100644 --- a/app/viewer/channel.go +++ b/app/viewer/channel.go @@ -7,6 +7,7 @@ import ( "darkbot/app/viewer/apis" "darkbot/app/viewer/views" "darkbot/app/viewer/views/baseview" + "darkbot/app/viewer/views/eventview" "darkbot/app/viewer/views/playerview" "strings" "time" @@ -24,6 +25,7 @@ func NewChannelView(api *apis.API, channelID types.DiscordChannelID) ChannelView view := ChannelView{api: api} view.views = append(view.views, baseview.NewTemplateBase(api)) view.views = append(view.views, playerview.NewTemplatePlayers(api)) + view.views = append(view.views, eventview.NewEventRenderer(api)) view.ChannelID = channelID return view @@ -41,7 +43,6 @@ func (v *ChannelView) Discover() error { for _, msg := range msgs { for _, view := range v.views { view.DiscoverMessageID(msg.Content, msg.ID) - view.DiscoverMessageID(msg.Content, msg.ID) } } diff --git a/app/viewer/views/eventview/event.go b/app/viewer/views/eventview/event.go new file mode 100644 index 0000000..23cfe2c --- /dev/null +++ b/app/viewer/views/eventview/event.go @@ -0,0 +1,88 @@ +package eventview + +import ( + "darkbot/app/settings/logus" + "darkbot/app/settings/types" + "darkbot/app/viewer/apis" + "darkbot/app/viewer/views" + _ "embed" + "encoding/json" + "fmt" + "strings" + "time" +) + +type EventRenderer struct { + main views.TemplateShared + api *apis.API +} + +func NewEventRenderer(api *apis.API) *EventRenderer { + base := EventRenderer{} + base.api = api + base.main.Header = "#darkbot-event-view" + return &base +} + +func (t *EventRenderer) DiscoverMessageID(content string, msgID types.DiscordMessageID) { + if strings.Contains(content, t.main.Header) { + t.main.MessageID = msgID + } +} + +func (t *EventRenderer) MatchMessageID(messageID types.DiscordMessageID) bool { + return messageID == t.main.MessageID +} + +func (t *EventRenderer) Send() { + t.main.Send(t.api) +} + +type PlayerTemplate struct { + Time string + Name string + System string +} + +func (t *EventRenderer) Render() error { + record, err := t.api.Scrappy.GetPlayerStorage().GetLatestRecord() + if logus.CheckWarn(err, "unable to get players") { + return err + } + + logus.Debug("rendered events", logus.DiscordMessageID(t.main.MessageID)) + + eventTags, err := t.api.Players.Events.TagsList(t.api.ChannelID) + logus.CheckWarn(err, "failed to acquire player event list", logus.ChannelID(t.api.ChannelID)) + + if len(eventTags) > 0 { + var sb strings.Builder + + sb.WriteString(fmt.Sprintf("**%s** %s\n", t.main.Header, time.Now().String())) + sb.WriteString("**Event table of players**\n") + sb.WriteString("```json\n") + + for _, eventTag := range eventTags { + sb.WriteString(fmt.Sprintf(`"%s": `, eventTag)) + + matchedPlayers := []PlayerTemplate{} + for _, player := range record.List { + if views.TagContains(player.Name, []types.Tag{eventTag}) { + matchedPlayers = append(matchedPlayers, PlayerTemplate{Name: player.Name, Time: player.Time, System: player.System}) + continue + } + } + result, err := json.Marshal(matchedPlayers) + logus.CheckError(err, "failed to marshal event matched players") + sb.WriteString(fmt.Sprintf("%s", string(result))) + + sb.WriteString("\n") + } + + sb.WriteString("```\n") + t.main.Content = sb.String() + + } + + return nil +} diff --git a/app/viewer/views/eventview/event_test.go b/app/viewer/views/eventview/event_test.go new file mode 100644 index 0000000..1f44da9 --- /dev/null +++ b/app/viewer/views/eventview/event_test.go @@ -0,0 +1,39 @@ +package eventview + +import ( + "darkbot/app/configurator" + "darkbot/app/scrappy" + "darkbot/app/scrappy/player" + "darkbot/app/scrappy/shared/records" + "darkbot/app/settings/logus" + "darkbot/app/settings/types" + "darkbot/app/viewer/apis" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPlayerEvent(t *testing.T) { + configurator.FixtureMigrator(func(dbpath types.Dbpath) { + channelID, _ := configurator.FixtureChannel(dbpath) + + configurator.NewConfiguratorPlayerEvent(configurator.NewConfigurator(dbpath)).TagsAdd(channelID, []types.Tag{"player1", "player2"}...) + + players := player.NewPlayerStorage(player.FixturePlayerAPIMock()) + storage := scrappy.FixtureMockedStorage(scrappy.WithPlayerStorage(players)) + api := apis.NewAPI(channelID, dbpath, apis.WithStorage(storage)) + record := records.NewStampedObjects[player.Player]() + record.Add(player.Player{Name: "player1", System: "system1", Region: "region1"}) + record.Add(player.Player{Name: "player2", System: "system2", Region: "region2"}) + record.Add(player.Player{Name: "player3", System: "system3", Region: "region3"}) + record.Add(player.Player{Name: "player4", System: "system4", Region: "region4"}) + players.Add(record) + + playerView := NewEventRenderer(api) + playerView.Render() + logus.Debug(playerView.main.Content) + logus.Debug("test TestPlayerEvent is finished") + + assert.NotEmpty(t, playerView.main.Content) + }) +} diff --git a/app/viewer/views/playerview/player.go b/app/viewer/views/playerview/player.go index d782e98..c35c2fc 100644 --- a/app/viewer/views/playerview/player.go +++ b/app/viewer/views/playerview/player.go @@ -55,18 +55,9 @@ func NewTemplatePlayers(api *apis.API) *PlayersTemplates { return &templator } -func TagContains(name string, tags []types.Tag) bool { - for _, tag := range tags { - if strings.Contains(name, string(tag)) { - return true - } - } - return false -} - func (t *PlayersTemplates) Render() error { record, err := t.api.Scrappy.GetPlayerStorage().GetLatestRecord() - if logus.CheckWarn(err, "unable to get player msgs") { + if logus.CheckWarn(err, "unable to get players") { return err } @@ -86,16 +77,16 @@ func (t *PlayersTemplates) Render() error { friendPlayers := []player.Player{} for _, player := range record.List { - if TagContains(player.Name, friendTags) { + if views.TagContains(player.Name, friendTags) { friendPlayers = append(friendPlayers, player) continue } - if !TagContains(player.System, systemTags) && !TagContains(player.Region, regionTags) { + if !views.TagContains(player.System, systemTags) && !views.TagContains(player.Region, regionTags) { continue } - if TagContains(player.Name, enemyTags) { + if views.TagContains(player.Name, enemyTags) { enemyPlayers = append(enemyPlayers, player) continue } @@ -162,7 +153,6 @@ func (t *PlayersTemplates) Send() { t.friends.alertTmpl.Send(t.api) t.neutral.alertTmpl.Send(t.api) t.enemies.alertTmpl.Send(t.api) - } func (t *PlayersTemplates) MatchMessageID(messageID types.DiscordMessageID) bool { diff --git a/app/viewer/views/playerview/player_test.go b/app/viewer/views/playerview/player_test.go index bd5f9a2..d9c6825 100644 --- a/app/viewer/views/playerview/player_test.go +++ b/app/viewer/views/playerview/player_test.go @@ -23,8 +23,7 @@ func TestPlayerViewerMadeUpData(t *testing.T) { configurator.NewConfiguratorPlayerFriend(configurator.NewConfigurator(dbpath)).TagsAdd(channelID, []types.Tag{"player4"}...) players := player.NewPlayerStorage(player.FixturePlayerAPIMock()) - - storage := scrappy.FixtureNewStorageWithPlayers(players) + storage := scrappy.FixtureMockedStorage(scrappy.WithPlayerStorage(players)) api := apis.NewAPI(channelID, dbpath, apis.WithStorage(storage)) record := records.NewStampedObjects[player.Player]() record.Add(player.Player{Name: "player1", System: "system1", Region: "region1"}) diff --git a/app/viewer/views/tags.go b/app/viewer/views/tags.go new file mode 100644 index 0000000..d6d20d0 --- /dev/null +++ b/app/viewer/views/tags.go @@ -0,0 +1,15 @@ +package views + +import ( + "darkbot/app/settings/types" + "strings" +) + +func TagContains(name string, tags []types.Tag) bool { + for _, tag := range tags { + if strings.Contains(name, string(tag)) { + return true + } + } + return false +} diff --git a/infra/tf/modules/darkbot/main.tf b/infra/tf/modules/darkbot/main.tf index c595305..2c488f0 100644 --- a/infra/tf/modules/darkbot/main.tf +++ b/infra/tf/modules/darkbot/main.tf @@ -48,4 +48,6 @@ resource "docker_container" "darkbot" { read_only = false host_path = "/var/lib/darklab/darkbot" } + + memory = 1000 # MBs }