427 lines
13 KiB
Go
427 lines
13 KiB
Go
package scoresaber
|
||
|
||
import (
|
||
"database/sql"
|
||
"encoding/json"
|
||
"errors"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"strconv"
|
||
"time"
|
||
|
||
"git.lxtend.com/qqbot/sqlite3"
|
||
"git.lxtend.com/qqbot/util"
|
||
_ "github.com/mattn/go-sqlite3"
|
||
)
|
||
|
||
func init() {
|
||
createBindTableSQL := `CREATE TABLE IF NOT EXISTS ssBind (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
qqid TEXT UNIQUE,
|
||
ssid TEXT UNIQUE
|
||
);`
|
||
|
||
createScoreTableSQL := `CREATE TABLE IF NOT EXISTS ssData (
|
||
id TEXT,
|
||
name TEXT,
|
||
country TEXT,
|
||
pp REAL,
|
||
rank INTEGER,
|
||
country_rank INTEGER,
|
||
total_score INTEGER,
|
||
total_ranked_score INTEGER,
|
||
average_ranked_accuracy REAL,
|
||
total_play_count INTEGER,
|
||
ranked_play_count INTEGER,
|
||
replays_watched INTEGER,
|
||
generated_time TEXT
|
||
);`
|
||
|
||
createRecordTableSQL := `CREATE TABLE IF NOT EXISTS ssRecordData (
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
score_id INT,
|
||
ss_id TEXT,
|
||
name TEXT,
|
||
country TEXT,
|
||
song_name VARCHAR(255),
|
||
song_sub_name VARCHAR(255),
|
||
song_author_name VARCHAR(255),
|
||
song_hash VARCHAR(64),
|
||
cover_image TEXT,
|
||
difficulty_raw VARCHAR(100),
|
||
pp REAL,
|
||
stars REAL,
|
||
weight REAL,
|
||
modifiers VARCHAR(255),
|
||
multiplier REAL,
|
||
rank INT,
|
||
bad_cuts INT,
|
||
missed_notes INT,
|
||
max_combo INT,
|
||
score INT,
|
||
max_score INT,
|
||
full_combo BOOLEAN,
|
||
device_hmd VARCHAR(100),
|
||
device_controller_left VARCHAR(100),
|
||
device_controller_right VARCHAR(100),
|
||
generated_time TEXT
|
||
);`
|
||
|
||
sqlite3.TryCreateTable(createBindTableSQL)
|
||
sqlite3.TryCreateTable(createScoreTableSQL)
|
||
sqlite3.TryCreateTable(createRecordTableSQL)
|
||
}
|
||
|
||
var SSQuery = &ssQuery{}
|
||
|
||
type ssQuery struct {
|
||
}
|
||
|
||
func (ss *ssQuery) BindSS(qqId string, ssId string) (reply string) {
|
||
tx, err := sqlite3.GetTran()
|
||
if err != nil {
|
||
log.Print(err)
|
||
}
|
||
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后重试"
|
||
}
|
||
//去重
|
||
if rows, err := tx.Query("SELECT ssid FROM ssBind WHERE qqid = ?", qqId); err == nil {
|
||
if rows.Next() {
|
||
var currentSsId string
|
||
err := rows.Scan(¤tSsId)
|
||
if err != nil {
|
||
log.Print(err)
|
||
}
|
||
rows.Close()
|
||
// 获取当前绑定账号的信息
|
||
if currentData, err := FetchPlayerData(currentSsId); err == nil && currentData.ID != "" {
|
||
return fmt.Sprintf("您已绑定至ss账号:%s,请先输入\"解绑ss\"解绑", currentData.Name)
|
||
}
|
||
return "您已绑定过ss账号,请先输入\"解绑ss\"解绑"
|
||
} else {
|
||
rows.Close()
|
||
}
|
||
}
|
||
// 检查是否已绑定
|
||
if rows, err := tx.Query("SELECT * FROM ssBind WHERE ssid = ?", ssId); err == nil {
|
||
if rows.Next() {
|
||
return "该ss账号已绑定至其他用户"
|
||
}
|
||
rows.Close()
|
||
}
|
||
_, err = tx.Exec("INSERT INTO ssBind(qqid, ssid) VALUES(?, ?)", qqId, ssId)
|
||
if err != nil {
|
||
return "绑定失败"
|
||
}
|
||
err = tx.Commit()
|
||
if err != nil {
|
||
return "无法提交事务"
|
||
}
|
||
return "和用户名为 " + data.Name + " 的用户绑定成功,同时也绑定了您的beatleader账号。输入\"查ss\"查看个人数据"
|
||
}
|
||
|
||
func (ss *ssQuery) UnbindSS(qqId string) (reply string) {
|
||
tx, err := sqlite3.GetTran()
|
||
if err != nil {
|
||
log.Print(err)
|
||
}
|
||
defer tx.Rollback()
|
||
//是否已绑定
|
||
if rows, err := tx.Query("SELECT * FROM ssBind WHERE qqid = ?", qqId); err == nil {
|
||
if !rows.Next() {
|
||
return "您未绑定ss账号,输入\"绑定ss [ssId]\"绑定"
|
||
}
|
||
rows.Close()
|
||
}
|
||
_, err = tx.Exec("DELETE FROM ssBind WHERE qqid = ?", qqId)
|
||
if err != nil {
|
||
return "解绑失败"
|
||
}
|
||
err = tx.Commit()
|
||
if err != nil {
|
||
return "无法提交事务"
|
||
}
|
||
return "解绑成功,重新绑定请输入\"绑定ss [ssId]\""
|
||
}
|
||
|
||
func (ss *ssQuery) GetScore(qqId string) (currentData *PlayerDataLite, lastData *PlayerDataLite, err error) {
|
||
db := sqlite3.GetDB() // 假设 sqlite3.GetDB() 返回 *sqlx.DB
|
||
tx, err := db.Beginx()
|
||
if err != nil {
|
||
log.Print(err)
|
||
return nil, nil, err
|
||
}
|
||
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
|
||
}
|
||
// 构建 PlayerDataLite 结构体
|
||
dataLite := 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"),
|
||
}
|
||
|
||
// 查询最近的玩家数据
|
||
var lastDataLite PlayerDataLite
|
||
err = tx.Get(&lastDataLite, "SELECT * FROM ssData WHERE id = ? ORDER BY generated_time DESC LIMIT 1", dataLite.ID)
|
||
if err != nil && err != sql.ErrNoRows {
|
||
log.Print(err)
|
||
return nil, nil, err
|
||
}
|
||
|
||
// 如果有新的数据,则插入
|
||
if lastDataLite.IsDiffFrom(dataLite) {
|
||
_, err = tx.NamedExec(`INSERT INTO ssData (id, name, country, pp, rank, country_rank, total_score,
|
||
total_ranked_score, average_ranked_accuracy, total_play_count, ranked_play_count, replays_watched, generated_time)
|
||
VALUES (:id, :name, :country, :pp, :rank, :country_rank, :total_score,
|
||
:total_ranked_score, :average_ranked_accuracy, :total_play_count, :ranked_play_count, :replays_watched, :generated_time)`, dataLite)
|
||
if err != nil {
|
||
log.Print(err)
|
||
return nil, nil, err
|
||
}
|
||
|
||
// 提交事务
|
||
err = tx.Commit()
|
||
if err != nil {
|
||
log.Print(err)
|
||
return nil, nil, err
|
||
}
|
||
|
||
// 返回差异信息
|
||
return &dataLite, &lastDataLite, nil
|
||
}
|
||
|
||
// 如果没有新数据,直接提交事务
|
||
err = tx.Commit()
|
||
if err != nil {
|
||
log.Print(err)
|
||
return nil, nil, err
|
||
}
|
||
|
||
// 返回当前数据的字符串表示
|
||
return &dataLite, nil, nil
|
||
}
|
||
|
||
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) {
|
||
db := sqlite3.GetDB() // 假设 sqlite3.GetDB() 返回 *sqlx.DB
|
||
tx, err := db.Beginx()
|
||
if err != nil {
|
||
log.Print(err)
|
||
return
|
||
}
|
||
defer tx.Rollback()
|
||
|
||
// 创建 RecordDataLite 结构体实例
|
||
dataLite := RecordDataLite{
|
||
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,
|
||
SongId: "",
|
||
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().Format("2006-01-02 15:04:05.999999999-07:00"),
|
||
}
|
||
|
||
// 检查设备信息并设置
|
||
if cmdData.Score.DeviceHmd != nil {
|
||
dataLite.DeviceHmd = *cmdData.Score.DeviceHmd
|
||
}
|
||
if cmdData.Score.DeviceControllerLeft != nil {
|
||
dataLite.DeviceControllerLeft = *cmdData.Score.DeviceControllerLeft
|
||
}
|
||
if cmdData.Score.DeviceControllerRight != nil {
|
||
dataLite.DeviceControllerRight = *cmdData.Score.DeviceControllerRight
|
||
}
|
||
|
||
// 使用 NamedExec 插入数据
|
||
_, err = tx.NamedExec(`INSERT INTO ssRecordData
|
||
(score_id, ss_id, name, country, song_name, song_sub_name, song_author_name,
|
||
song_hash, cover_image, difficulty_raw, pp, stars, weight, modifiers,
|
||
multiplier,rank, bad_cuts, missed_notes, max_combo, score, max_score,
|
||
full_combo, device_hmd, device_controller_left, device_controller_right,
|
||
generated_time)
|
||
VALUES (:score_id, :ss_id, :name, :country, :song_name, :song_sub_name,
|
||
:song_author_name, :song_hash, :cover_image, :difficulty_raw, :pp, :stars,
|
||
:weight, :modifiers, :multiplier, :rank,:bad_cuts, :missed_notes, :max_combo,
|
||
:score, :max_score, :full_combo, :device_hmd, :device_controller_left,
|
||
:device_controller_right, :generated_time)`, dataLite)
|
||
if err != nil {
|
||
log.Print(err)
|
||
return
|
||
}
|
||
|
||
// 提交事务
|
||
err = tx.Commit()
|
||
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
|
||
}
|