├── log ├── __init__.py └── Log.py ├── oj_api ├── __init__.py ├── contest.py ├── atc_api.py ├── lc_api.py ├── nc_api.py └── cf_api.py ├── web_operation ├── __init__.py └── operation.py ├── requirements.txt ├── .gitignore ├── README.md └── main.py /log/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /oj_api/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /web_operation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | lxml 2 | httpx 3 | requests 4 | yiri-mirai 5 | apscheduler 6 | yiri-mirai-trigger -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | mirai 3 | img 4 | cftest.py 5 | test.py 6 | log.txt 7 | oj_json 8 | Pipfile 9 | Pipfile.lock 10 | cfrs.txt 11 | log/__pycache__/ 12 | oj_api/__pycache__/ 13 | web_operation/__pycache__ 14 | -------------------------------------------------------------------------------- /log/Log.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import time 4 | 5 | 6 | class Logger(object): 7 | def __init__(self, filename="log.txt"): 8 | self.terminal = sys.stdout 9 | self.log = open(filename, "a", encoding='utf-8') 10 | 11 | def write(self, message): 12 | if str(message).isspace() is False: 13 | self.terminal.write(str(message) + '\n') 14 | self.log.write(str(time.strftime('%Y-%m-%d %H:%M:%S\n', time.localtime())) + str(message) + '\n\n') 15 | self.log.flush() # 缓冲区的内容及时更新到log文件中 16 | 17 | def flush(self): 18 | pass 19 | 20 | 21 | if __name__ == '__main__': 22 | path = os.path.abspath(os.path.dirname(__file__)) 23 | sys.stdout = Logger() 24 | 25 | # 之后用print输出的就既在屏幕上,又在log文件里 26 | print(path) 27 | -------------------------------------------------------------------------------- /web_operation/operation.py: -------------------------------------------------------------------------------- 1 | import httpx 2 | import asyncio 3 | 4 | 5 | async def get_html(url): 6 | headers = { 7 | 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36", 8 | 'Connection': 'close' 9 | } 10 | 11 | # r = requests.get(url=url, headers=headers) 12 | async with httpx.AsyncClient() as client: 13 | r = await client.get(url=url, headers=headers) 14 | 15 | # r.encoding = r.apparent_encoding 16 | # await asyncio.sleep(5) 17 | return r.text 18 | 19 | 20 | async def get_json(url): 21 | headers = { 22 | 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36", 23 | 'Connection': 'close' 24 | } 25 | try: 26 | async with httpx.AsyncClient() as client: 27 | response = await client.get(url=url, headers=headers) 28 | json_data = response.json() 29 | # await asyncio.sleep(5) 30 | return json_data 31 | except: 32 | return -1 33 | -------------------------------------------------------------------------------- /oj_api/contest.py: -------------------------------------------------------------------------------- 1 | import abc 2 | import asyncio 3 | import time 4 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 5 | 6 | 7 | class Contest(metaclass=abc.ABCMeta): 8 | 9 | def __init__(self): 10 | self.info, self.begin_time, self.during_time = asyncio.run(self.get_next_contest()) 11 | self.note_time = self.begin_time - 15 * 60 12 | self.end_time = self.begin_time + self.during_time 13 | self.update_time = self.end_time + 10 * 60 14 | 15 | async def update_contest(self): 16 | if await self.update_local_contest(): 17 | self.info, self.begin_time, self.during_time = await self.get_next_contest() 18 | self.note_time = self.begin_time - 15 * 60 19 | self.end_time = self.begin_time + self.during_time 20 | self.update_time = self.end_time + 10 * 60 21 | print('更新比赛成功!') 22 | return True 23 | else: 24 | print('更新比赛失败!') 25 | return False 26 | 27 | @abc.abstractmethod 28 | async def get_next_contest(self): 29 | pass 30 | 31 | @abc.abstractmethod 32 | async def get_rating(self, name): 33 | pass 34 | 35 | @abc.abstractmethod 36 | async def update_local_contest(self): 37 | pass 38 | 39 | @abc.abstractmethod 40 | async def get_contest_info(self): 41 | pass 42 | -------------------------------------------------------------------------------- /oj_api/atc_api.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import time 4 | 5 | from web_operation.operation import * 6 | from oj_api.contest import Contest 7 | 8 | 9 | class ATC(Contest): 10 | async def get_contest(self): 11 | with open('./oj_json/contests.json', 'r', encoding='utf-8') as f: 12 | contest_data = json.load(f) 13 | contest_list = [] 14 | if contest_data != []: 15 | for contest in contest_data: 16 | if contest['source'] == 'AtCoder': 17 | contest['contestName'] = contest['name'] 18 | start_time = int(time.mktime(time.strptime( 19 | contest['start_time'], "%Y-%m-%dT%H:%M:%S+00:00"))) + 8 * 3600 20 | contest['startTime'] = start_time 21 | end_time = int(time.mktime(time.strptime( 22 | contest['end_time'], "%Y-%m-%dT%H:%M:%S+00:00"))) + 8 * 3600 23 | contest['endTime'] = end_time 24 | durationSeconds = contest['endTime'] - contest['startTime'] 25 | if durationSeconds <= 18000 and contest['startTime'] >= int(time.time()): 26 | contest_list.append([contest, durationSeconds]) 27 | return contest_list 28 | 29 | async def get_contest_info(self): 30 | contest_list = await self.get_contest() 31 | contest_len = len(contest_list) 32 | if contest_len == 0: 33 | return "最近没有比赛~" 34 | if contest_len > 3: 35 | contest_len = 3 36 | res = '找到最近的 {} 场ATC比赛为:\n'.format(contest_len) 37 | for i in range(contest_len): 38 | next_contest, durationSeconds = contest_list[i][0], contest_list[i][1] 39 | res += await self.format_atc_contest(next_contest, durationSeconds) 40 | return res.rstrip('\n') 41 | 42 | async def get_next_contest(self): 43 | contest_list = await self.get_contest() 44 | if not contest_list: 45 | return "最近没有比赛~", 32536700000, 0 46 | next_contest, durationSeconds = contest_list[0][0], contest_list[0][1] 47 | res = await self.format_atc_contest(next_contest, durationSeconds) 48 | return res.rstrip('\n'), int(next_contest['startTime']), durationSeconds 49 | 50 | async def get_recent_info(self): 51 | recent, _, _ = await self.get_next_contest() 52 | return "ATC比赛还有15分钟就开始啦,没有报名的尽快报名~\n" + recent 53 | 54 | async def format_atc_contest(self, next_contest, durationSeconds): 55 | res = "比赛名称:{}\n" \ 56 | "开始时间:{}\n" \ 57 | "持续时间:{}\n" \ 58 | "比赛地址:{}\n".format( 59 | next_contest['contestName'], 60 | time.strftime("%Y-%m-%d %H:%M:%S", 61 | time.localtime(int(next_contest['startTime']))), 62 | "{}小时{:02d}分钟".format( 63 | durationSeconds // 3600, durationSeconds % 3600 // 60), 64 | next_contest['link'] 65 | ) 66 | return res 67 | 68 | async def get_rating(self, name): 69 | url = "https://atcoder.jp/users/" + name 70 | html = await get_html(url) 71 | r = r'Rating<\/th>(.*?)<\/span>' 72 | results = re.findall(r, html, re.S) 73 | try: 74 | return results[0][1] 75 | except: 76 | return -1 77 | 78 | async def update_local_contest(self): 79 | url = "https://contests.sdutacm.cn/contests.json" 80 | json_data = await get_json(url) 81 | if json_data == -1: 82 | return False 83 | with open('./oj_json/contests.json', 'w') as f: 84 | json.dump(json_data, f, indent=4) 85 | return True 86 | 87 | 88 | if __name__ == '__main__': 89 | name = "guke" 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 注意 2 | **由于yiri-mirai已经停止更新,目前已知添加图片与删除图片功能不正常,需要手动更改yiri-mirai库源码,修改处如下所示** 3 | ![image](https://user-images.githubusercontent.com/46668943/230928717-77bf39e8-fa87-4c62-89c3-f456d27892eb.png) 4 | ![image](https://user-images.githubusercontent.com/46668943/230928865-7a3f0a41-d38e-4196-a787-c720d4e13da1.png) 5 | 6 | --- 7 | 8 | 本项目是一个在群里可以通知打codeforces、牛客、AtCoder、LeetCode的qq机器人项目,基于 9 | ACM_Contest_QQbot修改(膜拜INGg巨巨 10 | ![worship.gif](https://s2.loli.net/2022/02/27/VexPg9Nb5AT8cD3.gif) )。 11 | 12 | ## 本项目与原项目的区别 13 | 14 | 1. 将 codeforces 、牛客、atc、LeetCode的比赛以json形式保存在本地; 15 | 2. ~~把本校ACM实验室成员的 codeforces rating 数据以json的形式保存在本地;~~ 将添加的cf用户rating数据以json形式保存到本地,同时将不同群添加的cf用户进行分离,每个群在进行cf总查询时仅显示本群所有cf用户信息 16 | 3. 把本校所有牛客用户的牛客 rating 数据以 json 形式保存在本地。 17 | 4. 将所有定时任务放入一个函数统一处理 18 | 5. 拥有今日人品功能 19 | 6. 添加图片时可判断该图片是否已在图库中存在 20 | 7. 可查询LeetCode分数 21 | 8. **不拥有**随机cf 22 | 9. 其他类似功能部分有所出入 23 | 24 | 提供定时/手动更新本地数据功能,仅在更新时与cf、牛客、atc、LeetCode进行交互,加速用户使用机器人时的查询速度并降低了被反爬虫的概率。 25 | 26 | ## 功能介绍 27 | - next -> 查询最近一场比赛 28 | - today -> 查询今天比赛 29 | - cf(不区分大小写)/牛客/atc(不区分大小写)/lc(不区分大小写) -> 查询最近三场cf/牛客/atc/lc比赛 30 | - 查询cf/牛客/atc分数id -> 查询对应id的cf/牛客/atc分数(已解决牛客模糊查询导致结果不准确的问题) 31 | - 更新cf/牛客分数 -> 更新本地所有用户的cf/牛客分数 32 | - 添加cf用户id -> 添加对应id的cf用户及其相关信息到本地json文件中 33 | - 删除cf用户id -> 删除本地json文件中的cf用户 34 | - 随机蕊神/来只蕊神 -> 随机蕊神语录 35 | - 来只清楚 -> 随机qcjj/固定群聊其他人的语录 36 | - 添加蕊神/添加清楚 -> 使用该命令回复图片即可添加到本地图库中(已做权限处理) 37 | - setu/涩图 -> 涩图 38 | - 每天定时发送当天比赛 39 | - cf、牛客、atc、LeetCode比赛前十五分钟提醒报名参加 40 | - cf的rating分群管理,不同群之间cf总查询的结果只会是当前群曾添加的cf用户 41 | - 订阅cf/牛客/lc/atc -> 在这些比赛开始前15分钟发送报名定时提醒,cf和牛客还有准时的上号提醒 42 | - 订阅每日提醒 -> 每天早上8点发送当日比赛 43 | - 取消订阅cf/牛客/lc/atc/每日提醒 -> 取消这些订阅 44 | 45 | ## 部署方法 46 | ps: [INGg巨巨](https://github.com/INGg)写的![3E408F36CA2E4C062603C154D242E2A1.gif](https://s2.loli.net/2022/09/28/oQ2pRUFLIenZEGM.gif) 47 | 48 | 1. 环境配置 49 | * 请参照YiriMirai的教程环境配置:https://yiri-mirai.wybxc.cc/tutorials/01/configuration 50 | * 建议更新Mirai到最新版本,使用命令`./mcl -u` 51 | 52 | 2. 将yirimirai部署教程中的net.mamoe.mirai-api-http文件夹下的setting.yml里的端口号改成7275 53 | 54 | 3. 使用Mirai登陆qq https://yiri-mirai.wybxc.cc/tutorials/01/configuration#4-%E7%99%BB%E5%BD%95-qq 55 | 56 | 4. 挂起服务(如果是linux服务器,参照官网教程,如何挂起而不退出:https://yiri-mirai.wybxc.cc/tutorials/02/linux) 57 | 58 | 5. clone到本地或者服务器中(不要直接下载源码,如果网速慢请挂梯子) 59 | 60 | 6. 修改`main.py`中bot的qq号为你自己的qq号 61 | ~~~python 62 | bot = Mirai( 63 | qq=*****, # 改成你的机器人的 QQ 号 64 | adapter=WebSocketAdapter( 65 | verify_key='yirimirai', host='localhost', port=7275 66 | ) 67 | ) 68 | ~~~ 69 | 70 | 7. 创建oj_json文件夹,在里面创建**cf_contest.json**、**cf_rating.json**、**lc_contest.json**、**nc_rating.json**、**contests.json**、**subscribe.json**等文件 71 | 8. 在cf_rating.json文件中添加以下内容 72 | ```json 73 | { 74 | "all_rating": {} 75 | } 76 | ``` 77 | 9. 在subscribe.json文件中添加以下内容 78 | ```json 79 | { 80 | "cf": {}, 81 | "\u725b\u5ba2": {}, 82 | "lc": {}, 83 | "atc": {}, 84 | "today": {} 85 | } 86 | ``` 87 | 10. 在cf_contest.json、lc_contest.json文件中添加以下内容 88 | ```json 89 | {} 90 | ``` 91 | 11. 在contests.json文件中添加以下内容 92 | ```json 93 | [] 94 | ``` 95 | 96 | 12. 安装对应的库 97 | ~~~shell 98 | pip3 install -r ./requirements.txt 99 | # 应该是全了qwq,如果不全请根据报错来安装相应的包,如果方便请您告知我,我将更新安装命令 100 | ~~~ 101 | 102 | 13. 启动bot 103 | ~~~shell 104 | python3 main.py 105 | # 或 python main.py 106 | # 自己编译安装python3.8的 python3.8 main.py 107 | ~~~ 108 | 109 | ## 一点建议 110 | 强烈建议使用本项目的朋友 clone 项目到本地后,使用 cloudflare 分别做 codeforces 和 atcoder 的镜像站,然后将 `cf_api.py` 和 `atc_api.py` 中的用户 rating 查询函数的链接替换为你自己的镜像站链接。 111 | 112 | 该操作将在一定程度上起到反爬虫、加速访问 codeforces 和 atcoder 以及避免这两个网站在访问峰值时访问过慢导致查询失败的作用。 113 | 114 | **cloudflare 可以使用免费版,每天有 10 万次请求额度,需要注意的是,workers.dev 域名被污染,在国内已经无法访问,需要使用自己的域名作为 worker 的路由,如果没有域名或者买不起域名,可以使用 freenom 申请免费域名。** 115 | 116 | **作者并不解答 cloudflare 制作镜像站以及 freenom 申请免费域名这两个操作中遇到的问题,如果需要使用该方案或者遇到问题请自行 google。** 117 | -------------------------------------------------------------------------------- /oj_api/lc_api.py: -------------------------------------------------------------------------------- 1 | import json 2 | import pprint 3 | import time 4 | from lxml import etree 5 | from web_operation.operation import * 6 | from oj_api.contest import Contest 7 | 8 | 9 | class LC(Contest): 10 | async def update_local_contest(self): 11 | url_data = "https://leetcode-cn.com/graphql" 12 | payload = { 13 | "operationName": "null", 14 | "query": "{\n contestUpcomingContests {\n containsPremium\n title\n cardImg\n titleSlug\n description\n startTime\n duration\n originStartTime\n isVirtual\n isLightCardFontColor\n company {\n watermark\n __typename\n }\n __typename\n }\n}\n", 15 | "variables": {} 16 | } 17 | headers = { 18 | "accept": "*/*", 19 | "accept-encoding": "gzip,deflate,br", 20 | "accept-language": "zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6", 21 | "cache-control": "no-cache", 22 | # "content-length": "329", 23 | "content-type": "application/json", 24 | # "cookie": response.cookies, 25 | "origin": "https://leetcode-cn.com", 26 | "pragma": "no-cache", 27 | "referer": "https://leetcode-cn.com/contest/", 28 | 'User-Agent': "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36", 29 | # 'x-csrftoken': response.cookies 30 | } 31 | try: 32 | async with httpx.AsyncClient() as client: 33 | response = await client.post(url=url_data, data=json.dumps(payload), headers=headers) 34 | 35 | url_text = response.content.decode() 36 | json_data = json.loads(url_text) 37 | with open("./oj_json/lc_contest.json", "w", encoding='utf-8') as f: 38 | json.dump(json_data, f) 39 | return True 40 | except: 41 | return False 42 | 43 | async def get_contest(self): 44 | res = [] 45 | with open("./oj_json/lc_contest.json", "r", encoding='utf-8') as f: 46 | json_data = json.load(f) 47 | if not json_data: 48 | return [] 49 | contest_info = json_data['data']['contestUpcomingContests'] 50 | for contest in contest_info: 51 | # try: 52 | # html = etree.HTML(contest['description']) 53 | # company = html.xpath("/html/body/div/div/div/p[1]/text()")[0] 54 | # except: 55 | # return [] 56 | info = "比赛名称:{}\n" \ 57 | "开始时间:{}\n" \ 58 | "持续时间:{}\n" \ 59 | "比赛地址:{}".format( 60 | "LeetCode " + contest['title'], 61 | time.strftime("%Y-%m-%d %H:%M:%S", 62 | time.localtime(contest['startTime'])), 63 | "{}小时{:02d}分钟".format( 64 | contest['duration'] // 3600, contest['duration'] % 3600 // 60), 65 | "https://leetcode-cn.com/contest/" + contest['titleSlug']) 66 | 67 | res.append([info, contest['startTime'], contest['duration']]) 68 | res.sort(key=lambda x: x[1], reverse=False) 69 | return res 70 | 71 | async def get_next_contest(self): 72 | res = await self.get_contest() 73 | if res: 74 | return res[0][0], res[0][1], res[0][2] 75 | else: 76 | return "最近没有比赛~", 32536700000, 0 77 | 78 | async def get_contest_info(self): 79 | contest_list_lately = await self.get_contest() 80 | if not contest_list_lately: 81 | return "最近没有比赛~" 82 | if len(contest_list_lately) > 3: 83 | find_contest = 3 84 | else: 85 | find_contest = len(contest_list_lately) 86 | res = "找到最近的 {} 场 LeetCode 比赛为:\n".format(find_contest) 87 | for contest in contest_list_lately[:find_contest]: 88 | res += contest[0] + '\n' 89 | return res.rstrip('\n') 90 | 91 | async def get_recent_info(self): 92 | recent, _, _ = await self.get_next_contest() 93 | return "LeetCode比赛还有15分钟就开始啦,没有报名的尽快报名~\n" + recent 94 | 95 | async def update_local_rating(self): 96 | json_data = await get_json( 97 | "https://raw.iqiq.io/chiehmin/leetcode-ranking-search/master/public/data/global-ranking.json") 98 | if json_data == -1: 99 | return False 100 | with open('./oj_json/lc_rating.json', 'w', encoding='utf-8') as f: 101 | json.dump(json_data, f) 102 | return True 103 | 104 | async def get_rating(self, name): 105 | with open('./oj_json/lc_rating.json', 'r', encoding='utf-8') as f: 106 | json_data = json.load(f) 107 | for user in json_data: 108 | if user["realName"] == name: 109 | return '"{}"rating为: {},全球ranking为: {}'.format(name, user["rating"].split(".")[0], user["globalRanking"]) 110 | 111 | 112 | if __name__ == '__main__': 113 | name = "guke" 114 | -------------------------------------------------------------------------------- /oj_api/nc_api.py: -------------------------------------------------------------------------------- 1 | import json 2 | from web_operation.operation import * 3 | import time 4 | from lxml import etree 5 | import requests 6 | from oj_api.contest import Contest 7 | 8 | 9 | class NC(Contest): 10 | async def request_rating(self, name): 11 | url = "https://ac.nowcoder.com/acm/contest/rating-index?searchUserName=" + name 12 | headers = { 13 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.102 Safari/537.36' 14 | } 15 | try: 16 | resp = requests.get(url=url, headers=headers) 17 | text = resp.text 18 | zm = text.encode(resp.encoding).decode('utf-8') 19 | xx = etree.fromstring(zm, parser=etree.HTMLParser()) 20 | return xx.xpath('/html/body/div/div[2]/div/div/div[2]/table/tbody/tr[*]') 21 | except: 22 | return False 23 | 24 | async def update_all_nc_rating(self): 25 | school_name = "黄冈师范学院" 26 | items = await self.request_rating(school_name) 27 | if items: 28 | all_rating = {} 29 | for item in items: 30 | username = item.xpath('./td[2]/a/span/text()')[0] 31 | rating = item.xpath('./td[5]/span/text()')[0] 32 | all_rating[username] = rating 33 | with open("./oj_json/nc_rating.json", 'w', encoding='utf-8') as f: 34 | json.dump(all_rating, f) 35 | return True 36 | else: 37 | return False 38 | 39 | async def get_rating(self, uname): 40 | with open("./oj_json/nc_rating.json", 'r', encoding='utf-8') as f: 41 | all_rating = json.load(f) 42 | if uname in all_rating: 43 | return "“{}”当前牛客rating为:{}".format(uname, all_rating[uname]) 44 | else: 45 | items = await self.request_rating(uname) 46 | if items: 47 | for item in items: 48 | username = item.xpath('./td[2]/a/span/text()')[0] 49 | rating = item.xpath('./td[5]/span/text()')[0] 50 | if username == uname: 51 | return "“{}”当前牛客rating为:{}".format(uname, rating) 52 | else: 53 | return False 54 | 55 | async def update_local_contest(self): 56 | url = "https://contests.sdutacm.cn/contests.json" 57 | json_data = await get_json(url) 58 | if json_data == -1: 59 | return False 60 | with open('./oj_json/contests.json', 'w') as f: 61 | json.dump(json_data, f, indent=4) 62 | return True 63 | 64 | async def get_contest(self): 65 | with open('./oj_json/contests.json', 'r', encoding='utf-8') as f: 66 | contest_data = json.load(f) 67 | contest_list = [] 68 | if contest_data != []: 69 | for contest in contest_data: 70 | if contest['source'] == '牛客竞赛' and "专题" not in contest['name']: 71 | contest['contestName'] = contest['name'] 72 | start_time = int(time.mktime(time.strptime( 73 | contest['start_time'], "%Y-%m-%dT%H:%M:%S+00:00"))) + 8 * 3600 74 | contest['startTime'] = start_time 75 | end_time = int(time.mktime(time.strptime( 76 | contest['end_time'], "%Y-%m-%dT%H:%M:%S+00:00"))) + 8 * 3600 77 | contest['endTime'] = end_time 78 | durationSeconds = contest['endTime'] - contest['startTime'] 79 | if durationSeconds <= 18000 and contest['startTime'] >= int(time.time()): 80 | contest_list.append([contest, durationSeconds]) 81 | return contest_list 82 | 83 | async def format_nc_contest(self, next_contest, durationSeconds): 84 | res = "比赛名称:{}\n" \ 85 | "开始时间:{}\n" \ 86 | "持续时间:{}\n" \ 87 | "比赛地址:{}\n".format( 88 | next_contest['contestName'], 89 | time.strftime("%Y-%m-%d %H:%M:%S", 90 | time.localtime(int(next_contest['startTime']))), 91 | "{}小时{:02d}分钟".format( 92 | durationSeconds // 3600, durationSeconds % 3600 // 60), 93 | next_contest['link'] 94 | ) 95 | return res 96 | 97 | async def get_next_contest(self): 98 | contest_list = await self.get_contest() 99 | if not contest_list: 100 | return "最近没有比赛~", 32536700000, 0 101 | next_contest, durationSeconds = contest_list[0][0], contest_list[0][1] 102 | res = await self.format_nc_contest(next_contest, durationSeconds) 103 | return res.rstrip('\n'), next_contest['startTime'], durationSeconds 104 | 105 | async def get_recent_info(self): 106 | recent, _, _ = await self.get_next_contest() 107 | return "牛客比赛还有15分钟就开始啦,没有报名的尽快报名~\n" + recent 108 | 109 | async def get_contest_info(self): 110 | contest_list = await self.get_contest() 111 | contest_len = len(contest_list) 112 | if contest_len == 0: 113 | return False 114 | if contest_len > 3: 115 | contest_len = 3 116 | res = '找到最近的 {} 场牛客比赛为:\n'.format(contest_len) 117 | for i in range(contest_len): 118 | next_contest, durationSeconds = contest_list[i][0], contest_list[i][1] 119 | res += await self.format_nc_contest(next_contest, durationSeconds) 120 | return res.rstrip('\n') 121 | 122 | 123 | if __name__ == '__main__': 124 | # pprint.pprint(asyncio.run(update_all_nc_rating())) 125 | # asyncio.run(update_all_nc_contest()) 126 | name = "guke" 127 | -------------------------------------------------------------------------------- /oj_api/cf_api.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | from web_operation.operation import * 4 | from oj_api.contest import Contest 5 | 6 | 7 | class CF(Contest): 8 | async def get_rating(self, name): 9 | def pd_color(rating): 10 | if rating < 1200: 11 | return "灰名隐藏大佬" 12 | if rating < 1400: 13 | return '绿名Pupil' 14 | if rating < 1600: 15 | return '青名Specialist' 16 | if rating < 1900: 17 | return '蓝名Expert' 18 | if rating < 2100: 19 | return '紫名Candidate master' 20 | if rating < 2300: 21 | return '橙名International Master' 22 | if rating < 2400: 23 | return '橙名Master' 24 | if rating < 2600: 25 | return '红名Grandmaster' 26 | if rating < 3000: 27 | return '红名巨巨' 28 | else: 29 | return '黑红名神犇' 30 | 31 | url = "https://codeforces.com/api/user.rating?handle=" + name 32 | json_data = await get_json(url) 33 | if json_data == -1: 34 | with open('./oj_json/cf_rating.json', 'r', encoding='utf-8') as f: 35 | all_rating = json.load(f) 36 | rating = all_rating["all_rating"] 37 | if name in rating: 38 | rating_info = rating[name] 39 | if rating_info[0] == 0: 40 | return '"{}"还未进行过比赛\n'.format(name) 41 | return '"{}"是{},当前rating为:{}'.format(name, pd_color(rating_info[0]), rating_info[0]) 42 | return "查询失败!" 43 | json_data = dict(json_data) 44 | if json_data['status'] == "OK": 45 | json_data = json_data['result'] 46 | contest_len = len(json_data) 47 | if contest_len == 0: 48 | return '"{}"还未进行过比赛'.format(name) 49 | final_contest = json_data[-1] 50 | if contest_len > 2: 51 | recent_contest = json_data[contest_len - 3:] 52 | else: 53 | recent_contest = json_data 54 | res = '"{}"是{},当前rating为:{}\n最近表现:'.format(name, pd_color(int(final_contest['newRating'])), 55 | final_contest['newRating']) 56 | for record in recent_contest: 57 | diff = record['newRating'] - record['oldRating'] 58 | if diff >= 0: 59 | diff = '+' + str(diff) 60 | res += '\n{}:{},rating:{}'.format( 61 | record['contestName'], diff, record['newRating']) 62 | return res 63 | else: 64 | return "该用户不存在!" 65 | 66 | # 查询cf最近一次结束的比赛,筛除Kotlin比赛及Div .1难度 67 | async def get_final_contest(self): 68 | url = "https://codeforces.com/api/contest.list?gym=false" 69 | json_data = await get_json(url) 70 | if json_data == -1: 71 | return '' 72 | json_data = dict(json_data) 73 | if json_data['status'] == "OK": 74 | contest_list_all = list(json_data['result']) 75 | for contest in contest_list_all: 76 | if contest['relativeTimeSeconds'] > 0: 77 | if 'Kotlin' not in contest['name']: 78 | if 'Unrated' not in contest['name']: 79 | if 'Div. 2' in contest['name'] or 'Div. 3' in contest['name'] or 'Div. 4' in contest[ 80 | 'name'] or 'Codeforces Global Round' in contest['name']: 81 | final_contest = contest 82 | return final_contest 83 | 84 | # 查询用户的rating总信息 85 | async def query_user_rating(self, uname, final_contest): 86 | url = "https://codeforces.com/api/user.rating?handle=" + uname 87 | json_data = await get_json(url) 88 | if json_data == -1: 89 | return [] 90 | json_data = dict(json_data) 91 | if json_data['status'] == "OK": 92 | json_data = json_data['result'] 93 | if len(json_data) == 0: 94 | return [0, 0] 95 | final_rating = json_data[-1] 96 | if final_rating['contestId'] != final_contest['id']: 97 | differ = 'X' 98 | else: 99 | differ = int(final_rating['newRating']) - \ 100 | int(final_rating['oldRating']) 101 | if differ > 0: 102 | differ = '+' + str(differ) 103 | else: 104 | differ = str(differ) 105 | return [final_rating['newRating'], differ] 106 | else: 107 | return [] 108 | 109 | # 更新本地存储用户的rating信息 110 | async def update_rating(self): 111 | res = '' 112 | rating = {} 113 | final_contest = await self.get_final_contest() 114 | if not final_contest: 115 | return '' 116 | f = open('./oj_json/cf_rating.json', 'r+', encoding='utf-8') 117 | all_rating = json.load(f) 118 | local_rating = all_rating["all_rating"] 119 | for uname in local_rating: 120 | print(uname) 121 | rating_info = await self.query_user_rating(uname, final_contest) 122 | if not rating_info: 123 | return '' 124 | rating[uname] = rating_info 125 | rating = dict( 126 | sorted(rating.items(), key=lambda x: x[1][0], reverse=True)) 127 | all_rating["all_rating"] = rating 128 | f.seek(0) 129 | f.truncate() 130 | json.dump(all_rating, f, indent=4) 131 | f.close() 132 | for uname in rating: 133 | res += await self.format_rating_res(uname, rating[uname]) 134 | return res.rstrip('\n') 135 | 136 | async def auto_update(self): 137 | final_contest = await self.get_final_contest() 138 | up = final_contest['startTimeSeconds'] + \ 139 | final_contest['durationSeconds'] 140 | if final_contest['type'] == 'ICPC': 141 | up += 25 * 60 * 60 142 | else: 143 | up += 6 * 60 * 60 144 | return up 145 | 146 | # 获取本地存储所有用户rating信息 147 | async def get_cf_rating(self, group_id): 148 | res = '' 149 | with open('./oj_json/cf_rating.json', 'r', encoding='utf-8') as f: 150 | all_rating = json.load(f) 151 | if group_id not in all_rating: 152 | return "本群暂未添加任何cf用户" 153 | unames = all_rating[group_id] 154 | rating = all_rating["all_rating"] 155 | for uname in rating: 156 | if uname in unames: 157 | res += await self.format_rating_res(uname, rating[uname]) 158 | return res.rstrip('\n') 159 | 160 | # 为本地添加新用户及rating信息 161 | async def add_cf_user(self, uname, group_id): 162 | with open('./oj_json/cf_rating.json', 'r+', encoding='utf-8') as f: 163 | all_rating = json.load(f) 164 | rating = all_rating["all_rating"] 165 | if group_id not in all_rating: 166 | all_rating[group_id] = [] 167 | if uname in all_rating[group_id]: 168 | return True 169 | else: 170 | if uname in rating: 171 | all_rating[group_id].append(uname) 172 | f.seek(0) 173 | f.truncate() 174 | json.dump(all_rating, f, indent=4) 175 | return True 176 | final_contest = await self.get_final_contest() 177 | if not final_contest: 178 | return False 179 | rating_info = await self.query_user_rating(uname, final_contest) 180 | all_rating[group_id].append(uname) 181 | if rating_info: 182 | rating[uname] = rating_info 183 | rating = dict( 184 | sorted(rating.items(), key=lambda x: x[1][0], reverse=True)) 185 | all_rating["all_rating"] = rating 186 | f.seek(0) 187 | f.truncate() 188 | json.dump(all_rating, f, indent=4) 189 | return True 190 | else: 191 | return False 192 | 193 | # 删除本地存储用户 194 | async def del_cf_user(self, uname, group_id): 195 | with open('./oj_json/cf_rating.json', 'r+', encoding='utf-8') as f: 196 | all_rating = json.load(f) 197 | rating = all_rating["all_rating"] 198 | if uname not in rating or uname == "wentaotao": 199 | return False 200 | for k in all_rating: 201 | if k != "all_rating" and k != group_id: 202 | if uname in all_rating[k]: 203 | break 204 | else: 205 | del rating[uname] 206 | all_rating[group_id].remove(uname) 207 | f.seek(0) 208 | f.truncate() 209 | json.dump(all_rating, f, indent=4) 210 | return True 211 | 212 | # 格式化查询cf rating输出 213 | async def format_rating_res(self, uname, rating_info): 214 | if rating_info[0] == 0: 215 | return '"{}"还未进行过比赛\n'.format(uname) 216 | return '"{}":{},{}\n'.format(uname, rating_info[0], rating_info[1]) 217 | 218 | async def update_local_contest(self): 219 | url = "https://codeforces.com/api/contest.list?gym=false" 220 | json_data = await get_json(url) 221 | if json_data == -1: 222 | return False 223 | json_data = dict(json_data) 224 | if json_data['status'] == "OK": 225 | with open('./oj_json/cf_contest.json', 'w') as f: 226 | json.dump(json_data, f) 227 | return True 228 | 229 | async def get_contest(self): 230 | with open('./oj_json/cf_contest.json', 'r') as f: 231 | json_data = json.load(f) 232 | if json_data == []: 233 | return [] 234 | contest_list_all = list(json_data['result']) 235 | contest_list_lately = [] 236 | 237 | for contest in contest_list_all: 238 | if contest['relativeTimeSeconds'] < 0: 239 | if contest['name'][:6] != 'Kotlin': 240 | if 'Unrated' not in contest['name']: 241 | contest_list_lately.append(contest) 242 | else: 243 | break 244 | if contest_list_lately: 245 | contest_list_lately.sort(key=lambda x: ( 246 | x['relativeTimeSeconds'], x['name']), reverse=True) 247 | return contest_list_lately 248 | 249 | async def get_next_contest(self): 250 | contest_list_lately = await self.get_contest() 251 | if not contest_list_lately: 252 | return "最近没有比赛~", 32536700000, 0 253 | else: 254 | for contest in contest_list_lately: 255 | res = await self.format_cf_contest(contest) 256 | return res, int(contest['startTimeSeconds']), int(contest['durationSeconds']) 257 | 258 | async def get_contest_info(self): 259 | contest_list_lately = await self.get_contest() 260 | if not contest_list_lately: 261 | return "最近没有比赛~" 262 | if len(contest_list_lately) > 3: 263 | find_contest = 3 264 | else: 265 | find_contest = len(contest_list_lately) 266 | res = "找到最近的 {} 场 cf 比赛为:\n".format(find_contest) 267 | for contest in contest_list_lately[:find_contest]: 268 | res += await self.format_cf_contest(contest) + '\n' 269 | return res.rstrip('\n') 270 | 271 | async def get_recent_info(self): 272 | recent, _, _ = await self.get_next_contest() 273 | return "cf比赛还有15分钟就开始啦,没有报名的尽快报名~\n" + recent 274 | 275 | async def format_cf_contest(self, contest): 276 | contest_url = "https://codeforces.com/contest/" 277 | return "比赛名称:{}\n开始时间:{}\n持续时间:{}\n比赛地址:{}".format( 278 | contest['name'], 279 | time.strftime("%Y-%m-%d %H:%M:%S", 280 | time.localtime(int(contest['startTimeSeconds']))), 281 | "{}小时{:02d}分钟".format( 282 | contest['durationSeconds'] // 3600, contest['durationSeconds'] % 3600 // 60), 283 | contest_url + str(contest['id']) 284 | ) 285 | 286 | 287 | if __name__ == '__main__': 288 | # name = input() 289 | name = "guke" 290 | 291 | # asyncio.run(get_usr_rating(name)) 292 | # while True: 293 | # name = input() 294 | # print(asyncio.run(get_usr_rating(name))) 295 | # pprint.pprint(asyncio.run(get_hgnu_rating())) 296 | # asyncio.run(update_cf_contest()) 297 | # pprint.pprint(asyncio.run(get_contest_info())) 298 | # pprint.pprint(asyncio.run(get_next_contest())) 299 | # pprint.pprint(asyncio.run(get_contest())) 300 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import re 2 | import sys 3 | import time 4 | import random 5 | import os 6 | import hashlib 7 | import json 8 | import asyncio 9 | 10 | from bisect import bisect_right 11 | from mirai.models.api import MessageFromIdResponse 12 | from log import Log 13 | from oj_api import atc_api, cf_api, nc_api, lc_api 14 | from mirai.models import NewFriendRequestEvent, Quote, Group, Friend 15 | from mirai import Startup, Shutdown, MessageEvent 16 | from apscheduler.schedulers.asyncio import AsyncIOScheduler 17 | from apscheduler.triggers.cron import CronTrigger 18 | from mirai_extensions.trigger import HandlerControl, Filter 19 | from mirai import Mirai, WebSocketAdapter, FriendMessage, At, Plain, MessageChain, Image, GroupMessage 20 | from mirai.exceptions import ApiError 21 | 22 | sys.stdout = Log.Logger() # 定义log类 23 | sys.stderr = Log.Logger() 24 | scheduler = AsyncIOScheduler() 25 | 26 | cf = cf_api.CF() 27 | nc = nc_api.NC() 28 | lc = lc_api.LC() 29 | atc = atc_api.ATC() 30 | 31 | 32 | async def get_md5(filepath): 33 | with open(filepath, 'rb') as file: 34 | f = file.read() 35 | return hashlib.md5(f).hexdigest() 36 | 37 | 38 | img_ruishen = [_.path for _ in os.scandir('./img/ruishen/')] 39 | img_qcjj = [_.path for _ in os.scandir('./img/qcjj/')] 40 | img_setu = [_.path for _ in os.scandir('./img/setu/')] 41 | img_dict = {'./img/ruishen/': img_ruishen, 42 | './img/qcjj/': img_qcjj, './img/setu/': img_setu} 43 | img_md5 = {asyncio.run(get_md5(_)): _ for _ in img_ruishen + img_qcjj} 44 | 45 | 46 | async def random_img(img_path): 47 | global img_ruishen 48 | global img_qcjj 49 | global img_setu 50 | img_list = img_dict[img_path] 51 | if not img_list: 52 | img_list = [_.path for _ in os.scandir(img_path)] 53 | img_local = random.choice(img_list) 54 | img_list.remove(img_local) 55 | return img_local 56 | 57 | 58 | async def update(): 59 | print() 60 | print(time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())) 61 | global cf, nc, lc, atc 62 | await cf.update_contest() 63 | await nc.update_contest() 64 | await lc.update_contest() 65 | await atc.update_contest() 66 | 67 | 68 | async def query_next_contest(): 69 | global cf, atc, nc, lc 70 | next_contest = [[cf.info, cf.begin_time], [atc.info, atc.begin_time], [nc.info, nc.begin_time], 71 | [lc.info, lc.begin_time]] 72 | next_contest.sort(key=lambda x: x[1]) 73 | return next_contest 74 | 75 | 76 | async def query_today_contest(): 77 | next_contest = await query_next_contest() 78 | res = "" 79 | mon = time.localtime().tm_mon 80 | day = time.localtime().tm_mday 81 | for contest in next_contest: 82 | tmp_time = contest[1] - 4 * 3600 83 | if time.localtime(tmp_time).tm_mon == mon and time.localtime(tmp_time).tm_mday == day: 84 | res += contest[0] + '\n' 85 | print(res) 86 | return res.rstrip('\n') 87 | 88 | 89 | async def sche_add(func, implement, id=None): 90 | scheduler.add_job(func, CronTrigger(month=time.localtime(implement).tm_mon, 91 | day=time.localtime(implement).tm_mday, 92 | hour=time.localtime(implement).tm_hour, 93 | minute=time.localtime( 94 | implement).tm_min, 95 | second=time.localtime( 96 | implement).tm_sec, 97 | timezone='Asia/Shanghai'), id=id, misfire_grace_time=60) 98 | 99 | 100 | if __name__ == '__main__': 101 | bot = Mirai( 102 | qq=1045760198, # 改成你的机器人的 QQ 号 103 | adapter=WebSocketAdapter( 104 | verify_key='yirimirai', host='localhost', port=7275 105 | ) 106 | ) 107 | hdc = HandlerControl(bot) # 事件接收器 108 | 109 | @bot.on(Startup) 110 | def start_scheduler(_): 111 | scheduler.start() # 启动定时器 112 | 113 | @bot.on(Shutdown) 114 | def stop_scheduler(_): 115 | scheduler.shutdown(True) # 结束定时器 116 | 117 | @bot.on(NewFriendRequestEvent) 118 | async def allow_request(event: NewFriendRequestEvent): # 有新用户好友申请就自动通过 119 | await bot.allow(event) 120 | 121 | @bot.on(MessageEvent) 122 | async def show_list(event: MessageEvent): # 功能列表展示 123 | msg = "".join(map(str, event.message_chain[Plain])) 124 | if msg == "help": 125 | await bot.send(event, ["next -> 最近一场比赛" 126 | "\ncf/牛客/lc/atc -> 最近cf/牛客/lc/atc比赛" 127 | "\ntoday -> 今日比赛" 128 | "\njrrp -> 今日人品" 129 | "\n查询cf/牛客/atc/力扣分数id -> 查询对应id的cf/牛客/atc/力扣分数" 130 | "\n添加/删除cf用户id -> 添加/删除本群cf用户" 131 | "\ncf总查询 -> 查询本群所有cf用户rating分" 132 | "\n订阅cf/牛客/lc/atc -> 在比赛开始前15分钟发送定时提醒" 133 | "\n订阅每日提醒 -> 每天早上8点提醒当日所有比赛" 134 | "\n取消订阅cf/牛客/lc/atc/每日提醒" 135 | "\n随机/来只蕊神 -> 随机蕊神语录" 136 | "\n来只清楚 -> 随机qcjj语录" 137 | "\nsetu/涩图 -> 涩图" 138 | "\n项目地址 -> 获取项目地址" 139 | "\nbug联系:2454256424"]) 140 | 141 | @bot.on(MessageEvent) 142 | async def on_group_message(event: MessageEvent): # 返回 143 | if At(bot.qq) in event.message_chain and ("".join(map(str, event.message_chain[Plain]))).strip() == '': 144 | message_chain = MessageChain([ 145 | await Image.from_local('./img/at_bot.gif') 146 | ]) 147 | await bot.send(event, message_chain) 148 | 149 | @bot.on(MessageEvent) 150 | async def project_address(event: MessageEvent): 151 | msg = "".join(map(str, event.message_chain[Plain])) 152 | if msg == '项目地址': 153 | await bot.send(event, "大佬可以点个star✨吗qwq\nhttps://github.com/guke1024/ACM_QQbot") 154 | 155 | @bot.on(MessageEvent) 156 | async def withdraw_message(event: MessageEvent): 157 | msg = "".join(map(str, event.message_chain[Plain])) 158 | if msg.strip() == "撤回": 159 | if event.sender.id == 2454256424: 160 | quotes = event.message_chain[Quote] 161 | if quotes: 162 | message: MessageFromIdResponse = await bot.message_from_id(quotes[0].id) 163 | try: 164 | await bot.recall(message.data.message_chain.message_id) 165 | except ApiError: 166 | await bot.send(event, "撤回失败!") 167 | pass 168 | 169 | @bot.on(MessageEvent) 170 | async def subscribe(event: MessageEvent): 171 | msg = "".join(map(str, event.message_chain[Plain])) 172 | if msg[:2] == '订阅': 173 | k = msg[2:].lower() 174 | if k == '每日提醒': 175 | k = "today" 176 | if k in ['cf', '牛客', 'lc', 'atc', 'today']: 177 | e_type = event.type 178 | if e_type == 'GroupMessage': 179 | id = event.sender.group.id 180 | else: 181 | id = event.sender.id 182 | id = str(id) 183 | with open('./oj_json/subscribe.json', 'r+', encoding='utf-8') as f: 184 | all_subscribe = json.load(f) 185 | if id in all_subscribe[k]: 186 | await bot.send(event, "该内容已订阅!") 187 | else: 188 | all_subscribe[k][id] = e_type 189 | f.seek(0) 190 | f.truncate() 191 | json.dump(all_subscribe, f, indent=4) 192 | await bot.send(event, "添加订阅成功!") 193 | else: 194 | await bot.send(event, "请输入正确的订阅内容!") 195 | 196 | @bot.on(MessageEvent) 197 | async def delete_subscribe(event: MessageEvent): 198 | msg = "".join(map(str, event.message_chain[Plain])) 199 | if msg[:4] == '取消订阅': 200 | k = msg[4:].lower() 201 | if k == '每日提醒': 202 | k = "today" 203 | if k in ['cf', '牛客', 'lc', 'atc', 'today']: 204 | with open('./oj_json/subscribe.json', 'r+', encoding='utf-8') as f: 205 | all_subscribe = json.load(f) 206 | e_type = event.type 207 | if e_type == 'GroupMessage': 208 | id = event.sender.group.id 209 | else: 210 | id = event.sender.id 211 | id = str(id) 212 | if id not in all_subscribe[k]: 213 | await bot.send(event, "暂未订阅该内容!") 214 | else: 215 | del all_subscribe[k][id] 216 | f.seek(0) 217 | f.truncate() 218 | json.dump(all_subscribe, f, indent=4) 219 | await bot.send(event, "取消订阅成功!") 220 | else: 221 | await bot.send(event, "请输入正确的订阅内容!") 222 | 223 | @bot.on(MessageEvent) 224 | async def practice_query(event: MessageEvent): 225 | msg = "".join(map(str, event.message_chain[Plain])) 226 | if msg.strip().lower() in ["jrrp", "今日人品"]: 227 | today_date = time.localtime() 228 | rp_str = str(today_date.tm_year) + str(today_date.tm_mon) + \ 229 | str(today_date.tm_mday) + str(event.sender.id) 230 | rp_hash = hashlib.sha256(rp_str.encode('utf-8')).hexdigest() 231 | random.seed(rp_hash) 232 | rp = random.randint(0, 100) 233 | rp_range = [10, 20, 40, 60, 80, 90, 101] 234 | rp_dict = {0: "大凶", 1: "凶", 2: "末吉", 235 | 3: "小吉", 4: "中吉", 5: "吉", 6: "大吉"} 236 | res = ' 今日人品是{},为“{}”'.format( 237 | rp, rp_dict[bisect_right(rp_range, rp)]) 238 | if event.sender.id == 80000000: 239 | res = '@匿名消息' + res 240 | await bot.send(event, res) 241 | else: 242 | await bot.send(event, [At(event.sender.id), res]) 243 | 244 | @bot.on(MessageEvent) 245 | async def qcjj_query(event: MessageEvent): 246 | msg = "".join(map(str, event.message_chain[Plain])) 247 | if msg.strip()[:4] in ["来只清楚", "随机蕊神", "来只蕊神", 'setu', "涩图"]: 248 | query_dict = {"来只清楚": './img/qcjj/', "随机蕊神": './img/ruishen/', 249 | "来只蕊神": './img/ruishen/', 'setu': './img/setu/', "涩图": './img/setu/'} 250 | img_path = query_dict[msg.strip()[:4]] 251 | img_local = await random_img(img_path) 252 | message_chain = MessageChain([ 253 | await Image.from_local(img_local) 254 | ]) 255 | await bot.send(event, message_chain) 256 | if msg.strip() == "色图": 257 | message_chain = MessageChain([ 258 | await Image.from_local('./img/color.jpg') 259 | ]) 260 | await bot.send(event, message_chain) 261 | 262 | @bot.on(MessageEvent) 263 | async def add_image(event: MessageEvent): 264 | msg = "".join(map(str, event.message_chain[Plain])) 265 | if msg.strip() == '添加蕊神' or msg.strip() == '添加清楚': 266 | global img_ruishen, img_qcjj, img_md5 267 | if msg.strip() == '添加蕊神': 268 | img_tmp = img_ruishen 269 | if event.sender.id == 2454256424: 270 | img_path = './img/ruishen/' 271 | else: 272 | await bot.send(event, "你没有该权限!") 273 | return 274 | else: 275 | img_tmp = img_qcjj 276 | if event.sender.id == 2454256424 or event.sender.group.id in [839594887, 959366007]: 277 | img_path = './img/qcjj/' 278 | else: 279 | await bot.send(event, "本群暂无权限,请联系管理员!") 280 | return 281 | quotes = event.message_chain[Quote] 282 | message: MessageFromIdResponse = await bot.message_from_id(quotes[0].target_id, quotes[0].id) 283 | images = message.data.message_chain[Image] 284 | flag = 0 285 | for image in images: 286 | suffix = image.image_id.split('.')[1] 287 | filename = img_path + time.strftime("%Y%m%d%H%M%S", time.localtime( 288 | )) + str(random.randint(1000, 9999)) + '.' + suffix 289 | await image.download(filename, None, False) 290 | tmp_md5 = await get_md5(filename) 291 | if tmp_md5 in img_md5: 292 | os.remove(filename) 293 | flag -= 1 294 | await bot.send(event, "已存在相同图片了哦,你火星了~") 295 | else: 296 | img_tmp.append(filename) 297 | img_md5[tmp_md5] = filename 298 | flag += 1 299 | if flag > 0: 300 | await bot.send(event, '%d 张图片添加成功!' % flag) 301 | 302 | @bot.on(MessageEvent) 303 | async def add_image(event: MessageEvent): 304 | msg = "".join(map(str, event.message_chain[Plain])) 305 | if msg.strip() == '删除图片': 306 | global img_ruishen, img_qcjj, img_md5 307 | if event.sender.id != 2454256424: 308 | await bot.send(event, "你没有该权限!") 309 | return 310 | quotes = event.message_chain[Quote] 311 | message: MessageFromIdResponse = await bot.message_from_id(quotes[0].target_id, quotes[0].id) 312 | image = message.data.message_chain[Image][0] 313 | suffix = image.image_id.split('.')[1] 314 | filename = './img/' + 'tmp.' + suffix 315 | await image.download(filename, None, False) 316 | tmp_md5 = await get_md5(filename) 317 | if tmp_md5 in img_md5: 318 | os.remove(filename) 319 | os.remove(img_md5[tmp_md5]) 320 | img = img_md5[tmp_md5] 321 | if img in img_ruishen: 322 | img_ruishen.remove(img) 323 | if img in img_qcjj: 324 | img_qcjj.remove(img) 325 | del img_md5[tmp_md5] 326 | await bot.send(event, '1 张图片删除成功!') 327 | else: 328 | os.remove(filename) 329 | await bot.send(event, '该图片不存在或已删除!') 330 | 331 | @bot.on(MessageEvent) 332 | async def next_contest(event: MessageEvent): # 查询近期比赛 333 | msg = "".join(map(str, event.message_chain[Plain])) 334 | if msg.strip().lower() == 'next': 335 | contest = await query_next_contest() 336 | if contest[0][1] != 32536799999: 337 | res = '找到最近的 1 场比赛如下:\n' + contest[0][0] 338 | await bot.send(event, res) 339 | else: 340 | await bot.send(event, '最近没有比赛哦~') 341 | 342 | @bot.on(MessageEvent) 343 | async def query_today(event: MessageEvent): 344 | msg = "".join(map(str, event.message_chain[Plain])) 345 | if msg.strip().lower() == 'today': 346 | res = await query_today_contest() 347 | await bot.send(event, "找到今天的比赛如下:\n" + res if res != '' else "今天没有比赛哦~") 348 | 349 | @bot.on(MessageEvent) 350 | async def update_cf_contest(event: MessageEvent): 351 | msg = "".join(map(str, event.message_chain[Plain])) 352 | if msg.strip().lower() == "更新cf比赛": 353 | global cf 354 | await bot.send(event, "更新cf比赛成功!" if await cf.update_contest() else "更新cf比赛失败!") 355 | 356 | @bot.on(MessageEvent) 357 | async def query_cf_contest(event: MessageEvent): 358 | msg = "".join(map(str, event.message_chain[Plain])) 359 | if msg.strip().lower() == 'cf': 360 | global cf 361 | res = await cf.get_contest_info() 362 | await bot.send(event, res) 363 | 364 | @bot.on(MessageEvent) 365 | async def update_all_q(event: MessageEvent): 366 | msg = "".join(map(str, event.message_chain[Plain])) 367 | if msg.strip().lower() == "更新cf分数": 368 | if event.sender.id == 2454256424: 369 | await auto_update_cf_user() 370 | else: 371 | await bot.send(event, "你没有该权限!") 372 | 373 | @bot.on(MessageEvent) 374 | async def add_cf_user(event: MessageEvent): 375 | msg = "".join(map(str, event.message_chain[Plain])) 376 | m = re.match(r'^添加cf用户\s*([\w.,-]+)\s*$', msg.strip(), re.I) 377 | if m: 378 | if event.type == "FriendMessage": 379 | await bot.send(event, "此功能暂时仅支持群聊!") 380 | else: 381 | unames = m.group(1).split(',') 382 | for uname in unames: 383 | group_id = str(event.sender.group.id) 384 | await bot.send(event, '添加成功!' if await cf.add_cf_user(uname, group_id) else "该用户不存在或cf api异常!") 385 | 386 | @bot.on(MessageEvent) 387 | async def del_cf_user(event: MessageEvent): 388 | msg = "".join(map(str, event.message_chain[Plain])) 389 | m = re.match(r'^删除cf用户\s*([\w.,-]+)\s*$', msg.strip(), re.I) 390 | if m: 391 | if event.type == "FriendMessage": 392 | await bot.send(event, "此功能暂时仅支持群聊!") 393 | else: 394 | unames = m.group(1).split(',') 395 | for uname in unames: 396 | group_id = str(event.sender.group.id) 397 | await bot.send(event, '删除成功!' if await cf.del_cf_user(uname, group_id) else '该用户不存在!') 398 | 399 | @bot.on(MessageEvent) 400 | async def query_cf_rank(event: MessageEvent): # 查询对应人的分数 401 | msg = "".join(map(str, event.message_chain[Plain])) 402 | m = re.match(r'^查询CF分数\s*([\w.-]+)\s*$', msg.strip(), re.I) 403 | if m is None: 404 | m = re.match(r'^查询(.*)的CF分数$', msg.strip(), re.I) 405 | if m: 406 | name = m.group(1) 407 | global cf 408 | statue = await cf.get_rating(name) 409 | await bot.send(event, statue) 410 | 411 | @bot.on(MessageEvent) 412 | async def all_q(event: MessageEvent): 413 | msg = "".join(map(str, event.message_chain[Plain])) 414 | if msg.strip().lower() == "cf总查询": 415 | if event.type == "FriendMessage": 416 | await bot.send(event, "此功能暂时仅支持群聊!") 417 | else: 418 | group_id = str(event.sender.group.id) 419 | res = await cf.get_cf_rating(group_id) 420 | await bot.send(event, res) 421 | 422 | @bot.on(MessageEvent) 423 | async def query_atc_contest(event: MessageEvent): 424 | msg = "".join(map(str, event.message_chain[Plain])) 425 | if msg.strip().lower() == 'atc': 426 | global atc 427 | res = await atc.get_contest_info() 428 | await bot.send(event, res if res else "获取比赛时出错,请联系管理员") 429 | 430 | @bot.on(MessageEvent) 431 | async def query_atc_rank(event: MessageEvent): 432 | msg = "".join(map(str, event.message_chain[Plain])) 433 | 434 | m = re.match(r'^查询ATC分数\s*([\w.-]+)\s*$', msg.strip(), re.I) 435 | if m is None: 436 | m = re.match(r'^查询(.*)的ATC分数$', msg.strip(), re.I) 437 | 438 | if m: 439 | name = m.group(1) 440 | global atc 441 | statue = await atc.get_rating(name) 442 | if statue != -1: 443 | await bot.send(event, statue) 444 | else: 445 | await bot.send(event, "不存在这个用户或查询出错哦") 446 | 447 | @bot.on(MessageEvent) 448 | async def update_atc_contest(event: MessageEvent): 449 | msg = "".join(map(str, event.message_chain[Plain])) 450 | if msg.strip() == "更新atc比赛": 451 | global atc 452 | await bot.send(event, "更新atc比赛成功!" if await atc.update_contest() else "更新atc比赛失败!") 453 | 454 | @bot.on(MessageEvent) 455 | async def update_nc_contest(event: MessageEvent): 456 | msg = "".join(map(str, event.message_chain[Plain])) 457 | if msg.strip() == "更新牛客比赛": 458 | global nc 459 | await bot.send(event, "更新牛客比赛成功!" if await nc.update_contest() else "更新牛客比赛失败!") 460 | 461 | @bot.on(MessageEvent) 462 | async def query_nc_contest(event: MessageEvent): 463 | msg = "".join(map(str, event.message_chain[Plain])) 464 | if msg.strip() == "牛客": 465 | global nc 466 | res = await nc.get_contest_info() 467 | await bot.send(event, res if res else "获取比赛时出错,请联系管理员") 468 | 469 | @bot.on(MessageEvent) 470 | async def update_nc_rating(event: MessageEvent): 471 | msg = "".join(map(str, event.message_chain[Plain])) 472 | if msg.strip() == "更新牛客分数" or msg.strip().lower() == "更新牛客rating": 473 | await bot.send(event, "更新牛客rating成功!" if await nc.update_all_nc_rating() else "更新牛客rating失败!") 474 | 475 | @bot.on(MessageEvent) 476 | async def query_nc_rating(event: MessageEvent): 477 | msg = "".join(map(str, event.message_chain[Plain])) 478 | m = re.match(r'^查询牛客分数\s*([\u4e00-\u9fa5\w.-]+)\s*$', msg.strip()) 479 | if m: 480 | uname = m.group(1) 481 | rating = await nc.get_rating(uname) 482 | await bot.send(event, rating if rating else "不存在这个用户或查询出错哦") 483 | 484 | @bot.on(MessageEvent) 485 | async def update_lc_contest(event: MessageEvent): 486 | msg = "".join(map(str, event.message_chain[Plain])) 487 | if msg.strip().lower() == "更新lc比赛" or msg.strip().lower() == "更新leetcode比赛": 488 | global lc 489 | await bot.send(event, "更新LeetCode比赛成功!" if await lc.update_contest() else "更新LeetCode比赛失败!") 490 | 491 | @bot.on(MessageEvent) 492 | async def query_lc_contest(event: MessageEvent): 493 | msg = "".join(map(str, event.message_chain[Plain])) 494 | if msg.strip().lower() == "lc": 495 | global lc 496 | res = await lc.get_contest_info() 497 | await bot.send(event, res) 498 | 499 | @bot.on(MessageEvent) 500 | async def update_all_lc_rating(event: MessageEvent): 501 | msg = "".join(map(str, event.message_chain[Plain])) 502 | if msg.strip().lower() == "更新力扣分数": 503 | res = await lc.update_local_rating() 504 | await bot.send(event, '更新成功!' if res else '更新失败!') 505 | 506 | @bot.on(MessageEvent) 507 | async def query_lc_rating(event: MessageEvent): 508 | msg = "".join(map(str, event.message_chain[Plain])) 509 | m = re.match(r'^查询力扣分数\s*([\u4e00-\u9fa5\w.-]+)\s*$', msg.strip()) 510 | if m: 511 | uname = m.group(1) 512 | rating = await lc.get_rating(uname) 513 | await bot.send(event, rating if rating else "不存在这个用户或查询出错哦") 514 | 515 | async def default(x): 516 | return '' 517 | 518 | async def note(name, content='未指定发送内容', func=default): 519 | with open('./oj_json/subscribe.json', 'r', encoding='utf-8') as f: 520 | all_subscribe = json.load(f) 521 | for user_id in all_subscribe[name]: 522 | if all_subscribe[name][user_id] == 'GroupMessage': 523 | event = Group(id=user_id, name='', permission='MEMBER') 524 | tmp = await func(user_id) 525 | message_chain = content + tmp 526 | else: 527 | event = Friend(id=user_id) 528 | message_chain = content 529 | if message_chain == '': 530 | continue 531 | try: 532 | await bot.send(event, message_chain) 533 | except: 534 | with open('./oj_json/subscribe.json', 'r+', encoding='utf-8') as f: 535 | all_subscribe = json.load(f) 536 | del all_subscribe[name][user_id] 537 | f.seek(0) 538 | f.truncate() 539 | json.dump(all_subscribe, f, indent=4) 540 | await bot.send_friend_message(2454256424, "{}已成功取消订阅!".format(user_id)) 541 | 542 | async def auto_update_cf_user(): 543 | flag = 0 544 | while(flag < 5): 545 | res = await cf.update_rating() 546 | if res: 547 | await note('cf', 'cf rating更新成功!\n', cf.get_cf_rating) 548 | return 549 | else: 550 | await bot.send_friend_message(2454256424, 'cf rating更新失败!') 551 | time.sleep(300) 552 | flag += 1 553 | 554 | @bot.on(MessageEvent) 555 | async def cancel_update_sche(event: MessageEvent): 556 | msg = "".join(map(str, event.message_chain[Plain])) 557 | if msg == '取消自动更新': 558 | scheduler.remove_job('up_rating') 559 | await bot.send(event, '取消成功!') 560 | 561 | async def cf_note(): 562 | global cf 563 | message_chain = await cf.get_recent_info() 564 | await note('cf', message_chain) 565 | 566 | async def cf_shang_hao(): 567 | message_chain = MessageChain([ 568 | await Image.from_local('./img/up_cf.jpg') 569 | ]) 570 | await note('cf', message_chain) 571 | 572 | async def cf_xia_hao(): 573 | message_chain = MessageChain([ 574 | await Image.from_local('./img/down_cf.jpg') 575 | ]) 576 | await note('cf', message_chain) 577 | 578 | async def nc_note(): 579 | global nc 580 | message_chain = await nc.get_recent_info() 581 | await note('牛客', message_chain) 582 | 583 | async def nc_shang_hao(): 584 | message_chain = MessageChain([ 585 | await Image.from_local('./img/up_nc.png') 586 | ]) 587 | await note('牛客', message_chain) 588 | 589 | async def lc_note(): 590 | global lc 591 | message_chain = await lc.get_recent_info() 592 | await note('lc', message_chain) 593 | 594 | async def atc_note(): 595 | global atc 596 | message_chain = await atc.get_recent_info() 597 | await note('atc', message_chain) 598 | 599 | async def notify_contest_info(): 600 | res = await query_today_contest() 601 | if res != '': 602 | msg = "早上好呀~今天的比赛有:\n" + res.strip() 603 | # else: 604 | # msg = "今天没有比赛哦~记得刷题呀!" 605 | await note('today', msg) 606 | 607 | @scheduler.scheduled_job('interval', minutes=10, timezone='Asia/Shanghai') 608 | async def refresh_job(): 609 | scheduler.remove_all_jobs() 610 | await update() 611 | await sche_job() 612 | # msg = 'success:' + time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()) 613 | # await bot.send_friend_message(2454256424, msg) 614 | 615 | async def sche_job(): 616 | global cf, atc, nc, lc 617 | scheduler.add_job(update, 'interval', hours=9, 618 | timezone='Asia/Shanghai', misfire_grace_time=60) 619 | scheduler.add_job(notify_contest_info, CronTrigger(hour=8, minute=0, timezone='Asia/Shanghai'), 620 | misfire_grace_time=60) 621 | scheduler.add_job(refresh_job, 'cron', hour=5, minute=0, second=0, timezone='Asia/Shanghai', 622 | misfire_grace_time=60) 623 | scheduler.add_job(lc.update_local_rating, 'cron', hour=12, minute=0, second=0, timezone='Asia/Shanghai', 624 | misfire_grace_time=60) 625 | await sche_add(update, cf.update_time) 626 | await sche_add(update, atc.update_time) 627 | await sche_add(update, nc.update_time) 628 | await sche_add(update, lc.update_time) 629 | await sche_add(cf_note, cf.note_time) 630 | await sche_add(cf_shang_hao, cf.begin_time) 631 | await sche_add(cf_xia_hao, cf.end_time) 632 | await sche_add(nc_note, nc.note_time) 633 | await sche_add(nc_shang_hao, nc.begin_time) 634 | await sche_add(lc_note, lc.note_time) 635 | await sche_add(atc_note, atc.note_time) 636 | up_time = await cf.auto_update() 637 | # auto_up_note = '下一次cf rating自动更新时间为:' + \ 638 | # time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(up_time)) 639 | # await bot.send_friend_message(2454256424, auto_up_note) 640 | await sche_add(auto_update_cf_user, up_time, id='up_rating') 641 | # scheduler.add_job(rs, 'cron', hour='0-23', timezone='Asia/Shanghai') 642 | # scheduler.add_job(notify_project, 'cron', hour=21, timezone='Asia/Shanghai', misfire_grace_time=60) 643 | 644 | bot.run() 645 | --------------------------------------------------------------------------------