feat(cf): 新增查询CodeForces Rating功能,快来实践你的队友吧👁👁

This commit is contained in:
ClovertaTheTrilobita 2025-04-08 17:31:41 +08:00
parent 722be73e14
commit f30312fd6b
6 changed files with 140 additions and 6 deletions

View file

@ -41,3 +41,7 @@ typing_extensions
psutil psutil
Beautifulsoup4 Beautifulsoup4
aiohttp aiohttp
pydantic
matplotlib
httpx
crypto

View file

@ -0,0 +1,34 @@
from tortoise import fields
from src.clover_sqlite.data_init.db_connect import Model
class CodeForces(Model):
cf_id = fields.CharField(max_length=128, null=True, discription="用户codeforces账号")
user_id = fields.CharField(max_length=64, discription="用户openid")
class Meta:
table = "codeforces"
table_description = "cf表"
@classmethod
async def get_cf_id(cls, user_id: str | None):
cf_table = await CodeForces.filter(user_id=user_id).first()
if cf_table:
return cf_table.cf_id
else:
return None
@classmethod
async def insert_cf_id(cls, cf_id: str | None, user_id: str | None):
if not cf_id:
return -1
cf_table = await CodeForces.filter(user_id=user_id).first()
if cf_table:
cf_table.cf_id = cf_id
await cf_table.save()
return 1
else:
await cls.create(cf_id=cf_id, user_id=user_id)
return 0

View file

@ -53,3 +53,9 @@ wenku8_password = "<passwd>"
多米HTTP代理api 多米HTTP代理api
""" """
proxy_api = "<KEY>" proxy_api = "<KEY>"
"""
CodeForces API
"""
codeforces_key = "<KEY>"
codeforces_secret = "<KEY>"

View file

@ -10,6 +10,9 @@ os.makedirs(image_local_qq_image_path, exist_ok=True)
# 个人图片路径 # 个人图片路径
image_local_path= path+"/image/MaoYuNa" image_local_path= path+"/image/MaoYuNa"
os.makedirs(image_local_path, exist_ok=True) os.makedirs(image_local_path, exist_ok=True)
# cf ratings路径
rating_path = path+'/image/cf_ratings'
os.makedirs(rating_path, exist_ok=True)
# 塔罗牌图片路径 # 塔罗牌图片路径
tarots_img_path = path+'/image/tarot/TarotImages/' tarots_img_path = path+'/image/tarot/TarotImages/'
os.makedirs(tarots_img_path, exist_ok=True) os.makedirs(tarots_img_path, exist_ok=True)

View file

@ -10,11 +10,11 @@ from src.clover_sqlite.models.user import UserList
menu = ["/重启","/今日运势","/今日塔罗","/图","/随机图","/搜番","/日报","/点歌","/摸摸头","/群老婆","/今日老婆", "/开启ai","/关闭ai","/角色列表","/添加人设", "/更新人设", "/删除人设", "/切换人设", "/管理员注册", menu = ["/重启","/今日运势","/今日塔罗","/图","/随机图","/搜番","/日报","/点歌","/摸摸头","/群老婆","/今日老婆", "/开启ai","/关闭ai","/角色列表","/添加人设", "/更新人设", "/删除人设", "/切换人设", "/管理员注册",
"/待办", "/test","/天气","我喜欢你", "", "/待办查询", "/新建待办", "/删除待办" ,"/cf","/B站搜索", "/BV搜索", "/喜报", "/悲报", "/luxun","/鲁迅说", "/待办", "/test","/天气","我喜欢你", "", "/待办查询", "/新建待办", "/删除待办" ,"/cf","/B站搜索", "/BV搜索", "/喜报", "/悲报", "/luxun","/鲁迅说",
"/奶龙", "/repo", "/info", "/menu", "/轻小说","/本季新番","/下季新番","/新番观察","/绝对色感" ,"/jm"] "/奶龙", "/repo", "/info", "/menu", "/轻小说","/本季新番","/下季新番","/新番观察","/绝对色感" ,"/jm", "/cfrt"]
send_menu = ["/menu","/今日运势","/今日塔罗","/图","/随机图","搜番","/日报","/点歌","/摸摸头","/群老婆","/待办","/天气", send_menu = ["/menu","/今日运势","/今日塔罗","/图","/随机图","搜番","/日报","/点歌","/摸摸头","/群老婆","/待办","/天气",
"/待办查询", "/新建待办", "/删除待办" ,"/cf","/B站搜索", "/BV搜索", "/喜报", "/悲报","/鲁迅说", "/待办查询", "/新建待办", "/删除待办" ,"/cf","/B站搜索", "/BV搜索", "/喜报", "/悲报","/鲁迅说",
"/轻小说","/本季新番","/新番观察","/下季新番","/绝对色感","/jm"] "/轻小说","/本季新番","/新番观察","/下季新番","/绝对色感","https://codeforces.com/api/user.rating?handle=/jm"]
async def check_value_in_menu(message: MessageEvent) -> bool: async def check_value_in_menu(message: MessageEvent) -> bool:
value = message.get_plaintext().strip().split(" ") value = message.get_plaintext().strip().split(" ")
@ -30,7 +30,7 @@ async def check_value_in_menu(message: MessageEvent) -> bool:
return True return True
check = on_message(rule=to_me() & Rule(check_value_in_menu), priority=10) check = on_message(rule=to_me() & Rule(check_value_in_menu), priority=1)
@check.handle() @check.handle()
async def handle_function(message: MessageEvent): async def handle_function(message: MessageEvent):

