Files
tiennm99bot/internal/modules/wordledaily/command.go
T
tiennm99 bc8a7d8869 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.
2026-10-09 17:23:17 +07:00

118 lines
3.6 KiB
Go

package wordledaily
import (
"context"
"strings"
"github.com/go-telegram/bot"
"github.com/go-telegram/bot/models"
"github.com/tiennm99/tiennm99bot/internal/modules/util/chathelper"
"github.com/tiennm99/tiennm99bot/internal/modules/util/subscription"
)
const (
msgChannel = "Wordle Daily can't be played in channels."
msgSendGameFail = "Couldn't send Wordle Daily. Try again later."
)
// handleCommand (/wordledaily) sends the BotFather game in the same chat and
// topic. No reply markup: Telegram then adds the Play button itself, which
// is the button a game message requires first.
func (s *service) handleCommand(ctx context.Context, b *bot.Bot, update *models.Update) error {
msg := update.Message
if msg == nil {
return nil
}
if msg.Chat.Type == models.ChatTypeChannel {
return chathelper.Reply(ctx, b, msg, msgChannel)
}
if !s.enabled() {
return chathelper.Reply(ctx, b, msg, msgDisabled)
}
if _, err := b.SendGame(ctx, &bot.SendGameParams{
ChatID: msg.Chat.ID,
MessageThreadID: topicOf(msg),
GameShorName: GameShortName, // the library's field name is misspelled
}); err != nil {
_ = chathelper.Reply(ctx, b, msg, msgSendGameFail)
return err
}
return nil
}
// handleSubscribe (/wordledaily_subscribe) opts the chat topic into the
// 07:00 ICT push. Anyone in the chat may subscribe, like the other daily
// pushes.
func (s *service) handleSubscribe(ctx context.Context, b *bot.Bot, update *models.Update) error {
msg := update.Message
if msg == nil {
return nil
}
if msg.Chat.Type == models.ChatTypeChannel {
return chathelper.Reply(ctx, b, msg, msgChannel)
}
// The push cron exists only when the game is enabled, so a
// subscription would never be served.
if !s.enabled() {
return chathelper.Reply(ctx, b, msg, msgDisabled)
}
thread := topicOf(msg)
s.subscribersMu.Lock()
defer s.subscribersMu.Unlock()
added, err := subscription.Add(ctx, s.subscribers, msg.Chat.ID, thread)
if err != nil {
return err
}
scope := scopeOf(thread)
if added {
return chathelper.Reply(ctx, b, msg,
"✅ Subscribed "+scope+" to Wordle Daily: a new puzzle and yesterday's group results every day at 07:00 ICT.\n"+
"If you block the bot, you'll be auto-unsubscribed on the next push.")
}
return chathelper.Reply(ctx, b, msg, "Already subscribed in "+scope+".")
}
// handleUnsubscribe (/wordledaily_unsubscribe) opts the chat topic out. It
// works while the game is disabled too, so a chat can always leave.
func (s *service) handleUnsubscribe(ctx context.Context, b *bot.Bot, update *models.Update) error {
msg := update.Message
if msg == nil {
return nil
}
if s.subscribers == nil {
return chathelper.Reply(ctx, b, msg, msgDisabled)
}
thread := topicOf(msg)
s.subscribersMu.Lock()
defer s.subscribersMu.Unlock()
removed, err := subscription.Remove(ctx, s.subscribers, msg.Chat.ID, thread)
if err != nil {
return err
}
scope := scopeOf(thread)
if removed {
return chathelper.Reply(ctx, b, msg, "Unsubscribed "+scope+".")
}
return chathelper.Reply(ctx, b, msg, strings.ToUpper(scope[:1])+scope[1:]+" wasn't subscribed.")
}
// topicOf is the message's forum topic, or 0 for the whole chat. Telegram
// also gives a reply chain in an ordinary supergroup a thread id;
// IsTopicMessage marks a real topic. Plays key the group results the same
// way (playClaims), so a subscription made by replying still gets its
// chat's recap and streak.
func topicOf(msg *models.Message) int {
if !msg.IsTopicMessage {
return 0
}
return msg.MessageThreadID
}
func scopeOf(thread int) string {
if thread != 0 {
return "this topic"
}
return "this chat"
}