mirror of
https://github.com/tiennm99/store-scraper-bot.git
synced 2026-08-31 16:26:46 +00:00
- Switch Telegram parse mode to HTML to match Java client. - Rename command identifiers to Java's: delgroup/delapple/delgoogle/ checkappscore/rawappleapp/rawgoogleapp. - Move admin singleton to "common" collection at _id="admin". - Store group _id as string-of-int64 to match Java AbstractModel schema. - Add `class` discriminator field on persisted models. - Expand AppleAppResponse (42 fields) and GoogleAppResponse (63 fields, with nested Category/Feature) to match Java records. - Add AppleAppRequest (track id or bundle id) / GoogleAppRequest types. - Implement Raw* commands as Telegram document attachments instead of truncated text. - Group-authorization gate (admin.HasGroup(chatId)) on non-admin commands. - Cache TTL via millis timestamp; round score to 1 decimal; weekend silent send in scheduler. - Match Java table renderer: " │ " separators, "─┼─" rows every 5 lines. - Prefer MONGODB_CONNECTION_STRING env (Java parity); auto-extract DB name from URI with fallback to "store-scraper-bot". Note: This is an AI-assisted port (not human-verified). Behavior parity with the Java implementation has not been tested end-to-end; treat as a starting point.
128 lines
4.5 KiB
Go
128 lines
4.5 KiB
Go
package bot
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
|
"github.com/miti99/store-scraper-bot-go/internal/api/apple"
|
|
"github.com/miti99/store-scraper-bot-go/internal/api/google"
|
|
"github.com/miti99/store-scraper-bot-go/internal/bot/command"
|
|
"github.com/miti99/store-scraper-bot-go/internal/config"
|
|
"github.com/miti99/store-scraper-bot-go/internal/repository"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// parseMode mirrors Java StoreScrapeBotTelegramClient: HTML for all messages.
|
|
const parseMode = "HTML"
|
|
|
|
type Bot struct {
|
|
api *tgbotapi.BotAPI
|
|
cfg *config.Config
|
|
commands map[string]command.Command
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func NewBot(
|
|
cfg *config.Config,
|
|
adminRepo *repository.AdminRepository,
|
|
groupRepo *repository.GroupRepository,
|
|
appleScraper *apple.AppleScraper,
|
|
googleScraper *google.GoogleScraper,
|
|
) (*Bot, error) {
|
|
api, err := tgbotapi.NewBotAPI(cfg.TelegramBotToken)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create telegram bot: %w", err)
|
|
}
|
|
api.Debug = cfg.Env == config.Development
|
|
cfg.Logger.Info("Authorized on account", zap.String("username", api.Self.UserName))
|
|
|
|
b := &Bot{api: api, cfg: cfg, commands: map[string]command.Command{}, logger: cfg.Logger}
|
|
|
|
// Java command identifiers (StoreScrapeBot constructor) — keep these strings
|
|
// matching exactly so existing users' muscle memory still works.
|
|
b.commands["info"] = command.NewInfoCommand(cfg)
|
|
b.commands["addgroup"] = command.NewAddGroupCommand(cfg, adminRepo, groupRepo)
|
|
b.commands["delgroup"] = command.NewDeleteGroupCommand(cfg, adminRepo, groupRepo)
|
|
b.commands["listgroup"] = command.NewListGroupCommand(cfg, adminRepo)
|
|
b.commands["addapple"] = command.NewAddAppleAppCommand(cfg, adminRepo, groupRepo, appleScraper)
|
|
b.commands["delapple"] = command.NewDeleteAppleAppCommand(cfg, adminRepo, groupRepo)
|
|
b.commands["addgoogle"] = command.NewAddGoogleAppCommand(cfg, adminRepo, groupRepo, googleScraper)
|
|
b.commands["delgoogle"] = command.NewDeleteGoogleAppCommand(cfg, adminRepo, groupRepo)
|
|
b.commands["listapp"] = command.NewListAppCommand(cfg, adminRepo, groupRepo)
|
|
b.commands["checkapp"] = command.NewCheckAppCommand(cfg, adminRepo, groupRepo, appleScraper, googleScraper)
|
|
b.commands["checkappscore"] = command.NewCheckAppScoresCommand(cfg, adminRepo, groupRepo, appleScraper, googleScraper)
|
|
b.commands["rawappleapp"] = command.NewRawAppleAppCommand(cfg, appleScraper)
|
|
b.commands["rawgoogleapp"] = command.NewRawGoogleAppCommand(cfg, googleScraper)
|
|
|
|
return b, nil
|
|
}
|
|
|
|
func (b *Bot) Start() {
|
|
u := tgbotapi.NewUpdate(0)
|
|
u.Timeout = 60
|
|
updates := b.api.GetUpdatesChan(u)
|
|
for update := range updates {
|
|
if update.Message == nil || !update.Message.IsCommand() {
|
|
continue
|
|
}
|
|
go b.handleCommand(update.Message)
|
|
}
|
|
}
|
|
|
|
func (b *Bot) handleCommand(message *tgbotapi.Message) {
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
b.logger.Error("panic in command", zap.Any("panic", r))
|
|
_ = b.SendMessage(message.Chat.ID, "Internal server error")
|
|
}
|
|
}()
|
|
name := message.Command()
|
|
cmd, ok := b.commands[name]
|
|
if !ok {
|
|
b.logger.Debug("Unknown command", zap.String("command", name))
|
|
return
|
|
}
|
|
b.logger.Info("Executing command",
|
|
zap.String("command", name),
|
|
zap.Int64("userId", message.From.ID),
|
|
zap.Int64("chatId", message.Chat.ID))
|
|
cmd.Execute(message, b)
|
|
}
|
|
|
|
// SendMessage sends an HTML-parsed message (Java parity).
|
|
func (b *Bot) SendMessage(chatID int64, html string) error {
|
|
msg := tgbotapi.NewMessage(chatID, html)
|
|
msg.ParseMode = parseMode
|
|
msg.DisableWebPagePreview = true
|
|
_, err := b.api.Send(msg)
|
|
if err != nil {
|
|
b.logger.Warn("send message failed", zap.Int64("chatId", chatID), zap.Error(err))
|
|
}
|
|
return err
|
|
}
|
|
|
|
// SendMessageSilent sends an HTML message with notifications muted (weekend behavior).
|
|
func (b *Bot) SendMessageSilent(chatID int64, html string) error {
|
|
msg := tgbotapi.NewMessage(chatID, html)
|
|
msg.ParseMode = parseMode
|
|
msg.DisableWebPagePreview = true
|
|
msg.DisableNotification = true
|
|
_, err := b.api.Send(msg)
|
|
if err != nil {
|
|
b.logger.Warn("send silent message failed", zap.Int64("chatId", chatID), zap.Error(err))
|
|
}
|
|
return err
|
|
}
|
|
|
|
// SendDocument sends body as a file attachment with the given filename
|
|
// (used by /rawappleapp and /rawgoogleapp).
|
|
func (b *Bot) SendDocument(chatID int64, filename, body string) error {
|
|
file := tgbotapi.FileBytes{Name: filename, Bytes: []byte(body)}
|
|
doc := tgbotapi.NewDocument(chatID, file)
|
|
_, err := b.api.Send(doc)
|
|
if err != nil {
|
|
b.logger.Warn("send document failed", zap.Int64("chatId", chatID), zap.Error(err))
|
|
}
|
|
return err
|
|
}
|