├── 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 | 
4 | 
5 |
6 | ---
7 |
8 | 本项目是一个在群里可以通知打codeforces、牛客、AtCoder、LeetCode的qq机器人项目,基于
9 | ACM_Contest_QQbot修改(膜拜INGg巨巨
10 |  )。
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)写的
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 |
--------------------------------------------------------------------------------
|