mirror of
https://github.com/Xtao-Labs/QQ-GitHub-Bot.git
synced 2025-01-30 15:08:54 +00:00
🚧 add github api
This commit is contained in:
parent
f93241afdc
commit
6a4ef989c9
@ -1,6 +1,6 @@
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8
|
||||
|
||||
RUN ./install_wkhtmltox buster_amd64
|
||||
RUN ./download_wkhtmltox buster_amd64
|
||||
|
||||
RUN dpkg -i wkhtmltox_*.deb
|
||||
|
||||
|
@ -6,12 +6,12 @@ services:
|
||||
- "/etc/localtime:/etc/localtime"
|
||||
- "./:/app/"
|
||||
ports:
|
||||
- "2333:2333"
|
||||
env_file:
|
||||
- ".env.prod"
|
||||
- "$PORT:$PORT"
|
||||
environment:
|
||||
- ENVIRONMENT=prod
|
||||
- HOST=$HOST
|
||||
- PORT=$PORT
|
||||
- APP_MODULE=bot:app
|
||||
- SECRET
|
||||
- ACCESS_TOKEN
|
||||
# - SECRET=$SECRET
|
||||
# - ACCESS_TOKEN=$ACCESS_TOKEN
|
||||
network_mode: bridge
|
||||
|
322
src/libs/github/__init__.py
Normal file
322
src/libs/github/__init__.py
Normal file
@ -0,0 +1,322 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Author : yanyongyu
|
||||
@Date : 2021-03-09 17:13:37
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2021-03-09 18:36:19
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
__author__ = "yanyongyu"
|
||||
|
||||
import time
|
||||
import datetime
|
||||
from typing import List, Optional
|
||||
from typing_extensions import Literal
|
||||
|
||||
from .request import Requester
|
||||
|
||||
DEFAULT_BASE_URL = "https://api.github.com"
|
||||
DEFAULT_STATUS_URL = "https://status.github.com"
|
||||
DEFAULT_TIMEOUT = 15
|
||||
DEFAULT_PER_PAGE = 30
|
||||
|
||||
|
||||
class Github:
|
||||
|
||||
def __init__(self,
|
||||
token_or_client_id: Optional[str] = None,
|
||||
client_secret: Optional[str] = None,
|
||||
base_url: str = DEFAULT_BASE_URL,
|
||||
timeout: int = DEFAULT_TIMEOUT,
|
||||
user_agent: str = "Python/GitHub",
|
||||
per_page: int = DEFAULT_PER_PAGE,
|
||||
retry: Optional[int] = None,
|
||||
verify: bool = True):
|
||||
self._requester = Requester(token_or_client_id, client_secret, base_url,
|
||||
timeout, user_agent, per_page, verify,
|
||||
retry)
|
||||
|
||||
@property
|
||||
def oauth_scopes(self):
|
||||
"""
|
||||
:type: list of string
|
||||
"""
|
||||
return self._requester.oauth_scopes
|
||||
|
||||
def get_rate_limit(self):
|
||||
"""
|
||||
GET /rate_limit
|
||||
|
||||
https://docs.github.com/en/rest/reference/rate-limit#get-rate-limit-status-for-the-authenticated-user
|
||||
"""
|
||||
headers, data = self._requester.requestJsonAndCheck(
|
||||
"GET", "/rate_limit")
|
||||
return RateLimit.RateLimit(self._requester, headers, data["resources"],
|
||||
True)
|
||||
|
||||
def get_license(self, key: str):
|
||||
"""
|
||||
GET /license/:license
|
||||
|
||||
https://docs.github.com/en/rest/reference/licenses#get-a-license
|
||||
"""
|
||||
|
||||
assert isinstance(key, str), key
|
||||
headers, data = self._requester.requestJsonAndCheck(
|
||||
"GET", f"/licenses/{key}")
|
||||
return github.License.License(self._requester,
|
||||
headers,
|
||||
data,
|
||||
completed=True)
|
||||
|
||||
def get_licenses(self):
|
||||
"""
|
||||
GET /licenses
|
||||
|
||||
https://docs.github.com/en/rest/reference/licenses#get-all-commonly-used-licenses
|
||||
"""
|
||||
|
||||
url_parameters = dict()
|
||||
|
||||
return github.PaginatedList.PaginatedList(github.License.License,
|
||||
self._requester, "/licenses",
|
||||
url_parameters)
|
||||
|
||||
def get_events(self):
|
||||
"""
|
||||
GET /events
|
||||
|
||||
https://docs.github.com/en/rest/reference/activity#list-public-events
|
||||
"""
|
||||
|
||||
return github.PaginatedList.PaginatedList(github.Event.Event,
|
||||
self._requester, "/events",
|
||||
None)
|
||||
|
||||
def get_user(self, username: Optional[str] = None):
|
||||
"""
|
||||
GET /users/:user
|
||||
|
||||
https://docs.github.com/en/rest/reference/users#get-a-user
|
||||
|
||||
GET /user
|
||||
https://docs.github.com/en/rest/reference/users#get-the-authenticated-user
|
||||
"""
|
||||
if not username:
|
||||
return AuthenticatedUser.AuthenticatedUser(self._requester, {},
|
||||
{"url": "/user"},
|
||||
completed=False)
|
||||
else:
|
||||
headers, data = self._requester.requestJsonAndCheck(
|
||||
"GET", f"/users/{username}")
|
||||
return github.NamedUser.NamedUser(self._requester,
|
||||
headers,
|
||||
data,
|
||||
completed=True)
|
||||
|
||||
def get_users(self, since: Optional[int] = None):
|
||||
"""
|
||||
GET /users
|
||||
|
||||
https://docs.github.com/en/rest/reference/users#list-users
|
||||
"""
|
||||
url_parameters = dict()
|
||||
if since is not None:
|
||||
url_parameters["since"] = since
|
||||
return github.PaginatedList.PaginatedList(github.NamedUser.NamedUser,
|
||||
self._requester, "/users",
|
||||
url_parameters)
|
||||
|
||||
def get_organization(self, org):
|
||||
"""
|
||||
GET /orgs/:org
|
||||
|
||||
https://docs.github.com/en/rest/reference/orgs#get-an-organization
|
||||
"""
|
||||
headers, data = self._requester.requestJsonAndCheck(
|
||||
"GET", f"/orgs/{org}")
|
||||
return github.Organization.Organization(self._requester,
|
||||
headers,
|
||||
data,
|
||||
completed=True)
|
||||
|
||||
def get_organizations(self, since: Optional[int] = None):
|
||||
"""
|
||||
GET /organizations
|
||||
|
||||
https://docs.github.com/en/rest/reference/orgs#list-organizations
|
||||
"""
|
||||
url_parameters = dict()
|
||||
if since is not None:
|
||||
url_parameters["since"] = since
|
||||
return github.PaginatedList.PaginatedList(
|
||||
github.Organization.Organization,
|
||||
self._requester,
|
||||
"/organizations",
|
||||
url_parameters,
|
||||
)
|
||||
|
||||
def get_repo(self, full_name: str, lazy: bool = False):
|
||||
"""
|
||||
GET /repos/:owner/:repo
|
||||
|
||||
https://docs.github.com/en/rest/reference/repos#get-a-repository
|
||||
"""
|
||||
url = f"/repos/{full_name}"
|
||||
if lazy:
|
||||
return Repository.Repository(self._requester, {}, {"url": url},
|
||||
completed=False)
|
||||
headers, data = self._requester.requestJsonAndCheck("GET", url)
|
||||
return Repository.Repository(self._requester,
|
||||
headers,
|
||||
data,
|
||||
completed=True)
|
||||
|
||||
def get_repos(
|
||||
self,
|
||||
since: Optional[int] = None,
|
||||
visibility: Literal["all", "public", "private", None] = None,
|
||||
affiliation: Optional[List[Literal["owner", "collaborator",
|
||||
"organization_member"]]] = None,
|
||||
type: Optional[Literal["all", "owner", "public", "private",
|
||||
"member"]] = None,
|
||||
sort: Optional[Literal["created", "updated", "pushed",
|
||||
"full_name"]] = None,
|
||||
direction: Optional[Literal["asc", "desc"]] = None):
|
||||
"""
|
||||
GET /user/repos
|
||||
|
||||
https://docs.github.com/en/rest/reference/repos#list-repositories-for-the-authenticated-user
|
||||
"""
|
||||
url_parameters = dict()
|
||||
if since is not None:
|
||||
url_parameters["since"] = since
|
||||
if visibility is not None:
|
||||
url_parameters["visibility"] = visibility
|
||||
if affiliation is not None:
|
||||
url_parameters["affiliation"] = ",".join(affiliation)
|
||||
if type is not None:
|
||||
url_parameters["type"] = type
|
||||
if sort is not None:
|
||||
url_parameters["sort"] = sort
|
||||
if direction is not None:
|
||||
url_parameters["direction"] = direction
|
||||
return github.PaginatedList.PaginatedList(
|
||||
github.Repository.Repository,
|
||||
self._requester,
|
||||
"/repositories",
|
||||
url_parameters,
|
||||
)
|
||||
|
||||
def get_project(self, id: int):
|
||||
"""
|
||||
GET /projects/:project_id
|
||||
|
||||
https://docs.github.com/en/rest/reference/projects#get-a-project
|
||||
"""
|
||||
headers, data = self._requester.requestJsonAndCheck(
|
||||
"GET",
|
||||
f"/projects/{id}",
|
||||
headers={"Accept": Consts.mediaTypeProjectsPreview},
|
||||
)
|
||||
return github.Project.Project(self._requester,
|
||||
headers,
|
||||
data,
|
||||
completed=True)
|
||||
|
||||
def get_project_column(self, id):
|
||||
"""
|
||||
GET /projects/columns/:column_id
|
||||
|
||||
https://docs.github.com/en/rest/reference/projects#get-a-project-column
|
||||
"""
|
||||
headers, data = self._requester.requestJsonAndCheck(
|
||||
"GET",
|
||||
"/projects/columns/%d" % id,
|
||||
headers={"Accept": Consts.mediaTypeProjectsPreview},
|
||||
)
|
||||
return github.ProjectColumn.ProjectColumn(self._requester,
|
||||
headers,
|
||||
data,
|
||||
completed=True)
|
||||
|
||||
def get_gist(self, id: str):
|
||||
"""
|
||||
GET /gists/:id
|
||||
|
||||
https://docs.github.com/en/rest/reference/gists#get-a-gist
|
||||
"""
|
||||
headers, data = self._requester.requestJsonAndCheck(
|
||||
"GET", f"/gists/{id}")
|
||||
return github.Gist.Gist(self._requester, headers, data, completed=True)
|
||||
|
||||
def get_gists(self, since: Optional[datetime.datetime] = None):
|
||||
"""
|
||||
GET /gists/public
|
||||
|
||||
https://docs.github.com/en/rest/reference/gists#list-public-gists
|
||||
"""
|
||||
url_parameters = dict()
|
||||
if since:
|
||||
url_parameters["since"] = since.strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||
return github.PaginatedList.PaginatedList(github.Gist.Gist,
|
||||
self._requester,
|
||||
"/gists/public",
|
||||
url_parameters)
|
||||
|
||||
def render_markdown(self,
|
||||
text: str,
|
||||
context: Optional[github.Repository.Repository] = None):
|
||||
"""
|
||||
POST /markdown
|
||||
|
||||
https://docs.github.com/en/rest/reference/markdown#render-a-markdown-document
|
||||
"""
|
||||
post_parameters = {"text": text}
|
||||
if context:
|
||||
post_parameters["mode"] = "gfm"
|
||||
post_parameters["context"] = context._identity
|
||||
status, headers, data = self._requester.requestJson(
|
||||
"POST", "/markdown", input=post_parameters)
|
||||
return data
|
||||
|
||||
def get_hook(self, full_name: str, id: str):
|
||||
"""
|
||||
GET /repo/:full_name/hooks/:name
|
||||
|
||||
https://docs.github.com/en/rest/reference/repos#get-a-repository-webhook
|
||||
"""
|
||||
headers, attributes = self._requester.requestJsonAndCheck(
|
||||
"GET", f"/repos/{full_name}/hooks/{id}")
|
||||
return HookDescription.HookDescription(self._requester,
|
||||
headers,
|
||||
attributes,
|
||||
completed=True)
|
||||
|
||||
def get_hooks(self):
|
||||
"""
|
||||
GET /repo/:full_name/hooks
|
||||
|
||||
https://docs.github.com/en/rest/reference/repos#list-repository-webhooks
|
||||
"""
|
||||
headers, data = self._requester.requestJsonAndCheck(
|
||||
"GET", f"/repo/{full_name}/hooks")
|
||||
return [
|
||||
HookDescription.HookDescription(self._requester,
|
||||
headers,
|
||||
attributes,
|
||||
completed=True)
|
||||
for attributes in data
|
||||
]
|
||||
|
||||
def get_emojis(self):
|
||||
"""
|
||||
GET /emojis
|
||||
|
||||
https://docs.github.com/en/rest/reference/emojis#get-emojis
|
||||
"""
|
||||
headers, attributes = self._requester.requestJsonAndCheck(
|
||||
"GET", "/emojis")
|
||||
return attributes
|
22
src/libs/github/request.py
Normal file
22
src/libs/github/request.py
Normal file
@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Author : yanyongyu
|
||||
@Date : 2021-03-09 17:34:53
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2021-03-09 18:36:58
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
__author__ = "yanyongyu"
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Requester:
|
||||
|
||||
def __init__(self, token_or_client_id: Optional[str],
|
||||
client_secret: Optional[str], base_url: str, timeout: int,
|
||||
user_agent: str, per_page: int, retry: Optional[int],
|
||||
verify: bool):
|
||||
pass
|
@ -4,17 +4,22 @@
|
||||
@Author : yanyongyu
|
||||
@Date : 2020-09-21 19:05:28
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2020-10-04 15:10:41
|
||||
@LastEditTime : 2021-03-09 16:23:44
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
__author__ = "yanyongyu"
|
||||
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import validator, BaseSettings
|
||||
|
||||
|
||||
class Config(BaseSettings):
|
||||
github_command_priority: int = 5
|
||||
github_client_id: Optional[str] = None
|
||||
github_client_secret: Optional[str] = None
|
||||
github_redirect_uri: Optional[str] = None
|
||||
|
||||
@validator("github_command_priority")
|
||||
def validate_priority(cls, v):
|
||||
|
11
src/plugins/github/libs/__init__.py
Normal file
11
src/plugins/github/libs/__init__.py
Normal file
@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Author : yanyongyu
|
||||
@Date : 2021-03-09 16:14:00
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2021-03-09 16:32:54
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
__author__ = "yanyongyu"
|
27
src/plugins/github/libs/auth.py
Normal file
27
src/plugins/github/libs/auth.py
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Author : yanyongyu
|
||||
@Date : 2021-03-09 16:30:16
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2021-03-09 16:32:14
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
__author__ = "yanyongyu"
|
||||
|
||||
import urllib.parse
|
||||
|
||||
from .. import github_config as config
|
||||
|
||||
assert config.github_client_id and config.github_client_secret and config.github_redirect_uri, "GitHub OAuth Application info not fully provided! OAuth plugin will not work!"
|
||||
|
||||
|
||||
async def get_auth_link(username: str) -> str:
|
||||
query = {
|
||||
"client_id": config.github_client_id,
|
||||
"redirect_uri": config.github_redirect_uri,
|
||||
# FIXME: encode username?
|
||||
"state": username
|
||||
}
|
||||
return f"https://github.com/login/oauth/authorize?{urllib.parse.urlencode(query)}"
|
16
src/plugins/github/libs/issue.py
Normal file
16
src/plugins/github/libs/issue.py
Normal file
@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Author : yanyongyu
|
||||
@Date : 2021-03-09 16:45:25
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2021-03-09 16:54:13
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
__author__ = "yanyongyu"
|
||||
|
||||
|
||||
async def get_issue(owner: str, repo: str, number: int):
|
||||
# TODO
|
||||
pass
|
34
src/plugins/github/plugins/github_auth/__init__.py
Normal file
34
src/plugins/github/plugins/github_auth/__init__.py
Normal file
@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Author : yanyongyu
|
||||
@Date : 2021-03-09 16:06:34
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2021-03-09 16:35:50
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
__author__ = "yanyongyu"
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters.cqhttp import Bot, PrivateMessageEvent, GroupMessageEvent
|
||||
|
||||
from ...libs.auth import get_auth_link
|
||||
from ... import github_config as config
|
||||
|
||||
auth = on_command("auth", priority=config.github_command_priority)
|
||||
auth.__doc__ = """
|
||||
/auth
|
||||
登录 github 账号
|
||||
"""
|
||||
|
||||
|
||||
@auth.handle()
|
||||
async def handle_private(bot: Bot, event: PrivateMessageEvent):
|
||||
await auth.finish("请前往以下链接进行授权:\n" +
|
||||
await get_auth_link(event.get_user_id()))
|
||||
|
||||
|
||||
@auth.handle()
|
||||
async def handle_group(bot: Bot, event: GroupMessageEvent):
|
||||
await auth.finish("请私聊我并使用 /auth 命令登录你的 GitHub 账号")
|
@ -4,7 +4,7 @@
|
||||
@Author : yanyongyu
|
||||
@Date : 2020-09-21 00:05:16
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2021-03-06 22:26:21
|
||||
@LastEditTime : 2021-03-09 16:43:58
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
@ -14,7 +14,7 @@ import inspect
|
||||
from functools import reduce
|
||||
|
||||
from nonebot import on_command
|
||||
from nonebot.adapters import Bot
|
||||
from nonebot.adapters.cqhttp import Bot
|
||||
|
||||
from ... import _sub_plugins, github_config as config
|
||||
|
||||
@ -28,8 +28,8 @@ help.__doc__ = """
|
||||
@help.handle()
|
||||
async def handle(bot: Bot):
|
||||
matchers = reduce(lambda x, y: x.union(y.matcher), _sub_plugins, set())
|
||||
docs = "命令列表:\n"
|
||||
docs += "\n".join(
|
||||
docs = "命令列表:\n\n"
|
||||
docs += "\n\n".join(
|
||||
map(lambda x: inspect.cleandoc(x.__doc__),
|
||||
filter(lambda x: x.__doc__, matchers)))
|
||||
await help.finish(docs)
|
||||
|
44
src/plugins/github/plugins/github_issue/__init__.py
Normal file
44
src/plugins/github/plugins/github_issue/__init__.py
Normal file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
@Author : yanyongyu
|
||||
@Date : 2021-03-09 15:15:02
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2021-03-09 16:37:02
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
__author__ = "yanyongyu"
|
||||
|
||||
import base64
|
||||
from typing import Dict
|
||||
|
||||
from nonebot import on_regex
|
||||
from nonebot.typing import T_State
|
||||
from nonebot.adapters.cqhttp import Bot, MessageEvent, MessageSegment
|
||||
|
||||
from src.libs import md2img
|
||||
from ... import github_config as config
|
||||
|
||||
issue = on_regex(
|
||||
r"^(?P<owner>[a-zA-Z0-9][a-zA-Z0-9\-]*)/(?P<repo>[a-zA-Z0-9_\-]+)#(?P<number>\d+)$",
|
||||
priority=config.github_command_priority)
|
||||
issue.__doc__ = """
|
||||
^owner/repo#number$
|
||||
获取指定仓库 issue / pr
|
||||
"""
|
||||
|
||||
|
||||
@issue.handle()
|
||||
async def handle(bot: Bot, event: MessageEvent, state: T_State):
|
||||
group: Dict[str, str] = state["_matched_dict"]
|
||||
owner = group["owner"]
|
||||
repo = group["repo"]
|
||||
number = group["number"]
|
||||
# TODO: Get user token (optional)
|
||||
token = None
|
||||
# TODO: Get issue content
|
||||
issue_content = ""
|
||||
img = await md2img.from_string(issue_content)
|
||||
await issue.finish(MessageSegment.image(f"base64://{base64.b64encode(img)}")
|
||||
)
|
@ -4,7 +4,7 @@
|
||||
@Author : yanyongyu
|
||||
@Date : 2020-11-23 18:44:25
|
||||
@LastEditors : yanyongyu
|
||||
@LastEditTime : 2020-11-23 22:18:01
|
||||
@LastEditTime : 2021-03-09 16:39:07
|
||||
@Description : None
|
||||
@GitHub : https://github.com/yanyongyu
|
||||
"""
|
||||
@ -21,6 +21,8 @@ driver = get_driver()
|
||||
global_config = driver.config
|
||||
config = Config(**global_config.dict())
|
||||
|
||||
assert config.sentry_dsn, "Sentry DSN must provided!"
|
||||
|
||||
sentry_sdk.init(**{
|
||||
key[7:]: value
|
||||
for key, value in config.dict().items()
|
||||
|
Loading…
Reference in New Issue
Block a user