2024-09-24 06:17:11 -04:00
|
|
|
package crawler
|
|
|
|
|
|
|
|
import (
|
|
|
|
"crypto/tls"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"net/url"
|
|
|
|
"regexp"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2024-11-15 02:02:45 -05:00
|
|
|
"github.com/nitezs/pcgamedb/db"
|
|
|
|
"github.com/nitezs/pcgamedb/model"
|
|
|
|
"github.com/nitezs/pcgamedb/utils"
|
|
|
|
|
2024-09-24 06:17:11 -04:00
|
|
|
"github.com/jlaffaye/ftp"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
ftpAddress = "72.21.17.26:13017"
|
|
|
|
ftpUsername = "ARMGDDNGames"
|
|
|
|
ftpPassword = "ARMGDDNGames"
|
|
|
|
)
|
|
|
|
|
|
|
|
type GameData struct {
|
|
|
|
NumberOfGame string `json:"Number of game"`
|
|
|
|
AppID string `json:"appid"`
|
|
|
|
FolderName string `json:"foldername"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type ARMGDDNCrawler struct {
|
|
|
|
logger zap.Logger
|
|
|
|
conn *ftp.ServerConn
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deprecated: ARMGDDN has changed resource distribution method
|
|
|
|
func NewARMGDDNCrawler(logger *zap.Logger) *ARMGDDNCrawler {
|
|
|
|
return &ARMGDDNCrawler{
|
|
|
|
logger: *logger,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ARMGDDNCrawler) connectFTP() error {
|
|
|
|
var err error
|
|
|
|
tlsConfig := &tls.Config{InsecureSkipVerify: true}
|
|
|
|
c.conn, err = ftp.Dial(ftpAddress, ftp.DialWithTimeout(5*time.Second), ftp.DialWithExplicitTLS(tlsConfig))
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err = c.conn.Login(ftpUsername, ftpPassword); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *ARMGDDNCrawler) fetchAndParseFTPData(filePath string) ([]GameData, error) {
|
|
|
|
r, err := c.conn.Retr(filePath)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer r.Close()
|
|
|
|
|
|
|
|
buf, err := io.ReadAll(r)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var data []GameData
|
|
|
|
if err = json.Unmarshal(buf, &data); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return data, nil
|
|
|
|
}
|
|
|
|
|
2024-11-16 00:48:48 -05:00
|
|
|
func (c *ARMGDDNCrawler) crawlGames(data []GameData, platform string, num int) ([]*model.GameItem, error) {
|
2024-09-24 06:17:11 -04:00
|
|
|
count := 0
|
2024-11-16 00:48:48 -05:00
|
|
|
var res []*model.GameItem
|
2024-09-24 06:17:11 -04:00
|
|
|
modTimeMap := make(map[string]time.Time)
|
|
|
|
entries, err := c.conn.List(fmt.Sprintf("/%s", platform))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
for _, entry := range entries {
|
|
|
|
if entry.Type == ftp.EntryTypeFolder {
|
|
|
|
modTimeMap[entry.Name] = entry.Time
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, v := range data {
|
|
|
|
if count == num {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
path := fmt.Sprintf("/%s/%s", platform, v.FolderName)
|
|
|
|
u := fmt.Sprintf("ARMGDDNGames/%s/%s", platform, v.NumberOfGame)
|
|
|
|
modTime, ok := modTimeMap[v.FolderName]
|
|
|
|
if !ok {
|
|
|
|
c.logger.Warn("mod time not found", zap.String("url", u))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
updateFlag := fmt.Sprintf("ARMGDDNGames/%s/%s/%s", platform, v.NumberOfGame, modTime.UTC().String())
|
|
|
|
if db.IsARMGDDNCrawled(updateFlag) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
c.logger.Info("Crawling", zap.String("url", u))
|
|
|
|
walker := c.conn.Walk(path)
|
2024-11-18 07:21:08 -05:00
|
|
|
size := uint64(0)
|
2024-09-24 06:17:11 -04:00
|
|
|
for walker.Next() {
|
|
|
|
if walker.Stat().Type == ftp.EntryTypeFile {
|
|
|
|
fileSize, err := c.conn.FileSize(walker.Path())
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Warn("file size error", zap.Error(err))
|
|
|
|
break
|
|
|
|
}
|
2024-11-18 07:21:08 -05:00
|
|
|
size += uint64(fileSize)
|
2024-09-24 06:17:11 -04:00
|
|
|
}
|
|
|
|
}
|
2024-11-16 00:48:48 -05:00
|
|
|
item, err := db.GetGameItemByUrl(u)
|
2024-09-24 06:17:11 -04:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
item.Url = u
|
|
|
|
item.Name = ARMGDDNFormatter(v.FolderName)
|
|
|
|
item.UpdateFlag = updateFlag
|
2024-11-18 07:21:08 -05:00
|
|
|
item.Size = utils.BytesToSize(size)
|
2024-09-24 06:17:11 -04:00
|
|
|
item.RawName = v.FolderName
|
|
|
|
item.Author = "ARMGDDN"
|
|
|
|
item.Download = fmt.Sprintf("ftpes://%s:%s@%s/%s/%s", ftpUsername, ftpPassword, ftpAddress, platform, url.QueryEscape(v.FolderName))
|
2024-11-16 00:48:48 -05:00
|
|
|
if err := db.SaveGameItem(item); err != nil {
|
2024-09-24 06:17:11 -04:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
res = append(res, item)
|
|
|
|
count++
|
|
|
|
var id int
|
|
|
|
var info *model.GameInfo
|
|
|
|
if v.AppID != "NONSTEAM" {
|
|
|
|
id, err = strconv.Atoi(v.AppID)
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Warn("strconv error", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
2024-11-16 00:48:48 -05:00
|
|
|
info, err = OrganizeGameItemWithSteam(id, item)
|
2024-09-24 06:17:11 -04:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
} else {
|
2024-11-16 00:48:48 -05:00
|
|
|
info, err = OrganizeGameItem(item)
|
2024-09-24 06:17:11 -04:00
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
err = db.SaveGameInfo(info)
|
|
|
|
if err != nil {
|
|
|
|
c.logger.Warn("save game info error", zap.Error(err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ARMGDDNFormatter(name string) string {
|
|
|
|
cleanedName := strings.ReplaceAll(strings.TrimSpace(name), "-ARMGDDN", "")
|
|
|
|
matchIndex := regexp.MustCompile(`v\d`).FindStringIndex(cleanedName)
|
|
|
|
if matchIndex == nil {
|
|
|
|
return cleanedName
|
|
|
|
}
|
|
|
|
return strings.TrimSpace(cleanedName[:matchIndex[0]])
|
|
|
|
}
|
|
|
|
|
2024-11-16 00:48:48 -05:00
|
|
|
func (c *ARMGDDNCrawler) CrawlPC(num int) ([]*model.GameItem, error) {
|
2024-09-24 06:17:11 -04:00
|
|
|
return c.crawlPlatform("/PC/currentserverPC-FTP.json", "PC", num)
|
|
|
|
}
|
|
|
|
|
2024-11-16 00:48:48 -05:00
|
|
|
func (c *ARMGDDNCrawler) CrawlPCVR(num int) ([]*model.GameItem, error) {
|
2024-09-24 06:17:11 -04:00
|
|
|
return c.crawlPlatform("/PCVR/currentserverPCVR-FTP.json", "PCVR", num)
|
|
|
|
}
|
|
|
|
|
2024-11-16 00:48:48 -05:00
|
|
|
func (c *ARMGDDNCrawler) Crawl(num int) ([]*model.GameItem, error) {
|
2024-09-24 06:17:11 -04:00
|
|
|
num1 := num / 2
|
|
|
|
num2 := num - num1
|
|
|
|
if num == -1 {
|
|
|
|
num1 = -1
|
|
|
|
num2 = -1
|
|
|
|
}
|
|
|
|
res1, err := c.CrawlPC(num1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
res2, err := c.CrawlPCVR(num2)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return append(res1, res2...), nil
|
|
|
|
}
|
|
|
|
|
2024-11-16 00:48:48 -05:00
|
|
|
func (c *ARMGDDNCrawler) CrawlAll() ([]*model.GameItem, error) {
|
2024-09-24 06:17:11 -04:00
|
|
|
return c.Crawl(-1)
|
|
|
|
}
|
|
|
|
|
2024-11-16 00:48:48 -05:00
|
|
|
func (c *ARMGDDNCrawler) crawlPlatform(jsonFile, platform string, num int) ([]*model.GameItem, error) {
|
2024-09-24 06:17:11 -04:00
|
|
|
err := c.connectFTP()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer func() { _ = c.conn.Quit() }()
|
|
|
|
|
|
|
|
data, err := c.fetchAndParseFTPData(jsonFile)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return c.crawlGames(data, platform, num)
|
|
|
|
}
|