feat(wordledaily): send the wordle BotFather game

Cards from /wordledaily and the 07:00 push now use the BotFather game
wordle, and Play answers it. The module, commands and page route keep the
wordledaily name.
This commit is contained in:
tiennm99 committed 2026-10-09 17:23:17 +07:00
1 parent c0231ceadf
commit bc8a7d8869
6 files changed
+18 -14

No files matched your search

+3 -1
View File
@@ -29,7 +29,9 @@ data and scoring through `internal/modules/wordle/wordlist`.
## Setup ## Setup
1. **BotFather:** create a game for the bot with `/newgame` and the short name 1. **BotFather:** create a game for the bot with `/newgame` and the short name
`wordledaily`. The share link is then `t.me/<bot username>?game=wordledaily`. `wordle`. The share link is then `t.me/<bot username>?game=wordle`. The
module, its commands and its routes keep the name `wordledaily`; only the
BotFather game is `wordle`.
2. **Public URL:** this uses the same `GAME_BASE_URL` as the 2. **Public URL:** this uses the same `GAME_BASE_URL` as the
[noitu game](noitu-game.md#setup). The page is served at [noitu game](noitu-game.md#setup). The page is served at
`<GAME_BASE_URL>/games/wordledaily/`. `<GAME_BASE_URL>/games/wordledaily/`.
@@ -50,7 +50,7 @@ func TestModule_Registration(t *testing.T) {
if strings.Join(names, " ") != "wordledaily wordledaily_subscribe wordledaily_unsubscribe" { if strings.Join(names, " ") != "wordledaily wordledaily_subscribe wordledaily_unsubscribe" {
t.Fatalf("commands = %v", names) t.Fatalf("commands = %v", names)
} }
if len(mod.Games) != 1 || mod.Games[0].ShortName != "wordledaily" { if len(mod.Games) != 1 || mod.Games[0].ShortName != "wordle" {
t.Fatalf("games = %+v", mod.Games) t.Fatalf("games = %+v", mod.Games)
} }
if len(mod.HTTP) != 0 || len(mod.Crons) != 0 { if len(mod.HTTP) != 0 || len(mod.Crons) != 0 {
@@ -84,7 +84,7 @@ func TestNew_FromEnv(t *testing.T) {
func TestPlay_DisabledAnswersAlert(t *testing.T) { func TestPlay_DisabledAnswersAlert(t *testing.T) {
rb := install(t, newService(config{}).module()) rb := install(t, newService(config{}).module())
rb.Bot.ProcessUpdate(context.Background(), testutil.NewGameCallback(1, 1, 5, ShortName)) rb.Bot.ProcessUpdate(context.Background(), testutil.NewGameCallback(1, 1, 5, GameShortName))
f := answerForm(t, rb) f := answerForm(t, rb)
if f["text"] != msgDisabled || f["show_alert"] != "true" || f["url"] != "" { if f["text"] != msgDisabled || f["show_alert"] != "true" || f["url"] != "" {
t.Fatalf("answer = %v", f) t.Fatalf("answer = %v", f)
@@ -108,7 +108,7 @@ func TestPlay_AnswersSignedURLForEveryCardForm(t *testing.T) {
h := newHarness(t) h := newHarness(t)
rb := install(t, h.svc.module()) rb := install(t, h.svc.module())
topic := testutil.NewGameCallback(42, groupChat, 9, ShortName) topic := testutil.NewGameCallback(42, groupChat, 9, GameShortName)
topic.CallbackQuery.From.LastName = "Nguyen" topic.CallbackQuery.From.LastName = "Nguyen"
topic.CallbackQuery.Message.Message.IsTopicMessage = true topic.CallbackQuery.Message.Message.IsTopicMessage = true
topic.CallbackQuery.Message.Message.MessageThreadID = 77 topic.CallbackQuery.Message.Message.MessageThreadID = 77
@@ -122,7 +122,7 @@ func TestPlay_AnswersSignedURLForEveryCardForm(t *testing.T) {
} }
inaccessible := &models.Update{CallbackQuery: &models.CallbackQuery{ inaccessible := &models.Update{CallbackQuery: &models.CallbackQuery{
ID: "q", From: models.User{ID: 42, FirstName: "A"}, GameShortName: ShortName, ID: "q", From: models.User{ID: 42, FirstName: "A"}, GameShortName: GameShortName,
Message: models.MaybeInaccessibleMessage{ Message: models.MaybeInaccessibleMessage{
Type: models.MaybeInaccessibleMessageTypeInaccessibleMessage, Type: models.MaybeInaccessibleMessageTypeInaccessibleMessage,
InaccessibleMessage: &models.InaccessibleMessage{Chat: models.Chat{ID: -5}, MessageID: 3}, InaccessibleMessage: &models.InaccessibleMessage{Chat: models.Chat{ID: -5}, MessageID: 3},
@@ -133,12 +133,12 @@ func TestPlay_AnswersSignedURLForEveryCardForm(t *testing.T) {
t.Fatalf("inaccessible claims = %+v", c) t.Fatalf("inaccessible claims = %+v", c)
} }
rb.Bot.ProcessUpdate(context.Background(), testutil.NewInlineGameCallback(42, "BAAAInline", ShortName)) rb.Bot.ProcessUpdate(context.Background(), testutil.NewInlineGameCallback(42, "BAAAInline", GameShortName))
if c := decodeURLToken(t, h, answerForm(t, rb)["url"]); c.InlineID != "BAAAInline" || c.ChatID != 0 { if c := decodeURLToken(t, h, answerForm(t, rb)["url"]); c.InlineID != "BAAAInline" || c.ChatID != 0 {
t.Fatalf("inline claims = %+v", c) t.Fatalf("inline claims = %+v", c)
} }
none := testutil.NewInlineGameCallback(42, "", ShortName) none := testutil.NewInlineGameCallback(42, "", GameShortName)
rb.Bot.ProcessUpdate(context.Background(), none) rb.Bot.ProcessUpdate(context.Background(), none)
if f := answerForm(t, rb); f["text"] != msgNoGameTarget || f["show_alert"] != "true" { if f := answerForm(t, rb); f["text"] != msgNoGameTarget || f["show_alert"] != "true" {
t.Fatalf("no target = %v", f) t.Fatalf("no target = %v", f)
@@ -172,7 +172,7 @@ func TestCommand_SendsGameKeepingTopic(t *testing.T) {
u.Message.MessageThreadID, u.Message.IsTopicMessage = 77, true u.Message.MessageThreadID, u.Message.IsTopicMessage = 77, true
rb.Bot.ProcessUpdate(context.Background(), u) rb.Bot.ProcessUpdate(context.Background(), u)
games := sentMethod(rb, "sendGame") games := sentMethod(rb, "sendGame")
if len(games) != 1 || games[0].Form["game_short_name"] != ShortName || games[0].Form["message_thread_id"] != "77" || games[0].ChatID() != "-100" { if len(games) != 1 || games[0].Form["game_short_name"] != GameShortName || games[0].Form["message_thread_id"] != "77" || games[0].ChatID() != "-100" {
t.Fatalf("sent %+v", rb.Sent()) t.Fatalf("sent %+v", rb.Sent())
} }
// A reply chain in an ordinary supergroup carries a thread id too, but // A reply chain in an ordinary supergroup carries a thread id too, but
+1 -1
View File
@@ -33,7 +33,7 @@ func (s *service) handleCommand(ctx context.Context, b *bot.Bot, update *models.
if _, err := b.SendGame(ctx, &bot.SendGameParams{ if _, err := b.SendGame(ctx, &bot.SendGameParams{
ChatID: msg.Chat.ID, ChatID: msg.Chat.ID,
MessageThreadID: topicOf(msg), MessageThreadID: topicOf(msg),
GameShorName: ShortName, // the library's field name is misspelled GameShorName: GameShortName, // the library's field name is misspelled
}); err != nil { }); err != nil {
_ = chathelper.Reply(ctx, b, msg, msgSendGameFail) _ = chathelper.Reply(ctx, b, msg, msgSendGameFail)
return err return err
+1 -1
View File
@@ -83,7 +83,7 @@ func (s *service) pushTo(ctx context.Context, api telegramAPI, num int, sub subs
} }
} }
} }
_, err := api.SendGame(ctx, &bot.SendGameParams{ChatID: sub.ChatID, MessageThreadID: sub.ThreadID, GameShorName: ShortName}) _, err := api.SendGame(ctx, &bot.SendGameParams{ChatID: sub.ChatID, MessageThreadID: sub.ThreadID, GameShorName: GameShortName})
return err return err
} }
+1 -1
View File
@@ -70,7 +70,7 @@ func TestPush_RecapThenCardWithCrownTiesAndLoss(t *testing.T) {
t.Fatalf("not sent to the topic: %+v", c) t.Fatalf("not sent to the topic: %+v", c)
} }
} }
if sent[1].Form["game_short_name"] != ShortName { if sent[1].Form["game_short_name"] != GameShortName {
t.Fatalf("card = %v", sent[1].Form) t.Fatalf("card = %v", sent[1].Form)
} }
// The same puzzle is never pushed twice. // The same puzzle is never pushed twice.
+5 -3
View File
@@ -30,9 +30,11 @@ import (
) )
const ( const (
// ShortName is the game's BotFather short name and the module's catalog // ShortName is the module's catalog key and command name, so its routes
// key, so its routes live under /games/wordledaily/. // live under /games/wordledaily/.
ShortName = "wordledaily" ShortName = "wordledaily"
// GameShortName is the BotFather game the cards send and Play answers.
GameShortName = "wordle"
subscribeCommand = ShortName + "_subscribe" subscribeCommand = ShortName + "_subscribe"
unsubscribeCommand = ShortName + "_unsubscribe" unsubscribeCommand = ShortName + "_unsubscribe"
@@ -203,7 +205,7 @@ func (s *service) module() modules.Module {
Handler: s.handleUnsubscribe, Handler: s.handleUnsubscribe,
}}, }},
Games: []modules.Game{{ Games: []modules.Game{{
ShortName: ShortName, ShortName: GameShortName,
Visibility: modules.VisibilityPublic, Visibility: modules.VisibilityPublic,
Handler: s.handlePlay, Handler: s.handlePlay,
}}, }},