qq_bot/handler/beatleader/beatleader.go
2025-05-05 03:42:33 +08:00

344 lines
9.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package beatleader
import (
"fmt"
"log"
"strconv"
"strings"
"sync"
"git.lxtend.com/qqbot/action"
"git.lxtend.com/qqbot/constants"
"git.lxtend.com/qqbot/handler"
"git.lxtend.com/qqbot/message"
"git.lxtend.com/qqbot/model"
"git.lxtend.com/qqbot/service/beatleader"
"git.lxtend.com/qqbot/service/scoresaber"
"git.lxtend.com/qqbot/util"
)
func init() {
handler.RegisterHandler("查bl", getMyBL, constants.LEVEL_USER)
handler.RegisterHelpInform("查bl", "beatleader", "查bl 查看您的最新分数")
handler.RegisterHandler("绑定bl", bindBL, constants.LEVEL_USER)
handler.RegisterHelpInform("绑定bl", "beatleader", "绑定bl 绑定您的beatleader账号")
handler.RegisterHandler("解绑bl", unbindBL, constants.LEVEL_USER)
handler.RegisterHelpInform("解绑bl", "beatleader", "解绑bl 解绑您的beatleader账号")
handler.RegisterHandler("最新bl", getMyRecentScore, constants.LEVEL_USER)
handler.RegisterHelpInform("最新bl", "beatleader", "最新bl 查看您的最新游戏记录")
handler.RegisterFrontMatchHandler("bl+", blPlus, constants.LEVEL_USER)
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主页截图")
}
func blPlus(msg model.Message) (reply *model.Reply) {
var (
resultStr strings.Builder
err error
maxRetries = 5 // 最大重试次数
attempts = 0
)
var N int
var isPlus bool
if strings.HasPrefix(msg.RawMsg, "bl+") {
isPlus = true
} else if strings.HasPrefix(msg.RawMsg, "bl-") {
isPlus = false
} else {
return &model.Reply{
ReplyMsg: "请输入bl+或bl-",
ReferOriginMsg: true,
FromMsg: msg,
}
}
if len(msg.RawMsg) > len("bl+") {
N, err = strconv.Atoi(msg.RawMsg[len("bl+"):])
if err != nil || N <= 0 {
return &model.Reply{
ReplyMsg: "请输入一个整数",
ReferOriginMsg: true,
FromMsg: msg,
}
}
}
if !isPlus {
N = -N
}
userIdStr := strconv.Itoa(int(msg.UserId))
userBLID, err := scoresaber.GetSSID(userIdStr)
if err != nil {
return &model.Reply{
ReplyMsg: err.Error(),
ReferOriginMsg: true,
FromMsg: msg,
}
}
var userInfo *beatleader.PlayerData
for attempts < maxRetries {
err = nil
userInfo, err = beatleader.FetchPlayerData(userBLID)
if err != nil {
break
}
attempts++
}
if err != nil {
return &model.Reply{
ReplyMsg: "获取您的分数时出现问题,请稍后重试。" + err.Error(),
ReferOriginMsg: true,
FromMsg: msg,
}
}
if userInfo.PP == 0 {
return &model.Reply{
ReplyMsg: "请先打几首Rank谱面再来使用此功能",
ReferOriginMsg: true,
FromMsg: msg,
}
}
resultStr.WriteString(fmt.Sprintf("您当前的BeatLeader全区排名为%d", userInfo.CountryRank))
// 获取当前用户所在区对应 + N位的玩家列表
leaderboard, err := beatleader.FetchCountryLeaderboard(userInfo.Country, userInfo.CountryRank-N, userInfo.ID)
if err != nil {
return &model.Reply{
ReplyMsg: "获取排行榜时出现问题,请稍后重试。" + err.Error(),
ReferOriginMsg: true,
FromMsg: msg,
}
}
if userInfo.CountryRank-N <= 0 {
resultStr.WriteString("\n")
if userInfo.CountryRank == 1 {
resultStr.WriteString(fmt.Sprintf("注意:你已经是%s区Top1了。", userInfo.Country))
} else {
resultStr.WriteString(fmt.Sprintf("注意:你最多只需要提升%d名就是%s区Top1了。", userInfo.CountryRank-1, userInfo.Country))
}
}
if userInfo.CountryRank != 1 {
//寻找leaderboard中排名为userInfo.CountryRank-N的玩家
var targetPlayer beatleader.PlayerDataLite
targetRank := userInfo.CountryRank - N
if targetRank <= 0 {
targetRank = 1
}
if len(leaderboard) == 0 {
resultStr.WriteString("\n")
resultStr.WriteString("未找到目标排名的玩家,请尝试更小的目标排名偏移量")
return &model.Reply{
ReplyMsg: resultStr.String(),
ReferOriginMsg: true,
FromMsg: msg,
}
}
var foundTargetPlayer bool
for _, player := range leaderboard {
if player.CountryRank == targetRank {
targetPlayer = player
foundTargetPlayer = true
break
}
}
if !foundTargetPlayer {
resultStr.WriteString("\n")
resultStr.WriteString("未找到目标排名的玩家,请尝试更小的目标排名偏移量")
return &model.Reply{
ReplyMsg: resultStr.String(),
ReferOriginMsg: true,
FromMsg: msg,
}
}
resultStr.WriteString("\n")
if isPlus {
resultStr.WriteString(fmt.Sprintf("您只需要再打出%.2fpp就能超越 %s达到%s区第%d名。", targetPlayer.PP-userInfo.PP, targetPlayer.Name, userInfo.Country, targetPlayer.CountryRank))
} else {
resultStr.WriteString(fmt.Sprintf("%s区的第%d名是 %s对方只需要再打出%.2fpp就能超过你。", userInfo.Country, targetPlayer.CountryRank, targetPlayer.Name, userInfo.PP-targetPlayer.PP))
}
}
return &model.Reply{
ReplyMsg: resultStr.String(),
ReferOriginMsg: true,
FromMsg: msg,
}
}
func getMyBL(msg model.Message) (reply *model.Reply) {
var (
resultStr string
err error
maxRetries = 3 // 最大重试次数
attempts = 0
)
userIdStr := strconv.Itoa(int(msg.UserId))
for attempts < maxRetries {
resultStr, err = beatleader.BLQuery.GetScore(userIdStr)
if err == nil {
break // 成功时退出循环
}
attempts++
log.Printf("获取分数时出错,第 %d 次重试: %v", attempts, err)
}
// 如果所有尝试都失败,返回适当的错误消息
if err != nil {
resultStr = "获取您的分数时出现问题,请稍后重试。"
}
return &model.Reply{
ReplyMsg: resultStr,
ReferOriginMsg: true,
FromMsg: msg,
}
}
func bindBL(msg model.Message) (reply *model.Reply) {
if len(msg.RawMsg) <= len("绑定bl ") {
return &model.Reply{
ReplyMsg: "请输入绑定的beatleader账号",
ReferOriginMsg: true,
FromMsg: msg,
}
}
return &model.Reply{
ReplyMsg: beatleader.BLQuery.BindBL(strconv.Itoa(int(msg.UserId)), msg.RawMsg[len("绑定bl "):]),
ReferOriginMsg: true,
FromMsg: msg,
}
}
func unbindBL(msg model.Message) (reply *model.Reply) {
return &model.Reply{
ReplyMsg: beatleader.BLQuery.UnbindBL(strconv.Itoa(int(msg.UserId))),
ReferOriginMsg: true,
FromMsg: msg,
}
}
func getMyRecentScore(msg model.Message) (reply *model.Reply) {
count := 1
scoreMsg := []any{}
if len(msg.RawMsg) > len("最新bl ") {
var err error
count, err = strconv.Atoi(msg.RawMsg[len("最新bl "):])
if err != nil || count <= 0 {
return &model.Reply{
ReplyMsg: "",
ReferOriginMsg: true,
FromMsg: msg,
}
}
if count > 10 {
count = 10
}
}
var userName string
recordCount := 0
records, err := beatleader.BLQuery.GetRecentScores(count, strconv.Itoa(int(msg.UserId)))
if err != nil {
return &model.Reply{
ReplyMsg: err.Error(),
ReferOriginMsg: true,
FromMsg: msg,
}
}
// 单独线程下载封面图片
coverImageMap := make(map[string]string)
wg := sync.WaitGroup{}
for _, record := range records {
coverImageMap[record.SongHash] = record.CoverImage
wg.Add(1)
go func(songHash string) {
defer wg.Done()
filePath, err := util.DownloadFile(coverImageMap[songHash], constants.TempDir, true)
if err != nil {
log.Printf("下载图片失败: %v", err)
return
}
newPath, err := util.ResizeImageByMaxHeight(filePath, 20)
if err != nil {
log.Printf("缩放图片失败: %v", err)
}
coverImageMap[songHash] = newPath
}(record.SongHash)
}
wg.Wait()
for _, record := range records {
imageMsg := message.ImageMessage{
Type: "image",
Data: message.ImageMessageData{
File: coverImageMap[record.SongHash],
},
}
textMsg := message.TextMessage{
Type: "text",
Data: message.TextMessageData{
Text: record.ToString(),
},
}
scoreMsg = append(scoreMsg, imageMsg, textMsg)
userName = record.Name
recordCount++
}
if len(scoreMsg) <= 0 {
return &model.Reply{
ReplyMsg: "无最近游戏记录",
ReferOriginMsg: true,
FromMsg: msg,
}
}
//如果消息行数太多,使用合并转发
if len(records) > 5 {
nodeMsg := util.NewSelfNodeMessage(append(
[]any{
&message.TextMessage{
Type: "text",
Data: message.TextMessageData{
Text: "玩家 " + userName + " 的" + strconv.Itoa(recordCount) + "条最近记录为:\n",
},
},
},
scoreMsg...,
)...)
action.ActionManager.SendForward(
&model.Reply{
ReplyMsg: []any{nodeMsg},
ReferOriginMsg: false,
FromMsg: msg,
},
)
return nil
}
return &model.Reply{
ReplyMsg: append(
[]any{
message.TextMessage{
Type: "text",
Data: message.TextMessageData{
Text: "玩家 " + userName + " 的" + strconv.Itoa(recordCount) + "条最近记录为:\n",
},
},
},
scoreMsg...,
),
ReferOriginMsg: true,
FromMsg: msg,
}
}
func screenShotBL(msg model.Message) (reply *model.Reply) {
imageMsg := message.ImageMessage{
Type: "image",
Data: message.ImageMessageData{
File: "file:///tmp/qqbot/" + beatleader.GetBLPicture(strconv.Itoa(int(msg.UserId))),
},
}
return &model.Reply{
ReplyMsg: imageMsg.ToCQString(),
ReferOriginMsg: true,
FromMsg: msg,
}
}