2025-07-20 04:49:59 +08:00

308 lines
9.4 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 beatleader
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.BLData{}, &service.BLRecordData{})
}
var BLQuery = &blQuery{}
type blQuery struct {
}
func (bl *blQuery) BindBL(qqId string, blId string) (reply string) {
db := sqlite3.GetGormDB()
tx := db.Begin()
defer tx.Rollback()
// blId为数字
if _, isNum := strconv.Atoi(blId); isNum != nil {
return "blId格式错误,应当为一串数字(是您的beatleader主页链接中的末尾数字部分,一般和您的steamID相同)"
}
data, err := FetchPlayerData(blId)
if data == nil {
if err != nil {
return "请求出错,报错如下,如果确定命令没问题可以重新试试:" + err.Error()
}
return "未找到玩家,请检查ID后重试"
}
//去重
if rows, err := tx.Select("ssid").Where("qqid = ?", qqId).Find(&service.SSBind{}).Rows(); err == nil {
if rows.Next() {
var currentBlId string
err := rows.Scan(&currentBlId)
if err != nil {
log.Print(err)
}
rows.Close()
// 获取当前绑定账号的信息
if currentData, err := FetchPlayerData(currentBlId); err == nil && currentData != nil {
return fmt.Sprintf("您已绑定至bl账号%s,请先输入\"解绑bl\"解绑", currentData.Name)
}
return "您已绑定过bl账号,请先输入\"解绑bl\"解绑"
} else {
rows.Close()
}
}
if rows, err := tx.Select("ssid").Where("ssid = ?", blId).Find(&service.SSBind{}).Rows(); err == nil {
if rows.Next() {
return "该bl账号已绑定至其他用户"
}
rows.Close()
}
err = tx.Create(&service.SSBind{QQID: qqId, SSID: blId}).Commit().Error
if err != nil {
return "绑定失败,请稍后重试:" + err.Error()
}
return "和用户名为 " + data.Name + " 的用户绑定成功同时也绑定了您的scoresaber账号。输入\"查bl\"查看个人数据"
}
func (bl *blQuery) UnbindBL(qqId string) (reply string) {
db := sqlite3.GetGormDB()
tx := db.Begin()
defer tx.Rollback()
//是否已绑定
if rows, err := tx.Select("ssid").Where("qqid = ?", qqId).Find(&service.SSBind{}).Rows(); err == nil {
if !rows.Next() {
return "您未绑定bl账号输入\"绑定bl [blId]\"绑定"
}
rows.Close()
}
err := tx.Delete(&service.SSBind{QQID: qqId}).Commit().Error
if err != nil {
return "解绑失败"
}
return "解绑成功,重新绑定请输入\"绑定bl [blId]\""
}
func (bl *blQuery) GetScore(qqId string) (currentData *PlayerDataLite, lastData *PlayerDataLite, err error) {
db := sqlite3.GetGormDB()
tx := db.Begin()
defer tx.Rollback()
blId, err := getBLID(qqId)
if err != nil {
return nil, nil, err
}
// 查询玩家数据
data, err := FetchPlayerData(blId)
if data == nil && err == nil {
return nil, nil, errors.New("查询出错,服务器返回了空数据")
}
if err != nil {
log.Print(err)
return nil, nil, err
}
// 构建 PlayerDataLite 结构体
dataLite := data.ToDataLite()
newBLData := service.BLData{
ID: dataLite.ID,
Name: dataLite.Name,
Country: dataLite.Country,
PP: dataLite.PP,
Rank: dataLite.Rank,
CountryRank: dataLite.CountryRank,
TotalScore: dataLite.TotalScore,
TotalRankedScore: dataLite.TotalRankedScore,
AverageRankedAccuracy: dataLite.AverageRankedAccuracy,
TotalPlayCount: dataLite.TotalPlayCount,
RankedPlayCount: dataLite.RankedPlayCount,
ReplaysWatched: dataLite.ReplaysWatched,
GeneratedTime: time.Now(),
}
// 查询最近的玩家数据
lastBLData, err := service.GetLatestBLData(dataLite.ID)
if err != nil {
log.Print(err)
return nil, nil, err
}
currentDataLite := PlayerDataLite{
ID: dataLite.ID,
Name: dataLite.Name,
Avatar: dataLite.Avatar,
Country: dataLite.Country,
PP: dataLite.PP,
Rank: dataLite.Rank,
CountryRank: dataLite.CountryRank,
TotalScore: dataLite.TotalScore,
TotalRankedScore: dataLite.TotalRankedScore,
AverageRankedAccuracy: dataLite.AverageRankedAccuracy,
TotalPlayCount: dataLite.TotalPlayCount,
RankedPlayCount: dataLite.RankedPlayCount,
ReplaysWatched: dataLite.ReplaysWatched,
GeneratedTime: time.Now().Format("2006-01-02 15:04:05.999999999-07:00"),
}
if lastBLData == nil || hasDataChanged(lastBLData, &newBLData) {
err = tx.Create(&newBLData).Error
if err != nil {
log.Print(err)
return nil, nil, err
}
err = tx.Delete(&service.BLData{ID: dataLite.ID}).Error
if err != nil {
log.Print(err)
return nil, nil, err
}
}
// 如果有新的数据,则插入
if lastBLData == nil || hasDataChanged(lastBLData, &newBLData) {
//删掉旧数据
err = tx.Delete(&PlayerDataLite{ID: dataLite.ID}).Error
if err != nil {
log.Print(err)
return nil, nil, err
}
err = tx.Create(&newBLData).Error
if err != nil {
log.Print(err)
return nil, nil, err
}
if lastBLData != nil {
lastDataLite := PlayerDataLite{
ID: lastBLData.ID,
Name: lastBLData.Name,
Country: lastBLData.Country,
PP: lastBLData.PP,
Rank: lastBLData.Rank,
CountryRank: lastBLData.CountryRank,
TotalScore: lastBLData.TotalScore,
TotalRankedScore: lastBLData.TotalRankedScore,
AverageRankedAccuracy: lastBLData.AverageRankedAccuracy,
TotalPlayCount: lastBLData.TotalPlayCount,
RankedPlayCount: lastBLData.RankedPlayCount,
ReplaysWatched: lastBLData.ReplaysWatched,
GeneratedTime: lastBLData.GeneratedTime.Format("2006-01-02 15:04:05.999999999-07:00"),
}
// 返回差异信息
return &currentDataLite, &lastDataLite, tx.Commit().Error
}
}
return &dataLite, nil, tx.Commit().Error
}
func hasDataChanged(old *service.BLData, new *service.BLData) bool {
return old.PP != new.PP || old.Rank != new.Rank || old.CountryRank != new.CountryRank ||
old.TotalScore != new.TotalScore || old.TotalRankedScore != new.TotalRankedScore
}
func (bl *blQuery) GetScoreWithoutUpdate(qqId string) (currentData *PlayerDataLite, err error) {
blId, err := getBLID(qqId)
if err != nil {
return nil, err
}
// 查询玩家数据
data, err := FetchPlayerData(blId)
if err != nil {
return nil, errors.New("查询出错,报错如下" + err.Error())
}
if data.ID == "" {
return nil, errors.New("未找到玩家,请检查ID后重试")
}
dataLite := data.ToDataLite()
// 返回当前数据的字符串表示
return &dataLite, nil
}
func (bl *blQuery) GetRecentScores(count int, qqId string) ([]RecordDataLite, error) {
// 查询绑定的 blId
blId, err := getBLID(qqId)
if err != nil {
return nil, err
}
// return records, nil
playerData, err := FetchPlayerData(blId)
if err != nil {
return nil, err
}
//从线上获取
historyUrl := "https://api.beatleader.xyz/player/%s/scores?leaderboardContext=general&page=1&sortBy=date&order=desc&includeIO=true&count=%d"
fullUrl := fmt.Sprintf(historyUrl, blId, count)
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
}
var response struct {
Data []ScoreData `json:"data"`
}
err = json.Unmarshal(body, &response)
if err != nil {
return nil, err
}
scores := response.Data
records := make([]RecordDataLite, 0)
for _, score := range scores {
dataLite := RecordDataLite{
ScoreID: score.ID,
BlID: blId,
Name: playerData.Name,
Country: playerData.Country,
SongName: score.Leaderboard.Song.Name,
SongSubName: score.Leaderboard.Song.SubName,
SongAuthorName: score.Leaderboard.Song.Author,
SongHash: score.Leaderboard.Song.Hash,
SongId: "",
CoverImage: score.Leaderboard.Song.CoverImage,
DifficultyRaw: score.Leaderboard.Difficulty.DifficultyName,
PP: score.Pp,
Stars: 0,
Weight: score.Weight,
Modifiers: score.Modifiers,
Multiplier: float64(score.ModifiedScore) / float64(score.BaseScore),
BadCuts: score.BadCuts,
Score: score.ModifiedScore,
Rank: score.Rank,
MaxScore: int(float64(score.ModifiedScore) / score.Accuracy),
FullCombo: score.FullCombo,
DeviceHmd: GetHMDStr(score.HMD),
DeviceControllerLeft: GetControllerStr(score.Controller),
DeviceControllerRight: GetControllerStr(score.Controller),
GeneratedTime: time.Unix(score.Timepost, 0).Format("2006-01-02 15:04:05.999999999-07:00"),
}
if score.Leaderboard.Difficulty.Stars != nil {
dataLite.Stars = *score.Leaderboard.Difficulty.Stars
}
records = append(records, dataLite)
}
// 获取歌曲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 := 0; i < len(records); i++ {
records[i].SongId = hashToSongId[records[i].SongHash]
}
return records, nil
}