From f5e9b74c5a137363571e71783c874bcab7902e4c Mon Sep 17 00:00:00 2001 From: lixiangwuxian Date: Wed, 9 Oct 2024 00:50:42 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=9F=A5=E6=88=90?= =?UTF-8?q?=E7=BB=A9=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- handler/scoresaber/score.go | 68 ++++++++++++++--- service/scoresaber/bind_ss.go | 137 ++++++++++++++++++++++++++++++++-- service/scoresaber/hot.go | 20 +++-- service/scoresaber/model.go | 109 ++++++++++++++++++++------- 4 files changed, 280 insertions(+), 54 deletions(-) diff --git a/handler/scoresaber/score.go b/handler/scoresaber/score.go index 1f2c167..bc550d2 100644 --- a/handler/scoresaber/score.go +++ b/handler/scoresaber/score.go @@ -8,19 +8,17 @@ import ( "git.lxtend.com/qqbot/service/scoresaber" ) -var scoresaberInstance *scoresaber.SSQuery - func init() { - scoresaberInstance = scoresaber.NewSSQuery() handler.RegisterHandler("查ss", getScore) handler.RegisterHandler("绑定ss", bindSS) handler.RegisterHandler("解绑ss", unbindSS) - handler.RegisterHandler("最新ss", getRecentScore) + handler.RegisterHandler("最新ss", getMyRecentScore) + handler.RegisterHandler("最热ss", getRecentScore) } func getScore(msg model.Message) (reply model.Reply) { return model.Reply{ - ReplyMsg: scoresaberInstance.GetScore(strconv.Itoa(int(msg.UserId))), + ReplyMsg: scoresaber.SSQuery.GetScore(strconv.Itoa(int(msg.UserId))), ReferOriginMsg: true, FromMsg: msg, } @@ -28,7 +26,7 @@ func getScore(msg model.Message) (reply model.Reply) { func bindSS(msg model.Message) (reply model.Reply) { return model.Reply{ - ReplyMsg: scoresaberInstance.BindSS(strconv.Itoa(int(msg.UserId)), msg.Msg[len("绑定ss "):]), + ReplyMsg: scoresaber.SSQuery.BindSS(strconv.Itoa(int(msg.UserId)), msg.Msg[len("绑定ss "):]), ReferOriginMsg: true, FromMsg: msg, } @@ -36,7 +34,7 @@ func bindSS(msg model.Message) (reply model.Reply) { func unbindSS(msg model.Message) (reply model.Reply) { return model.Reply{ - ReplyMsg: scoresaberInstance.UnbindSS(strconv.Itoa(int(msg.UserId))), + ReplyMsg: scoresaber.SSQuery.UnbindSS(strconv.Itoa(int(msg.UserId))), ReferOriginMsg: true, FromMsg: msg, } @@ -44,10 +42,10 @@ func unbindSS(msg model.Message) (reply model.Reply) { func getRecentScore(msg model.Message) (reply model.Reply) { count := 1 - if len(msg.Msg) > len("最新ss ") { + if len(msg.Msg) > len("最热ss ") { var err error - count, err = strconv.Atoi(msg.Msg[len("最新ss "):]) - if err != nil { + count, err = strconv.Atoi(msg.Msg[len("最热ss "):]) + if err != nil || count <= 0 { return model.Reply{ ReplyMsg: "", ReferOriginMsg: true, @@ -57,7 +55,10 @@ func getRecentScore(msg model.Message) (reply model.Reply) { } scoreMsg := "" for _, v := range scoresaber.ScoresManager.GetRecentScores(count) { - scoreMsg += v.ToString() + "\n" + scoreMsg += v.ToString() + "\n\n" + } + if len(scoreMsg) > 0 { + scoreMsg = scoreMsg[:len(scoreMsg)-len("\n\n")] } return model.Reply{ ReplyMsg: scoreMsg, @@ -65,3 +66,48 @@ func getRecentScore(msg model.Message) (reply model.Reply) { FromMsg: msg, } } + +func getMyRecentScore(msg model.Message) (reply model.Reply) { + count := 1 + scoreMsg := "" + if len(msg.Msg) > len("最新ss ") { + var err error + count, err = strconv.Atoi(msg.Msg[len("最新ss "):]) + if err != nil || count <= 0 { + return model.Reply{ + ReplyMsg: "", + ReferOriginMsg: true, + FromMsg: msg, + } + } + } + var userName string + recordCount := 0 + records, err := scoresaber.SSQuery.GetRecentScores(count, strconv.Itoa(int(msg.UserId))) + if err != nil { + return model.Reply{ + ReplyMsg: err.Error(), + ReferOriginMsg: true, + FromMsg: msg, + } + } + for _, v := range records { + scoreMsg += v.ToString() + "\n\n" + userName = v.Name + recordCount++ + } + if len(scoreMsg) > 0 { + scoreMsg = scoreMsg[:len(scoreMsg)-len("\n\n")] //去掉最后一个换行符 + } else { + return model.Reply{ + ReplyMsg: "无最近游戏记录", + ReferOriginMsg: true, + FromMsg: msg, + } + } + return model.Reply{ + ReplyMsg: "玩家 " + userName + " 的" + strconv.Itoa(recordCount) + "条最近记录为:\n" + scoreMsg, + ReferOriginMsg: true, + FromMsg: msg, + } +} diff --git a/service/scoresaber/bind_ss.go b/service/scoresaber/bind_ss.go index 401591e..818f392 100644 --- a/service/scoresaber/bind_ss.go +++ b/service/scoresaber/bind_ss.go @@ -2,6 +2,7 @@ package scoresaber import ( "database/sql" + "errors" "log" "strconv" "time" @@ -38,6 +39,34 @@ func initDB() { 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, + bad_cuts INT, + missed_notes INT, + max_combo INT, + score INT, + full_combo BOOLEAN, + device_hmd VARCHAR(100), + device_controller_left VARCHAR(100), + device_controller_right VARCHAR(100), + generated_time TEXT + );` + _, err = db.Exec(createBindTableSQL) if err != nil { log.Fatal(err) @@ -46,22 +75,28 @@ func initDB() { if err != nil { log.Fatal(err) } + _, err = db.Exec(createRecordTableSQL) + if err != nil { + log.Fatal(err) + } } -type SSQuery struct { +var SSQuery = &ssQuery{} + +type ssQuery struct { db *sql.DB } -func NewSSQuery() *SSQuery { +func init() { initDB() db, err := sql.Open("sqlite3", "./bindss.db") if err != nil { log.Fatal(err) } - return &SSQuery{db: db} + SSQuery = &ssQuery{db: db} } -func (ss *SSQuery) BindSS(qqId string, ssId string) (reply string) { +func (ss *ssQuery) BindSS(qqId string, ssId string) (reply string) { tx, err := ss.db.Begin() if err != nil { log.Fatal(err) @@ -93,7 +128,7 @@ func (ss *SSQuery) BindSS(qqId string, ssId string) (reply string) { return "和用户名为 " + data.Name + " 的用户绑定成功,输入\"查ss\"查看个人数据" } -func (ss *SSQuery) UnbindSS(qqId string) (reply string) { +func (ss *ssQuery) UnbindSS(qqId string) (reply string) { tx, err := ss.db.Begin() if err != nil { log.Fatal(err) @@ -117,7 +152,7 @@ func (ss *SSQuery) UnbindSS(qqId string) (reply string) { return "解绑成功,重新绑定请输入\"绑定ss [ssId]\"" } -func (ss *SSQuery) GetScore(qqId string) (reply string) { +func (ss *ssQuery) GetScore(qqId string) (reply string) { tx, err := ss.db.Begin() if err != nil { log.Fatal(err) @@ -156,13 +191,99 @@ func (ss *SSQuery) GetScore(qqId string) (reply string) { tx.Exec("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(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13)", dataLite.ID, dataLite.Name, dataLite.Country, dataLite.PP, dataLite.Rank, dataLite.CountryRank, dataLite.TotalScore, dataLite.TotalRankedScore, dataLite.AverageRankedAccuracy, dataLite.TotalPlayCount, dataLite.RankedPlayCount, dataLite.ReplaysWatched, dataLite.GeneratedTime) err = tx.Commit() if err != nil { - return "无法提交事务" + return "SQL事务提交失败,请重试" } return data.LastDiffToString(lastDataLite) } err = tx.Commit() if err != nil { - return "无法提交事务" + return "SQL事务提交失败,请重试" } return data.ToString() } + +func (ss *ssQuery) SaveRecord(cmdData CommandData) { + tx, err := ss.db.Begin() + if err != nil { + log.Fatal(err) + } + defer tx.Rollback() + 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, + 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, + BadCuts: cmdData.Score.BadCuts, + Score: cmdData.Score.ModifiedScore, + 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 + } + if _, err = tx.Exec("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, bad_cuts, missed_notes, max_combo,score, full_combo, device_hmd, device_controller_left, device_controller_right, generated_time) VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23,?24)", dataLite.ScoreID, dataLite.SsID, dataLite.Name, dataLite.Country, dataLite.SongName, dataLite.SongSubName, dataLite.SongAuthorName, dataLite.SongHash, dataLite.CoverImage, dataLite.DifficultyRaw, dataLite.PP, dataLite.Stars, dataLite.Weight, dataLite.Modifiers, dataLite.Multiplier, dataLite.BadCuts, dataLite.MissedNotes, dataLite.MaxCombo, dataLite.Score, dataLite.FullCombo, dataLite.DeviceHmd, dataLite.DeviceControllerLeft, dataLite.DeviceControllerRight, dataLite.GeneratedTime); err != nil { + log.Fatal(err) + } + err = tx.Commit() + if err != nil { + log.Fatal(err) + } +} + +func (ss *ssQuery) GetRecentScores(count int, qqId string) ([]RecordDataLite, error) { + tx, err := ss.db.Begin() + if err != nil { + log.Fatal(err) + } + defer tx.Rollback() + var ssId string + tx.QueryRow("SELECT ssid FROM ssBind WHERE qqid = ?", qqId).Scan(&ssId) + if ssId == "" { + return nil, errors.New("未绑定ss账号,输入\"绑定ss [ssId]\"绑定") + } + rows, err := tx.Query("SELECT * FROM ssRecordData WHERE ss_id = ? ORDER BY generated_time DESC LIMIT ?", ssId, count) + if err != nil { + if err == sql.ErrNoRows { + return nil, errors.New("未查询到数据") + } + log.Println("Query error:", err) + return nil, errors.New("") + } + defer rows.Close() + var records []RecordDataLite + for rows.Next() { + var record RecordDataLite + if err := rows.Scan(&record.ID, &record.ScoreID, &record.SsID, &record.Name, &record.Country, &record.SongName, &record.SongSubName, &record.SongAuthorName, &record.SongHash, &record.CoverImage, &record.DifficultyRaw, &record.PP, &record.Stars, &record.Weight, &record.Modifiers, &record.Multiplier, &record.BadCuts, &record.MissedNotes, &record.MaxCombo, &record.Score, &record.FullCombo, &record.DeviceHmd, &record.DeviceControllerLeft, &record.DeviceControllerRight, &record.GeneratedTime); err != nil { + log.Println("Scan error:", err) + return nil, err + } + records = append(records, record) + } + err = tx.Commit() + if err != nil { + log.Fatal(err) + } + return records, nil +} diff --git a/service/scoresaber/hot.go b/service/scoresaber/hot.go index 03957bf..0329c78 100644 --- a/service/scoresaber/hot.go +++ b/service/scoresaber/hot.go @@ -12,7 +12,6 @@ const wsURL = "wss://scoresaber.com/ws" var ScoresManager = scoresManager{} -// scoresManager 管理最近的 5 条数据 type scoresManager struct { recentScores []Command mu sync.Mutex @@ -34,7 +33,7 @@ func (sm *scoresManager) connect() error { return err } sm.retryTimes = 0 - sm.recentScores = make([]Command, 0, 50) + sm.recentScores = make([]Command, 0) go sm.receiveData() return nil } @@ -49,12 +48,17 @@ func (sm *scoresManager) receiveData() { for { var cmd Command err := sm.conn.ReadJSON(&cmd) - if cmd.CommandData.Score.LeaderboardPlayerInfo.Country != "CN" { - log.Printf("非国内玩家数据:%v", cmd) + if err != nil { + log.Print("读取数据失败:", err) + time.Sleep(time.Second) + sm.retryTimes++ + if sm.retryTimes > 3 { + return + } continue } - if err != nil { - log.Printf("读取数据失败:", err) + SSQuery.SaveRecord(cmd.CommandData) + if cmd.CommandData.Score.LeaderboardPlayerInfo.Country != "CN" { continue } sm.mu.Lock() @@ -67,11 +71,11 @@ func (sm *scoresManager) receiveData() { } func (sm *scoresManager) GetRecentScores(count int) []Command { + sm.mu.Lock() + defer sm.mu.Unlock() if count > len(sm.recentScores) { count = len(sm.recentScores) } - sm.mu.Lock() - defer sm.mu.Unlock() scoresCopy := make([]Command, count) copy(scoresCopy, sm.recentScores[len(sm.recentScores)-count:]) return scoresCopy diff --git a/service/scoresaber/model.go b/service/scoresaber/model.go index a29c3ab..6a5b429 100644 --- a/service/scoresaber/model.go +++ b/service/scoresaber/model.go @@ -2,6 +2,7 @@ package scoresaber import ( "fmt" + "strings" "time" ) @@ -15,12 +16,19 @@ func (c Command) ToString() string { if c.CommandName != "score" { return "" } - strWithRank := "玩家 %s 使用 %s 在 %s 的 %s 难度中获得了 %d 分,排名第 %d,pp 为 %.2f。" + strWithRank := "玩家 %s 使用 %s 在 %s 的 %s 难度(星级为%.1f)中获得了 %d 分,排名第 %d,pp 为 %.2f。" strWithoutRank := "玩家 %s 使用 %s 在 %s 的 %s 难度中获得了 %d 分,排名第 %d。" - if c.CommandData.Leaderboard.Ranked { - return fmt.Sprintf(strWithRank, c.CommandData.Score.LeaderboardPlayerInfo.Name, *c.CommandData.Score.DeviceHmd, c.CommandData.Leaderboard.SongName, c.CommandData.Leaderboard.Difficulty.DifficultyRaw, c.CommandData.Score.ModifiedScore, c.CommandData.Score.Rank, c.CommandData.Score.Pp) + strWithOutDevice := "玩家 %s 在 %s 的 %s 难度(星级为%.1f)中获得了 %d 分,排名第 %d,pp 为 %.2f。" + strWithOutDeviceAndRank := "玩家 %s 在 %s 的 %s 难度(星级为%.1f)中获得了 %d 分。" + hardStr := strings.Split(c.CommandData.Leaderboard.Difficulty.DifficultyRaw, "_")[1] + if c.CommandData.Leaderboard.Ranked && c.CommandData.Score.DeviceHmd != nil { + return fmt.Sprintf(strWithRank, c.CommandData.Score.LeaderboardPlayerInfo.Name, *c.CommandData.Score.DeviceHmd, c.CommandData.Leaderboard.SongName, hardStr, c.CommandData.Leaderboard.Stars, c.CommandData.Score.ModifiedScore, c.CommandData.Score.Rank, c.CommandData.Score.Pp) + } else if !c.CommandData.Leaderboard.Ranked && c.CommandData.Score.DeviceHmd != nil { + return fmt.Sprintf(strWithoutRank, c.CommandData.Score.LeaderboardPlayerInfo.Name, *c.CommandData.Score.DeviceHmd, c.CommandData.Leaderboard.SongName, hardStr, c.CommandData.Score.ModifiedScore, c.CommandData.Score.Rank) + } else if c.CommandData.Leaderboard.Ranked && c.CommandData.Score.DeviceHmd == nil { + return fmt.Sprintf(strWithOutDevice, c.CommandData.Score.LeaderboardPlayerInfo.Name, c.CommandData.Leaderboard.SongName, hardStr, c.CommandData.Leaderboard.Stars, c.CommandData.Score.ModifiedScore, c.CommandData.Score.Rank, c.CommandData.Score.Pp) } else { - return fmt.Sprintf(strWithoutRank, c.CommandData.Score.LeaderboardPlayerInfo.Name, *c.CommandData.Score.DeviceHmd, c.CommandData.Leaderboard.SongName, c.CommandData.Leaderboard.Difficulty.DifficultyRaw, c.CommandData.Score.ModifiedScore, c.CommandData.Score.Rank) + return fmt.Sprintf(strWithOutDeviceAndRank, c.CommandData.Score.LeaderboardPlayerInfo.Name, c.CommandData.Leaderboard.SongName, hardStr, c.CommandData.Leaderboard.Stars, c.CommandData.Score.ModifiedScore) } } @@ -68,29 +76,29 @@ type Difficulty struct { // Leaderboard 表示排行榜的信息 type Leaderboard struct { - ID int `json:"id"` - SongHash string `json:"songHash"` - SongName string `json:"songName"` - SongSubName string `json:"songSubName"` - SongAuthorName string `json:"songAuthorName"` - LevelAuthorName string `json:"levelAuthorName"` - Difficulty Difficulty `json:"difficulty"` - MaxScore int `json:"maxScore"` - CreatedDate time.Time `json:"createdDate"` - RankedDate *time.Time `json:"rankedDate"` - QualifiedDate *time.Time `json:"qualifiedDate"` - LovedDate *time.Time `json:"lovedDate"` - Ranked bool `json:"ranked"` - Qualified bool `json:"qualified"` - Loved bool `json:"loved"` - MaxPP float64 `json:"maxPP"` - Stars float64 `json:"stars"` - Plays int `json:"plays"` - DailyPlays int `json:"dailyPlays"` - PositiveModifiers bool `json:"positiveModifiers"` - PlayerScore *string `json:"playerScore"` - CoverImage string `json:"coverImage"` - Difficulties *string `json:"difficulties"` + ID int `json:"id"` + SongHash string `json:"songHash"` + SongName string `json:"songName"` + SongSubName string `json:"songSubName"` + SongAuthorName string `json:"songAuthorName"` + LevelAuthorName string `json:"levelAuthorName"` + Difficulty Difficulty `json:"difficulty"` + MaxScore int `json:"maxScore"` + CreatedDate time.Time `json:"createdDate"` + RankedDate *time.Time `json:"rankedDate"` + QualifiedDate *time.Time `json:"qualifiedDate"` + LovedDate *time.Time `json:"lovedDate"` + Ranked bool `json:"ranked"` + Qualified bool `json:"qualified"` + Loved bool `json:"loved"` + MaxPP float64 `json:"maxPP"` + Stars float64 `json:"stars"` + Plays int `json:"plays"` + DailyPlays int `json:"dailyPlays"` + PositiveModifiers bool `json:"positiveModifiers"` + PlayerScore *string `json:"playerScore"` + CoverImage string `json:"coverImage"` + Difficulties interface{} `json:"difficulties"` } // CommandData 表示命令的数据 @@ -99,6 +107,52 @@ type CommandData struct { Leaderboard Leaderboard `json:"leaderboard"` } +// 表示记录的数据,本地储存 +type RecordDataLite struct { + ID int `json:"id" db:"id"` + ScoreID int `json:"scoreId" db:"score_id"` + SsID string `json:"ssId" db:"ss_id"` + Name string `json:"name" db:"name"` + Country string `json:"country" db:"country"` + SongName string `json:"songName" db:"song_name"` + SongSubName string `json:"songSubName" db:"song_sub_name"` + SongAuthorName string `json:"songAuthorName" db:"song_author_name"` + SongHash string `json:"songHash" db:"song_hash"` + CoverImage string `json:"coverImage" db:"cover_image"` + DifficultyRaw string `json:"difficultyRaw" db:"difficulty_raw"` + Stars float64 `json:"stars" db:"stars"` + PP float64 `json:"pp" db:"pp"` + Weight float64 `json:"weight" db:"weight"` + Modifiers string `json:"modifiers" db:"modifiers"` + Multiplier float64 `json:"multiplier" db:"multiplier"` + BadCuts int `json:"badCuts" db:"bad_cuts"` + MissedNotes int `json:"missedNotes" db:"missed_notes"` + MaxCombo int `json:"maxCombo" db:"max_combo"` + Score int `json:"score" db:"score"` + FullCombo bool `json:"fullCombo" db:"full_combo"` + DeviceHmd string `json:"deviceHmd" db:"device_hmd"` + DeviceControllerLeft string `json:"deviceControllerLeft" db:"device_controller_left"` + DeviceControllerRight string `json:"deviceControllerRight" db:"device_controller_right"` + GeneratedTime string `json:"generatedTime" db:"generated_time"` +} + +func (r RecordDataLite) ToString() string { + formatedStrRanked := "使用 %s 在 %s 的 %s 难度(%.1f星级)中获得了 %d 分,pp 为 %.2f。" + formatedStrUnranked := "使用 %s 在 %s 的 %s 难度中获得了 %d 分。" + formatedStrWithoutDevice := "在 %s 的 %s 难度(%.1f星级)中获得了 %d 分,pp 为 %.2f。" + formatedStrWithoutDeviceAndRank := "在 %s 的 %s 难度(%.1f星级)中获得了 %d 分。" + hardStr := strings.Split(r.DifficultyRaw, "_")[1] + if r.Stars == 0 && r.DeviceHmd != "" { + return fmt.Sprintf(formatedStrUnranked, r.DeviceHmd, r.SongName, hardStr, r.Score) + } else if r.Stars != 0 && r.DeviceHmd != "" { + return fmt.Sprintf(formatedStrRanked, r.DeviceHmd, r.SongName, hardStr, r.Stars, r.Score, r.PP) + } else if r.Stars != 0 && r.DeviceHmd == "" { + return fmt.Sprintf(formatedStrWithoutDevice, r.SongName, hardStr, r.Stars, r.Score, r.PP) + } else { + return fmt.Sprintf(formatedStrWithoutDeviceAndRank, r.SongName, hardStr, r.Stars, r.Score) + } +} + //用户信息 // ScoreStats 存储分数统计信息 @@ -135,6 +189,7 @@ type PlayerDataLite struct { ID string `json:"id" db:"id"` Name string `json:"name" db:"name"` Country string `json:"country" db:"country"` + Device string `json:"device" db:"device"` PP float64 `json:"pp" db:"pp"` Rank int `json:"rank" db:"rank"` CountryRank int `json:"countryRank" db:"country_rank"`