mirror of
https://github.com/Xtao-Labs/QQ-GitHub-Bot.git
synced 2025-01-30 15:08:54 +00:00
✨ rewrite status plugin with template support
This commit is contained in:
parent
3ef1f0a8c3
commit
e94f1fa601
13
.env
13
.env
@ -20,10 +20,15 @@ REDIS_PASSWORD=
|
|||||||
REDIS_USERNAME=
|
REDIS_USERNAME=
|
||||||
|
|
||||||
# nonebot-plugin-status
|
# nonebot-plugin-status
|
||||||
SERVER_STATUS_CPU=true
|
SERVER_STATUS_TEMPLATE="
|
||||||
SERVER_STATUS_PER_CPU=false
|
CPU: {{ '%02d' % cpu_usage }}%
|
||||||
SERVER_STATUS_MEMORY=true
|
Memory: {{ '%02d' % memory_usage }}%
|
||||||
SERVER_STATUS_DISK=true
|
Disk:
|
||||||
|
{%- for name, usage in disk_usage.items() %}
|
||||||
|
{{ name }}: {{ '%02d' % usage.percent }}%
|
||||||
|
{%- endfor %}
|
||||||
|
Uptime: {{ uptime }}
|
||||||
|
"
|
||||||
|
|
||||||
# nonebot-plugin-sentry
|
# nonebot-plugin-sentry
|
||||||
# leave sentry_dsn empty to disable sentry bug trace
|
# leave sentry_dsn empty to disable sentry bug trace
|
||||||
|
1038
poetry.lock
generated
1038
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -8,10 +8,10 @@ license = "MIT"
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8"
|
python = "^3.8"
|
||||||
psutil = "^5.7.2"
|
psutil = "^5.7.2"
|
||||||
httpx = "^0.21.0"
|
httpx = "^0.22.0"
|
||||||
Jinja2 = "^3.0.0"
|
Jinja2 = "^3.0.0"
|
||||||
unidiff = "^0.7.0"
|
unidiff = "^0.7.0"
|
||||||
humanize = "^3.5.0"
|
humanize = "^4.0.0"
|
||||||
pydantic = "^1.9.0"
|
pydantic = "^1.9.0"
|
||||||
Markdown = "^3.3.4"
|
Markdown = "^3.3.4"
|
||||||
sentry-sdk = "^1.0.0"
|
sentry-sdk = "^1.0.0"
|
||||||
@ -23,8 +23,7 @@ redis = { version = "^4.0.0", extras = ["hiredis"] }
|
|||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
isort = "^5.9.3"
|
isort = "^5.9.3"
|
||||||
black = "^21.8b0"
|
black = "^22.1.0"
|
||||||
nb-cli = "^0.6.0"
|
|
||||||
nonebug = "^0.2.0"
|
nonebug = "^0.2.0"
|
||||||
|
|
||||||
# [[tool.poetry.source]]
|
# [[tool.poetry.source]]
|
||||||
@ -33,13 +32,13 @@ nonebug = "^0.2.0"
|
|||||||
# default = true
|
# default = true
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
line-length = 80
|
line-length = 88
|
||||||
extend-exclude = '''
|
extend-exclude = '''
|
||||||
'''
|
'''
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
profile = "black"
|
profile = "black"
|
||||||
line_length = 80
|
line_length = 88
|
||||||
length_sort = true
|
length_sort = true
|
||||||
skip_gitignore = true
|
skip_gitignore = true
|
||||||
force_sort_within_sections = true
|
force_sort_within_sections = true
|
||||||
|
@ -53,20 +53,14 @@ class Github:
|
|||||||
return await self._requester.close()
|
return await self._requester.close()
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
async def get_repo(
|
async def get_repo(self, full_name: str, lazy: Literal[True]) -> LazyRepository:
|
||||||
self, full_name: str, lazy: Literal[True]
|
|
||||||
) -> LazyRepository:
|
|
||||||
...
|
...
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
async def get_repo(
|
async def get_repo(self, full_name: str, lazy: Literal[False]) -> Repository:
|
||||||
self, full_name: str, lazy: Literal[False]
|
|
||||||
) -> Repository:
|
|
||||||
...
|
...
|
||||||
|
|
||||||
async def get_repo(
|
async def get_repo(self, full_name: str, lazy: bool = False) -> LazyRepository:
|
||||||
self, full_name: str, lazy: bool = False
|
|
||||||
) -> LazyRepository:
|
|
||||||
"""
|
"""
|
||||||
GET /repos/:owner/:repo
|
GET /repos/:owner/:repo
|
||||||
|
|
||||||
@ -74,17 +68,11 @@ class Github:
|
|||||||
"""
|
"""
|
||||||
url = f"/repos/{full_name}"
|
url = f"/repos/{full_name}"
|
||||||
if lazy:
|
if lazy:
|
||||||
return LazyRepository(
|
return LazyRepository(full_name=full_name, requester=self._requester)
|
||||||
full_name=full_name, requester=self._requester
|
|
||||||
)
|
|
||||||
response = await self._requester.request_json("GET", url)
|
response = await self._requester.request_json("GET", url)
|
||||||
return Repository.parse_obj(
|
return Repository.parse_obj({"requester": self._requester, **response.json()})
|
||||||
{"requester": self._requester, **response.json()}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def render_markdown(
|
async def render_markdown(self, text: str, context: Optional[Repository] = None):
|
||||||
self, text: str, context: Optional[Repository] = None
|
|
||||||
):
|
|
||||||
"""
|
"""
|
||||||
POST /markdown
|
POST /markdown
|
||||||
|
|
||||||
@ -95,9 +83,7 @@ class Github:
|
|||||||
# if context:
|
# if context:
|
||||||
# data["mode"] = "gfm"
|
# data["mode"] = "gfm"
|
||||||
# data["context"] = context._identity
|
# data["context"] = context._identity
|
||||||
response = await self._requester.request_json(
|
response = await self._requester.request_json("POST", "/markdown", json=data)
|
||||||
"POST", "/markdown", json=data
|
|
||||||
)
|
|
||||||
return response.text
|
return response.text
|
||||||
|
|
||||||
# def get_repos(
|
# def get_repos(
|
||||||
|
@ -58,12 +58,7 @@ C = TypeVar("C", bound=BaseModel)
|
|||||||
|
|
||||||
class PaginatedList(AsyncIterator, Generic[C]):
|
class PaginatedList(AsyncIterator, Generic[C]):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self, cls: Type[C], requester: Requester, *args, per_page: int = 30, **kwargs
|
||||||
cls: Type[C],
|
|
||||||
requester: Requester,
|
|
||||||
*args,
|
|
||||||
per_page: int = 30,
|
|
||||||
**kwargs
|
|
||||||
):
|
):
|
||||||
self.cls = cls
|
self.cls = cls
|
||||||
self.requester = requester
|
self.requester = requester
|
||||||
@ -85,11 +80,7 @@ class PaginatedList(AsyncIterator, Generic[C]):
|
|||||||
|
|
||||||
def __aiter__(self) -> "PaginatedList[C]":
|
def __aiter__(self) -> "PaginatedList[C]":
|
||||||
return PaginatedList(
|
return PaginatedList(
|
||||||
self.cls,
|
self.cls, self.requester, *self.args, per_page=self.per_page, **self.kwargs
|
||||||
self.requester,
|
|
||||||
*self.args,
|
|
||||||
per_page=self.per_page,
|
|
||||||
**self.kwargs
|
|
||||||
)
|
)
|
||||||
|
|
||||||
async def _get_next_page(self) -> List[C]:
|
async def _get_next_page(self) -> List[C]:
|
||||||
|
@ -159,9 +159,7 @@ class Issue(BaseModel):
|
|||||||
response = await self.requester.request(
|
response = await self.requester.request(
|
||||||
"GET", self.pull_request.url, headers=headers
|
"GET", self.pull_request.url, headers=headers
|
||||||
)
|
)
|
||||||
return PullRequest.parse_obj(
|
return PullRequest.parse_obj({"requester": self.requester, **response.json()})
|
||||||
{"requester": self.requester, **response.json()}
|
|
||||||
)
|
|
||||||
|
|
||||||
async def get_diff(self) -> str:
|
async def get_diff(self) -> str:
|
||||||
"""
|
"""
|
||||||
@ -170,7 +168,5 @@ class Issue(BaseModel):
|
|||||||
if not self.pull_request:
|
if not self.pull_request:
|
||||||
raise RuntimeError(f"Issue {self.number} is not a pull request")
|
raise RuntimeError(f"Issue {self.number} is not a pull request")
|
||||||
|
|
||||||
response = await self.requester.request(
|
response = await self.requester.request("GET", self.pull_request.diff_url)
|
||||||
"GET", self.pull_request.diff_url
|
|
||||||
)
|
|
||||||
return response.text
|
return response.text
|
||||||
|
@ -30,9 +30,7 @@ class LazyRepository(BaseModel):
|
|||||||
|
|
||||||
https://docs.github.com/en/rest/reference/issues#get-an-issue
|
https://docs.github.com/en/rest/reference/issues#get-an-issue
|
||||||
"""
|
"""
|
||||||
headers = {
|
headers = {"Accept": "application/vnd.github.mockingbird-preview.full+json"}
|
||||||
"Accept": "application/vnd.github.mockingbird-preview.full+json"
|
|
||||||
}
|
|
||||||
response = await self.requester.request(
|
response = await self.requester.request(
|
||||||
"GET", f"/repos/{self.full_name}/issues/{number}", headers=headers
|
"GET", f"/repos/{self.full_name}/issues/{number}", headers=headers
|
||||||
)
|
)
|
||||||
@ -109,9 +107,7 @@ class LazyRepository(BaseModel):
|
|||||||
"""
|
"""
|
||||||
data = {}
|
data = {}
|
||||||
data["config"] = config.dict(exclude_unset=True)
|
data["config"] = config.dict(exclude_unset=True)
|
||||||
data["config"]["insecure_ssl"] = (
|
data["config"]["insecure_ssl"] = "1" if data["config"]["insecure_ssl"] else "0"
|
||||||
"1" if data["config"]["insecure_ssl"] else "0"
|
|
||||||
)
|
|
||||||
if events is not None:
|
if events is not None:
|
||||||
data["events"] = events
|
data["events"] = events
|
||||||
if active is not None:
|
if active is not None:
|
||||||
|
@ -216,6 +216,4 @@ async def config(
|
|||||||
:param meta_tag_prefix: the prefix for ``pdfkit`` specific meta tags
|
:param meta_tag_prefix: the prefix for ``pdfkit`` specific meta tags
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return await Config(
|
return await Config(wkhtmltoimage=wkhtmltoimage, meta_tag_prefix=meta_tag_prefix)
|
||||||
wkhtmltoimage=wkhtmltoimage, meta_tag_prefix=meta_tag_prefix
|
|
||||||
)
|
|
||||||
|
@ -86,9 +86,7 @@ class Config(object):
|
|||||||
@property
|
@property
|
||||||
def xvfb(self) -> Union[str, bytes]:
|
def xvfb(self) -> Union[str, bytes]:
|
||||||
if not self._xvfb:
|
if not self._xvfb:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"xvfb not installed or Config {self} is never awaited!")
|
||||||
f"xvfb not installed or Config {self} is never awaited!"
|
|
||||||
)
|
|
||||||
return self._xvfb
|
return self._xvfb
|
||||||
|
|
||||||
@xvfb.setter
|
@xvfb.setter
|
||||||
|
@ -96,9 +96,7 @@ class IMGKit(object):
|
|||||||
def xvfb(self):
|
def xvfb(self):
|
||||||
return self.config.xvfb
|
return self.config.xvfb
|
||||||
|
|
||||||
def _gegetate_args(
|
def _gegetate_args(self, options: OPTION_TYPE) -> Generator[str, None, None]:
|
||||||
self, options: OPTION_TYPE
|
|
||||||
) -> Generator[str, None, None]:
|
|
||||||
"""
|
"""
|
||||||
Generator of args parts based on options specification.
|
Generator of args parts based on options specification.
|
||||||
"""
|
"""
|
||||||
@ -157,9 +155,7 @@ class IMGKit(object):
|
|||||||
|
|
||||||
if "</head>" in source:
|
if "</head>" in source:
|
||||||
self.source = StringSource(
|
self.source = StringSource(
|
||||||
inp.replace(
|
inp.replace("</head>", self._style_tag(css_data) + "</head>")
|
||||||
"</head>", self._style_tag(css_data) + "</head>"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.source = StringSource(self._style_tag(css_data) + inp)
|
self.source = StringSource(self._style_tag(css_data) + inp)
|
||||||
|
@ -21,9 +21,7 @@ from src.libs import html2img
|
|||||||
|
|
||||||
async def from_string(
|
async def from_string(
|
||||||
string: str,
|
string: str,
|
||||||
extensions: Optional[
|
extensions: Optional[Sequence[Union[str, markdown.extensions.Extension]]] = None,
|
||||||
Sequence[Union[str, markdown.extensions.Extension]]
|
|
||||||
] = None,
|
|
||||||
extension_configs: Optional[Mapping[str, Mapping[str, Any]]] = None,
|
extension_configs: Optional[Mapping[str, Mapping[str, Any]]] = None,
|
||||||
output_format: Optional[Literal["xhtml", "html"]] = None,
|
output_format: Optional[Literal["xhtml", "html"]] = None,
|
||||||
tab_length: Optional[int] = None,
|
tab_length: Optional[int] = None,
|
||||||
@ -41,9 +39,7 @@ async def from_string(
|
|||||||
async def from_file(
|
async def from_file(
|
||||||
input: Optional[Union[str, BinaryIO]] = None,
|
input: Optional[Union[str, BinaryIO]] = None,
|
||||||
encoding: Optional[str] = None,
|
encoding: Optional[str] = None,
|
||||||
extensions: Optional[
|
extensions: Optional[Sequence[Union[str, markdown.extensions.Extension]]] = None,
|
||||||
Sequence[Union[str, markdown.extensions.Extension]]
|
|
||||||
] = None,
|
|
||||||
extension_configs: Optional[Mapping[str, Mapping[str, Any]]] = None,
|
extension_configs: Optional[Mapping[str, Mapping[str, Any]]] = None,
|
||||||
output_format: Optional[Literal["xhtml", "html"]] = None,
|
output_format: Optional[Literal["xhtml", "html"]] = None,
|
||||||
tab_length: Optional[int] = None,
|
tab_length: Optional[int] = None,
|
||||||
|
@ -26,8 +26,6 @@ _sub_plugins = set()
|
|||||||
# load all github plugin config from global config
|
# load all github plugin config from global config
|
||||||
github_config = Config(**nonebot.get_driver().config.dict())
|
github_config = Config(**nonebot.get_driver().config.dict())
|
||||||
|
|
||||||
_sub_plugins |= nonebot.load_plugins(
|
_sub_plugins |= nonebot.load_plugins(str((Path(__file__).parent / "plugins").resolve()))
|
||||||
str((Path(__file__).parent / "plugins").resolve())
|
|
||||||
)
|
|
||||||
|
|
||||||
from . import apis
|
from . import apis
|
||||||
|
@ -51,9 +51,7 @@ async def create_hook(
|
|||||||
) -> Hook:
|
) -> Hook:
|
||||||
async with Github(token) as g:
|
async with Github(token) as g:
|
||||||
repo = await g.get_repo(repo, True) if isinstance(repo, str) else repo
|
repo = await g.get_repo(repo, True) if isinstance(repo, str) else repo
|
||||||
config = (
|
config = HookConfig.parse_obj(config) if isinstance(config, dict) else config
|
||||||
HookConfig.parse_obj(config) if isinstance(config, dict) else config
|
|
||||||
)
|
|
||||||
|
|
||||||
return await repo.create_hook(config, events, active)
|
return await repo.create_hook(config, events, active)
|
||||||
|
|
||||||
|
@ -48,9 +48,7 @@ async def _gen_image(
|
|||||||
html: str, width: int, height: int, wkhtmltoimage: bool = False
|
html: str, width: int, height: int, wkhtmltoimage: bool = False
|
||||||
) -> Optional[bytes]:
|
) -> Optional[bytes]:
|
||||||
if not wkhtmltoimage:
|
if not wkhtmltoimage:
|
||||||
async with get_new_page(
|
async with get_new_page(viewport={"width": width, "height": height}) as page:
|
||||||
viewport={"width": width, "height": height}
|
|
||||||
) as page:
|
|
||||||
await page.set_content(html)
|
await page.set_content(html)
|
||||||
img = await page.screenshot(full_page=True)
|
img = await page.screenshot(full_page=True)
|
||||||
return img
|
return img
|
||||||
|
@ -12,18 +12,8 @@ __author__ = "yanyongyu"
|
|||||||
|
|
||||||
from ... import redis
|
from ... import redis
|
||||||
from .state import get_state_bind_user, set_state_bind_user
|
from .state import get_state_bind_user, set_state_bind_user
|
||||||
from .hook import (
|
from .hook import get_repo_hook, set_repo_hook, delete_repo_hook, exists_repo_hook
|
||||||
get_repo_hook,
|
from .token import get_user_token, set_user_token, delete_user_token, exists_user_token
|
||||||
set_repo_hook,
|
|
||||||
delete_repo_hook,
|
|
||||||
exists_repo_hook,
|
|
||||||
)
|
|
||||||
from .token import (
|
|
||||||
get_user_token,
|
|
||||||
set_user_token,
|
|
||||||
delete_user_token,
|
|
||||||
exists_user_token,
|
|
||||||
)
|
|
||||||
from .bind import (
|
from .bind import (
|
||||||
get_group_bind_repo,
|
get_group_bind_repo,
|
||||||
set_group_bind_repo,
|
set_group_bind_repo,
|
||||||
|
@ -35,34 +35,26 @@ class SubscribeConfig:
|
|||||||
|
|
||||||
def set_subscribe(group_id: str, repo_name: str, **kwargs) -> Optional[bool]:
|
def set_subscribe(group_id: str, repo_name: str, **kwargs) -> Optional[bool]:
|
||||||
return redis.set(
|
return redis.set(
|
||||||
SUBSCRIBE_GROUP_REPO_FORMAT.format(
|
SUBSCRIBE_GROUP_REPO_FORMAT.format(group_id=group_id, repo_name=repo_name),
|
||||||
group_id=group_id, repo_name=repo_name
|
|
||||||
),
|
|
||||||
json.dumps(asdict(SubscribeConfig(**kwargs))),
|
json.dumps(asdict(SubscribeConfig(**kwargs))),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def delete_subscribe(group_id: str, repo_name: str) -> int:
|
def delete_subscribe(group_id: str, repo_name: str) -> int:
|
||||||
return redis.delete(
|
return redis.delete(
|
||||||
SUBSCRIBE_GROUP_REPO_FORMAT.format(
|
SUBSCRIBE_GROUP_REPO_FORMAT.format(group_id=group_id, repo_name=repo_name)
|
||||||
group_id=group_id, repo_name=repo_name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def exists_subscribe(group_id: str, repo_name: str) -> int:
|
def exists_subscribe(group_id: str, repo_name: str) -> int:
|
||||||
return redis.exists(
|
return redis.exists(
|
||||||
SUBSCRIBE_GROUP_REPO_FORMAT.format(
|
SUBSCRIBE_GROUP_REPO_FORMAT.format(group_id=group_id, repo_name=repo_name)
|
||||||
group_id=group_id, repo_name=repo_name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_subscribe(group_id: str, repo_name: str) -> Optional[SubscribeConfig]:
|
def get_subscribe(group_id: str, repo_name: str) -> Optional[SubscribeConfig]:
|
||||||
value = redis.get(
|
value = redis.get(
|
||||||
SUBSCRIBE_GROUP_REPO_FORMAT.format(
|
SUBSCRIBE_GROUP_REPO_FORMAT.format(group_id=group_id, repo_name=repo_name)
|
||||||
group_id=group_id, repo_name=repo_name
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
return value if value is None else SubscribeConfig(**json.loads(value))
|
return value if value is None else SubscribeConfig(**json.loads(value))
|
||||||
|
|
||||||
|
@ -42,9 +42,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
get_user_token = None
|
get_user_token = None
|
||||||
|
|
||||||
REPO_REGEX: str = (
|
REPO_REGEX: str = r"^(?P<owner>[a-zA-Z0-9][a-zA-Z0-9\-]*)/(?P<repo>[a-zA-Z0-9_\-\.]+)$"
|
||||||
r"^(?P<owner>[a-zA-Z0-9][a-zA-Z0-9\-]*)/(?P<repo>[a-zA-Z0-9_\-\.]+)$"
|
|
||||||
)
|
|
||||||
|
|
||||||
bind = on_command(
|
bind = on_command(
|
||||||
"bind",
|
"bind",
|
||||||
@ -83,9 +81,7 @@ async def check_exists(event: GroupMessageEvent, matcher: Matcher):
|
|||||||
prompt="绑定仓库的全名?(e.g. owner/repo)",
|
prompt="绑定仓库的全名?(e.g. owner/repo)",
|
||||||
parameterless=[Depends(allow_cancel)],
|
parameterless=[Depends(allow_cancel)],
|
||||||
)
|
)
|
||||||
async def process_repo(
|
async def process_repo(event: GroupMessageEvent, full_name: str = ArgPlainText()):
|
||||||
event: GroupMessageEvent, full_name: str = ArgPlainText()
|
|
||||||
):
|
|
||||||
matched = re.match(REPO_REGEX, full_name)
|
matched = re.match(REPO_REGEX, full_name)
|
||||||
if not matched:
|
if not matched:
|
||||||
await bind.reject(f"仓库名 {full_name} 不合法!请重新发送或取消")
|
await bind.reject(f"仓库名 {full_name} 不合法!请重新发送或取消")
|
||||||
|
@ -96,9 +96,7 @@ async def handle(bot: Bot, event: MessageEvent, state: T_State):
|
|||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
number,
|
number,
|
||||||
MessageSegment.image(
|
MessageSegment.image(f"base64://{base64.b64encode(img).decode()}"),
|
||||||
f"base64://{base64.b64encode(img).decode()}"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -147,7 +145,5 @@ async def handle_short(bot: Bot, event: GroupMessageEvent, state: T_State):
|
|||||||
owner,
|
owner,
|
||||||
repo,
|
repo,
|
||||||
number,
|
number,
|
||||||
MessageSegment.image(
|
MessageSegment.image(f"base64://{base64.b64encode(img).decode()}"),
|
||||||
f"base64://{base64.b64encode(img).decode()}"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
@ -57,9 +57,7 @@ async def handle_content(bot: Bot, event: MessageEvent, state: T_State):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
img = await issue_to_image(
|
img = await issue_to_image(message_info.owner, message_info.repo, issue_)
|
||||||
message_info.owner, message_info.repo, issue_
|
|
||||||
)
|
|
||||||
except TimeoutException:
|
except TimeoutException:
|
||||||
await content.finish(f"获取issue数据超时!请尝试重试")
|
await content.finish(f"获取issue数据超时!请尝试重试")
|
||||||
except Error:
|
except Error:
|
||||||
@ -71,7 +69,5 @@ async def handle_content(bot: Bot, event: MessageEvent, state: T_State):
|
|||||||
message_info.owner,
|
message_info.owner,
|
||||||
message_info.repo,
|
message_info.repo,
|
||||||
message_info.number,
|
message_info.number,
|
||||||
MessageSegment.image(
|
MessageSegment.image(f"base64://{base64.b64encode(img).decode()}"),
|
||||||
f"base64://{base64.b64encode(img).decode()}"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
@ -29,9 +29,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
get_user_token = None
|
get_user_token = None
|
||||||
|
|
||||||
diff = on_command(
|
diff = on_command("diff", is_github_reply, priority=config.github_command_priority)
|
||||||
"diff", is_github_reply, priority=config.github_command_priority
|
|
||||||
)
|
|
||||||
diff.__doc__ = """
|
diff.__doc__ = """
|
||||||
/diff
|
/diff
|
||||||
回复机器人一条github pr信息,给出pr diff
|
回复机器人一条github pr信息,给出pr diff
|
||||||
@ -57,9 +55,7 @@ async def handle_diff(bot: Bot, event: MessageEvent, state: T_State):
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
img = await issue_diff_to_image(
|
img = await issue_diff_to_image(message_info.owner, message_info.repo, issue_)
|
||||||
message_info.owner, message_info.repo, issue_
|
|
||||||
)
|
|
||||||
except TimeoutException:
|
except TimeoutException:
|
||||||
await diff.finish(f"获取diff数据超时!请尝试重试")
|
await diff.finish(f"获取diff数据超时!请尝试重试")
|
||||||
except Error:
|
except Error:
|
||||||
@ -71,7 +67,5 @@ async def handle_diff(bot: Bot, event: MessageEvent, state: T_State):
|
|||||||
message_info.owner,
|
message_info.owner,
|
||||||
message_info.repo,
|
message_info.repo,
|
||||||
message_info.number,
|
message_info.number,
|
||||||
MessageSegment.image(
|
MessageSegment.image(f"base64://{base64.b64encode(img).decode()}"),
|
||||||
f"base64://{base64.b64encode(img).decode()}"
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
@ -18,9 +18,7 @@ from ...libs.redis import MessageInfo
|
|||||||
from ...utils import send_github_message
|
from ...utils import send_github_message
|
||||||
from . import KEY_GITHUB_REPLY, config, is_github_reply
|
from . import KEY_GITHUB_REPLY, config, is_github_reply
|
||||||
|
|
||||||
link = on_command(
|
link = on_command("link", is_github_reply, priority=config.github_command_priority)
|
||||||
"link", is_github_reply, priority=config.github_command_priority
|
|
||||||
)
|
|
||||||
link.__doc__ = """
|
link.__doc__ = """
|
||||||
/link
|
/link
|
||||||
回复机器人一条github信息,给出对应链接
|
回复机器人一条github信息,给出对应链接
|
||||||
|
@ -97,9 +97,7 @@ else:
|
|||||||
elif e.response.status_code == 404:
|
elif e.response.status_code == 404:
|
||||||
await subscribe.reject(f"仓库名 {owner}/{repo_name} 不存在!请重新发送或取消")
|
await subscribe.reject(f"仓库名 {owner}/{repo_name} 不存在!请重新发送或取消")
|
||||||
return
|
return
|
||||||
logger.opt(colors=True, exception=e).error(
|
logger.opt(colors=True, exception=e).error(f"github_subscribe: create_hook")
|
||||||
f"github_subscribe: create_hook"
|
|
||||||
)
|
|
||||||
await subscribe.finish("订阅仓库时发生错误,请联系开发者或重试")
|
await subscribe.finish("订阅仓库时发生错误,请联系开发者或重试")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* @Author : yanyongyu
|
* @Author : yanyongyu
|
||||||
* @Date : 2020-11-15 14:40:25
|
* @Date : 2020-11-15 14:40:25
|
||||||
* @LastEditors : yanyongyu
|
* @LastEditors : yanyongyu
|
||||||
* @LastEditTime : 2022-01-13 16:32:41
|
* @LastEditTime : 2022-05-23 05:58:29
|
||||||
* @Description : None
|
* @Description : None
|
||||||
* @GitHub : https://github.com/yanyongyu
|
* @GitHub : https://github.com/yanyongyu
|
||||||
-->
|
-->
|
||||||
@ -58,26 +58,26 @@ OneBot:
|
|||||||
> SUPERUSERS=["your qq id"]
|
> SUPERUSERS=["your qq id"]
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
### server_status_cpu
|
### server_status_template
|
||||||
|
|
||||||
- 类型: `bool`
|
- 类型: `str`
|
||||||
- 默认: `True`
|
- 默认: 请参考示例
|
||||||
- 说明: 是否显示 CPU 占用百分比
|
- 说明:发送的消息模板,支持的变量以及类型如下:
|
||||||
|
- cpu_usage (`float`): CPU 使用率
|
||||||
|
- memory_usage (`float`): 内存使用率
|
||||||
|
- disk_usage (`Dict[str, psutil._common.sdiskusage]`): 磁盘使用率,包含 total, used, free, percent 属性
|
||||||
|
- uptime (`timedelta`): 服务器运行时间
|
||||||
|
|
||||||
### server_status_per_cpu
|
配置文件示例(默认模板)
|
||||||
|
|
||||||
- 类型: `bool`
|
```dotenv
|
||||||
- 默认: `False`
|
SERVER_STATUS_TEMPLATE="
|
||||||
- 说明: 是否显示每个 CPU 核心占用百分比
|
CPU: {{ '%02d' % cpu_usage }}%
|
||||||
|
Memory: {{ '%02d' % memory_usage }}%
|
||||||
### server_status_memory
|
Disk:
|
||||||
|
{%- for name, usage in disk_usage.items() %}
|
||||||
- 类型: `bool`
|
{{ name }}: {{ '%02d' % usage.percent }}%
|
||||||
- 默认: `True`
|
{%- endfor %}
|
||||||
- 说明: 是否显示 Memory 占用百分比
|
Uptime: {{ uptime }}
|
||||||
|
"
|
||||||
### server_status_disk
|
```
|
||||||
|
|
||||||
- 类型: `bool`
|
|
||||||
- 默认: `True`
|
|
||||||
- 说明: 是否显示磁盘占用百分比
|
|
||||||
|
@ -4,22 +4,26 @@
|
|||||||
@Author : yanyongyu
|
@Author : yanyongyu
|
||||||
@Date : 2020-09-18 00:00:13
|
@Date : 2020-09-18 00:00:13
|
||||||
@LastEditors : yanyongyu
|
@LastEditors : yanyongyu
|
||||||
@LastEditTime : 2022-01-13 21:01:33
|
@LastEditTime : 2022-05-23 05:41:46
|
||||||
@Description : None
|
@Description : None
|
||||||
@GitHub : https://github.com/yanyongyu
|
@GitHub : https://github.com/yanyongyu
|
||||||
"""
|
"""
|
||||||
__author__ = "yanyongyu"
|
__author__ = "yanyongyu"
|
||||||
|
|
||||||
|
from jinja2 import Environment
|
||||||
from nonebot.matcher import Matcher
|
from nonebot.matcher import Matcher
|
||||||
from nonebot.permission import SUPERUSER
|
from nonebot.permission import SUPERUSER
|
||||||
from nonebot import on_notice, get_driver, on_command, on_message
|
from nonebot import on_notice, get_driver, on_command, on_message
|
||||||
|
|
||||||
from .config import Config
|
from .config import Config
|
||||||
from .data_source import cpu_status, disk_usage, memory_status, per_cpu_status
|
from .data_source import uptime, cpu_status, disk_usage, memory_status, per_cpu_status
|
||||||
|
|
||||||
global_config = get_driver().config
|
global_config = get_driver().config
|
||||||
status_config = Config(**global_config.dict())
|
status_config = Config(**global_config.dict())
|
||||||
|
|
||||||
|
_ev = Environment(autoescape=False, enable_async=True)
|
||||||
|
_t = _ev.from_string(status_config.server_status_template)
|
||||||
|
|
||||||
command = on_command(
|
command = on_command(
|
||||||
"状态",
|
"状态",
|
||||||
permission=(status_config.server_status_only_superusers or None) and SUPERUSER,
|
permission=(status_config.server_status_only_superusers or None) and SUPERUSER,
|
||||||
@ -29,25 +33,14 @@ command = on_command(
|
|||||||
|
|
||||||
@command.handle()
|
@command.handle()
|
||||||
async def server_status(matcher: Matcher):
|
async def server_status(matcher: Matcher):
|
||||||
data = []
|
message = await _t.render_async(
|
||||||
|
cpu_usage=cpu_status(),
|
||||||
if status_config.server_status_cpu:
|
per_cpu_usage=per_cpu_status(),
|
||||||
if status_config.server_status_per_cpu:
|
memory_usage=memory_status(),
|
||||||
data.append("CPU:")
|
disk_usage=disk_usage(),
|
||||||
for index, per_cpu in enumerate(per_cpu_status()):
|
uptime=uptime(),
|
||||||
data.append(f" core{index + 1}: {int(per_cpu):02d}%")
|
)
|
||||||
else:
|
await matcher.send(message=message.strip("\n"))
|
||||||
data.append(f"CPU: {int(cpu_status()):02d}%")
|
|
||||||
|
|
||||||
if status_config.server_status_memory:
|
|
||||||
data.append(f"Memory: {int(memory_status()):02d}%")
|
|
||||||
|
|
||||||
if status_config.server_status_disk:
|
|
||||||
data.append("Disk:")
|
|
||||||
for k, v in disk_usage().items():
|
|
||||||
data.append(f" {k}: {int(v.percent):02d}%")
|
|
||||||
|
|
||||||
await matcher.send(message="\n".join(data))
|
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -4,21 +4,73 @@
|
|||||||
@Author : yanyongyu
|
@Author : yanyongyu
|
||||||
@Date : 2020-10-04 16:32:00
|
@Date : 2020-10-04 16:32:00
|
||||||
@LastEditors : yanyongyu
|
@LastEditors : yanyongyu
|
||||||
@LastEditTime : 2021-09-10 12:42:16
|
@LastEditTime : 2022-05-23 05:46:55
|
||||||
@Description : None
|
@Description : None
|
||||||
@GitHub : https://github.com/yanyongyu
|
@GitHub : https://github.com/yanyongyu
|
||||||
"""
|
"""
|
||||||
__author__ = "yanyongyu"
|
__author__ = "yanyongyu"
|
||||||
|
|
||||||
from pydantic import BaseSettings
|
import warnings
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
|
from pydantic import BaseSettings, root_validator
|
||||||
|
|
||||||
|
CPU_TEMPLATE = "CPU: {{ '%02d' % cpu_usage }}%"
|
||||||
|
PER_CPU_TEMPLATE = (
|
||||||
|
"CPU:\n"
|
||||||
|
"{%- for core in per_cpu_usage %}\n"
|
||||||
|
" core{{ loop.index }}: {{ '%02d' % core }}%\n"
|
||||||
|
"{%- endfor %}"
|
||||||
|
)
|
||||||
|
MEMORY_TEMPLATE = "Memory: {{ '%02d' % memory_usage }}%"
|
||||||
|
DISK_TEMPLATE = (
|
||||||
|
"Disk:\n"
|
||||||
|
"{%- for name, usage in disk_usage.items() %}\n"
|
||||||
|
" {{ name }}: {{ '%02d' % usage.percent }}%\n"
|
||||||
|
"{%- endfor %}"
|
||||||
|
)
|
||||||
|
UPTIME_TEMPLATE = "Uptime: {{ uptime }}"
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseSettings):
|
class Config(BaseSettings):
|
||||||
server_status_only_superusers: bool = True
|
server_status_only_superusers: bool = True
|
||||||
|
|
||||||
|
# Deprecated: legacy settings
|
||||||
server_status_cpu: bool = True
|
server_status_cpu: bool = True
|
||||||
server_status_per_cpu: bool = False
|
server_status_per_cpu: bool = False
|
||||||
server_status_memory: bool = True
|
server_status_memory: bool = True
|
||||||
server_status_disk: bool = True
|
server_status_disk: bool = True
|
||||||
|
|
||||||
|
# template
|
||||||
|
server_status_template: str = "\n".join(
|
||||||
|
(CPU_TEMPLATE, MEMORY_TEMPLATE, DISK_TEMPLATE, UPTIME_TEMPLATE)
|
||||||
|
)
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
extra = "ignore"
|
extra = "ignore"
|
||||||
|
|
||||||
|
@root_validator(pre=True)
|
||||||
|
def transform_legacy_settings(cls, value: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
if "server_status_template" not in value and (
|
||||||
|
"server_status_cpu" in value
|
||||||
|
or "server_status_per_cpu" in value
|
||||||
|
or "server_status_memory" in value
|
||||||
|
or "server_status_disk" in value
|
||||||
|
):
|
||||||
|
warnings.warn(
|
||||||
|
"Settings for status plugin is deprecated, "
|
||||||
|
"please use `server_status_template` instead.",
|
||||||
|
DeprecationWarning,
|
||||||
|
)
|
||||||
|
templates = []
|
||||||
|
if value.get("server_status_cpu"):
|
||||||
|
templates.append(CPU_TEMPLATE)
|
||||||
|
if value.get("server_status_per_cpu"):
|
||||||
|
templates.append(PER_CPU_TEMPLATE)
|
||||||
|
if value.get("server_status_memory"):
|
||||||
|
templates.append(MEMORY_TEMPLATE)
|
||||||
|
if value.get("server_status_disk"):
|
||||||
|
templates.append(DISK_TEMPLATE)
|
||||||
|
value.setdefault("server_status_template", "\n".join(templates))
|
||||||
|
|
||||||
|
return value
|
||||||
|
@ -4,13 +4,15 @@
|
|||||||
@Author : yanyongyu
|
@Author : yanyongyu
|
||||||
@Date : 2020-09-18 00:15:21
|
@Date : 2020-09-18 00:15:21
|
||||||
@LastEditors : yanyongyu
|
@LastEditors : yanyongyu
|
||||||
@LastEditTime : 2021-03-16 16:59:22
|
@LastEditTime : 2022-05-23 04:38:37
|
||||||
@Description : None
|
@Description : None
|
||||||
@GitHub : https://github.com/yanyongyu
|
@GitHub : https://github.com/yanyongyu
|
||||||
"""
|
"""
|
||||||
__author__ = "yanyongyu"
|
__author__ = "yanyongyu"
|
||||||
|
|
||||||
|
import time
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
|
|
||||||
@ -29,10 +31,12 @@ def memory_status() -> float:
|
|||||||
|
|
||||||
def disk_usage() -> Dict[str, psutil._common.sdiskusage]:
|
def disk_usage() -> Dict[str, psutil._common.sdiskusage]:
|
||||||
disk_parts = psutil.disk_partitions()
|
disk_parts = psutil.disk_partitions()
|
||||||
disk_usages = {
|
return {d.mountpoint: psutil.disk_usage(d.mountpoint) for d in disk_parts}
|
||||||
d.mountpoint: psutil.disk_usage(d.mountpoint) for d in disk_parts
|
|
||||||
}
|
|
||||||
return disk_usages
|
def uptime() -> timedelta:
|
||||||
|
diff = time.time() - psutil.boot_time()
|
||||||
|
return timedelta(seconds=diff)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "nonebot-plugin-status"
|
name = "nonebot-plugin-status"
|
||||||
version = "0.3.0"
|
version = "0.4.0"
|
||||||
description = "Check your server status (CPU, Memory, Disk Usage) via nonebot"
|
description = "Check your server status (CPU, Memory, Disk Usage) via nonebot"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
authors = ["yanyongyu <yanyongyu_1@126.com>"]
|
authors = ["yanyongyu <yanyongyu_1@126.com>"]
|
||||||
@ -16,6 +16,7 @@ packages = [
|
|||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.7.3"
|
python = "^3.7.3"
|
||||||
psutil = "^5.7.2"
|
psutil = "^5.7.2"
|
||||||
|
Jinja2 = "^3.0.0"
|
||||||
nonebot2 = "^2.0.0-beta.1"
|
nonebot2 = "^2.0.0-beta.1"
|
||||||
nonebot-adapter-onebot = { version = "^2.0.0-beta.1", optional = true }
|
nonebot-adapter-onebot = { version = "^2.0.0-beta.1", optional = true }
|
||||||
|
|
||||||
|
@ -52,9 +52,7 @@ def get_cache(sign: str) -> Any:
|
|||||||
|
|
||||||
|
|
||||||
def save_cache(sign: str, cache: Any, ex: Optional[timedelta] = None) -> None:
|
def save_cache(sign: str, cache: Any, ex: Optional[timedelta] = None) -> None:
|
||||||
redis_client.set(
|
redis_client.set(CACHE_KEY_FORMAT.format(signature=sign), pickle.dumps(cache), ex)
|
||||||
CACHE_KEY_FORMAT.format(signature=sign), pickle.dumps(cache), ex
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Export something for other plugin
|
# Export something for other plugin
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
import psutil
|
import psutil
|
||||||
import pytest
|
import pytest
|
||||||
@ -15,11 +16,7 @@ async def test_status(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
PrivateMessageEvent,
|
PrivateMessageEvent,
|
||||||
)
|
)
|
||||||
|
|
||||||
from src.plugins.nonebot_plugin_status import (
|
from src.plugins.nonebot_plugin_status import command, group_poke
|
||||||
command,
|
|
||||||
group_poke,
|
|
||||||
status_config,
|
|
||||||
)
|
|
||||||
|
|
||||||
driver = nonebot.get_driver()
|
driver = nonebot.get_driver()
|
||||||
|
|
||||||
@ -36,18 +33,18 @@ async def test_status(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
def _per_cpu_status() -> List[float]:
|
def _per_cpu_status() -> List[float]:
|
||||||
return [10.0, 11.0]
|
return [10.0, 11.0]
|
||||||
|
|
||||||
monkeypatch.setattr(
|
def _uptime() -> timedelta:
|
||||||
"src.plugins.nonebot_plugin_status.cpu_status", _cpu_status
|
return timedelta(days=1, seconds=1)
|
||||||
)
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr("src.plugins.nonebot_plugin_status.cpu_status", _cpu_status)
|
||||||
"src.plugins.nonebot_plugin_status.disk_usage", _disk_usage
|
monkeypatch.setattr("src.plugins.nonebot_plugin_status.disk_usage", _disk_usage)
|
||||||
)
|
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"src.plugins.nonebot_plugin_status.memory_status", _memory_status
|
"src.plugins.nonebot_plugin_status.memory_status", _memory_status
|
||||||
)
|
)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
"src.plugins.nonebot_plugin_status.per_cpu_status", _per_cpu_status
|
"src.plugins.nonebot_plugin_status.per_cpu_status", _per_cpu_status
|
||||||
)
|
)
|
||||||
|
monkeypatch.setattr("src.plugins.nonebot_plugin_status.uptime", _uptime)
|
||||||
|
|
||||||
with monkeypatch.context() as m:
|
with monkeypatch.context() as m:
|
||||||
m.setattr(driver.config, "superusers", {"123"})
|
m.setattr(driver.config, "superusers", {"123"})
|
||||||
@ -78,6 +75,7 @@ async def test_status(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
"Memory: 10%",
|
"Memory: 10%",
|
||||||
"Disk:",
|
"Disk:",
|
||||||
" test: 00%",
|
" test: 00%",
|
||||||
|
"Uptime: 1 day, 0:00:01",
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
True,
|
True,
|
||||||
@ -105,41 +103,7 @@ async def test_status(app: App, monkeypatch: pytest.MonkeyPatch):
|
|||||||
"Memory: 10%",
|
"Memory: 10%",
|
||||||
"Disk:",
|
"Disk:",
|
||||||
" test: 00%",
|
" test: 00%",
|
||||||
]
|
"Uptime: 1 day, 0:00:01",
|
||||||
),
|
|
||||||
True,
|
|
||||||
)
|
|
||||||
|
|
||||||
m.setattr(status_config, "server_status_per_cpu", True)
|
|
||||||
|
|
||||||
async with app.test_matcher(command) as ctx:
|
|
||||||
adapter = ctx.create_adapter(base=Adapter)
|
|
||||||
bot = ctx.create_bot(base=Bot, adapter=adapter)
|
|
||||||
|
|
||||||
event = PrivateMessageEvent(
|
|
||||||
time=0,
|
|
||||||
self_id=0,
|
|
||||||
post_type="message",
|
|
||||||
sub_type="friend",
|
|
||||||
user_id=123,
|
|
||||||
message_type="private",
|
|
||||||
message_id=0,
|
|
||||||
message=Message("/状态"),
|
|
||||||
raw_message="/状态",
|
|
||||||
font=0,
|
|
||||||
sender=Sender(),
|
|
||||||
)
|
|
||||||
ctx.receive_event(bot, event)
|
|
||||||
ctx.should_call_send(
|
|
||||||
event,
|
|
||||||
"\n".join(
|
|
||||||
[
|
|
||||||
"CPU:",
|
|
||||||
" core1: 10%",
|
|
||||||
" core2: 11%",
|
|
||||||
"Memory: 10%",
|
|
||||||
"Disk:",
|
|
||||||
" test: 00%",
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
True,
|
True,
|
||||||
|
Loading…
Reference in New Issue
Block a user