792 lines
28 KiB
Go
792 lines
28 KiB
Go
package beatleader
|
||
|
||
import (
|
||
"fmt"
|
||
"image"
|
||
"image/color"
|
||
"image/draw"
|
||
"log"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"git.lxtend.com/lixiangwuxian/imagedd/font2img"
|
||
"git.lxtend.com/lixiangwuxian/imagedd/sprite"
|
||
"git.lxtend.com/qqbot/message"
|
||
"git.lxtend.com/qqbot/util"
|
||
)
|
||
|
||
type ScoreData struct {
|
||
ContextExtensions []ContextExtension `json:"contextExtensions"`
|
||
MyScore *string `json:"myScore"`
|
||
ValidContexts int `json:"validContexts"`
|
||
Leaderboard Leaderboard `json:"leaderboard"`
|
||
AccLeft float64 `json:"accLeft"`
|
||
AccRight float64 `json:"accRight"`
|
||
ID int `json:"id"`
|
||
BaseScore int `json:"baseScore"`
|
||
ModifiedScore int `json:"modifiedScore"`
|
||
Accuracy float64 `json:"accuracy"`
|
||
PlayerID string `json:"playerId"`
|
||
Pp float64 `json:"pp"`
|
||
BonusPp float64 `json:"bonusPp"`
|
||
PassPp float64 `json:"passPP"`
|
||
AccPp float64 `json:"accPP"`
|
||
TechPp float64 `json:"techPP"`
|
||
Rank int `json:"rank"`
|
||
Country string `json:"country"`
|
||
FcAccuracy float64 `json:"fcAccuracy"`
|
||
FcPp float64 `json:"fcPp"`
|
||
Weight float64 `json:"weight"`
|
||
Replay string `json:"replay"`
|
||
Modifiers string `json:"modifiers"`
|
||
BadCuts int `json:"badCuts"`
|
||
MissedNotes int `json:"missedNotes"`
|
||
BombCuts int `json:"bombCuts"`
|
||
WallsHit int `json:"wallsHit"`
|
||
Pauses int `json:"pauses"`
|
||
FullCombo bool `json:"fullCombo"`
|
||
Platform string `json:"platform"`
|
||
MaxCombo int `json:"maxCombo"`
|
||
MaxStreak *int `json:"maxStreak"`
|
||
HMD int `json:"hmd"`
|
||
Controller int `json:"controller"`
|
||
LeaderboardID string `json:"leaderboardId"`
|
||
Timeset string `json:"timeset"`
|
||
Timepost int64 `json:"timepost"`
|
||
ReplaysWatched int `json:"replaysWatched"`
|
||
PlayCount int `json:"playCount"`
|
||
LastTryTime int64 `json:"lastTryTime"`
|
||
Priority int `json:"priority"`
|
||
Player Player `json:"player"`
|
||
ScoreImprovement ScoreImprovement `json:"scoreImprovement"`
|
||
// RankVoting *string `json:"rankVoting"`
|
||
// Metadata *string `json:"metadata"`
|
||
Offsets Offsets `json:"offsets"`
|
||
}
|
||
|
||
type ContextExtension struct {
|
||
ID int `json:"id"`
|
||
PlayerID string `json:"playerId"`
|
||
Weight float64 `json:"weight"`
|
||
Rank int `json:"rank"`
|
||
BaseScore int `json:"baseScore"`
|
||
ModifiedScore int `json:"modifiedScore"`
|
||
Accuracy float64 `json:"accuracy"`
|
||
Pp float64 `json:"pp"`
|
||
PassPp float64 `json:"passPP"`
|
||
AccPp float64 `json:"accPP"`
|
||
TechPp float64 `json:"techPP"`
|
||
BonusPp float64 `json:"bonusPp"`
|
||
Modifiers string `json:"modifiers"`
|
||
Context int `json:"context"`
|
||
ScoreImprovement ScoreImprovement `json:"scoreImprovement"`
|
||
}
|
||
|
||
type ScoreImprovement struct {
|
||
ID int `json:"id"`
|
||
Timeset string `json:"timeset"`
|
||
Score int `json:"score"`
|
||
Accuracy float64 `json:"accuracy"`
|
||
Pp float64 `json:"pp"`
|
||
BonusPp float64 `json:"bonusPp"`
|
||
Rank int `json:"rank"`
|
||
AccRight float64 `json:"accRight"`
|
||
AccLeft float64 `json:"accLeft"`
|
||
AverageRankedAccuracy float64 `json:"averageRankedAccuracy"`
|
||
TotalPp float64 `json:"totalPp"`
|
||
TotalRank int `json:"totalRank"`
|
||
BadCuts int `json:"badCuts"`
|
||
MissedNotes int `json:"missedNotes"`
|
||
BombCuts int `json:"bombCuts"`
|
||
WallsHit int `json:"wallsHit"`
|
||
Pauses int `json:"pauses"`
|
||
}
|
||
|
||
type Leaderboard struct {
|
||
ID string `json:"id"`
|
||
Song Song `json:"song"`
|
||
Difficulty Difficulty `json:"difficulty"`
|
||
}
|
||
|
||
type Song struct {
|
||
ID string `json:"id"`
|
||
Hash string `json:"hash"`
|
||
Name string `json:"name"`
|
||
SubName string `json:"subName"`
|
||
Author string `json:"author"`
|
||
Mapper string `json:"mapper"`
|
||
MapperID int `json:"mapperId"`
|
||
CollaboratorIds *string `json:"collaboratorIds"`
|
||
CoverImage string `json:"coverImage"`
|
||
Bpm float64 `json:"bpm"`
|
||
Duration float64 `json:"duration"`
|
||
FullCoverImage *string `json:"fullCoverImage"`
|
||
}
|
||
|
||
type Difficulty struct {
|
||
ID int `json:"id"`
|
||
Value int `json:"value"`
|
||
Mode int `json:"mode"`
|
||
DifficultyName string `json:"difficultyName"`
|
||
ModeName string `json:"modeName"`
|
||
Status int `json:"status"`
|
||
ModifierValues ModifierValues `json:"modifierValues"`
|
||
// ModifiersRating *string `json:"modifiersRating"`
|
||
NominatedTime int64 `json:"nominatedTime"`
|
||
QualifiedTime int64 `json:"qualifiedTime"`
|
||
RankedTime int64 `json:"rankedTime"`
|
||
SpeedTags int `json:"speedTags"`
|
||
StyleTags int `json:"styleTags"`
|
||
FeatureTags int `json:"featureTags"`
|
||
Stars *float64 `json:"stars"`
|
||
PredictedAcc float64 `json:"predictedAcc"`
|
||
}
|
||
|
||
type ModifierValues struct {
|
||
ModifierID int `json:"modifierId"`
|
||
Da float64 `json:"da"`
|
||
Fs float64 `json:"fs"`
|
||
Sf float64 `json:"sf"`
|
||
// 其他修改器字段...
|
||
}
|
||
|
||
type Player struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Platform string `json:"platform"`
|
||
Avatar string `json:"avatar"`
|
||
Country string `json:"country"`
|
||
Alias *string `json:"alias"`
|
||
Bot bool `json:"bot"`
|
||
Pp float64 `json:"pp"`
|
||
Rank int `json:"rank"`
|
||
CountryRank int `json:"countryRank"`
|
||
Role string `json:"role"`
|
||
// Socials *string `json:"socials"`
|
||
ContextExtensions *string `json:"contextExtensions"`
|
||
}
|
||
|
||
type Offsets struct {
|
||
ID int `json:"id"`
|
||
Frames int `json:"frames"`
|
||
Notes int `json:"notes"`
|
||
Walls int `json:"walls"`
|
||
Heights int `json:"heights"`
|
||
Pauses int `json:"pauses"`
|
||
}
|
||
|
||
// 表示记录的数据,本地储存
|
||
type RecordDataLite struct {
|
||
ID int `json:"id" db:"id"`
|
||
ScoreID int `json:"scoreId" db:"score_id"`
|
||
BlID string `json:"blId" db:"bl_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 := r.DifficultyRaw
|
||
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 PlayerData struct {
|
||
MapperID int `json:"mapperId"`
|
||
Banned bool `json:"banned"`
|
||
Inactive bool `json:"inactive"`
|
||
// BanDescription *string `json:"banDescription"`
|
||
ExternalProfileURL string `json:"externalProfileUrl"`
|
||
RichBioTimeset int64 `json:"richBioTimeset"`
|
||
SpeedrunStart int64 `json:"speedrunStart"`
|
||
LinkedIDs LinkedIDs `json:"linkedIds"`
|
||
// History *string `json:"history"`
|
||
// Badges []string `json:"badges"`
|
||
// PinnedScores *string `json:"pinnedScores"`
|
||
// Changes []Change `json:"changes"`
|
||
AccPp float64 `json:"accPp"`
|
||
PassPp float64 `json:"passPp"`
|
||
TechPp float64 `json:"techPp"`
|
||
ScoreStats ScoreStats `json:"scoreStats"`
|
||
LastWeekPp float64 `json:"lastWeekPp"`
|
||
LastWeekRank int `json:"lastWeekRank"`
|
||
LastWeekCountryRank int `json:"lastWeekCountryRank"`
|
||
ExtensionID int `json:"extensionId"`
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Platform string `json:"platform"`
|
||
Avatar string `json:"avatar"`
|
||
Country string `json:"country"`
|
||
// Alias *string `json:"alias"`
|
||
Bot bool `json:"bot"`
|
||
PP float64 `json:"pp"`
|
||
Rank int `json:"rank"`
|
||
CountryRank int `json:"countryRank"`
|
||
Role string `json:"role"`
|
||
// Socials []string `json:"socials"`
|
||
// ContextExtensions *string `json:"contextExtensions"`
|
||
// PatreonFeatures *string `json:"patreonFeatures"`
|
||
// ProfileSettings ProfileSettings `json:"profileSettings"`
|
||
// ClanOrder string `json:"clanOrder"`
|
||
// Clans []string `json:"clans"`
|
||
}
|
||
|
||
type LinkedIDs struct {
|
||
QuestID int `json:"questId"`
|
||
SteamID string `json:"steamId"`
|
||
OculusPCID string `json:"oculusPCId"`
|
||
}
|
||
|
||
type Change struct {
|
||
ID int64 `json:"id"`
|
||
Timestamp int64 `json:"timestamp"`
|
||
PlayerID string `json:"playerId"`
|
||
OldName string `json:"oldName"`
|
||
NewName string `json:"newName"`
|
||
OldCountry string `json:"oldCountry"`
|
||
NewCountry string `json:"newCountry"`
|
||
Changer *string `json:"changer"`
|
||
}
|
||
|
||
type ScoreStats struct {
|
||
ID int64 `json:"id"`
|
||
TotalScore int64 `json:"totalScore"`
|
||
TotalUnrankedScore int64 `json:"totalUnrankedScore"`
|
||
TotalRankedScore int64 `json:"totalRankedScore"`
|
||
LastScoreTime int64 `json:"lastScoreTime"`
|
||
LastUnrankedScoreTime int64 `json:"lastUnrankedScoreTime"`
|
||
LastRankedScoreTime int64 `json:"lastRankedScoreTime"`
|
||
AverageRankedAccuracy float64 `json:"averageRankedAccuracy"`
|
||
AverageWeightedRankedAccuracy float64 `json:"averageWeightedRankedAccuracy"`
|
||
AverageUnrankedAccuracy float64 `json:"averageUnrankedAccuracy"`
|
||
AverageAccuracy float64 `json:"averageAccuracy"`
|
||
MedianRankedAccuracy float64 `json:"medianRankedAccuracy"`
|
||
MedianAccuracy float64 `json:"medianAccuracy"`
|
||
TopRankedAccuracy float64 `json:"topRankedAccuracy"`
|
||
TopUnrankedAccuracy float64 `json:"topUnrankedAccuracy"`
|
||
TopAccuracy float64 `json:"topAccuracy"`
|
||
TopPp float64 `json:"topPp"`
|
||
TopBonusPP float64 `json:"topBonusPP"`
|
||
TopPassPP float64 `json:"topPassPP"`
|
||
TopAccPP float64 `json:"topAccPP"`
|
||
TopTechPP float64 `json:"topTechPP"`
|
||
PeakRank int `json:"peakRank"`
|
||
RankedMaxStreak int `json:"rankedMaxStreak"`
|
||
UnrankedMaxStreak int `json:"unrankedMaxStreak"`
|
||
MaxStreak int `json:"maxStreak"`
|
||
AverageLeftTiming float64 `json:"averageLeftTiming"`
|
||
AverageRightTiming float64 `json:"averageRightTiming"`
|
||
RankedPlayCount int `json:"rankedPlayCount"`
|
||
UnrankedPlayCount int `json:"unrankedPlayCount"`
|
||
TotalPlayCount int `json:"totalPlayCount"`
|
||
RankedImprovementsCount int `json:"rankedImprovementsCount"`
|
||
UnrankedImprovementsCount int `json:"unrankedImprovementsCount"`
|
||
TotalImprovementsCount int `json:"totalImprovementsCount"`
|
||
RankedTop1Count int `json:"rankedTop1Count"`
|
||
UnrankedTop1Count int `json:"unrankedTop1Count"`
|
||
Top1Count int `json:"top1Count"`
|
||
RankedTop1Score int64 `json:"rankedTop1Score"`
|
||
UnrankedTop1Score int64 `json:"unrankedTop1Score"`
|
||
Top1Score int64 `json:"top1Score"`
|
||
AverageRankedRank float64 `json:"averageRankedRank"`
|
||
AverageWeightedRankedRank float64 `json:"averageWeightedRankedRank"`
|
||
AverageUnrankedRank float64 `json:"averageUnrankedRank"`
|
||
AverageRank float64 `json:"averageRank"`
|
||
SspPlays int `json:"sspPlays"`
|
||
SsPlays int `json:"ssPlays"`
|
||
SpPlays int `json:"spPlays"`
|
||
SPlays int `json:"sPlays"`
|
||
APlays int `json:"aPlays"`
|
||
TopPlatform string `json:"topPlatform"`
|
||
TopHMD int `json:"topHMD"`
|
||
AllHMDs string `json:"allHMDs"`
|
||
TopPercentile float64 `json:"topPercentile"`
|
||
CountryTopPercentile float64 `json:"countryTopPercentile"`
|
||
DailyImprovements int `json:"dailyImprovements"`
|
||
AuthorizedReplayWatched int `json:"authorizedReplayWatched"`
|
||
AnonimusReplayWatched int `json:"anonimusReplayWatched"`
|
||
WatchedReplays int `json:"watchedReplays"`
|
||
}
|
||
|
||
type ProfileSettings struct {
|
||
ID int64 `json:"id"`
|
||
Bio *string `json:"bio"`
|
||
Message *string `json:"message"`
|
||
EffectName string `json:"effectName"`
|
||
ProfileAppearance string `json:"profileAppearance"`
|
||
Hue int `json:"hue"`
|
||
Saturation float64 `json:"saturation"`
|
||
LeftSaberColor *string `json:"leftSaberColor"`
|
||
RightSaberColor *string `json:"rightSaberColor"`
|
||
ProfileCover *string `json:"profileCover"`
|
||
StarredFriends string `json:"starredFriends"`
|
||
HorizontalRichBio bool `json:"horizontalRichBio"`
|
||
RankedMapperSort *string `json:"rankedMapperSort"`
|
||
ShowBots bool `json:"showBots"`
|
||
ShowAllRatings bool `json:"showAllRatings"`
|
||
ShowStatsPublic bool `json:"showStatsPublic"`
|
||
ShowStatsPublicPinned bool `json:"showStatsPublicPinned"`
|
||
}
|
||
|
||
type PlayerDataLite struct {
|
||
ID string `json:"id" db:"id"`
|
||
Name string `json:"name" db:"name"`
|
||
Country string `json:"country" db:"country"`
|
||
Avatar string `json:"avatar" db:"avatar"`
|
||
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 int64 `json:"totalScore" db:"total_score"`
|
||
TotalRankedScore int64 `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 {
|
||
|
||
filePath, err := util.DownloadFile(p.Avatar, "/tmp/qqbot", false)
|
||
if err != nil {
|
||
log.Default().Printf("下载头像失败,url:%s,err:%v", p.Avatar, err)
|
||
}
|
||
defer os.Remove(filePath)
|
||
outFile, err := util.ResizeImageByMaxHeight2File(filePath, 20)
|
||
if err != nil {
|
||
log.Default().Printf("缩放头像失败,url:%s,err:%v", p.Avatar, 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*100,
|
||
p.ScoreStats.TotalPlayCount,
|
||
p.ScoreStats.RankedPlayCount,
|
||
p.ScoreStats.WatchedReplays)
|
||
}
|
||
|
||
func (p PlayerDataLite) LastDiffToString(lastDayQueryData PlayerDataLite) string {
|
||
filePath, err := util.DownloadFile(p.Avatar, "/tmp/qqbot", false)
|
||
if err != nil {
|
||
log.Default().Printf("下载头像失败,url:%s,err:%v", p.Avatar, err)
|
||
}
|
||
defer os.Remove(filePath)
|
||
outFile, err := util.ResizeImageByMaxHeight2File(filePath, 20)
|
||
if err != nil {
|
||
log.Default().Printf("缩放头像失败,url:%s,err:%v", p.Avatar, err)
|
||
}
|
||
picMsg := message.ImageMessage{
|
||
Type: message.TypeImage,
|
||
Data: message.ImageMessageData{
|
||
File: outFile,
|
||
},
|
||
}
|
||
|
||
var sb strings.Builder
|
||
sb.WriteString(fmt.Sprintf("玩家 %s\n", p.Name))
|
||
sb.WriteString(picMsg.ToCQString())
|
||
sb.WriteString(fmt.Sprintf("区域 %s\n", p.Country))
|
||
|
||
// PP值
|
||
ppDiff := p.PP - lastDayQueryData.PP
|
||
if ppDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("PP %.1f\n", p.PP))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("PP %.1f(%+.1f)\n", p.PP, ppDiff))
|
||
} // 全球排名
|
||
rankDiff := lastDayQueryData.Rank - p.Rank
|
||
if rankDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("全球排名 %d\n", p.Rank))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("全球排名 %d(%+d)\n", p.Rank, rankDiff))
|
||
}
|
||
|
||
// 区域排名
|
||
countryRankDiff := lastDayQueryData.CountryRank - p.CountryRank
|
||
if countryRankDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("区域排名 %d\n", p.CountryRank))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("区域排名 %d(%+d)\n", p.CountryRank, countryRankDiff))
|
||
}
|
||
|
||
// Ranked谱面均准
|
||
accDiff := (p.AverageRankedAccuracy - lastDayQueryData.AverageRankedAccuracy) * 100
|
||
if accDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("Ranked谱面均准 %.2f%%\n", p.AverageRankedAccuracy*100))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("Ranked谱面均准 %.2f%%(%+.2f%%)\n", p.AverageRankedAccuracy*100, accDiff))
|
||
}
|
||
// 总游玩记数
|
||
totalPlayDiff := p.TotalPlayCount - lastDayQueryData.TotalPlayCount
|
||
if totalPlayDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("总游玩记数 %d\n", p.TotalPlayCount))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("总游玩记数 %d(%+d)\n", p.TotalPlayCount, totalPlayDiff))
|
||
}
|
||
|
||
// Ranked谱面游玩记数
|
||
rankedPlayDiff := p.RankedPlayCount - lastDayQueryData.RankedPlayCount
|
||
if rankedPlayDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("Ranked谱面游玩记数 %d\n", p.RankedPlayCount))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("Ranked谱面游玩记数 %d(%+d)\n", p.RankedPlayCount, rankedPlayDiff))
|
||
}
|
||
// 回放被观看次数
|
||
sb.WriteString(fmt.Sprintf("回放被观看次数 %d", p.ReplaysWatched))
|
||
|
||
return sb.String()
|
||
}
|
||
|
||
func (p PlayerDataLite) LastDiffToImage(lastDayQueryData PlayerDataLite) string {
|
||
filePath, err := util.DownloadFile(p.Avatar, "/tmp/qqbot", false)
|
||
if err != nil {
|
||
log.Default().Printf("下载头像失败,url:%s,err:%v", p.Avatar, err)
|
||
}
|
||
defer os.Remove(filePath)
|
||
|
||
baseboard := sprite.NewNamedSpriteBoard()
|
||
|
||
avatar, err := util.ResizeImageByMaxHeight2Image(filePath, 50)
|
||
if err != nil {
|
||
log.Default().Printf("缩放头像失败,url:%s,err:%v", p.Avatar, err)
|
||
}
|
||
avatarSpirit := sprite.Sprite{
|
||
Name: "avatar",
|
||
Image: avatar,
|
||
Index: 1,
|
||
}
|
||
if avatar == nil {
|
||
avatarSpirit.Image = image.NewRGBA(image.Rect(0, 0, 0, 0))
|
||
}
|
||
baseboard.AddSprite(&avatarSpirit)
|
||
|
||
var sb strings.Builder
|
||
sb.WriteString(fmt.Sprintf("玩家 %s\n", p.Name))
|
||
sb.WriteString(fmt.Sprintf("区域 %s\n", p.Country))
|
||
|
||
// PP值
|
||
ppDiff := p.PP - lastDayQueryData.PP
|
||
if ppDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("PP %.1f\n", p.PP))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("PP %.1f(%+.1f)\n", p.PP, ppDiff))
|
||
} // 全球排名
|
||
rankDiff := lastDayQueryData.Rank - p.Rank
|
||
if rankDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("全球排名 %d\n", p.Rank))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("全球排名 %d(%+d)\n", p.Rank, rankDiff))
|
||
}
|
||
|
||
// 区域排名
|
||
countryRankDiff := lastDayQueryData.CountryRank - p.CountryRank
|
||
if countryRankDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("区域排名 %d\n", p.CountryRank))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("区域排名 %d(%+d)\n", p.CountryRank, countryRankDiff))
|
||
}
|
||
|
||
// Ranked谱面均准
|
||
accDiff := (p.AverageRankedAccuracy - lastDayQueryData.AverageRankedAccuracy) * 100
|
||
if accDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("Ranked谱面均准 %.2f%%\n", p.AverageRankedAccuracy*100))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("Ranked谱面均准 %.2f%%(%+.2f%%)\n", p.AverageRankedAccuracy*100, accDiff))
|
||
}
|
||
// 总游玩记数
|
||
totalPlayDiff := p.TotalPlayCount - lastDayQueryData.TotalPlayCount
|
||
if totalPlayDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("总游玩记数 %d\n", p.TotalPlayCount))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("总游玩记数 %d(%+d)\n", p.TotalPlayCount, totalPlayDiff))
|
||
}
|
||
|
||
// Ranked谱面游玩记数
|
||
rankedPlayDiff := p.RankedPlayCount - lastDayQueryData.RankedPlayCount
|
||
if rankedPlayDiff == 0 {
|
||
sb.WriteString(fmt.Sprintf("Ranked谱面游玩记数 %d\n", p.RankedPlayCount))
|
||
} else {
|
||
sb.WriteString(fmt.Sprintf("Ranked谱面游玩记数 %d(%+d)\n", p.RankedPlayCount, rankedPlayDiff))
|
||
}
|
||
// 回放被观看次数
|
||
sb.WriteString(fmt.Sprintf("回放被观看次数 %d", p.ReplaysWatched))
|
||
|
||
text := sb.String()
|
||
textImg, err := font2img.RenderTextToTrimmedImage(nil, text, 12, color.Black, 0, 0)
|
||
if err != nil {
|
||
log.Default().Printf("渲染文字失败,err:%v", err)
|
||
}
|
||
textSpirit := sprite.Sprite{
|
||
Name: "text",
|
||
Image: textImg,
|
||
Index: 2,
|
||
Position: image.Point{X: avatarSpirit.Position.X + avatarSpirit.Image.Bounds().Dx() + 3, Y: 0},
|
||
}
|
||
baseboard.AddSprite(&textSpirit)
|
||
|
||
minX, minY, maxX, maxY := baseboard.GetRenderBounds()
|
||
totalWidth := maxX - minX
|
||
totalHeight := maxY - minY
|
||
|
||
background := image.NewRGBA(image.Rect(0, 0, int(totalWidth+10), int(totalHeight+10)))
|
||
draw.Draw(background, background.Bounds(), image.White, image.Point{}, draw.Src)
|
||
backgroundSpirit := sprite.Sprite{
|
||
Name: "background",
|
||
Image: background,
|
||
Index: 0,
|
||
Position: image.Point{X: minX - 5, Y: minY - 5},
|
||
}
|
||
baseboard.AddSprite(&backgroundSpirit)
|
||
|
||
if err := baseboard.SaveToPng(util.GenTempFilePath("cbl.png")); err != nil {
|
||
log.Default().Printf("保存图片失败,err:%v", err)
|
||
}
|
||
return util.GenTempFilePath("cbl.png")
|
||
}
|
||
|
||
func GetControllerStr(controller int) string {
|
||
switch controller {
|
||
case 1:
|
||
return "Oculus Touch 手柄"
|
||
case 16:
|
||
return "Oculus Touch 2 手柄"
|
||
case 79:
|
||
return "Meta Quest 3 手柄"
|
||
case 256:
|
||
return "Quest 2 手柄"
|
||
case 2:
|
||
return "VIVE wands"
|
||
case 4:
|
||
return "VIVE 2 wands"
|
||
case 128:
|
||
return "VIVE cosmos controllers"
|
||
case 8:
|
||
return "WMR 手柄"
|
||
case 33:
|
||
return "Pico 手柄"
|
||
case 34:
|
||
return "Pico 手柄"
|
||
case 35:
|
||
return "VIVE Pro dudads"
|
||
case 37:
|
||
return "Miramar 手柄"
|
||
case 44:
|
||
return "disco 手柄"
|
||
case 61:
|
||
return "Quest Pro 手柄"
|
||
case 62:
|
||
return "VIVE tracker"
|
||
case 63:
|
||
return "VIVE tracker 2"
|
||
case 64:
|
||
return "Knuckles"
|
||
case 65:
|
||
return "nolo手柄"
|
||
case 66:
|
||
return "Pico phoenix"
|
||
case 67:
|
||
return "双手🙌"
|
||
case 68:
|
||
return "VIVE tracker 3"
|
||
case 75:
|
||
return "游戏手柄🎮"
|
||
case 76:
|
||
return "Joy-Con"
|
||
case 77:
|
||
return "Steam Deck"
|
||
case 78:
|
||
return "Etee"
|
||
}
|
||
return "神秘手柄"
|
||
}
|
||
|
||
func GetHMDStr(hmd int) string {
|
||
switch hmd {
|
||
case 256:
|
||
return "Quest 2"
|
||
case 512:
|
||
return "Quest 3"
|
||
case 64:
|
||
return "Valve Index"
|
||
case 513:
|
||
return "Quest 3S"
|
||
case 1:
|
||
return "Rift CV1"
|
||
case 2:
|
||
return "Vive"
|
||
case 60:
|
||
return "Pico 4"
|
||
case 61:
|
||
return "Quest Pro"
|
||
case 70:
|
||
return "PS VR2"
|
||
case 8:
|
||
return "WMR"
|
||
case 16:
|
||
return "Rift S"
|
||
case 65:
|
||
return "Controllable"
|
||
case 32:
|
||
return "Quest"
|
||
case 4:
|
||
return "Vive Pro"
|
||
case 35:
|
||
return "Vive Pro 2"
|
||
case 128:
|
||
return "Vive Cosmos"
|
||
case 36:
|
||
return "Vive Elite"
|
||
case 47:
|
||
return "Vive Focus"
|
||
case 38:
|
||
return "Pimax 8K"
|
||
case 39:
|
||
return "Pimax 5K"
|
||
case 40:
|
||
return "Pimax Artisan"
|
||
case 33:
|
||
return "Pico Neo 3"
|
||
case 34:
|
||
return "Pico Neo 2"
|
||
case 41:
|
||
return "HP Reverb"
|
||
case 42:
|
||
return "Samsung WMR"
|
||
case 43:
|
||
return "Qiyu Dream"
|
||
case 45:
|
||
return "Lenovo Explorer"
|
||
case 46:
|
||
return "Acer WMR"
|
||
case 66:
|
||
return "Bigscreen Beyond"
|
||
case 67:
|
||
return "NOLO Sonic"
|
||
case 68:
|
||
return "Hypereal"
|
||
case 48:
|
||
return "Arpara"
|
||
case 49:
|
||
return "Dell Visor"
|
||
case 71:
|
||
return "MeganeX VG1"
|
||
case 55:
|
||
return "Huawei VR"
|
||
case 56:
|
||
return "Asus WMR"
|
||
case 51:
|
||
return "Vive DVT"
|
||
case 52:
|
||
return "glasses20"
|
||
case 53:
|
||
return "Varjo"
|
||
case 69:
|
||
return "Varjo Aero"
|
||
case 54:
|
||
return "Vaporeon"
|
||
case 57:
|
||
return "Cloud XR"
|
||
case 58:
|
||
return "VRidge"
|
||
case 50:
|
||
return "e3"
|
||
case 59:
|
||
return "Medion Eraser"
|
||
case 37:
|
||
return "Miramar"
|
||
case 0:
|
||
return "Unknown headset"
|
||
case 44:
|
||
return "Disco"
|
||
default:
|
||
return "神秘头显"
|
||
}
|
||
}
|