refactor: 优化 getMySSPic 函数,支持从消息中提取用户ID并处理头像为 nil 的情况,同时调整数据获取逻辑以提升代码灵活性

This commit is contained in:
lixiangwuxian
2025-05-11 02:12:13 +08:00
parent f20edf3e78
commit 22f09b7097
7 changed files with 301 additions and 177 deletions

View File

@@ -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) {

View File

@@ -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")
}

View File

@@ -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
}
// {