-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.py
484 lines (382 loc) · 17.9 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
import os
from dotenv import load_dotenv
import discord
import sqlite3
import requests
import re
load_dotenv()
intents = discord.Intents.default()
intents.messages = True
intents.message_content = True
intents.guilds = True
intents.members = True
client = discord.Client(intents=intents)
repo_owner_g = 'SwiftGGTeam'
repo_name_g = 'swiftui-tutorial-chinese'
def create_table():
conn = sqlite3.connect('user_accounts.db')
c = conn.cursor()
c.execute('''CREATE TABLE IF NOT EXISTS accounts
(user_id TEXT PRIMARY KEY, github_id TEXT)''')
conn.commit()
conn.close()
def update_github_id(user_id, github_id):
github_id = github_id.lower()
conn = sqlite3.connect('user_accounts.db')
c = conn.cursor()
# 检查是否存在目标用户的记录
c.execute('SELECT COUNT(*) FROM accounts WHERE user_id = ?', (user_id,))
count = c.fetchone()[0]
if count == 0:
# 如果记录不存在,插入一条新的记录
c.execute('INSERT INTO accounts (user_id, github_id) VALUES (?, ?)', (user_id, github_id))
else:
# 如果记录存在,更新 GitHub ID
c.execute('UPDATE accounts SET github_id = ? WHERE user_id = ?', (github_id, user_id))
conn.commit()
conn.close()
def get_github_id(user_id):
conn = sqlite3.connect('user_accounts.db')
c = conn.cursor()
# 查询目标用户的记录,并返回对应的 github_id
c.execute('SELECT github_id FROM accounts WHERE user_id = ?', (user_id,))
result = c.fetchone()
conn.close()
if result:
return result[0]
else:
return None
def get_discord_id(github_id):
github_id = github_id.lower()
conn = sqlite3.connect('user_accounts.db')
c = conn.cursor()
# 查询目标用户的记录,并返回对应的 discord_id
c.execute('SELECT user_id FROM accounts WHERE github_id = ?', (github_id,))
result = c.fetchone()
conn.close()
if result:
return result[0]
else:
return None
def send_issues(channel, issues):
target_issues = []
for issue in issues:
if "/pull" not in issue['html_url']:
target_issues.append(issue)
if len(target_issues) > 0:
list_string = ''
for issue in target_issues:
number = issue['number']
title = issue['title']
url = issue['html_url']
body = issue.get('body')
if not body:
body = '无描述'
author = issue['user']['login']
list_string += f"#{number} {title}\n"
return f"任务列表:\n{list_string}"
else:
return '目前没有待处理的任务'
def send_issue_detail(channel, issue):
if "/pull" in issue['html_url']:
return '这不是一个合法任务'
else:
number = issue['number']
title = issue['title']
url = issue['html_url']
body = issue.get('body')
if not body:
body = '无描述'
author = issue['user']['login']
msg = f"**任务**:{number}\n**标题**:{title}\n**作者**:{author}\n**描述**:{body}\n**URL**:{url}\n"
msg = re.sub(r"(https?://[^\s]+)", r"<\1>", msg)
return msg
def get_assigned_issue_ids(issues, github_id):
github_id = github_id.lower()
assigned_issue_ids = []
for issue in issues:
if "/pull" not in issue['html_url']:
assignees = issue.get('assignees', [])
assignee_logins = [assignee['login'].lower() for assignee in assignees]
if github_id.lower() in assignee_logins:
issue_id = issue['number']
assigned_issue_ids.append(issue_id)
return sorted(assigned_issue_ids)
def add_collaborator(repo_owner, repo_name, github_id):
github_id = github_id.lower()
# 替换为你的 GitHub token
token = os.getenv('GH_TOKEN')
# 构建 API 请求的 URL
url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/collaborators/{github_id}'
# 设置请求头,包括身份验证 token
headers = {
'Authorization': 'Token ' + token,
'Accept': 'application/vnd.github.v3+json'
}
# 设置权限级别为 "read",即只读权限
data = {
'permission': 'write'
}
# 发送 PUT 请求将用户添加为 Collaborator
response = requests.put(url, headers=headers, json=data)
# 检查响应状态码
if response.status_code == 201:
return f"您还不是项目协作者,已发送邀请,请接受后再尝试领取任务:https://github.com/{repo_owner}/{repo_name}/invitations"
else:
return f"在添加您为仓库协作者时失败,请联系管理员"
def is_collaborator(repo_owner, repo_name, github_id):
github_id = github_id.lower()
user = repo_owner
repo = repo_name
collaborator = github_id
access_token = os.getenv('GH_TOKEN')
headers = {
'Authorization': 'Token ' + access_token,
'Accept': 'application/vnd.github.v3+json'
}
url = f'https://api.github.com/repos/{user}/{repo}/collaborators'
response = requests.get(url, headers=headers)
if response.status_code == 200:
collaborators = response.json()
# 检查指定的协作者是否在协作者列表中
is_collaborator = any(c['login'] == collaborator for c in collaborators)
return is_collaborator
else:
return None
def is_member(repo_owner, github_id):
github_id = github_id.lower()
username = github_id
org_name = repo_owner
access_token = os.getenv('GH_TOKEN')
# 发送GET请求获取组织成员列表
url = f"https://api.github.com/orgs/{org_name}/members"
headers = {'Authorization': f'token {access_token}'}
response = requests.get(url, headers=headers)
if response.status_code == 200:
members = [member['login'].lower() for member in response.json()]
if username in members:
return True
else:
return False
else:
return None
@client.event
async def on_ready():
create_table()
print('We have logged in as {0.user}'.format(client))
@client.event
async def on_message(message):
msg_return = ''
# 无视机器人自己的消息
if message.author == client.user:
return
# 调试用:看看这条消息
# await message.channel.send(message)
# 是否为私聊
is_private_chat = isinstance(message.channel, discord.DMChannel)
if message.content.startswith('/'):
command = message.content[1:] # 获取命令名,去除前缀 /
# info: 列出我的所有信息
# - GitHub 账号
# - 已领取的任务(根据 GitHub)
if command == 'help':
help_msg = [
'指令列表:',
'/info:列出我的所有账号信息',
'/bind [github_id]:绑定 GitHub 账号',
'/tasks:列出全部可领取的任务,Issue 序号即为任务 ID',
'/task [task_id]:查询任务详情,Issue 序号即为任务 ID',
'/claim [task_id]:领取某任务,Issue 序号即为任务 ID'
]
msg_return = "\n".join(map(str, help_msg))
pass
# info: 列出我的所有信息
# - GitHub 账号
# - 已领取的任务(根据 GitHub)
elif command == 'info':
# 处理 /info 命令
# 根据 discord_id 获取对应的 github_id
discord_id = message.author.id
github_id = get_github_id(discord_id)
if github_id:
info_msg = f'您绑定的 GitHub ID 是:{github_id}\n'
# 获取仓库的名称
repo_owner = repo_owner_g
repo_name = repo_name_g
# 构建请求头
access_token = os.getenv('GH_TOKEN')
headers = {'Authorization': f'token {access_token}'}
# 调用 GitHub API 获取仓库的所有 open issue
response = requests.get(f"https://api.github.com/repos/{repo_owner}/{repo_name}/issues?state=open", headers=headers)
# 检查 API 响应状态码
if response.status_code == 200:
issues = response.json()
# 对获取到的 issue 进行处理并发送 issue ID
assigned_issue_ids = get_assigned_issue_ids(issues, github_id)
if assigned_issue_ids:
info_msg += f'指派给您的任务 ID 为:{", ".join(map(str, assigned_issue_ids))}'
else:
info_msg += f'目前没有找到指派给您的任务'
else:
info_msg += f"获取任务列表失败:{response.json()['message']}"
msg_return = info_msg
else:
msg_return = '您尚未绑定 GitHub ID,请执行 bind [github_id] 命令进行绑定'
pass
# bind: 绑定 GitHub 账号
elif command.startswith('bind'):
# 处理 /bind 命令
# 使用空格将消息拆分成多个部分
parts = message.content.split(' ')
if len(parts) < 2:
msg_return = '请提供 GitHub ID'
else:
github_id = parts[1] # 获取参数 github_id
discord_id = message.author.id # 获取 discord_id
update_github_id(discord_id, github_id)
msg_return = f'绑定成功,您绑定的 GitHub ID 是 {github_id}'
pass
# task: 查询所有 Open Issue
elif command == 'tasks':
# 处理 /task 命令
# 获取仓库的名称
repo_owner = repo_owner_g
repo_name = repo_name_g
# 构建请求头
access_token = os.getenv('GH_TOKEN')
headers = {'Authorization': f'token {access_token}'}
# 调用 GitHub API 获取仓库的所有 open issue
response = requests.get(f"https://api.github.com/repos/{repo_owner}/{repo_name}/issues?state=open", headers=headers)
# 检查 API 响应状态码
if response.status_code == 200:
issues = response.json()
# 对获取到的 issue 进行处理并以美观的样式发送出去
msg_return = send_issues(message.channel, issues)
else:
msg_return = f"获取任务列表失败:{response.json()['message']}"
pass
# bind: 绑定 GitHub 账号
elif command.startswith('task'):
# 处理 /bind 命令
# 使用空格将消息拆分成多个部分
parts = message.content.split(' ')
if len(parts) < 2:
msg_return = '请提供任务 ID'
else:
issue_number = parts[1] # 获取参数 github_id
# 获取仓库的名称
repo_owner = repo_owner_g
repo_name = repo_name_g
# 构建请求头
access_token = os.getenv('GH_TOKEN')
headers = {'Authorization': f'token {access_token}'}
# 调用 GitHub API 获取仓库的指定 issue
response = requests.get(f"https://api.github.com/repos/{repo_owner}/{repo_name}/issues/{issue_number}", headers=headers)
# 检查 API 响应状态码
if response.status_code == 200:
issue_detail = response.json()
# 对获取到的 issue 进行处理并以美观的样式发送出去
msg_return = send_issue_detail(message.channel, issue_detail)
else:
msg_return = f"获取任务详情失败:{response.json()['message']}"
pass
pass
# claim: 领取某 Issue
# - 单独给申请者发送通知消息,告知任务领取状态,如果领取成功发送仓库权限邀请消息,以及对应的 issue 链接。
# - 通知 issue 作者
elif command.startswith('claim'):
# 处理 /claim 命令
discord_id = message.author.id # 获取 discord_id
github_id = get_github_id(discord_id)
if github_id:
# 使用空格将消息拆分成多个部分
parts = message.content.split(' ')
if len(parts) < 2:
msg_return = '请提供任务 ID'
else:
issue_id = parts[1] # 获取参数 issue_id
# 指定仓库信息
repo_owner = repo_owner_g
repo_name = repo_name_g
issue_number = issue_id
# 构建请求头
access_token = os.getenv('GH_TOKEN')
headers = {'Authorization': f'token {access_token}'}
# 调用 GitHub API 获取仓库的指定 issue
response = requests.get(f"https://api.github.com/repos/{repo_owner}/{repo_name}/issues/{issue_number}", headers=headers)
# 检查 API 响应状态码
if response.status_code == 200:
issue_detail = response.json()
assignees = issue_detail.get('assignees', [])
if len(assignees) != 0:
assignee_logins = [assignee['login'].lower() for assignee in assignees]
if github_id.lower() in assignee_logins:
msg_return = f'您已领取该任务 {issue_number}'
else:
msg_return = f'该任务 {issue_number} 已被其他用户领取'
elif "/pull" in issue_detail['html_url']:
msg_return = '这不是一个合法任务'
elif 'open' != issue_detail['state']:
msg_return = '这不是一个活跃任务'
else:
if is_member(repo_owner_g, github_id) == True or is_collaborator(repo_owner, repo_name, github_id) == True:
# 指定 GitHub 用户登录名
assignee_login = github_id
# 读取 GitHub 开发者 Token
gh_token = os.getenv('GH_TOKEN')
# 构建 API 请求 URL
url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/issues/{issue_number}/assignees'
# 构建请求头
headers = {
'Accept': 'application/vnd.github.v3+json',
'Authorization': f'token {gh_token}'
}
# 构建请求体
data = {
'assignees': [assignee_login]
}
# 发送请求
response = requests.post(url, headers=headers, json=data)
# 检查请求是否成功
if response.status_code == 201:
msg_return = f'领取任务 {issue_number} 成功'
# 领取任务成功单独私信,获取要私信的用户
target_user = client.get_user(discord_id)
pri_msg = f"任务链接:<https://github.com/{repo_owner}/{repo_name}/issues/{issue_number}>"
if is_private_chat == False:
pri_msg = f'{msg_return}\n{pri_msg}'
await target_user.send(pri_msg)
# 领取任务成功单独私信,获取要私信的用户
creator_github_id = issue_detail['user']['login']
creator_discord_id = get_discord_id(creator_github_id)
if creator_discord_id:
target_creator = client.get_user(creator_discord_id)
pri_creator_msg = f"您提交的 Issue 已被认领\n任务链接:<https://github.com/{repo_owner}/{repo_name}/issues/{issue_number}>"
await target_user.send(pri_creator_msg)
pass
else:
msg_return = f"任务领取失败:{response.json()['message']}"
pass
else:
add_collaborator_result = add_collaborator(repo_owner, repo_name, github_id)
msg_return = f"{add_collaborator_result}"
pass
else:
msg_return = f"获取任务详情失败:{response.json()['message']}"
pass
pass
else:
msg_return = '您尚未绑定 GitHub ID,请执行 bind [github_id] 命令进行绑定'
pass
else:
# 如果命令不是这些预定义的命令
msg_return = '无效命令,请执行 /help 查看帮助'
pass
if len(msg_return) != 0:
msg_content = msg_return
if is_private_chat:
msg_content = msg_return
else:
msg_content = f"<@{message.author.id}> {msg_return}"
await message.channel.send(msg_content)
client.run(os.getenv('TOKEN'))