From f30312fd6b7d541bb1416dae536ba5fcda988824 Mon Sep 17 00:00:00 2001 From: ClovertaTheTrilobita Date: Tue, 8 Apr 2025 17:31:41 +0800 Subject: [PATCH] =?UTF-8?q?feat(cf):=20=E6=96=B0=E5=A2=9E=E6=9F=A5?= =?UTF-8?q?=E8=AF=A2CodeForces=20Rating=E5=8A=9F=E8=83=BD=EF=BC=8C?= =?UTF-8?q?=E5=BF=AB=E6=9D=A5=E5=AE=9E=E8=B7=B5=E4=BD=A0=E7=9A=84=E9=98=9F?= =?UTF-8?q?=E5=8F=8B=E5=90=A7=F0=9F=91=81=F0=9F=91=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 6 +- src/clover_sqlite/models/codeforces.py | 34 ++++++++++ src/configs/api_config_example.py | 8 ++- src/configs/path_config.py | 3 + src/plugins/check.py | 6 +- src/plugins/codeforces.py | 89 +++++++++++++++++++++++++- 6 files changed, 140 insertions(+), 6 deletions(-) create mode 100644 src/clover_sqlite/models/codeforces.py diff --git a/requirements.txt b/requirements.txt index 513bd9d..5d4b3df 100644 --- a/requirements.txt +++ b/requirements.txt @@ -40,4 +40,8 @@ openai typing_extensions psutil Beautifulsoup4 -aiohttp \ No newline at end of file +aiohttp +pydantic +matplotlib +httpx +crypto \ No newline at end of file diff --git a/src/clover_sqlite/models/codeforces.py b/src/clover_sqlite/models/codeforces.py new file mode 100644 index 0000000..2bf4c64 --- /dev/null +++ b/src/clover_sqlite/models/codeforces.py @@ -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 \ No newline at end of file diff --git a/src/configs/api_config_example.py b/src/configs/api_config_example.py index 6f62abc..f2df3e2 100644 --- a/src/configs/api_config_example.py +++ b/src/configs/api_config_example.py @@ -52,4 +52,10 @@ wenku8_password = "" """ 多米HTTP代理api """ -proxy_api = "" \ No newline at end of file +proxy_api = "" + +""" +CodeForces API +""" +codeforces_key = "" +codeforces_secret = "" \ No newline at end of file diff --git a/src/configs/path_config.py b/src/configs/path_config.py index 06afcd7..bc51c45 100644 --- a/src/configs/path_config.py +++ b/src/configs/path_config.py @@ -10,6 +10,9 @@ os.makedirs(image_local_qq_image_path, exist_ok=True) # 个人图片路径 image_local_path= path+"/image/MaoYuNa" 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/' os.makedirs(tarots_img_path, exist_ok=True) diff --git a/src/plugins/check.py b/src/plugins/check.py index 27e54b1..b1cc804 100644 --- a/src/plugins/check.py +++ b/src/plugins/check.py @@ -10,11 +10,11 @@ from src.clover_sqlite.models.user import UserList menu = ["/重启","/今日运势","/今日塔罗","/图","/随机图","/搜番","/日报","/点歌","/摸摸头","/群老婆","/今日老婆", "/开启ai","/关闭ai","/角色列表","/添加人设", "/更新人设", "/删除人设", "/切换人设", "/管理员注册", "/待办", "/test","/天气","我喜欢你", "❤", "/待办查询", "/新建待办", "/删除待办" ,"/cf","/B站搜索", "/BV搜索", "/喜报", "/悲报", "/luxun","/鲁迅说", - "/奶龙", "/repo", "/info", "/menu", "/轻小说","/本季新番","/下季新番","/新番观察","/绝对色感" ,"/jm"] + "/奶龙", "/repo", "/info", "/menu", "/轻小说","/本季新番","/下季新番","/新番观察","/绝对色感" ,"/jm", "/cfrt"] send_menu = ["/menu","/今日运势","/今日塔罗","/图","/随机图","搜番","/日报","/点歌","/摸摸头","/群老婆","/待办","/天气", "/待办查询", "/新建待办", "/删除待办" ,"/cf","/B站搜索", "/BV搜索", "/喜报", "/悲报","/鲁迅说", - "/轻小说","/本季新番","/新番观察","/下季新番","/绝对色感","/jm"] + "/轻小说","/本季新番","/新番观察","/下季新番","/绝对色感","https://codeforces.com/api/user.rating?handle=/jm"] async def check_value_in_menu(message: MessageEvent) -> bool: value = message.get_plaintext().strip().split(" ") @@ -30,7 +30,7 @@ async def check_value_in_menu(message: MessageEvent) -> bool: 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() async def handle_function(message: MessageEvent): diff --git a/src/plugins/codeforces.py b/src/plugins/codeforces.py index 9ae8ac2..79c8c34 100644 --- a/src/plugins/codeforces.py +++ b/src/plugins/codeforces.py @@ -3,7 +3,12 @@ from nonebot.plugin import on_command from nonebot.rule import to_me from nonebot.adapters.qq import Message, MessageEvent, MessageSegment 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.handle() @@ -34,6 +39,88 @@ async def get_cf_rounds(): 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): phase_map = { "BEFORE": "未开始",