mirror of
https://github.com/tiennm99/store-scraper-bot.git
synced 2026-08-31 06:28:43 +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.
121 lines
3.9 KiB
Go
121 lines
3.9 KiB
Go
package command
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
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/config"
|
|
"github.com/miti99/store-scraper-bot-go/internal/model"
|
|
"github.com/miti99/store-scraper-bot-go/internal/repository"
|
|
"github.com/miti99/store-scraper-bot-go/internal/util"
|
|
)
|
|
|
|
// /checkapp — Java CheckAppCommand. Reports update status per app, per store.
|
|
type CheckAppCommand struct {
|
|
cfg *config.Config
|
|
adminRepo *repository.AdminRepository
|
|
groupRepo *repository.GroupRepository
|
|
appleScraper *apple.AppleScraper
|
|
googleScraper *google.GoogleScraper
|
|
}
|
|
|
|
func NewCheckAppCommand(cfg *config.Config, adminRepo *repository.AdminRepository, groupRepo *repository.GroupRepository, a *apple.AppleScraper, g *google.GoogleScraper) *CheckAppCommand {
|
|
return &CheckAppCommand{cfg: cfg, adminRepo: adminRepo, groupRepo: groupRepo, appleScraper: a, googleScraper: g}
|
|
}
|
|
|
|
func (c *CheckAppCommand) Execute(msg *tgbotapi.Message, sender Sender) {
|
|
if !authorizeGroup(msg.Chat.ID, c.adminRepo, sender) {
|
|
return
|
|
}
|
|
if len(splitArgs(msg.CommandArguments())) != 0 {
|
|
_ = sender.SendMessage(msg.Chat.ID, "Invalid arguments")
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
|
|
defer cancel()
|
|
group, err := c.groupRepo.Get(ctx, msg.Chat.ID)
|
|
if err != nil {
|
|
_ = sender.SendMessage(msg.Chat.ID, "Internal server error")
|
|
return
|
|
}
|
|
now := time.Now()
|
|
threshold := c.cfg.NumDaysWarningNotUpdated
|
|
|
|
headers := []string{"AppId", "Updated", "Days", "OK"}
|
|
appleRows := c.appleRows(group.AppleApps, now, threshold)
|
|
googleRows := c.googleRows(group.GoogleApps, now, threshold)
|
|
|
|
var sb strings.Builder
|
|
sb.WriteString("<b>Apple Apps</b>\n")
|
|
if len(appleRows) == 0 {
|
|
sb.WriteString("<i>(none)</i>\n")
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("<pre>%s</pre>\n", util.BuildTable(headers, appleRows)))
|
|
}
|
|
sb.WriteString("\n<b>Google Apps</b>\n")
|
|
if len(googleRows) == 0 {
|
|
sb.WriteString("<i>(none)</i>\n")
|
|
} else {
|
|
sb.WriteString(fmt.Sprintf("<pre>%s</pre>\n", util.BuildTable(headers, googleRows)))
|
|
}
|
|
_ = sender.SendMessage(msg.Chat.ID, sb.String())
|
|
}
|
|
|
|
func (c *CheckAppCommand) appleRows(apps []model.AppInfo, now time.Time, threshold int) [][]string {
|
|
rows := make([][]string, 0, len(apps))
|
|
for _, a := range apps {
|
|
resp, err := c.appleScraper.GetApp(a.AppID, a.Country)
|
|
if err != nil || resp == nil {
|
|
rows = append(rows, []string{a.AppID, "?", "?", okMark(false)})
|
|
continue
|
|
}
|
|
updated, days, ok := evalAppleUpdated(resp.Updated, now, threshold)
|
|
rows = append(rows, []string{a.AppID, updated, fmt.Sprintf("%d", days), okMark(ok)})
|
|
}
|
|
return rows
|
|
}
|
|
|
|
func (c *CheckAppCommand) googleRows(apps []model.AppInfo, now time.Time, threshold int) [][]string {
|
|
rows := make([][]string, 0, len(apps))
|
|
for _, a := range apps {
|
|
resp, err := c.googleScraper.GetApp(a.AppID, a.Country)
|
|
if err != nil || resp == nil {
|
|
rows = append(rows, []string{a.AppID, "?", "?", okMark(false)})
|
|
continue
|
|
}
|
|
updated, days, ok := evalGoogleUpdated(resp.Updated, now, threshold)
|
|
rows = append(rows, []string{a.AppID, updated, fmt.Sprintf("%d", days), okMark(ok)})
|
|
}
|
|
return rows
|
|
}
|
|
|
|
// evalAppleUpdated parses Apple's ISO 8601 timestamp and returns (yyyy-MM-dd,
|
|
// days since update, OK).
|
|
func evalAppleUpdated(updated string, now time.Time, threshold int) (string, int, bool) {
|
|
t, err := time.Parse(time.RFC3339, updated)
|
|
if err != nil {
|
|
return updated, 0, false
|
|
}
|
|
days := int(now.Sub(t).Hours() / 24)
|
|
return t.Format("2006-01-02"), days, days <= threshold
|
|
}
|
|
|
|
func evalGoogleUpdated(millis int64, now time.Time, threshold int) (string, int, bool) {
|
|
t := time.UnixMilli(millis)
|
|
days := int(now.Sub(t).Hours() / 24)
|
|
return t.Format("2006-01-02"), days, days <= threshold
|
|
}
|
|
|
|
func okMark(ok bool) string {
|
|
if ok {
|
|
return "✅"
|
|
}
|
|
return "❌"
|
|
}
|