feat(noitu): make /noitu the group game and add /noitutop

/noitu now posts the group play card that /noitupvp used to, and the solo
game against the bot moves to /noitubot. In a private chat /noitu points
the user to a group, or to /noitubot.

/noitutop ranks a group's players across all of its cards by wins, best
game and games played.

A one-time startup migration moves /stats history to the new names in
order: noitu to noitubot first, then noitupvp to noitu.
This commit is contained in:
tiennm99 committed 2026-10-09 16:22:54 +07:00
1 parent b8565164b3
commit d84bc3d1fb
21 files changed
+778 -74

No files matched your search

+2 -1
View File
@@ -74,7 +74,8 @@ func TestCommandDiscovery_AllPublicCommandsHaveSafeMetadata(t *testing.T) {
"monkeyd_crawl": "<url> [font_size]",
"monkeyd_tags": "<url>",
"noitu": "",
"noitupvp": "",
"noitubot": "",
"noitutop": "",
"random": "<option,...>",
"addsticker": "[emoji...]",
"alias": "<name>",
+2 -1
View File
@@ -44,6 +44,7 @@ import (
"github.com/tiennm99/tiennm99bot/internal/modules/wordle"
"github.com/tiennm99/tiennm99bot/internal/server"
"github.com/tiennm99/tiennm99bot/internal/storage"
"github.com/tiennm99/tiennm99bot/internal/systemstate"
"github.com/tiennm99/tiennm99bot/internal/telegram"
)
@@ -143,7 +144,7 @@ func main() {
}
defer closeProvider()
if err := stats.InitStore(rootCtx, provider.Collection("stats")); err != nil {
if err := stats.InitStore(rootCtx, provider.Collection("stats"), provider.Collection(systemstate.CollectionName)); err != nil {
log.Fatal("stats storage init failed", "err", err)
}
if err := lol.InitStore(rootCtx, provider.Collection(lol.CollectionName)); err != nil {
+8 -3
View File
@@ -100,7 +100,7 @@ func TestFactoriesRegistersBlacklistCommands(t *testing.T) {
}
}
// noitu is in the catalog and, with GAME_BASE_URL unset, registers its command
// noitu is in the catalog and, with GAME_BASE_URL unset, registers its commands
// and game but no public HTTP route.
func TestFactoriesRegistersNoituDisabledByDefault(t *testing.T) {
t.Setenv("GAME_BASE_URL", "")
@@ -109,8 +109,13 @@ func TestFactoriesRegistersNoituDisabledByDefault(t *testing.T) {
if err != nil {
t.Fatalf("Build noitu: %v", err)
}
if _, ok := reg.AllCommands["noitu"]; !ok {
t.Fatal("missing command noitu")
for _, name := range []string{"noitu", "noitubot", "noitutop"} {
if _, ok := reg.AllCommands[name]; !ok {
t.Fatalf("missing command %s", name)
}
}
if _, ok := reg.AllCommands["noitupvp"]; ok {
t.Fatal("renamed command noitupvp is still registered")
}
if routes := serverRoutes(reg); len(routes) != 0 {
t.Fatalf("disabled game exposes routes: %+v", routes)