package scoresaber 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/scoresaber" "git.lxtend.com/qqbot/util" ) func init() { handler.RegisterHandler("查ss", getSSProfile, constants.LEVEL_USER) handler.RegisterHelpInform("查ss", "scoresaber", " 查看您的最新分数") handler.RegisterHandler("绑定ss", bindSS, constants.LEVEL_USER) handler.RegisterHelpInform("绑定ss", "scoresaber", "绑定您的scoresaber账号") handler.RegisterHandler("解绑ss", unbindSS, constants.LEVEL_USER) handler.RegisterHelpInform("解绑ss", "scoresaber", "解绑您的scoresaber账号") handler.RegisterHandler("最新ss", getMyRecentScore, constants.LEVEL_USER) handler.RegisterHelpInform("最新ss", "scoresaber", "查看您的最新游戏记录") handler.RegisterHandler("截ss", screenshotSS, constants.LEVEL_USER) handler.RegisterHelpInform("ss+n", "scoresaber", "查看您需要打多少pp才能达到当前区服的第N名") handler.RegisterFrontMatchHandler("ss+", ssPlusN, constants.LEVEL_USER) handler.RegisterHelpInform("ss-n", "scoresaber", "查看落后您N名的玩家需要打多少pp才会超过您") handler.RegisterFrontMatchHandler("ss-", ssPlusN, constants.LEVEL_USER) } func ssPlusN(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, "ss+") { isPlus = true } else if strings.HasPrefix(msg.RawMsg, "ss-") { isPlus = false } else { return &model.Reply{ ReplyMsg: "请输入ss+或ss-", ReferOriginMsg: true, FromMsg: msg, } } if len(msg.RawMsg) > len("ss+") { N, err = strconv.Atoi(msg.RawMsg[len("ss+"):]) if err != nil || N <= 0 { return &model.Reply{ ReplyMsg: "请输入一个整数", ReferOriginMsg: true, FromMsg: msg, } } } if !isPlus { N = -N } // 获取当前用户在区中的排名 userIdStr := strconv.Itoa(int(msg.UserId)) userSSID, err := scoresaber.GetSSID(userIdStr) if err != nil { return &model.Reply{ ReplyMsg: err.Error(), ReferOriginMsg: true, FromMsg: msg, } } var userInfo scoresaber.PlayerData for attempts < maxRetries { err = nil userInfo, err = scoresaber.FetchPlayerData(userSSID) 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("您当前的ScoreSaber全区排名为:%d", userInfo.CountryRank)) // 获取当前用户所在区对应+N位的玩家列表 leaderboard, err := scoresaber.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 scoresaber.PlayerData targetRank := userInfo.CountryRank - N if targetRank <= 0 { targetRank = 1 } if len(leaderboard.Players) == 0 { resultStr.WriteString("\n") resultStr.WriteString("未找到目标排名的玩家,请尝试更小的目标排名偏移量") return &model.Reply{ ReplyMsg: resultStr.String(), ReferOriginMsg: true, FromMsg: msg, } } var foundTargetPlayer bool for _, player := range leaderboard.Players { 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 getSSProfile(msg model.Message) (reply *model.Reply) { var ( resultStr string err error maxRetries = 5 // 最大重试次数 attempts = 0 noUpdate = false ) var ssId string if len(msg.RawMsg) > len("查ss ") { ssId = strings.Split(msg.RawMsg, " ")[1] noUpdate = true } userIdStr := strconv.Itoa(int(msg.UserId)) 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) } else { resultStr, err = scoresaber.SSQuery.GetScoreWithoutUpdate(ssId) } if err == nil { break } attempts++ log.Printf("获取分数时出错,第 %d 次重试: %v", attempts, err) } // 如果所有尝试都失败,返回适当的错误消息 if err != nil { resultStr = "获取您的分数时出现问题,请稍后重试。" + err.Error() } return &model.Reply{ ReplyMsg: resultStr, ReferOriginMsg: true, FromMsg: msg, } } func bindSS(msg model.Message) (reply *model.Reply) { return &model.Reply{ ReplyMsg: scoresaber.SSQuery.BindSS(strconv.Itoa(int(msg.UserId)), msg.RawMsg[len("绑定ss "):]), ReferOriginMsg: true, FromMsg: msg, } } func unbindSS(msg model.Message) (reply *model.Reply) { return &model.Reply{ ReplyMsg: scoresaber.SSQuery.UnbindSS(strconv.Itoa(int(msg.UserId))), ReferOriginMsg: true, FromMsg: msg, } } func getMyRecentScore(msg model.Message) (reply *model.Reply) { count := 1 scoreMsg := "" if len(msg.RawMsg) > len("最新ss ") { var err error count, err = strconv.Atoi(msg.RawMsg[len("最新ss "):]) 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 := scoresaber.SSQuery.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], }, } scoreMsg += imageMsg.ToCQString() + record.ToString() + "\n" userName = record.Name recordCount++ } if len(scoreMsg) > 0 { scoreMsg = scoreMsg[:len(scoreMsg)-len("\n")] //去掉最后一个换行符 } else { return &model.Reply{ ReplyMsg: "无最近游戏记录", ReferOriginMsg: true, FromMsg: msg, } } //如果消息行数太多,使用合并转发 if len(records) > 5 { nodeMsg := util.NewSelfNodeMessage(&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: "玩家 " + userName + " 的" + strconv.Itoa(recordCount) + "条最近记录为:\n" + scoreMsg, ReferOriginMsg: true, FromMsg: msg, } } func screenshotSS(msg model.Message) (reply *model.Reply) { return &model.Reply{ ReplyMsg: "[CQ:image,file=file:///tmp/qqbot/" + scoresaber.GetSSPicture(strconv.Itoa(int(msg.UserId))) + "]", ReferOriginMsg: true, FromMsg: msg, } }