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.
115 lines
3.0 KiB
Go
115 lines
3.0 KiB
Go
package google
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/miti99/store-scraper-bot-go/internal/api/google/request"
|
|
"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"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// BaseURL mirrors Java GooglePlayScraper (api/google/GooglePlayScraper.java).
|
|
const BaseURL = "https://store-scraper.vercel.app/google"
|
|
|
|
type GoogleScraper struct {
|
|
repo *repository.GoogleAppRepository
|
|
cfg *config.Config
|
|
client *http.Client
|
|
logger *zap.Logger
|
|
}
|
|
|
|
func NewGoogleScraper(repo *repository.GoogleAppRepository, cfg *config.Config) *GoogleScraper {
|
|
return &GoogleScraper{
|
|
repo: repo,
|
|
cfg: cfg,
|
|
client: &http.Client{Timeout: 30 * time.Second},
|
|
logger: cfg.Logger,
|
|
}
|
|
}
|
|
|
|
func (s *GoogleScraper) RawApp(req request.GoogleAppRequest) (string, error) {
|
|
body, err := json.Marshal(req)
|
|
if err != nil {
|
|
return "", fmt.Errorf("marshal google request: %w", err)
|
|
}
|
|
httpReq, err := http.NewRequestWithContext(context.Background(), http.MethodPost, BaseURL+"/app", bytes.NewReader(body))
|
|
if err != nil {
|
|
return "", fmt.Errorf("build google request: %w", err)
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := s.client.Do(httpReq)
|
|
if err != nil {
|
|
return "", fmt.Errorf("google HTTP error: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
return "", fmt.Errorf("google HTTP status %d", resp.StatusCode)
|
|
}
|
|
raw, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", fmt.Errorf("read google body: %w", err)
|
|
}
|
|
return string(raw), nil
|
|
}
|
|
|
|
func (s *GoogleScraper) App(req request.GoogleAppRequest) (*model.GoogleAppResponse, error) {
|
|
raw, err := s.RawApp(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out := &model.GoogleAppResponse{}
|
|
if err := json.Unmarshal([]byte(raw), out); err != nil {
|
|
return nil, fmt.Errorf("decode google response: %w", err)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (s *GoogleScraper) GetApp(appID, country string) (*model.GoogleAppResponse, error) {
|
|
if cached, _ := s.repo.GetCached(appID); cached != nil {
|
|
return &cached.App, nil
|
|
}
|
|
resp, err := s.App(request.New(appID, country))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.cache(resp, appID)
|
|
return resp, nil
|
|
}
|
|
|
|
func (s *GoogleScraper) FetchAndCache(req request.GoogleAppRequest) (*model.GoogleAppResponse, error) {
|
|
resp, err := s.App(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.cache(resp, req.AppID)
|
|
return resp, nil
|
|
}
|
|
|
|
func (s *GoogleScraper) cache(resp *model.GoogleAppResponse, fallbackID string) {
|
|
if resp == nil {
|
|
return
|
|
}
|
|
id := resp.AppID
|
|
if id == "" {
|
|
id = fallbackID
|
|
}
|
|
if id == "" {
|
|
return
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
entry := model.NewGoogleApp(id, *resp, time.Now().UnixMilli())
|
|
if err := s.repo.Save(ctx, entry); err != nil {
|
|
s.logger.Warn("failed to cache google app", zap.String("appId", id), zap.Error(err))
|
|
}
|
|
}
|