View file

@ -3,7 +3,12 @@ from nonebot.plugin import on_command
from nonebot.rule import to_me from nonebot.rule import to_me
from nonebot.adapters.qq import Message, MessageEvent, MessageSegment from nonebot.adapters.qq import Message, MessageEvent, MessageSegment
from pathlib import Path from pathlib import Path
from src.configs.path_config import path
from src.clover_sqlite.models.codeforces import CodeForces
from src.configs.path_config import path, rating_path
from src.clover_sqlite.models.user import UserList
from datetime import datetime
import matplotlib.pyplot as plt
cf_query = on_command("cf", rule=to_me(), priority=10, block=True) cf_query = on_command("cf", rule=to_me(), priority=10, block=True)
@cf_query.handle() @cf_query.handle()
@ -34,6 +39,88 @@ async def get_cf_rounds():
await cf_query.finish(msg) await cf_query.finish(msg)
cf_ratings = on_command("cfrt", rule=to_me(), priority=10, block=True)
@cf_ratings.handle()
async def get_cf_ratings(message: MessageEvent):
content = message.get_plaintext().strip().split(" ")
if len(content) > 1:
status = await CodeForces.insert_cf_id(content[1], message.get_user_id())
if status == 1:
await cf_ratings.send(f"成功将cf账号换绑为{content[1]}🥳")
elif status == 0:
await cf_ratings.send(f"成功绑定cf账号{content[1]}🎉")
else:
await cf_ratings.finish(f"出现未知错误,我也不知道啥原因,但是能触发这个没什么用的异常也挺厉害的。")
cf_id = await CodeForces.get_cf_id(message.author.id)
if cf_id:
await cf_ratings.send("正在查询CodeForces Rating请稍后👀")
try:
results = requests.get("https://codeforces.com/api/user.rating?handle={}".format(cf_id)).json()
except BaseException:
await cf_ratings.finish("API请求失败这绝对不是咱的错绝对不是")
if results["status"] != "OK":
await cf_ratings.finish(f"未查询到该用户的信息哦,请检查用户名{cf_id}是否正确💦\n可使用 /cfrt+用户名 换绑")
# 准备绘图数据
timestamps = []
ratings = []
# 遍历所有比赛记录
for result in results["result"]:
# 提取时间戳和rating数据
timestamps.append(result["ratingUpdateTimeSeconds"])
ratings.append(result["newRating"])
if not timestamps:
await cf_ratings.finish("未找到Rating变化记录哦~")
# 转换时间戳为日期格式
dates = [datetime.fromtimestamp(ts) for ts in timestamps]
# 创建图表
plt.figure(figsize=(12, 6))
plt.plot(
dates,
ratings,
marker='o',
linestyle='-',
color='#2196F3',
linewidth=2,
markersize=6,
label='CF Rating'
)
# 美化图表
plt.title(f'Codeforces Rating Trend ({cf_id})', fontsize=14)
plt.xlabel('Contest Time', fontsize=12)
plt.ylabel('Rating', fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
# 自动旋转日期标签
plt.gcf().autofmt_xdate()
# 添加最新分数标注
last_rating = ratings[-1]
plt.annotate(f'{last_rating}',
xy=(dates[-1], last_rating),
xytext=(10, -20),
textcoords='offset points',
arrowprops=dict(arrowstyle="->"))
# 保存图片
save_path = rating_path + f'cf_rating_{cf_id}.png'
plt.savefig(save_path, bbox_inches='tight', dpi=300)
plt.close() # 释放内存
# 发送图片(根据你的机器人框架调整发送逻辑)
await cf_ratings.finish(MessageSegment.file_image(Path(save_path)))
else:
await cf_ratings.send("您还未绑定CodeForces账户\n请输入 /cfrt+账户名 来绑定吧。")
def get_match_phase(phase): def get_match_phase(phase):
phase_map = { phase_map = {
"BEFORE": "未开始", "BEFORE": "未开始",