package beatleader import ( "database/sql" "encoding/json" "errors" "fmt" "io" "log" "net/http" "strconv" "time" "git.lxtend.com/qqbot/sqlite3" _ "github.com/mattn/go-sqlite3" ) func init() { createScoreTableSQL := `CREATE TABLE IF NOT EXISTS blData ( 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 blRecordData ( id INTEGER PRIMARY KEY AUTOINCREMENT, score_id INT, bl_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, rank 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(createScoreTableSQL) sqlite3.TryCreateTable(createRecordTableSQL) } var BLQuery = &blQuery{} type blQuery struct { } func (bl *blQuery) BindBL(qqId string, blId string) (reply string) { tx, err := sqlite3.GetTran() if err != nil { log.Print(err) } defer tx.Rollback() // blId为数字 if _, isNum := strconv.Atoi(blId); isNum != nil { return "blId格式错误,应当为一串数字(大部分情况下是你的steamID)" } data, err := FetchPlayerData(blId) if data == nil { if err != nil { return "未找到玩家,请检查ID后重试:" + err.Error() } return "未找到玩家,请检查ID后重试" } //去重 if rows, err := tx.Query("SELECT * FROM ssBind WHERE qqid = ?", qqId); err == nil { if rows.Next() { return "您已绑定过bl账号,请先输入\"解绑bl\"解绑" } rows.Close() } if rows, err := tx.Query("SELECT * FROM ssBind WHERE ssid = ?", blId); err == nil { if rows.Next() { return "该bl账号已绑定至其他用户" } rows.Close() } _, err = tx.Exec("INSERT INTO ssBind(qqid, ssid) VALUES(?, ?)", qqId, blId) if err != nil { return "绑定失败,请稍后重试:" + err.Error() } err = tx.Commit() if err != nil { return "无法提交事务" } return "和用户名为 " + data.Name + " 的用户绑定成功,输入\"查bl\"查看个人数据" } func (bl *blQuery) UnbindBL(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 "您未绑定bl账号,输入\"绑定bl [blId]\"绑定" } rows.Close() } _, err = tx.Exec("DELETE FROM ssBind WHERE qqid = ?", qqId) if err != nil { return "解绑失败" } err = tx.Commit() if err != nil { return "无法提交事务" } return "解绑成功,重新绑定请输入\"绑定bl [blId]\"" } func (bl *blQuery) GetScore(qqId string) (reply string, err error) { db := sqlite3.GetDB() // 假设 sqlite3.GetDB() 返回 *sqlx.DB tx, err := db.Beginx() if err != nil { log.Print(err) return "数据库连接失败,请稍后重试", err } defer tx.Rollback() blId, err := getBLID(qqId) if err != nil { return err.Error(), nil } // 查询玩家数据 data, err := FetchPlayerData(blId) if data == nil && err == nil { return "查询出错,服务器返回了空数据", errors.New("查询出错,服务器返回了空数据") } if err != nil { log.Print(err) return "查询出错,服务器返回了空数据" + err.Error(), err } // 构建 PlayerDataLite 结构体 dataLite := PlayerDataLite{ ID: data.ID, Name: data.Name, Country: data.Country, PP: data.AccPp + data.PassPp + data.TechPp, 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.WatchedReplays, GeneratedTime: time.Now().Format("2006-01-02 15:04:05.999999999-07:00"), } // 查询最近的玩家数据 var lastDataLite PlayerDataLite err = tx.Get(&lastDataLite, "SELECT * FROM blData WHERE id = ? ORDER BY generated_time DESC LIMIT 1", dataLite.ID) if err != nil && err != sql.ErrNoRows { log.Print(err) return "查询历史数据时出错", err } // 如果有新的数据,则插入 if lastDataLite.IsDiffFrom(dataLite) { _, err = tx.NamedExec(`INSERT INTO blData (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 "插入新数据时出错", err } // 提交事务 err = tx.Commit() if err != nil { log.Print(err) return "SQL事务提交失败,请重试", err } // 返回差异信息 return data.LastDiffToString(lastDataLite), nil } // 如果没有新数据,直接提交事务 err = tx.Commit() if err != nil { log.Print(err) return "SQL事务提交失败,请重试", err } // 返回当前数据的字符串表示 return data.ToString(), nil } func (bl *blQuery) SaveRecord(scoreData ScoreData) { 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: scoreData.ID, BlID: scoreData.Player.ID, Name: scoreData.Player.Name, Country: scoreData.Player.Country, SongName: scoreData.Leaderboard.Song.Name, SongSubName: scoreData.Leaderboard.Song.SubName, SongAuthorName: scoreData.Leaderboard.Song.Author, SongHash: scoreData.Leaderboard.Song.Hash, CoverImage: scoreData.Leaderboard.Song.CoverImage, DifficultyRaw: scoreData.Leaderboard.Difficulty.DifficultyName, Stars: 0, PP: scoreData.Pp, Weight: scoreData.Weight, Modifiers: scoreData.Modifiers, Multiplier: float64(scoreData.ModifiedScore) / float64(scoreData.BaseScore), BadCuts: scoreData.BadCuts, Score: scoreData.ModifiedScore, Rank: scoreData.Rank, MaxScore: int(float64(scoreData.ModifiedScore) / scoreData.Accuracy), MissedNotes: scoreData.MissedNotes, MaxCombo: scoreData.MaxCombo, FullCombo: scoreData.FullCombo, DeviceHmd: GetHMDStr(scoreData.HMD), DeviceControllerLeft: GetControllerStr(scoreData.Controller), DeviceControllerRight: GetControllerStr(scoreData.Controller), GeneratedTime: time.Now().Format("2006-01-02 15:04:05.999999999-07:00"), } if scoreData.Leaderboard.Difficulty.Stars != nil { dataLite.Stars = *scoreData.Leaderboard.Difficulty.Stars } // 使用 NamedExec 插入数据 _, err = tx.NamedExec(`INSERT INTO blRecordData (score_id, bl_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,rank, max_score, full_combo, device_hmd, device_controller_left, device_controller_right, generated_time) VALUES (:score_id, :bl_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,:rank, :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 (bl *blQuery) GetRecentScores(count int, qqId string) ([]RecordDataLite, error) { db := sqlite3.GetDB() // 假设 sqlite3.GetDB() 返回 *sqlx.DB tx, err := db.Beginx() if err != nil { log.Print(err) return nil, errors.New("数据库连接失败,请稍后重试") } defer tx.Rollback() // 查询绑定的 blId blId, err := getBLID(qqId) if err != nil { return nil, err } // // 查询记录 // var records []RecordDataLite // err = tx.Select(&records, "SELECT * FROM blRecordData WHERE bl_id = ? ORDER BY generated_time DESC LIMIT ?", blId, count) // if err != nil { // if err == sql.ErrNoRows { // return nil, errors.New("未查询到数据") // } // log.Println("查询数据出错:", err) // return nil, errors.New("查询记录失败") // } // // 提交事务 // err = tx.Commit() // if err != nil { // log.Print(err) // return nil, errors.New("提交事务失败") // } // return records, nil //从线上获取 historyUrl := "https://api.beatleader.xyz/player/%s/scores" fullUrl := fmt.Sprintf(historyUrl, blId) 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 scores []ScoreData err = json.Unmarshal(body, &scores) if err != nil { return nil, err } records := make([]RecordDataLite, 0) for _, score := range scores { records = append(records, RecordDataLite{ ScoreID: score.ID, BlID: score.Player.ID, Name: score.Player.Name, Country: score.Player.Country, SongName: score.Leaderboard.Song.Name, SongSubName: score.Leaderboard.Song.SubName, SongAuthorName: score.Leaderboard.Song.Author, SongHash: score.Leaderboard.Song.Hash, SongId: score.Leaderboard.Song.ID, CoverImage: score.Leaderboard.Song.CoverImage, DifficultyRaw: score.Leaderboard.Difficulty.DifficultyName, PP: score.Pp, Stars: *score.Leaderboard.Difficulty.Stars, Weight: score.Weight, Modifiers: score.Modifiers, Multiplier: float64(score.ModifiedScore) / float64(score.BaseScore), BadCuts: score.BadCuts, Score: score.ModifiedScore, Rank: score.Rank, MaxScore: score.ModifiedScore, FullCombo: score.FullCombo, DeviceHmd: GetHMDStr(score.HMD), DeviceControllerLeft: GetControllerStr(score.Controller), DeviceControllerRight: GetControllerStr(score.Controller), GeneratedTime: time.Now().Format("2006-01-02 15:04:05.999999999-07:00"), }) } return records, nil }