Files
store-scraper-bot/internal/bot/command/check_app.go
T
tiennm99 e695d9b223 feat: port logic from Java original to align with current Java behavior
- 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.
2026-04-25 20:26:25 +07:00

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 "❌"
}