package scoresaber import ( "encoding/json" "errors" "fmt" "io" "log" "net/http" "strconv" "time" "git.lxtend.com/lixiangwuxian/qqbot/service" "git.lxtend.com/lixiangwuxian/qqbot/sqlite3" "git.lxtend.com/lixiangwuxian/qqbot/util" _ "github.com/mattn/go-sqlite3" ) func init() { // 使用GORM自动迁移替代手写SQL sqlite3.AutoMigrate(&service.SSBind{}, &service.SSData{}, &service.SSRecordData{}) } var SSQuery = &ssQuery{} type ssQuery struct { } func (ss *ssQuery) BindSS(qqId string, ssId string) (reply string) { tx := sqlite3.GetGormDB().Begin() defer tx.Rollback() // ssId为数字 if _, isNum := strconv.Atoi(ssId); isNum != nil { return "ssId格式错误,应当为一串数字(是您的scoresaber主页链接中的末尾数字部分,一般和您的steamID相同)" } data, err := FetchPlayerData(ssId) if data.ID == "" { if err != nil { return "请求出错,报错如下,如果确定命令没问题可以重新试试:" + err.Error() } return "未找到玩家,请检查ID后重试" } // 检查QQ是否已绑定 exists, currentSsId, err := service.CheckSSBindExists(qqId) if err != nil { log.Print(err) return "检查绑定状态失败" } if exists { // 获取当前绑定账号的信息 if currentData, err := FetchPlayerData(currentSsId); err == nil && currentData.ID != "" { return fmt.Sprintf("您已绑定至ss账号:%s,请先输入\"解绑ss\"解绑", currentData.Name) } return "您已绑定过ss账号,请先输入\"解绑ss\"解绑" } // 检查SSID是否已绑定 ssidExists, err := service.CheckSSIDExists(ssId) if err != nil { log.Print(err) return "检查SSID绑定状态失败" } if ssidExists { return "该ss账号已绑定至其他用户" } // 创建绑定 err = service.CreateSSBind(qqId, ssId) if err != nil { return "绑定失败" } return "和用户名为 " + data.Name + " 的用户绑定成功,同时也绑定了您的beatleader账号。输入\"查ss\"查看个人数据" } func (ss *ssQuery) UnbindSS(qqId string) (reply string) { // 检查是否已绑定 exists, _, err := service.CheckSSBindExists(qqId) if err != nil { log.Print(err) return "检查绑定状态失败" } if !exists { return "您未绑定ss账号,输入\"绑定ss [ssId]\"绑定" } // 删除绑定 err = service.DeleteSSBind(qqId) if err != nil { return "解绑失败" } return "解绑成功,重新绑定请输入\"绑定ss [ssId]\"" } func (ss *ssQuery) GetScore(qqId string) (currentData *PlayerDataLite, lastData *PlayerDataLite, err error) { ssId, err := GetSSID(qqId) if err != nil { return nil, nil, err } // 查询玩家数据 data, err := FetchPlayerData(ssId) if data == nil && err == nil { return nil, nil, errors.New("查询出错,服务器返回了空数据") } if err != nil { log.Print(err) return nil, nil, err } // 构建新的数据结构 newSSData := service.SSData{ ID: data.ID, Name: data.Name, Country: data.Country, PP: data.PP, Rank: data.Rank, CountryRank: data.CountryRank, TotalScore: int64(data.ScoreStats.TotalScore), TotalRankedScore: int64(data.ScoreStats.TotalRankedScore), AverageRankedAccuracy: data.ScoreStats.AverageRankedAccuracy, TotalPlayCount: data.ScoreStats.TotalPlayCount, RankedPlayCount: data.ScoreStats.RankedPlayCount, ReplaysWatched: data.ScoreStats.ReplaysWatched, GeneratedTime: time.Now(), } // 查询最近的玩家数据 lastSSData, err := service.GetLatestSSData(data.ID) if err != nil { log.Print(err) return nil, nil, err } // 构建返回的PlayerDataLite结构 currentDataLite := PlayerDataLite{ ID: data.ID, Name: data.Name, ProfilePicture: data.ProfilePicture, Country: data.Country, PP: data.PP, Rank: data.Rank, CountryRank: data.CountryRank, TotalScore: data.ScoreStats.TotalScore, TotalRankedScore: data.ScoreStats.TotalRankedScore, AverageRankedAccuracy: data.ScoreStats.AverageRankedAccuracy, TotalPlayCount: data.ScoreStats.TotalPlayCount, RankedPlayCount: data.ScoreStats.RankedPlayCount, ReplaysWatched: data.ScoreStats.ReplaysWatched, GeneratedTime: time.Now().Format("2006-01-02 15:04:05.999999999-07:00"), } // 如果有新的数据且与上次不同,则插入 if lastSSData == nil || hasDataChanged(lastSSData, &newSSData) { err = service.CreateSSData(newSSData) if err != nil { log.Print(err) return nil, nil, err } // 如果有历史数据,构建历史数据的PlayerDataLite if lastSSData != nil { lastDataLite := PlayerDataLite{ ID: lastSSData.ID, Name: lastSSData.Name, Country: lastSSData.Country, PP: lastSSData.PP, Rank: lastSSData.Rank, CountryRank: lastSSData.CountryRank, TotalScore: int(lastSSData.TotalScore), TotalRankedScore: int(lastSSData.TotalRankedScore), AverageRankedAccuracy: lastSSData.AverageRankedAccuracy, TotalPlayCount: lastSSData.TotalPlayCount, RankedPlayCount: lastSSData.RankedPlayCount, ReplaysWatched: lastSSData.ReplaysWatched, GeneratedTime: lastSSData.GeneratedTime.Format("2006-01-02 15:04:05.999999999-07:00"), } return ¤tDataLite, &lastDataLite, nil } return ¤tDataLite, nil, nil } return ¤tDataLite, nil, nil } // 辅助函数:检查数据是否有变化 func hasDataChanged(old *service.SSData, new *service.SSData) bool { return old.PP != new.PP || old.Rank != new.Rank || old.CountryRank != new.CountryRank || old.TotalScore != new.TotalScore || old.TotalRankedScore != new.TotalRankedScore } func (ss *ssQuery) GetScoreWithoutUpdate(qqId string) (currentData *PlayerDataLite, err error) { // 查询玩家数据 ssId, err := GetSSID(qqId) if err != nil { return nil, err } data, err := FetchPlayerData(ssId) if err != nil { return nil, errors.New("查询出错,报错如下" + err.Error()) } if data.ID == "" { return nil, errors.New("未找到玩家,请检查ID后重试") } dataLite := data.ToDataLite() // 返回当前数据的字符串表示 return &dataLite, nil } func (ss *ssQuery) SaveRecord(cmdData CommandData) { // 创建 SSRecordData 结构体实例 recordData := service.SSRecordData{ ScoreID: cmdData.Score.ID, SSID: cmdData.Score.LeaderboardPlayerInfo.ID, Name: cmdData.Score.LeaderboardPlayerInfo.Name, Country: cmdData.Score.LeaderboardPlayerInfo.Country, SongName: cmdData.Leaderboard.SongName, SongSubName: cmdData.Leaderboard.SongSubName, SongAuthorName: cmdData.Leaderboard.SongAuthorName, SongHash: cmdData.Leaderboard.SongHash, CoverImage: cmdData.Leaderboard.CoverImage, DifficultyRaw: cmdData.Leaderboard.Difficulty.DifficultyRaw, Stars: cmdData.Leaderboard.Stars, PP: cmdData.Score.Pp, Weight: cmdData.Score.Weight, Modifiers: cmdData.Score.Modifiers, Multiplier: cmdData.Score.Multiplier, Rank: cmdData.Score.Rank, BadCuts: cmdData.Score.BadCuts, Score: cmdData.Score.ModifiedScore, MaxScore: cmdData.Leaderboard.MaxScore, MissedNotes: cmdData.Score.MissedNotes, MaxCombo: cmdData.Score.MaxCombo, FullCombo: cmdData.Score.FullCombo, DeviceHmd: "", DeviceControllerLeft: "", DeviceControllerRight: "", GeneratedTime: time.Now(), } // 检查设备信息并设置 if cmdData.Score.DeviceHmd != nil { recordData.DeviceHmd = *cmdData.Score.DeviceHmd } if cmdData.Score.DeviceControllerLeft != nil { recordData.DeviceControllerLeft = *cmdData.Score.DeviceControllerLeft } if cmdData.Score.DeviceControllerRight != nil { recordData.DeviceControllerRight = *cmdData.Score.DeviceControllerRight } // 使用GORM保存记录 err := service.CreateSSRecordData(recordData) if err != nil { log.Print(err) } } func (ss *ssQuery) GetRecentScores(count int, qqId string) ([]RecordDataLite, error) { ssId, err := GetSSID(qqId) if err != nil { return nil, err } playerData, err := FetchPlayerData(ssId) if err != nil { return nil, err } //一页8条,超过8条则取两页 historyUrl := "https://scoresaber.com/api/player/%s/scores?page=%d&sort=recent" var response struct { Data []struct { Score Score `json:"score"` Leaderboard Leaderboard `json:"leaderboard"` } `json:"playerScores"` } records := make([]RecordDataLite, 0) for i := range (count-1)/8 + 1 { fullUrl := fmt.Sprintf(historyUrl, ssId, i+1) resp, err := http.Get(fullUrl) if err != nil { return nil, err } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, err } err = json.Unmarshal(body, &response) if err != nil { return nil, err } scores := response.Data for _, score := range scores { if len(records) >= count { break } record := RecordDataLite{ ScoreID: score.Score.ID, SsID: ssId, Name: playerData.Name, Country: playerData.Country, SongName: score.Leaderboard.SongName, SongSubName: score.Leaderboard.SongSubName, SongAuthorName: score.Leaderboard.SongAuthorName, SongHash: score.Leaderboard.SongHash, SongId: "", CoverImage: score.Leaderboard.CoverImage, DifficultyRaw: score.Leaderboard.Difficulty.DifficultyRaw, Stars: score.Leaderboard.Stars, PP: score.Score.Pp, Weight: score.Score.Weight, Modifiers: score.Score.Modifiers, Multiplier: score.Score.Multiplier, Rank: score.Score.Rank, BadCuts: score.Score.BadCuts, Score: score.Score.ModifiedScore, MaxScore: score.Leaderboard.MaxScore, FullCombo: score.Score.FullCombo, DeviceHmd: "", DeviceControllerLeft: "", DeviceControllerRight: "", GeneratedTime: score.Score.TimeSet.Format("2006-01-02 15:04:05.999999999-07:00"), } // 检查设备信息并设置 if score.Score.DeviceHmd != nil { record.DeviceHmd = *score.Score.DeviceHmd } if score.Score.DeviceControllerLeft != nil { record.DeviceControllerLeft = *score.Score.DeviceControllerLeft } if score.Score.DeviceControllerRight != nil { record.DeviceControllerRight = *score.Score.DeviceControllerRight } records = append(records, record) } } // 获取歌曲ID hashs := make([]string, 0) for _, record := range records { hashs = append(hashs, record.SongHash) } hashToSongId, err := util.GetSongIdsByHash(hashs) if err != nil { return nil, err } for i := range records { records[i].SongId = hashToSongId[records[i].SongHash] } return records, nil }