361 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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) {
tx := sqlite3.GetGormDB().Begin()
defer tx.Rollback()
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 = tx.Delete(&service.SSData{ID: data.ID}).Error
if err != nil {
log.Print(err)
return nil, nil, err
}
err = tx.Create(&newSSData).Error
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 &currentDataLite, &lastDataLite, tx.Commit().Error
}
}
return &currentDataLite, nil, tx.Commit().Error
}
// 辅助函数:检查数据是否有变化
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
}