diff --git a/handler/beatleader/beatleader.go b/handler/beatleader/beatleader.go index 634a1ad..9e732a4 100644 --- a/handler/beatleader/beatleader.go +++ b/handler/beatleader/beatleader.go @@ -30,9 +30,9 @@ func init() { handler.RegisterHelpInform("bl+n", "beatleader", "查看您需要打多少pp才能达到当前区服的第N名") handler.RegisterFrontMatchHandler("bl-", blPlus, constants.LEVEL_USER) handler.RegisterHelpInform("bl-n", "beatleader", "查看落后您N名的玩家需要打多少pp才会超过您") - // handler.RegisterHandler("截bl", screenShotBL, constants.LEVEL_USER) - // handler.RegisterHandler("jbl", screenShotBL, constants.LEVEL_USER) - // handler.RegisterHelpInform("截bl", "beatleader", "截bl 截bl 截bl主页截图") + handler.RegisterHandler("截bl", screenShotBL, constants.LEVEL_USER) + handler.RegisterHandler("jbl", screenShotBL, constants.LEVEL_USER) + handler.RegisterHelpInform("截bl", "beatleader", "截bl 截bl 截bl主页截图") } func blPlus(msg model.Message) (reply *model.Reply) { @@ -175,15 +175,21 @@ func getMyBLPic(msg model.Message) (reply *model.Reply) { tokens := util.SplitN(msg.RawMsg, 2) var userIdStr string + var noUpdate bool = false if len(tokens) == 2 { userIdStr = tokens[1] } else { userIdStr = strconv.Itoa(int(msg.UserId)) + noUpdate = true } var data *beatleader.PlayerDataLite var lastData *beatleader.PlayerDataLite for attempts < maxRetries { - data, lastData, err = beatleader.BLQuery.GetScore(userIdStr) + if noUpdate { + data, err = beatleader.BLQuery.GetScoreWithoutUpdate(userIdStr) + } else { + data, lastData, err = beatleader.BLQuery.GetScore(userIdStr) + } if err == nil { break // 成功时退出循环 } @@ -193,7 +199,11 @@ func getMyBLPic(msg model.Message) (reply *model.Reply) { // 如果所有尝试都失败,返回适当的错误消息 if err != nil { - resultStr = "获取您的分数时出现问题,请稍后重试。" + return &model.Reply{ + ReplyMsg: "获取您的分数时出现问题,请稍后重试。" + err.Error(), + ReferOriginMsg: true, + FromMsg: msg, + } } if lastData != nil { diff --git a/handler/scoresaber/score.go b/handler/scoresaber/score.go index 1adba06..2e9602f 100644 --- a/handler/scoresaber/score.go +++ b/handler/scoresaber/score.go @@ -17,7 +17,7 @@ import ( ) func init() { - handler.RegisterHandler("查ss", getSSProfile, constants.LEVEL_USER) + handler.RegisterHandler("查ss", getMySSPic, constants.LEVEL_USER) handler.RegisterHelpInform("查ss", "scoresaber", " 查看您的最新分数") handler.RegisterHandler("绑定ss", bindSS, constants.LEVEL_USER) handler.RegisterHelpInform("绑定ss", "scoresaber", "绑定您的scoresaber账号") @@ -75,7 +75,7 @@ func ssPlusN(msg model.Message) (reply *model.Reply) { FromMsg: msg, } } - var userInfo scoresaber.PlayerData + var userInfo *scoresaber.PlayerData for attempts < maxRetries { err = nil userInfo, err = scoresaber.FetchPlayerData(userSSID) @@ -163,7 +163,7 @@ func ssPlusN(msg model.Message) (reply *model.Reply) { } } -func getSSProfile(msg model.Message) (reply *model.Reply) { +func getMySSPic(msg model.Message) (reply *model.Reply) { var ( resultStr string err error @@ -171,32 +171,24 @@ func getSSProfile(msg model.Message) (reply *model.Reply) { attempts = 0 noUpdate = false ) - var ssId string + var userIdStr string if len(msg.RawMsg) > len("查ss ") { - ssId = strings.Split(msg.RawMsg, " ")[1] + userIdStr = strings.Split(msg.RawMsg, " ")[1] noUpdate = true + } else { + userIdStr = strconv.Itoa(int(msg.UserId)) } - userIdStr := strconv.Itoa(int(msg.UserId)) + var data *scoresaber.PlayerDataLite + var lastData *scoresaber.PlayerDataLite for attempts < maxRetries { - err = nil - if ssId == "" { - ssId, err = scoresaber.GetSSID(userIdStr) - } - if err != nil { - return &model.Reply{ - ReplyMsg: "您未绑定ss账号,输入\"绑定ss [ssId]\"绑定(ssId为您的scoresaber主页链接中的数字部分)", - ReferOriginMsg: true, - FromMsg: msg, - } - } - if !noUpdate { - resultStr, err = scoresaber.SSQuery.GetScore(ssId) + if noUpdate { + data, err = scoresaber.SSQuery.GetScoreWithoutUpdate(userIdStr) } else { - resultStr, err = scoresaber.SSQuery.GetScoreWithoutUpdate(ssId) + data, lastData, err = scoresaber.SSQuery.GetScore(userIdStr) } if err == nil { - break + break // 成功时退出循环 } attempts++ log.Printf("获取分数时出错,第 %d 次重试: %v", attempts, err) @@ -204,11 +196,27 @@ func getSSProfile(msg model.Message) (reply *model.Reply) { // 如果所有尝试都失败,返回适当的错误消息 if err != nil { - resultStr = "获取您的分数时出现问题,请稍后重试。" + err.Error() + return &model.Reply{ + ReplyMsg: "获取您的分数时出现问题,请稍后重试。" + err.Error(), + ReferOriginMsg: true, + FromMsg: msg, + } + } + if lastData != nil { + resultStr = data.LastDiffToImage(*lastData) + } else { + resultStr = data.LastDiffToImage(*data) + } + + imageMsg := message.ImageMessage{ + Type: "image", + Data: message.ImageMessageData{ + File: resultStr, + }, } return &model.Reply{ - ReplyMsg: resultStr, + ReplyMsg: imageMsg.ToCQString(), ReferOriginMsg: true, FromMsg: msg, } diff --git a/service/beatleader/bind_bl.go b/service/beatleader/bind_bl.go index efa76ad..c2e61c5 100644 --- a/service/beatleader/bind_bl.go +++ b/service/beatleader/bind_bl.go @@ -174,22 +174,7 @@ func (bl *blQuery) GetScore(qqId string) (currentData *PlayerDataLite, lastData } // 构建 PlayerDataLite 结构体 - dataLite := PlayerDataLite{ - ID: data.ID, - Name: data.Name, - Country: data.Country, - Avatar: data.Avatar, - 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"), - } + dataLite := data.ToDataLite() // 查询最近的玩家数据 var lastDataLite PlayerDataLite @@ -232,6 +217,24 @@ func (bl *blQuery) GetScore(qqId string) (currentData *PlayerDataLite, lastData return &dataLite, nil, nil } +func (bl *blQuery) GetScoreWithoutUpdate(qqId string) (currentData *PlayerDataLite, err error) { + blId, err := getBLID(qqId) + if err != nil { + return nil, err + } + // 查询玩家数据 + data, err := FetchPlayerData(blId) + if err != nil { + return nil, errors.New("查询出错,报错如下" + err.Error()) + } + if data.ID == "" { + return nil, errors.New("未找到玩家,请检查ID后重试") + } + dataLite := data.ToDataLite() + // 返回当前数据的字符串表示 + return &dataLite, nil +} + func (bl *blQuery) SaveRecord(scoreData ScoreData) { db := sqlite3.GetDB() // 假设 sqlite3.GetDB() 返回 *sqlx.DB tx, err := db.Beginx() diff --git a/service/beatleader/model.go b/service/beatleader/model.go index 0440955..aad17db 100644 --- a/service/beatleader/model.go +++ b/service/beatleader/model.go @@ -289,6 +289,26 @@ type PlayerData struct { // Clans []string `json:"clans"` } +func (p PlayerData) ToDataLite() PlayerDataLite { + dataLite := PlayerDataLite{ + ID: p.ID, + Name: p.Name, + Country: p.Country, + Avatar: p.Avatar, + PP: p.AccPp + p.PassPp + p.TechPp, + Rank: p.Rank, + CountryRank: p.CountryRank, + TotalScore: p.ScoreStats.TotalScore, + TotalRankedScore: p.ScoreStats.TotalRankedScore, + AverageRankedAccuracy: p.ScoreStats.AverageRankedAccuracy, + TotalPlayCount: p.ScoreStats.TotalPlayCount, + RankedPlayCount: p.ScoreStats.RankedPlayCount, + ReplaysWatched: p.ScoreStats.WatchedReplays, + GeneratedTime: time.Now().Format("2006-01-02 15:04:05.999999999-07:00"), + } + return dataLite +} + type LinkedIDs struct { QuestID int `json:"questId"` SteamID string `json:"steamId"` @@ -413,46 +433,7 @@ func (p PlayerDataLite) IsDiffFrom(p2 PlayerDataLite) bool { 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 { +func (p PlayerDataLite) LastDiffToString(lastQueryData PlayerDataLite) string { filePath, err := util.DownloadFile(p.Avatar, "/tmp/qqbot", false) if err != nil { log.Default().Printf("下载头像失败,url:%s,err:%v", p.Avatar, err) @@ -475,13 +456,13 @@ func (p PlayerDataLite) LastDiffToString(lastDayQueryData PlayerDataLite) string sb.WriteString(fmt.Sprintf("区域 %s\n", p.Country)) // PP值 - ppDiff := p.PP - lastDayQueryData.PP + ppDiff := p.PP - lastQueryData.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 + rankDiff := lastQueryData.Rank - p.Rank if rankDiff == 0 { sb.WriteString(fmt.Sprintf("全球排名 %d\n", p.Rank)) } else { @@ -489,7 +470,7 @@ func (p PlayerDataLite) LastDiffToString(lastDayQueryData PlayerDataLite) string } // 区域排名 - countryRankDiff := lastDayQueryData.CountryRank - p.CountryRank + countryRankDiff := lastQueryData.CountryRank - p.CountryRank if countryRankDiff == 0 { sb.WriteString(fmt.Sprintf("区域排名 %d\n", p.CountryRank)) } else { @@ -497,14 +478,14 @@ func (p PlayerDataLite) LastDiffToString(lastDayQueryData PlayerDataLite) string } // Ranked谱面均准 - accDiff := (p.AverageRankedAccuracy - lastDayQueryData.AverageRankedAccuracy) * 100 + accDiff := (p.AverageRankedAccuracy - lastQueryData.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 + totalPlayDiff := p.TotalPlayCount - lastQueryData.TotalPlayCount if totalPlayDiff == 0 { sb.WriteString(fmt.Sprintf("总游玩记数 %d\n", p.TotalPlayCount)) } else { @@ -512,7 +493,7 @@ func (p PlayerDataLite) LastDiffToString(lastDayQueryData PlayerDataLite) string } // Ranked谱面游玩记数 - rankedPlayDiff := p.RankedPlayCount - lastDayQueryData.RankedPlayCount + rankedPlayDiff := p.RankedPlayCount - lastQueryData.RankedPlayCount if rankedPlayDiff == 0 { sb.WriteString(fmt.Sprintf("Ranked谱面游玩记数 %d\n", p.RankedPlayCount)) } else { @@ -524,7 +505,7 @@ func (p PlayerDataLite) LastDiffToString(lastDayQueryData PlayerDataLite) string return sb.String() } -func (p PlayerDataLite) LastDiffToImage(lastDayQueryData PlayerDataLite) string { +func (p PlayerDataLite) LastDiffToImage(lastQueryData PlayerDataLite) string { filePath, err := util.DownloadFile(p.Avatar, "/tmp/qqbot", false) if err != nil { log.Default().Printf("下载头像失败,url:%s,err:%v", p.Avatar, err) @@ -550,54 +531,54 @@ func (p PlayerDataLite) LastDiffToImage(lastDayQueryData PlayerDataLite) string var sb strings.Builder sb.WriteString(fmt.Sprintf("玩家 %s\n", p.Name)) sb.WriteString(fmt.Sprintf("区域 %s\n", p.Country)) + { + // PP值 + ppDiff := p.PP - lastQueryData.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 := lastQueryData.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)) + } - // 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 := lastQueryData.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)) + } - // 区域排名 - 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 - lastQueryData.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 - lastQueryData.TotalPlayCount + if totalPlayDiff == 0 { + sb.WriteString(fmt.Sprintf("总游玩记数 %d\n", p.TotalPlayCount)) + } else { + sb.WriteString(fmt.Sprintf("总游玩记数 %d(%+d)\n", p.TotalPlayCount, totalPlayDiff)) + } - // 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)) + // Ranked谱面游玩记数 + rankedPlayDiff := p.RankedPlayCount - lastQueryData.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)) } - // 总游玩记数 - 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 { diff --git a/service/scoresaber/bind_ss.go b/service/scoresaber/bind_ss.go index 56dcc45..907f1e6 100644 --- a/service/scoresaber/bind_ss.go +++ b/service/scoresaber/bind_ss.go @@ -156,27 +156,34 @@ func (ss *ssQuery) UnbindSS(qqId string) (reply string) { return "解绑成功,重新绑定请输入\"绑定ss [ssId]\"" } -func (ss *ssQuery) GetScore(ssId string) (reply string, err error) { +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 "数据库连接失败,请稍后重试", err + return nil, nil, err } defer tx.Rollback() + ssId, err := GetSSID(qqId) + if err != nil { + return nil, nil, err + } + // 查询玩家数据 data, err := FetchPlayerData(ssId) - if err != nil { - return "查询出错,报错如下" + err.Error(), errors.New("查询出错,报错如下" + err.Error()) + if data == nil && err == nil { + return nil, nil, errors.New("查询出错,服务器返回了空数据") } - if data.ID == "" { - return "未找到玩家,请检查ID后重试", errors.New("未找到玩家,请检查ID后重试") + 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, @@ -195,7 +202,7 @@ func (ss *ssQuery) GetScore(ssId string) (reply string, err error) { 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 "查询历史数据时出错", err + return nil, nil, err } // 如果有新的数据,则插入 @@ -206,42 +213,47 @@ func (ss *ssQuery) GetScore(ssId string) (reply string, err error) { :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 + return nil, nil, err } // 提交事务 err = tx.Commit() if err != nil { log.Print(err) - return "SQL事务提交失败,请重试", err + return nil, nil, err } // 返回差异信息 - return data.LastDiffToString(lastDataLite), nil + return &dataLite, &lastDataLite, nil } // 如果没有新数据,直接提交事务 err = tx.Commit() if err != nil { log.Print(err) - return "SQL事务提交失败,请重试", err + return nil, nil, err } // 返回当前数据的字符串表示 - return data.ToString(), nil + return &dataLite, nil, nil } -func (ss *ssQuery) GetScoreWithoutUpdate(ssId string) (reply string, err error) { +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 "查询出错,报错如下" + err.Error(), errors.New("查询出错,报错如下" + err.Error()) + return nil, errors.New("查询出错,报错如下" + err.Error()) } if data.ID == "" { - return "未找到玩家,请检查ID后重试", errors.New("未找到玩家,请检查ID后重试") + return nil, errors.New("未找到玩家,请检查ID后重试") } + dataLite := data.ToDataLite() // 返回当前数据的字符串表示 - return data.ToString(), nil + return &dataLite, nil } func (ss *ssQuery) SaveRecord(cmdData CommandData) { diff --git a/service/scoresaber/model.go b/service/scoresaber/model.go index 47dfce8..be30b5b 100644 --- a/service/scoresaber/model.go +++ b/service/scoresaber/model.go @@ -2,11 +2,16 @@ package scoresaber 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" ) @@ -200,6 +205,7 @@ type PlayerData struct { type PlayerDataLite struct { ID string `json:"id" db:"id"` Name string `json:"name" db:"name"` + ProfilePicture string `json:"profilePicture" db:"profile_picture"` Country string `json:"country" db:"country"` Device string `json:"device" db:"device"` PP float64 `json:"pp" db:"pp"` @@ -261,26 +267,23 @@ func (p PlayerData) ToString() string { 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) ToDataLite() PlayerDataLite { + return PlayerDataLite{ + ID: p.ID, + Name: p.Name, + ProfilePicture: p.ProfilePicture, + Country: p.Country, + PP: p.PP, + Rank: p.Rank, + CountryRank: p.CountryRank, + TotalScore: p.ScoreStats.TotalScore, + TotalRankedScore: p.ScoreStats.TotalRankedScore, + AverageRankedAccuracy: p.ScoreStats.AverageRankedAccuracy, + TotalPlayCount: p.ScoreStats.TotalPlayCount, + RankedPlayCount: p.ScoreStats.RankedPlayCount, + ReplaysWatched: p.ScoreStats.ReplaysWatched, + GeneratedTime: p.FirstSeen.Format(time.RFC3339), + } } func (p PlayerData) LastDiffToString(lastDayQueryData PlayerDataLite) string { @@ -320,3 +323,110 @@ func (p PlayerData) LastDiffToString(lastDayQueryData PlayerDataLite) string { p.ScoreStats.RankedPlayCount, p.ScoreStats.RankedPlayCount-lastDayQueryData.RankedPlayCount, p.ScoreStats.ReplaysWatched) } + +func (p PlayerDataLite) LastDiffToImage(lastQueryData PlayerDataLite) string { + filePath, err := util.DownloadFile(p.ProfilePicture, "/tmp/qqbot", false) + if err != nil { + log.Default().Printf("下载头像失败,url:%s,err:%v", p.ProfilePicture, 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.ProfilePicture, 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 - lastQueryData.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 := lastQueryData.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 := lastQueryData.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 - lastQueryData.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 - lastQueryData.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 - lastQueryData.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("css.png") +} diff --git a/service/scoresaber/user_info.go b/service/scoresaber/user_info.go index 00631fb..759fbd0 100644 --- a/service/scoresaber/user_info.go +++ b/service/scoresaber/user_info.go @@ -9,13 +9,13 @@ import ( ) // fetchPlayerData 函数请求 Scoresaber API,并解析完整的玩家信息 -func FetchPlayerData(ssID string) (PlayerData, error) { +func FetchPlayerData(ssID string) (*PlayerData, error) { url := fmt.Sprintf("https://scoresaber.com/api/player/%s/full", ssID) // 创建请求 req, err := http.NewRequest("GET", url, nil) if err != nil { - return PlayerData{}, err + return nil, err } // 设置请求头 @@ -41,7 +41,7 @@ func FetchPlayerData(ssID string) (PlayerData, error) { resp, err = client.Do(req) } if err != nil { - return PlayerData{}, err + return nil, err } defer resp.Body.Close() @@ -50,7 +50,7 @@ func FetchPlayerData(ssID string) (PlayerData, error) { if resp.Header.Get("Content-Encoding") == "gzip" { reader, err = gzip.NewReader(resp.Body) if err != nil { - return PlayerData{}, err + return nil, err } defer reader.(*gzip.Reader).Close() } else { @@ -62,10 +62,10 @@ func FetchPlayerData(ssID string) (PlayerData, error) { err = json.NewDecoder(reader).Decode(&playerData) if err != nil { // log.Printf("got body %v", reader.) - return PlayerData{}, err + return nil, err } - return playerData, nil + return &playerData, nil } // {