package scoresaber import ( "fmt" "log" "os" "strings" "time" "git.lxtend.com/qqbot/message" "git.lxtend.com/qqbot/util" ) // Command 表示从 WebSocket 收到的数据结构 type Command struct { CommandName string `json:"commandName"` CommandData CommandData `json:"commandData"` } // type Badge struct { // Description string `json:"description"` // Image string `json:"image"` // } // LeaderboardPlayerInfo 表示玩家的信息 type LeaderboardPlayerInfo struct { ID string `json:"id"` Name string `json:"name"` ProfilePicture string `json:"profilePicture"` Country string `json:"country"` Permissions int `json:"permissions"` } // Score 表示分数的信息 type Score struct { ID int `json:"id"` LeaderboardPlayerInfo LeaderboardPlayerInfo `json:"leaderboardPlayerInfo"` Rank int `json:"rank"` BaseScore int `json:"baseScore"` ModifiedScore int `json:"modifiedScore"` Pp float64 `json:"pp"` Weight float64 `json:"weight"` Modifiers string `json:"modifiers"` Multiplier float64 `json:"multiplier"` BadCuts int `json:"badCuts"` MissedNotes int `json:"missedNotes"` MaxCombo int `json:"maxCombo"` FullCombo bool `json:"fullCombo"` Hmd int `json:"hmd"` TimeSet time.Time `json:"timeSet"` HasReplay bool `json:"hasReplay"` DeviceHmd *string `json:"deviceHmd"` DeviceControllerLeft *string `json:"deviceControllerLeft"` DeviceControllerRight *string `json:"deviceControllerRight"` } // Difficulty 表示关卡难度信息 type Difficulty struct { LeaderboardID int `json:"leaderboardId"` Difficulty int `json:"difficulty"` GameMode string `json:"gameMode"` DifficultyRaw string `json:"difficultyRaw"` } // 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 interface{} `json:"difficulties"` } // CommandData 表示命令的数据 type CommandData struct { Score Score `json:"score"` 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"` SongId string `json:"songId" db:"song_id"` 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"` Rank int `json:"rank" db:"rank"` 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"` MaxScore int `json:"maxScore" db:"max_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 在 %s(%s) 的 %s 难度(%.1f🌟)中打到了全球排名第%d,pp 为 %.2f,准度为 %s。" formatedStrUnranked := "%s, %s 使用 %s 在 %s(%s) 的 %s 难度中打到了全球排名第%d,准度为 %s。" formatedStrWithoutDevice := "%s, %s 在 %s(%s) 的 %s 难度(%.1f🌟)中打到了全球排名第%d,pp 为 %.2f,准度为 %s。" formatedStrWithoutDeviceAndRank := "%s, %s 在 %s(%s) 的 %s 难度中打到了全球排名第%d,准度为 %s。" hardStr := strings.Split(r.DifficultyRaw, "_")[1] layout := "2006-01-02 15:04:05.999999999-07:00" parsedTime, _ := time.Parse(layout, r.GeneratedTime) duration := time.Since(parsedTime) timeStr := timeConvert(duration) if r.Stars == 0 && r.DeviceHmd != "" { return fmt.Sprintf(formatedStrUnranked, timeStr, r.Name, r.DeviceHmd, r.SongName, r.SongId, hardStr, r.Rank, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100)) } else if r.Stars != 0 && r.DeviceHmd != "" { return fmt.Sprintf(formatedStrRanked, timeStr, r.Name, r.DeviceHmd, r.SongName, r.SongId, hardStr, r.Stars, r.Rank, r.PP, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100)) } else if r.Stars != 0 && r.DeviceHmd == "" { return fmt.Sprintf(formatedStrWithoutDevice, timeStr, r.Name, r.SongName, r.SongId, hardStr, r.Stars, r.Rank, r.PP, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100)) } else { return fmt.Sprintf(formatedStrWithoutDeviceAndRank, timeStr, r.Name, r.SongName, r.SongId, hardStr, r.Rank, fmt.Sprintf("%.2f%%", float64(r.Score)/float64(r.MaxScore)*100)) } } func timeConvert(duration time.Duration) string { var result string if duration.Hours() >= 24 { days := int(duration.Hours() / 24) result = fmt.Sprintf("%d天前", days) } else if duration.Hours() >= 1 { hours := int(duration.Hours()) result = fmt.Sprintf("%d小时前", hours) } else if duration.Minutes() >= 1 { minutes := int(duration.Minutes()) result = fmt.Sprintf("%d分钟前", minutes) } else { result = "刚刚" } return result } //用户信息 // ScoreStats 存储分数统计信息 type ScoreStats struct { TotalScore int `json:"totalScore" db:"total_score"` TotalRankedScore int `json:"totalRankedScore" db:"total_ranked_score"` AverageRankedAccuracy float64 `json:"averageRankedAccuracy" db:"average_ranked_accuracy"` TotalPlayCount int `json:"totalPlayCount" db:"total_play_count"` RankedPlayCount int `json:"rankedPlayCount" db:"ranked_play_count"` ReplaysWatched int `json:"replaysWatched" db:"replays_watched"` } // PlayerData 存储玩家的完整信息 type PlayerData struct { ID string `json:"id" db:"id"` Name string `json:"name" db:"name"` ProfilePicture string `json:"profilePicture" db:"profile_picture"` Bio *string `json:"bio" db:"bio"` Country string `json:"country" db:"country"` PP float64 `json:"pp" db:"pp"` Rank int `json:"rank" db:"rank"` CountryRank int `json:"countryRank" db:"country_rank"` Role *string `json:"role" db:"role"` // Badges []string `json:"badges" db:"badges"` Histories string `json:"histories" db:"histories"` Permissions int `json:"permissions" db:"permissions"` Banned bool `json:"banned" db:"banned"` Inactive bool `json:"inactive" db:"inactive"` ScoreStats ScoreStats `json:"scoreStats" db:"score_stats"` FirstSeen time.Time `json:"firstSeen" db:"first_seen"` } 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"` TotalScore int `json:"totalScore" db:"total_score"` TotalRankedScore int `json:"totalRankedScore" db:"total_ranked_score"` AverageRankedAccuracy float64 `json:"averageRankedAccuracy" db:"average_ranked_accuracy"` TotalPlayCount int `json:"totalPlayCount" db:"total_play_count"` RankedPlayCount int `json:"rankedPlayCount" db:"ranked_play_count"` ReplaysWatched int `json:"replaysWatched" db:"replays_watched"` GeneratedTime string `json:"generatedTime" db:"generated_time"` } func (p PlayerDataLite) IsDiffFrom(p2 PlayerDataLite) bool { return p.TotalScore != p2.TotalScore || p.TotalRankedScore != p2.TotalRankedScore || p.AverageRankedAccuracy != p2.AverageRankedAccuracy || p.TotalPlayCount != p2.TotalPlayCount || p.RankedPlayCount != p2.RankedPlayCount || p.ReplaysWatched != p2.ReplaysWatched } func (p PlayerData) ToString() string { err := util.DownloadFile(p.ProfilePicture, util.GenTempFilePath(p.ID+".jpg")) if err != nil { log.Default().Printf("下载头像失败,url:%s,err:%v", p.ProfilePicture, err) } defer os.Remove(util.GenTempFilePath(p.ID + ".jpg")) outFile, err := util.ResizeImageByMaxHeight(util.GenTempFilePath(p.ID+".jpg"), 20) if err != nil { log.Default().Printf("缩放头像失败,url:%s,err:%v", p.ProfilePicture, err) } picMsg := message.ImageMessage{ Type: message.TypeImage, Data: message.ImageMessageData{ File: outFile, }, } formatedStr := "玩家 %s\n" + picMsg.ToCQString() + "区域 %s\n" + "PP %.1f\n" + "全球排名 %d\n" + "区域排名 %d\n" + "Ranked谱面均准 %.2f%%\n" + "总游玩记数 %d\n" + "Ranked谱面游玩记数 %d\n" + "回放被观看次数 %d" return fmt.Sprintf(formatedStr, p.Name, p.Country, p.PP, p.Rank, p.CountryRank, p.ScoreStats.AverageRankedAccuracy, p.ScoreStats.TotalPlayCount, p.ScoreStats.RankedPlayCount, p.ScoreStats.ReplaysWatched) } func (p PlayerDataLite) ToString() string { formatedStr := "玩家 %s\n" + "区域 %s\n" + "PP %.1f\n" + "全球排名 %d\n" + "区域排名 %d\n" + "Ranked谱面均准 %.2f%%\n" + "总游玩记数 %d\n" + "Ranked谱面游玩记数 %d\n" + "回放被观看次数 %d" return fmt.Sprintf(formatedStr, p.Name, p.Country, p.PP, p.Rank, p.CountryRank, p.AverageRankedAccuracy, p.TotalPlayCount, p.RankedPlayCount, p.ReplaysWatched) } func (p PlayerData) LastDiffToString(lastDayQueryData PlayerDataLite) string { err := util.DownloadFile(p.ProfilePicture, util.GenTempFilePath(p.ID+".jpg")) if err != nil { log.Default().Printf("下载头像失败,url:%s,err:%v", p.ProfilePicture, err) } defer os.Remove(util.GenTempFilePath(p.ID + ".jpg")) outFile, err := util.ResizeImageByMaxHeight(util.GenTempFilePath(p.ID+".jpg"), 20) if err != nil { log.Default().Printf("缩放头像失败,url:%s,err:%v", p.ProfilePicture, err) } picMsg := message.ImageMessage{ Type: message.TypeImage, Data: message.ImageMessageData{ File: outFile, }, } formatedStr := "玩家 %s\n" + picMsg.ToCQString() + "区域 %s\n" + "PP %.1f(%+.1f)\n" + "全球排名 %d(%+d)\n" + "区域排名 %d(%+d)\n" + "Ranked谱面均准 %.2f%%(%+.2f%%)\n" + "总游玩记数 %d(%+d)\n" + "Ranked谱面游玩记数 %d(%+d)\n" + "回放被观看次数 %d" return fmt.Sprintf(formatedStr, p.Name, p.Country, p.PP, p.PP-lastDayQueryData.PP, p.Rank, lastDayQueryData.Rank-p.Rank, p.CountryRank, lastDayQueryData.CountryRank-p.CountryRank, p.ScoreStats.AverageRankedAccuracy, p.ScoreStats.AverageRankedAccuracy-lastDayQueryData.AverageRankedAccuracy, p.ScoreStats.TotalPlayCount, p.ScoreStats.TotalPlayCount-lastDayQueryData.TotalPlayCount, p.ScoreStats.RankedPlayCount, p.ScoreStats.RankedPlayCount-lastDayQueryData.RankedPlayCount, p.ScoreStats.ReplaysWatched) }