fix: OrganizeGameItem
fix: save twitchtoken remove: getSteamID remove: update cmd&api add: getIGDBIDBySteamSearch add: update game info task
This commit is contained in:
parent
71a2ac545b
commit
8702d3e93f
65
cmd/task.go
65
cmd/task.go
@ -10,32 +10,63 @@ import (
|
||||
)
|
||||
|
||||
type taskCommandConfig struct {
|
||||
Crawl bool
|
||||
CrawlCron string
|
||||
Cron string
|
||||
Now bool
|
||||
}
|
||||
|
||||
var taskCmdCfg taskCommandConfig
|
||||
var taskCommandCfg taskCommandConfig
|
||||
|
||||
var crawlTaskCmd = &cobra.Command{
|
||||
Use: "crawl",
|
||||
Long: "Start crawl task",
|
||||
Short: "Start crawl task",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if taskCommandCfg.Now {
|
||||
task.Crawl(log.Logger)
|
||||
}
|
||||
c := cron.New()
|
||||
_, err := c.AddFunc(taskCommandCfg.Cron, func() { task.Crawl(log.Logger) })
|
||||
if err != nil {
|
||||
log.Logger.Error("Failed to add task", zap.Error(err))
|
||||
}
|
||||
c.Start()
|
||||
select {}
|
||||
},
|
||||
}
|
||||
|
||||
var updateTaskCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Long: "Start update outdated game infos task",
|
||||
Short: "Start update outdated game infos task",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if taskCommandCfg.Now {
|
||||
task.UpdateOutdatedGameInfos(log.Logger)
|
||||
}
|
||||
c := cron.New()
|
||||
_, err := c.AddFunc(taskCommandCfg.Cron, func() { task.UpdateOutdatedGameInfos(log.Logger) })
|
||||
if err != nil {
|
||||
log.Logger.Error("Failed to add task", zap.Error(err))
|
||||
}
|
||||
c.Start()
|
||||
select {}
|
||||
},
|
||||
}
|
||||
|
||||
var taskCmd = &cobra.Command{
|
||||
Use: "task",
|
||||
Long: "Start task",
|
||||
Short: "Start task",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if taskCmdCfg.Crawl {
|
||||
task.Crawl(log.Logger)
|
||||
c := cron.New()
|
||||
_, err := c.AddFunc(taskCmdCfg.CrawlCron, func() { task.Crawl(log.Logger) })
|
||||
if err != nil {
|
||||
log.Logger.Error("Failed to add task", zap.Error(err))
|
||||
}
|
||||
c.Start()
|
||||
select {}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskCmd.Flags().BoolVar(&taskCmdCfg.Crawl, "crawl", false, "enable auto crawl")
|
||||
taskCmd.Flags().StringVar(&taskCmdCfg.CrawlCron, "crawl-cron", "0 */3 * * *", "crawl cron expression")
|
||||
crawlTaskCmd.Flags().StringVar(&taskCommandCfg.Cron, "cron", "0 */3 * * *", "cron expression")
|
||||
crawlTaskCmd.Flags().BoolVar(&taskCommandCfg.Now, "now", false, "run task immediately")
|
||||
|
||||
updateTaskCmd.Flags().StringVar(&taskCommandCfg.Cron, "cron", "0 */3 * * *", "cron expression")
|
||||
updateTaskCmd.Flags().BoolVar(&taskCommandCfg.Now, "now", false, "run task immediately")
|
||||
|
||||
taskCmd.AddCommand(crawlTaskCmd)
|
||||
taskCmd.AddCommand(updateTaskCmd)
|
||||
|
||||
RootCmd.AddCommand(taskCmd)
|
||||
}
|
||||
|
@ -1,57 +0,0 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"pcgamedb/crawler"
|
||||
"pcgamedb/db"
|
||||
"pcgamedb/log"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var updateCmd = &cobra.Command{
|
||||
Use: "update",
|
||||
Long: "Update game info by game data platform",
|
||||
Short: "Update game info by game data platform",
|
||||
Run: updateRun,
|
||||
}
|
||||
|
||||
type updateCommandConfig struct {
|
||||
PlatformID int
|
||||
Platform string
|
||||
ID string
|
||||
}
|
||||
|
||||
var updateCmdcfx updateCommandConfig
|
||||
|
||||
func init() {
|
||||
updateCmd.Flags().IntVarP(&updateCmdcfx.PlatformID, "platform-id", "p", 0, "platform id")
|
||||
updateCmd.Flags().StringVarP(&updateCmdcfx.Platform, "platform", "t", "", "platform")
|
||||
updateCmd.Flags().StringVarP(&updateCmdcfx.ID, "game-id", "i", "", "game info id")
|
||||
RootCmd.AddCommand(updateCmd)
|
||||
}
|
||||
|
||||
func updateRun(cmd *cobra.Command, args []string) {
|
||||
id, err := primitive.ObjectIDFromHex(updateCmdcfx.ID)
|
||||
if err != nil {
|
||||
log.Logger.Error("Failed to parse game info id", zap.Error(err))
|
||||
return
|
||||
}
|
||||
oldInfo, err := db.GetGameInfoByID(id)
|
||||
if err != nil {
|
||||
log.Logger.Error("Failed to get game info", zap.Error(err))
|
||||
return
|
||||
}
|
||||
newInfo, err := crawler.GenerateGameInfo(updateCmdcfx.Platform, updateCmdcfx.PlatformID)
|
||||
if err != nil {
|
||||
log.Logger.Error("Failed to generate game info", zap.Error(err))
|
||||
return
|
||||
}
|
||||
newInfo.ID = id
|
||||
newInfo.GameIDs = oldInfo.GameIDs
|
||||
err = db.SaveGameInfo(newInfo)
|
||||
if err != nil {
|
||||
log.Logger.Error("Failed to save game info", zap.Error(err))
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ const (
|
||||
Steam250Top250URL = "https://steam250.com/top250"
|
||||
Steam250BestOfTheYearURL = "https://steam250.com/%v"
|
||||
Steam250WeekTop50URL = "https://steam250.com/7day"
|
||||
Steam250MonthTop50URL = "https://steam250.com/30day"
|
||||
Steam250MostPlayedURL = "https://steam250.com/most_played"
|
||||
FitGirlURL = "https://fitgirl-repacks.site/page/%v/"
|
||||
SteamRIPBaseURL = "https://steamrip.com"
|
||||
|
@ -6,11 +6,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"pcgamedb/db"
|
||||
"pcgamedb/model"
|
||||
"pcgamedb/utils"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
@ -26,14 +27,14 @@ func GenerateGameInfo(platform string, id int) (*model.GameInfo, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// OrganizeGameItem Organize and save GameInfo
|
||||
// OrganizeGameItem Organize game item and save game info to database
|
||||
func OrganizeGameItem(game *model.GameItem) error {
|
||||
hasOriganized, _ := db.HasGameItemOrganized(game.ID)
|
||||
if hasOriganized {
|
||||
return nil
|
||||
}
|
||||
|
||||
item, err := OrganizeGameItemWithIGDB(0, game)
|
||||
item, err := OrganizeGameItemWithIGDB(game)
|
||||
if err == nil {
|
||||
if item.SteamID == 0 {
|
||||
// get steam id from igdb
|
||||
@ -41,20 +42,6 @@ func OrganizeGameItem(game *model.GameItem) error {
|
||||
if err == nil {
|
||||
item.SteamID = steamID
|
||||
}
|
||||
err = db.SaveGameInfo(item)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
item, err = OrganizeGameItemWithSteam(0, game)
|
||||
if err == nil {
|
||||
if item.IGDBID == 0 {
|
||||
igdbID, err := GetIGDBIDBySteamIDCache(item.SteamID)
|
||||
if err == nil {
|
||||
item.IGDBID = igdbID
|
||||
}
|
||||
}
|
||||
err = db.SaveGameInfo(item)
|
||||
if err != nil {
|
||||
@ -100,7 +87,7 @@ func OrganizeGameItemManually(gameID primitive.ObjectID, platform string, platfo
|
||||
}
|
||||
}
|
||||
if platform == "steam" {
|
||||
igdbID, err := GetIGDBIDBySteamIDCache(platformID)
|
||||
igdbID, err := GetIGDBIDBySteamAppIDCache(platformID)
|
||||
if err == nil {
|
||||
info.IGDBID = igdbID
|
||||
}
|
||||
@ -138,7 +125,7 @@ func SupplementPlatformIDToGameInfo(logger *zap.Logger) error {
|
||||
changed = true
|
||||
}
|
||||
if info.SteamID != 0 && info.IGDBID == 0 {
|
||||
igdbID, err := GetIGDBIDBySteamIDCache(info.SteamID)
|
||||
igdbID, err := GetIGDBIDBySteamAppIDCache(info.SteamID)
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
if err != nil {
|
||||
continue
|
||||
@ -147,9 +134,38 @@ func SupplementPlatformIDToGameInfo(logger *zap.Logger) error {
|
||||
changed = true
|
||||
}
|
||||
if changed {
|
||||
logger.Info("Supplemented platform id for game info", zap.String("name", info.Name), zap.Int("igdb", info.IGDBID), zap.Int("steam", info.SteamID))
|
||||
logger.Info("supp", zap.String("name", info.Name), zap.Int("igdb", info.IGDBID), zap.Int("steam", info.SteamID))
|
||||
_ = db.SaveGameInfo(info)
|
||||
} else {
|
||||
logger.Info("skip", zap.String("name", info.Name), zap.Int("igdb", info.IGDBID), zap.Int("steam", info.SteamID))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateGameInfo(num int) (chan *model.GameInfo, error) {
|
||||
infos, err := db.GetOutdatedGameInfos(num)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
updateChan := make(chan *model.GameInfo)
|
||||
|
||||
go func() {
|
||||
for _, info := range infos {
|
||||
if info.IGDBID != 0 {
|
||||
newInfo, err := GenerateIGDBGameInfo(info.IGDBID)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
db.MergeGameInfo(info, newInfo)
|
||||
err = db.SaveGameInfo(newInfo)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
updateChan <- newInfo
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return updateChan, nil
|
||||
}
|
||||
|
173
crawler/igdb.go
173
crawler/igdb.go
@ -1,6 +1,7 @@
|
||||
package crawler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@ -18,14 +19,18 @@ import (
|
||||
"pcgamedb/db"
|
||||
"pcgamedb/model"
|
||||
"pcgamedb/utils"
|
||||
|
||||
"github.com/PuerkitoBio/goquery"
|
||||
)
|
||||
|
||||
type twitchToken struct {
|
||||
token string
|
||||
expires time.Time
|
||||
Token string `json:"token"`
|
||||
Expires time.Time `json:"expires"`
|
||||
once sync.Once
|
||||
}
|
||||
|
||||
var token = twitchToken{}
|
||||
|
||||
func (t *twitchToken) getToken() (string, error) {
|
||||
t.once.Do(func() {
|
||||
if config.Config.RedisAvaliable {
|
||||
@ -34,19 +39,19 @@ func (t *twitchToken) getToken() (string, error) {
|
||||
}
|
||||
}
|
||||
})
|
||||
if t.token == "" || time.Now().After(t.expires) {
|
||||
if t.Token == "" || time.Now().After(t.Expires) {
|
||||
token, expires, err := loginTwitch()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to login twitch: %w", err)
|
||||
}
|
||||
t.token = token
|
||||
t.expires = expires
|
||||
t.Token = token
|
||||
t.Expires = expires
|
||||
j, err := json.Marshal(t)
|
||||
if err == nil {
|
||||
_ = cache.Add("twitch_token", j)
|
||||
}
|
||||
}
|
||||
return t.token, nil
|
||||
return t.Token, nil
|
||||
}
|
||||
|
||||
func loginTwitch() (string, time.Time, error) {
|
||||
@ -100,8 +105,6 @@ func igdbFetch(URL string, dataBody any) (*utils.FetchResponse, error) {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var token = twitchToken{}
|
||||
|
||||
func getIGDBID(name string) (int, error) {
|
||||
var err error
|
||||
resp, err := igdbFetch(constant.IGDBSearchURL, fmt.Sprintf(`search "%s"; fields *; limit 50; where game.platforms = [6] | game.platforms=[130] | game.platforms=[384] | game.platforms=[163];`, name))
|
||||
@ -110,6 +113,9 @@ func getIGDBID(name string) (int, error) {
|
||||
}
|
||||
if string(resp.Data) == "[]" {
|
||||
resp, err = igdbFetch(constant.IGDBSearchURL, fmt.Sprintf(`search "%s"; fields *; limit 50;`, name))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
var data model.IGDBSearches
|
||||
@ -150,6 +156,83 @@ func getIGDBID(name string) (int, error) {
|
||||
return 0, fmt.Errorf("IGDB ID not found: %s", name)
|
||||
}
|
||||
|
||||
func getIGDBIDBySteamSearch(name string) (int, error) {
|
||||
baseURL, _ := url.Parse(constant.SteamSearchURL)
|
||||
params := url.Values{}
|
||||
params.Add("term", name)
|
||||
baseURL.RawQuery = params.Encode()
|
||||
|
||||
resp, err := utils.Fetch(utils.FetchConfig{
|
||||
Url: baseURL.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
doc, err := goquery.NewDocumentFromReader(bytes.NewReader(resp.Data))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
type searchResult struct {
|
||||
ID int
|
||||
Type string
|
||||
Name string
|
||||
}
|
||||
var items []searchResult
|
||||
doc.Find(".search_result_row").Each(func(i int, s *goquery.Selection) {
|
||||
if itemKey, exist := s.Attr("data-ds-itemkey"); exist {
|
||||
if strings.HasPrefix(itemKey, "App_") {
|
||||
id, err := strconv.Atoi(itemKey[4:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
name := s.Find(".title").Text()
|
||||
items = append(items, searchResult{
|
||||
ID: id,
|
||||
Type: "App",
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
if strings.HasPrefix(itemKey, "Bundle_") {
|
||||
id, err := strconv.Atoi(itemKey[7:])
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
name := s.Find(".title").Text()
|
||||
items = append(items, searchResult{
|
||||
ID: id,
|
||||
Type: "Bundle",
|
||||
Name: name,
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
maxSim := 0.0
|
||||
var maxSimItem searchResult
|
||||
for _, item := range items {
|
||||
if strings.EqualFold(strings.TrimSpace(item.Name), strings.TrimSpace(name)) {
|
||||
maxSimItem = item
|
||||
break
|
||||
} else {
|
||||
sim := utils.Similarity(item.Name, name)
|
||||
if sim >= 0.8 && sim > maxSim {
|
||||
maxSim = sim
|
||||
maxSimItem = item
|
||||
}
|
||||
}
|
||||
}
|
||||
if maxSim != 0 {
|
||||
if maxSimItem.Type == "App" {
|
||||
return GetIGDBIDBySteamAppIDCache(maxSimItem.ID)
|
||||
}
|
||||
if maxSimItem.Type == "Bundle" {
|
||||
return GetIGDBIDBySteamBundleIDCache(maxSimItem.ID)
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("steam ID not found: %s", name)
|
||||
}
|
||||
|
||||
// GetIGDBAppParent returns the parent of the game, if no parent return itself
|
||||
func GetIGDBAppParent(id int) (int, error) {
|
||||
detail, err := GetIGDBAppDetailCache(id)
|
||||
@ -226,7 +309,7 @@ func GetIGDBAppParentCache(id int) (int, error) {
|
||||
return GetIGDBAppParent(id)
|
||||
}
|
||||
|
||||
// GetIGDBID returns the IGDB ID of the game, try raw name first then formated names
|
||||
// GetIGDBID returns the IGDB ID of the game, try directly IGDB api first, then steam search
|
||||
func GetIGDBID(name string) (int, error) {
|
||||
name1 := name
|
||||
name2 := FormatName(name)
|
||||
@ -240,6 +323,12 @@ func GetIGDBID(name string) (int, error) {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
for _, name := range names {
|
||||
id, err := getIGDBIDBySteamSearch(name)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("IGDB ID not found")
|
||||
}
|
||||
|
||||
@ -305,7 +394,7 @@ func GetIGDBAppDetailCache(id int) (*model.IGDBGameDetail, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = cache.Add(key, dataBytes)
|
||||
_ = cache.AddWithExpire(key, dataBytes, 7*24*time.Hour)
|
||||
return data, nil
|
||||
}
|
||||
} else {
|
||||
@ -361,6 +450,7 @@ func GenerateIGDBGameInfo(id int) (*model.GameInfo, error) {
|
||||
item.Name = detail.Name
|
||||
item.Description = detail.Summary
|
||||
item.Cover = strings.Replace(detail.Cover.URL, "t_thumb", "t_original", 1)
|
||||
item.InfoUpdatedAt = time.Now()
|
||||
|
||||
for _, lang := range detail.LanguageSupports {
|
||||
if lang.LanguageSupportType == 3 {
|
||||
@ -399,13 +489,10 @@ func GenerateIGDBGameInfo(id int) (*model.GameInfo, error) {
|
||||
}
|
||||
|
||||
// OrganizeGameItemWithIGDB Will add GameItem.ID to the newly added GameInfo.GameIDs
|
||||
func OrganizeGameItemWithIGDB(id int, game *model.GameItem) (*model.GameInfo, error) {
|
||||
var err error
|
||||
if id == 0 {
|
||||
id, err = GetIGDBIDCache(game.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func OrganizeGameItemWithIGDB(game *model.GameItem) (*model.GameInfo, error) {
|
||||
id, err := GetIGDBIDCache(game.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
d, err := db.GetGameInfoByPlatformID("igdb", id)
|
||||
if err == nil {
|
||||
@ -422,7 +509,7 @@ func OrganizeGameItemWithIGDB(id int, game *model.GameItem) (*model.GameInfo, er
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func GetIGDBIDBySteamID(id int) (int, error) {
|
||||
func GetIGDBIDBySteamAppID(id int) (int, error) {
|
||||
var err error
|
||||
resp, err := igdbFetch(constant.IGDBWebsitesURL, fmt.Sprintf(`where url = "https://store.steampowered.com/app/%v" | url = "https://store.steampowered.com/app/%v/"*; fields *; limit 500;`, id, id))
|
||||
if err != nil {
|
||||
@ -438,19 +525,19 @@ func GetIGDBIDBySteamID(id int) (int, error) {
|
||||
return 0, errors.New("not found")
|
||||
}
|
||||
if data[0].Game == 0 {
|
||||
return GetIGDBIDBySteamID(id)
|
||||
return GetIGDBIDBySteamAppID(id)
|
||||
}
|
||||
return GetIGDBAppParentCache(data[0].Game)
|
||||
}
|
||||
|
||||
func GetIGDBIDBySteamIDCache(id int) (int, error) {
|
||||
func GetIGDBIDBySteamAppIDCache(id int) (int, error) {
|
||||
if config.Config.RedisAvaliable {
|
||||
key := fmt.Sprintf("igdb_id_by_steam_id:%v", id)
|
||||
key := fmt.Sprintf("igdb_id_by_steam_app_id:%v", id)
|
||||
val, exist := cache.Get(key)
|
||||
if exist {
|
||||
return strconv.Atoi(val)
|
||||
} else {
|
||||
data, err := GetIGDBIDBySteamID(id)
|
||||
data, err := GetIGDBIDBySteamAppID(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -458,7 +545,47 @@ func GetIGDBIDBySteamIDCache(id int) (int, error) {
|
||||
return data, nil
|
||||
}
|
||||
} else {
|
||||
return GetIGDBIDBySteamID(id)
|
||||
return GetIGDBIDBySteamAppID(id)
|
||||
}
|
||||
}
|
||||
|
||||
func GetIGDBIDBySteamBundleID(id int) (int, error) {
|
||||
var err error
|
||||
resp, err := igdbFetch(constant.IGDBWebsitesURL, fmt.Sprintf(`where url = "https://store.steampowered.com/bundle/%v" | url = "https://store.steampowered.com/bundle/%v/"*; fields *; limit 500;`, id, id))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var data []struct {
|
||||
Game int `json:"game"`
|
||||
}
|
||||
if err = json.Unmarshal(resp.Data, &data); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if len(data) == 0 {
|
||||
return 0, errors.New("not found")
|
||||
}
|
||||
if data[0].Game == 0 {
|
||||
return GetIGDBIDBySteamBundleID(id)
|
||||
}
|
||||
return GetIGDBAppParentCache(data[0].Game)
|
||||
}
|
||||
|
||||
func GetIGDBIDBySteamBundleIDCache(id int) (int, error) {
|
||||
if config.Config.RedisAvaliable {
|
||||
key := fmt.Sprintf("igdb_id_by_steam_bundle_id:%v", id)
|
||||
val, exist := cache.Get(key)
|
||||
if exist {
|
||||
return strconv.Atoi(val)
|
||||
} else {
|
||||
data, err := GetIGDBIDBySteamBundleID(id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_ = cache.Add(key, strconv.Itoa(data))
|
||||
return data, nil
|
||||
}
|
||||
} else {
|
||||
return GetIGDBIDBySteamBundleID(id)
|
||||
}
|
||||
}
|
||||
|
||||
|
111
crawler/steam.go
111
crawler/steam.go
@ -8,99 +8,15 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"pcgamedb/cache"
|
||||
"pcgamedb/config"
|
||||
"pcgamedb/constant"
|
||||
"pcgamedb/db"
|
||||
"pcgamedb/model"
|
||||
"pcgamedb/utils"
|
||||
)
|
||||
|
||||
func getSteamID(name string) (int, error) {
|
||||
baseURL, _ := url.Parse(constant.SteamSearchURL)
|
||||
params := url.Values{}
|
||||
params.Add("term", name)
|
||||
baseURL.RawQuery = params.Encode()
|
||||
|
||||
resp, err := utils.Fetch(utils.FetchConfig{
|
||||
Url: baseURL.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
idRegex := regexp.MustCompile(`data-ds-appid="(.*?)"`)
|
||||
nameRegex := regexp.MustCompile(`<span class="title">(.*?)</span>`)
|
||||
idRegexRes := idRegex.FindAllStringSubmatch(string(resp.Data), -1)
|
||||
nameRegexRes := nameRegex.FindAllStringSubmatch(string(resp.Data), -1)
|
||||
|
||||
if len(idRegexRes) == 0 {
|
||||
return 0, fmt.Errorf("steam ID not found: %s", name)
|
||||
}
|
||||
|
||||
maxSim := 0.0
|
||||
maxSimID := 0
|
||||
for i, id := range idRegexRes {
|
||||
idStr := id[1]
|
||||
nameStr := nameRegexRes[i][1]
|
||||
if index := strings.Index(idStr, ","); index != -1 {
|
||||
idStr = idStr[:index]
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(nameStr), strings.TrimSpace(name)) {
|
||||
return strconv.Atoi(idStr)
|
||||
} else {
|
||||
sim := utils.Similarity(nameStr, name)
|
||||
if sim >= 0.8 && sim > maxSim {
|
||||
maxSim = sim
|
||||
maxSimID, _ = strconv.Atoi(idStr)
|
||||
}
|
||||
}
|
||||
}
|
||||
if maxSimID != 0 {
|
||||
return maxSimID, nil
|
||||
}
|
||||
return 0, fmt.Errorf("steam ID not found: %s", name)
|
||||
}
|
||||
|
||||
func GetSteamID(name string) (int, error) {
|
||||
name1 := name
|
||||
name2 := FormatName(name)
|
||||
names := []string{name1}
|
||||
if name1 != name2 {
|
||||
names = append(names, name2)
|
||||
}
|
||||
for _, n := range names {
|
||||
id, err := getSteamID(n)
|
||||
if err == nil {
|
||||
return id, nil
|
||||
}
|
||||
}
|
||||
return 0, errors.New("steam ID not found")
|
||||
}
|
||||
|
||||
func GetSteamIDCache(name string) (int, error) {
|
||||
if config.Config.RedisAvaliable {
|
||||
key := fmt.Sprintf("steam_id:%s", name)
|
||||
val, exist := cache.Get(key)
|
||||
if exist {
|
||||
id, err := strconv.Atoi(val)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return id, nil
|
||||
} else {
|
||||
id, err := GetSteamID(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
_ = cache.Add(key, id)
|
||||
return id, nil
|
||||
}
|
||||
} else {
|
||||
return GetSteamID(name)
|
||||
}
|
||||
}
|
||||
|
||||
func GetSteamAppDetail(id int) (*model.SteamAppDetail, error) {
|
||||
baseURL, _ := url.Parse(constant.SteamAppDetailURL)
|
||||
params := url.Values{}
|
||||
@ -168,6 +84,7 @@ func GenerateSteamGameInfo(id int) (*model.GameInfo, error) {
|
||||
item.Cover = fmt.Sprintf("https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/%v/library_600x900_2x.jpg", id)
|
||||
item.Developers = detail.Data.Developers
|
||||
item.Publishers = detail.Data.Publishers
|
||||
item.InfoUpdatedAt = time.Now()
|
||||
var screenshots []string
|
||||
for _, screenshot := range detail.Data.Screenshots {
|
||||
screenshots = append(screenshots, screenshot.PathFull)
|
||||
@ -176,30 +93,6 @@ func GenerateSteamGameInfo(id int) (*model.GameInfo, error) {
|
||||
return item, nil
|
||||
}
|
||||
|
||||
// OrganizeGameItemWithSteam Will add GameItem.ID to the newly added GameInfo.GameIDs
|
||||
func OrganizeGameItemWithSteam(id int, game *model.GameItem) (*model.GameInfo, error) {
|
||||
var err error
|
||||
if id == 0 {
|
||||
id, err = GetSteamIDCache(game.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
d, err := db.GetGameInfoByPlatformID("steam", id)
|
||||
if err == nil {
|
||||
d.GameIDs = append(d.GameIDs, game.ID)
|
||||
d.GameIDs = utils.Unique(d.GameIDs)
|
||||
return d, nil
|
||||
}
|
||||
detail, err := GenerateGameInfo("steam", id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
detail.GameIDs = append(detail.GameIDs, game.ID)
|
||||
detail.GameIDs = utils.Unique(detail.GameIDs)
|
||||
return detail, nil
|
||||
}
|
||||
|
||||
func GetSteamIDByIGDBID(IGDBID int) (int, error) {
|
||||
var err error
|
||||
resp, err := igdbFetch(constant.IGDBWebsitesURL, fmt.Sprintf(`where game = %v; fields *; limit 500;`, IGDBID))
|
||||
|
@ -62,6 +62,10 @@ func GetSteam250WeekTop50() ([]*model.GameInfo, error) {
|
||||
return GetSteam250(constant.Steam250WeekTop50URL)
|
||||
}
|
||||
|
||||
func GetSteam250MonthTop50() ([]*model.GameInfo, error) {
|
||||
return GetSteam250(constant.Steam250MonthTop50URL)
|
||||
}
|
||||
|
||||
func GetSteam250MostPlayed() ([]*model.GameInfo, error) {
|
||||
return GetSteam250(constant.Steam250MostPlayedURL)
|
||||
}
|
||||
|
36
db/game.go
36
db/game.go
@ -141,6 +141,9 @@ func SaveGameInfo(item *model.GameInfo) error {
|
||||
if item.CreatedAt.IsZero() {
|
||||
item.CreatedAt = time.Now()
|
||||
}
|
||||
if item.InfoUpdatedAt.IsZero() {
|
||||
item.InfoUpdatedAt = item.CreatedAt
|
||||
}
|
||||
item.UpdatedAt = time.Now()
|
||||
filter := bson.M{"_id": item.ID}
|
||||
update := bson.M{"$set": item}
|
||||
@ -153,7 +156,7 @@ func SaveGameInfo(item *model.GameInfo) error {
|
||||
}
|
||||
|
||||
func SaveGameInfos(items []*model.GameInfo) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
||||
operations := make([]mongo.WriteModel, len(items))
|
||||
@ -164,6 +167,9 @@ func SaveGameInfos(items []*model.GameInfo) error {
|
||||
if item.CreatedAt.IsZero() {
|
||||
item.CreatedAt = time.Now()
|
||||
}
|
||||
if item.InfoUpdatedAt.IsZero() {
|
||||
item.InfoUpdatedAt = item.CreatedAt
|
||||
}
|
||||
item.UpdatedAt = time.Now()
|
||||
operations[i] = mongo.NewUpdateOneModel().
|
||||
SetFilter(bson.D{{Key: "_id", Value: item.ID}}).
|
||||
@ -889,3 +895,31 @@ func GetGameInfoByGameItemID(id primitive.ObjectID) (*model.GameInfo, error) {
|
||||
}
|
||||
return res[0], nil
|
||||
}
|
||||
|
||||
func GetOutdatedGameInfos(maxNum int) ([]*model.GameInfo, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
opts := options.Find().SetLimit(int64(maxNum))
|
||||
filter := bson.M{
|
||||
"info_updated_at": bson.M{"$lt": time.Now().Add(-24 * time.Hour * 30)},
|
||||
}
|
||||
cursor, err := GameInfoCollection.Find(ctx, filter, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var res []*model.GameInfo
|
||||
if err = cursor.All(ctx, &res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func MergeGameInfo(oldInfo *model.GameInfo, newInfo *model.GameInfo) {
|
||||
newInfo.ID = oldInfo.ID
|
||||
newInfo.UpdatedAt = time.Now()
|
||||
newInfo.GameIDs = oldInfo.GameIDs
|
||||
newInfo.IGDBID = oldInfo.IGDBID
|
||||
newInfo.SteamID = oldInfo.SteamID
|
||||
newInfo.CreatedAt = oldInfo.CreatedAt
|
||||
newInfo.InfoUpdatedAt = time.Now()
|
||||
}
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -7,21 +7,22 @@ import (
|
||||
)
|
||||
|
||||
type GameInfo struct {
|
||||
ID primitive.ObjectID `json:"id" bson:"_id"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
Description string `json:"description" bson:"description"`
|
||||
Aliases []string `json:"aliases" bson:"aliases"`
|
||||
Developers []string `json:"developers" bson:"developers"`
|
||||
Publishers []string `json:"publishers" bson:"publishers"`
|
||||
IGDBID int `json:"igdb_id" bson:"igdb_id"`
|
||||
SteamID int `json:"steam_id" bson:"steam_id"`
|
||||
Cover string `json:"cover" bson:"cover"`
|
||||
Languages []string `json:"languages" bson:"languages"`
|
||||
Screenshots []string `json:"screenshots" bson:"screenshots"`
|
||||
GameIDs []primitive.ObjectID `json:"game_ids" bson:"games"`
|
||||
Games []*GameItem `json:"game_downloads" bson:"-"`
|
||||
CreatedAt time.Time `json:"created_at" bson:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
|
||||
ID primitive.ObjectID `json:"id" bson:"_id"`
|
||||
Name string `json:"name" bson:"name"`
|
||||
Description string `json:"description" bson:"description"`
|
||||
Aliases []string `json:"aliases" bson:"aliases"`
|
||||
Developers []string `json:"developers" bson:"developers"`
|
||||
Publishers []string `json:"publishers" bson:"publishers"`
|
||||
IGDBID int `json:"igdb_id" bson:"igdb_id"`
|
||||
SteamID int `json:"steam_id" bson:"steam_id"`
|
||||
Cover string `json:"cover" bson:"cover"`
|
||||
Languages []string `json:"languages" bson:"languages"`
|
||||
Screenshots []string `json:"screenshots" bson:"screenshots"`
|
||||
GameIDs []primitive.ObjectID `json:"game_ids" bson:"games"`
|
||||
Games []*GameItem `json:"games" bson:"-"`
|
||||
CreatedAt time.Time `json:"created_at" bson:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at" bson:"updated_at"`
|
||||
InfoUpdatedAt time.Time `json:"info_updated_at" bson:"info_updated_at"`
|
||||
}
|
||||
|
||||
type GameItem struct {
|
||||
|
@ -17,7 +17,7 @@ type GetGameItemByRawNameRequest struct {
|
||||
type GetGameItemByRawNameResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
GameItem []*model.GameItem `json:"game_downloads,omitempty"`
|
||||
GameItem []*model.GameItem `json:"games,omitempty"`
|
||||
}
|
||||
|
||||
// GetGameItemByRawName retrieves game download details by raw name.
|
||||
@ -26,7 +26,7 @@ type GetGameItemByRawNameResponse struct {
|
||||
// @Tags game
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param name path string true "Game Download Raw Name"
|
||||
// @Param name path string true "Game Raw Name"
|
||||
// @Success 200 {object} GetGameItemByRawNameResponse
|
||||
// @Failure 400 {object} GetGameItemByRawNameResponse
|
||||
// @Failure 500 {object} GetGameItemByRawNameResponse
|
||||
|
@ -19,10 +19,10 @@ type GetGameItemsByAuthorResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
TotalPage int `json:"total_page"`
|
||||
GameItems []*model.GameItem `json:"game_downloads,omitempty"`
|
||||
GameItems []*model.GameItem `json:"games,omitempty"`
|
||||
}
|
||||
|
||||
// GetGameItemsByAuthorHandler returns all game downloads by author
|
||||
// GetGameItemsByAuthorHandler returns games by author
|
||||
// @Summary Get game downloads by author
|
||||
// @Description Get game downloads by author
|
||||
// @Tags game
|
||||
|
@ -1,11 +1,12 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"net/http"
|
||||
"pcgamedb/crawler"
|
||||
"pcgamedb/db"
|
||||
"pcgamedb/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type GetPopularGamesResponse struct {
|
||||
@ -20,7 +21,7 @@ type GetPopularGamesResponse struct {
|
||||
// @Tags popular
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param type path string true "Type(igdb-most-visited, igdb-most-wanted-to-play, igdb-most-playing, igdb-most-played, steam-top, steam-week-top, steam-best-of-the-year, steam-most-played)"
|
||||
// @Param type path string true "Type(igdb-most-visited, igdb-most-wanted-to-play, igdb-most-playing, igdb-most-played, steam-top, steam-week-top, steam-month-top, steam-best-of-the-year, steam-most-played)"
|
||||
// @Success 200 {object} GetPopularGamesResponse
|
||||
// @Failure 400 {object} GetPopularGamesResponse
|
||||
// @Failure 500 {object} GetPopularGamesResponse
|
||||
@ -52,6 +53,8 @@ func GetPopularGameInfosHandler(c *gin.Context) {
|
||||
steam250Func = crawler.GetSteam250BestOfTheYear
|
||||
case "steam-most-played":
|
||||
steam250Func = crawler.GetSteam250MostPlayed
|
||||
case "steam-month-top":
|
||||
steam250Func = crawler.GetSteam250MonthTop50
|
||||
default:
|
||||
c.JSON(http.StatusBadRequest, GetPopularGamesResponse{
|
||||
Status: "error",
|
||||
|
@ -17,16 +17,16 @@ type GetUnorganizedGameItemsResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Size int `json:"size,omitempty"`
|
||||
GameItems []*model.GameItem `json:"game_downloads,omitempty"`
|
||||
GameItems []*model.GameItem `json:"games,omitempty"`
|
||||
}
|
||||
|
||||
// GetUnorganizedGameItems retrieves a list of unorganized game downloads.
|
||||
// @Summary List unorganized game downloads
|
||||
// @Description Retrieves game downloads that have not been organized
|
||||
// GetUnorganizedGameItems retrieves a list of unorganized games.
|
||||
// @Summary List unorganized games
|
||||
// @Description Retrieves games that have not been organized
|
||||
// @Tags game
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param num query int false "Number of game downloads to retrieve"
|
||||
// @Param num query int false "Number of games to retrieve"
|
||||
// @Success 200 {object} GetUnorganizedGameItemsResponse
|
||||
// @Failure 400 {object} GetUnorganizedGameItemsResponse
|
||||
// @Failure 500 {object} GetUnorganizedGameItemsResponse
|
||||
|
@ -16,15 +16,14 @@ import (
|
||||
type HealthCheckResponse struct {
|
||||
Version string `json:"version"`
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Date string `json:"date"`
|
||||
Uptime string `json:"uptime"`
|
||||
Alloc string `json:"alloc"`
|
||||
AutoCrawl bool `json:"auto_crawl"`
|
||||
AutoCrawlCron string `json:"auto_crawl_cron,omitempty"`
|
||||
GameItem int64 `json:"game_download,omitempty"`
|
||||
GameInfo int64 `json:"game_info,omitempty"`
|
||||
Unorganized int64 `json:"unorganized,omitempty"`
|
||||
AutoCrawlCron string `json:"auto_crawl_cron"`
|
||||
GameItem int64 `json:"game_num"`
|
||||
GameInfo int64 `json:"game_info_num"`
|
||||
Unorganized int64 `json:"unorganized_game_num"`
|
||||
RedisAvaliable bool `json:"redis_avaliable"`
|
||||
OnlineFixAvaliable bool `json:"online_fix_avaliable"`
|
||||
MegaAvaliable bool `json:"mega_avaliable"`
|
||||
|
@ -1,100 +0,0 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"pcgamedb/crawler"
|
||||
"pcgamedb/db"
|
||||
"pcgamedb/model"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
)
|
||||
|
||||
type UpdateGameInfoRequest struct {
|
||||
GameID string `json:"game_id" binding:"required"`
|
||||
Platform string `json:"platform" binding:"required"`
|
||||
PlatformID int `json:"platform_id" binding:"required"`
|
||||
}
|
||||
|
||||
type UpdateGameInfoResponse struct {
|
||||
Status string `json:"status"`
|
||||
Message string `json:"message"`
|
||||
GameInfo *model.GameInfo `json:"game_info,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateGameInfoHandler updates game information.
|
||||
// @Summary Update game info
|
||||
// @Description Updates details of a game
|
||||
// @Tags game
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param Authorization header string true "Authorization: Bearer <api_key>"
|
||||
// @Param body body handler.UpdateGameInfoRequest true "Update Game Info Request"
|
||||
// @Success 200 {object} handler.UpdateGameInfoResponse
|
||||
// @Failure 400 {object} handler.UpdateGameInfoResponse
|
||||
// @Failure 401 {object} handler.UpdateGameInfoResponse
|
||||
// @Failure 500 {object} handler.UpdateGameInfoResponse
|
||||
// @Router /game/update [post]
|
||||
func UpdateGameInfoHandler(c *gin.Context) {
|
||||
var req UpdateGameInfoRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, UpdateGameInfoResponse{
|
||||
Status: "error",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
req.Platform = strings.ToLower(req.Platform)
|
||||
platformMap := map[string]bool{
|
||||
"steam": true,
|
||||
"igdb": true,
|
||||
}
|
||||
if _, ok := platformMap[req.Platform]; !ok {
|
||||
c.JSON(http.StatusBadRequest, UpdateGameInfoResponse{
|
||||
Status: "error",
|
||||
Message: "Invalid platform",
|
||||
})
|
||||
return
|
||||
}
|
||||
objID, err := primitive.ObjectIDFromHex(req.GameID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, UpdateGameInfoResponse{
|
||||
Status: "error",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
info, err := db.GetGameInfoByID(objID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, UpdateGameInfoResponse{
|
||||
Status: "error",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
newInfo, err := crawler.GenerateGameInfo(req.Platform, req.PlatformID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, UpdateGameInfoResponse{
|
||||
Status: "error",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
newInfo.ID = objID
|
||||
newInfo.GameIDs = info.GameIDs
|
||||
err = db.SaveGameInfo(newInfo)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, UpdateGameInfoResponse{
|
||||
Status: "error",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, UpdateGameInfoResponse{
|
||||
Status: "ok",
|
||||
Message: "Game info updated successfully",
|
||||
GameInfo: newInfo,
|
||||
})
|
||||
}
|
@ -31,7 +31,6 @@ func initRoute(app *gin.Engine) {
|
||||
GameInfoGroup.GET("/name/:name", handler.GetGameInfosByNameHandler)
|
||||
GameInfoGroup.GET("/platform/:platform_type/:platform_id", handler.GetGameInfoByPlatformIDHandler)
|
||||
GameInfoGroup.GET("/id/:id", handler.GetGameInfoByIDHandler)
|
||||
GameInfoGroup.PUT("/update", middleware.Auth(), handler.UpdateGameInfoHandler)
|
||||
GameInfoGroup.DELETE("/id/:id", middleware.Auth(), handler.DeleteGameInfoHandler)
|
||||
|
||||
app.GET("/popular/:type", handler.GetPopularGameInfosHandler)
|
||||
|
@ -30,6 +30,8 @@ func Run() {
|
||||
app.Use(middleware.Recovery())
|
||||
initRoute(app)
|
||||
log.Logger.Info("Server running", zap.String("port", config.Config.Server.Port))
|
||||
|
||||
// Start auto-crawl task
|
||||
if config.Config.Server.AutoCrawl {
|
||||
go func() {
|
||||
c := cron.New()
|
||||
@ -40,6 +42,17 @@ func Run() {
|
||||
c.Start()
|
||||
}()
|
||||
}
|
||||
|
||||
// Start auto-update task
|
||||
go func() {
|
||||
c := cron.New()
|
||||
_, err := c.AddFunc("0 */3 * * *", func() { task.UpdateOutdatedGameInfos(log.TaskLogger) })
|
||||
if err != nil {
|
||||
log.Logger.Error("Error adding cron job", zap.Error(err))
|
||||
}
|
||||
c.Start()
|
||||
}()
|
||||
|
||||
err := app.Run(":" + config.Config.Server.Port)
|
||||
if err != nil {
|
||||
log.Logger.Panic("Failed to run server", zap.Error(err))
|
||||
|
27
task/update_game_info.go
Normal file
27
task/update_game_info.go
Normal file
@ -0,0 +1,27 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"pcgamedb/crawler"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func UpdateOutdatedGameInfos(logger *zap.Logger) {
|
||||
channel, err := crawler.UpdateGameInfo(10)
|
||||
count := 0
|
||||
if err != nil {
|
||||
logger.Error("Failed to update game info", zap.Error(err))
|
||||
return
|
||||
}
|
||||
for info := range channel {
|
||||
logger.Info("Updated game info",
|
||||
zap.String("id", info.ID.String()),
|
||||
zap.String("name", info.Name),
|
||||
)
|
||||
count++
|
||||
if count == 10 {
|
||||
break
|
||||
}
|
||||
}
|
||||
logger.Info("Updated game info count", zap.Int("count", count))
|
||||
}
|
Loading…
Reference in New Issue
Block a user