mirror of
https://github.com/LiteyukiStudio/LiteyukiBot.git
synced 2024-11-14 13:47:24 +08:00
📦 docs: 资源商店新增发布资源功能
This commit is contained in:
parent
72e71124b8
commit
b5b15c82f8
44
.github/workflows/ISSUE_TEMPLATE.md
vendored
44
.github/workflows/ISSUE_TEMPLATE.md
vendored
@ -1,44 +0,0 @@
|
||||
# 问题反馈
|
||||
|
||||
## **请确保**
|
||||
|
||||
- 已认真阅读[文档]("https://bot.liteyuki.icu"),该问题不是文档提及的或你自己操作不当造成的
|
||||
- 你的问题是在最新版本的代码上测试的
|
||||
- 请勿重复提交相同或类似的issue
|
||||
|
||||
|
||||
## **描述问题**
|
||||
|
||||
请在此简单描述问题
|
||||
|
||||
|
||||
|
||||
## **如何复现**
|
||||
|
||||
请阐述一下如何重现这个问题
|
||||
### 预期
|
||||
|
||||
描述你期望发生的事情
|
||||
|
||||
### 实际
|
||||
|
||||
描述实际发生的事情
|
||||
|
||||
|
||||
|
||||
## **日志或截图**
|
||||
```
|
||||
日志内容
|
||||
```
|
||||
|
||||
|
||||
## **设备信息**
|
||||
- **系统**: [例如 Ubuntu 22.04]
|
||||
- **CPU**: [例如 Intel i7-7700K]
|
||||
- **内存**: [例如 16GB]
|
||||
- **Python**: [例如CPython 3.10.7]
|
||||
|
||||
|
||||
**补充内容**
|
||||
|
||||
可选,推荐提供`pip freeze`的输出,以及其他相关信息,以及你的建议
|
3
liteyuki_flow/__init__.py
Normal file
3
liteyuki_flow/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
"""
|
||||
Module docs
|
||||
"""
|
60
liteyuki_flow/__main__.py
Normal file
60
liteyuki_flow/__main__.py
Normal file
@ -0,0 +1,60 @@
|
||||
"""
|
||||
Module docs
|
||||
"""
|
||||
import os
|
||||
from github import Github
|
||||
from argparse import ArgumentParser
|
||||
|
||||
from liteyuki_flow.const import PLUGIN_PREFIX, RESOURCE_PREFIX
|
||||
from liteyuki_flow.typ import err, nil # type: ignore
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument("--handle", action="store_true") # 处理issue
|
||||
|
||||
parser.add_argument("-p", "--parse", action="store_true") # 解析markdown文件
|
||||
parser.add_argument("-i", "--input", type=str, help="Path to the markdown file.")
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.handle:
|
||||
print("Starting the issue handler module...")
|
||||
ISSUE_NUMBER = os.getenv("ISSUE_NUMBER")
|
||||
REPOSITORY = os.getenv("REPOSITORY")
|
||||
ACT_TYPE = os.getenv("ACT_TYPE") # opened, edited, closed, reopened
|
||||
if ISSUE_NUMBER is None or REPOSITORY is None or ACT_TYPE is None:
|
||||
raise ValueError("Issue number, repository and action type are required.")
|
||||
|
||||
g = Github(os.getenv("GITHUB_TOKEN"))
|
||||
repo = g.get_repo(REPOSITORY)
|
||||
issue = g.get_repo(REPOSITORY).get_issue(int(ISSUE_NUMBER))
|
||||
|
||||
# 审资源
|
||||
if issue.title.strip().startswith(RESOURCE_PREFIX):
|
||||
from resource_handler import handle_resource # type: ignore
|
||||
handle_resource(github=g, issue=issue, repo=repo, act_type=ACT_TYPE)
|
||||
|
||||
# 审插件
|
||||
elif issue.title.strip().startswith(PLUGIN_PREFIX):
|
||||
from plugin_handler import handle_plugin # type: ignore
|
||||
pass
|
||||
|
||||
else:
|
||||
print("No handler found for the issue.")
|
||||
|
||||
elif args.parse:
|
||||
print("Starting the markdown parser module...")
|
||||
from .markdown_parser import MarkdownParser # type: ignore
|
||||
|
||||
if args.input is None:
|
||||
raise ValueError("Input file is required.")
|
||||
with open(args.input, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
md_parser = MarkdownParser(content) # type: ignore
|
||||
err = md_parser.parse_front_matters() # type: ignore
|
||||
if err != nil:
|
||||
print(f"Err: {err}")
|
||||
for k, v in md_parser.front_matters.content.items():
|
||||
print(f"{k}: {v}")
|
||||
else:
|
||||
print("No module specified.")
|
15
liteyuki_flow/const.py
Normal file
15
liteyuki_flow/const.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
Module docs
|
||||
"""
|
||||
|
||||
OPENED = "opened"
|
||||
EDITED = "edited"
|
||||
CLOSED = "closed"
|
||||
REOPENED = "reopened"
|
||||
RESOURCE_PREFIX = "Resource: "
|
||||
PLUGIN_PREFIX = "Plugin: "
|
||||
|
||||
PLUGIN_JSON = "docs/publics/plugins.json"
|
||||
RESOURCE_JSON = "docs/publics/resources.json"
|
||||
|
||||
edit_tip = "若要修改请编辑这段front matter,不要编辑正文/If you want to modify, please edit the front matter, do not edit the body"
|
136
liteyuki_flow/markdown_parser.py
Normal file
136
liteyuki_flow/markdown_parser.py
Normal file
@ -0,0 +1,136 @@
|
||||
"""
|
||||
从markdown提取插件/资源信息
|
||||
"""
|
||||
from typing import Any
|
||||
|
||||
from liteyuki_flow.typ import Nil, err, nil # type: ignore
|
||||
|
||||
|
||||
|
||||
|
||||
# # xxx
|
||||
class Header:
|
||||
def __init__(self, level: int, content: str):
|
||||
self.level = level
|
||||
self.content = content
|
||||
|
||||
def __str__(self):
|
||||
return f'Header({self.level}, {self.content})'
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
# - xxx
|
||||
class List:
|
||||
def __init__(self, level: int, content: str):
|
||||
self.level = level
|
||||
self.content = content
|
||||
|
||||
def __str__(self):
|
||||
return f'List({self.level}, {self.content})'
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
|
||||
class FrontMatter:
|
||||
def __init__(self, content: dict[str, str]):
|
||||
self.content = content
|
||||
|
||||
def __setitem__(self, key: str, value: str):
|
||||
self.content[key] = value
|
||||
|
||||
def get(self, key, default=None) -> Any:
|
||||
return self.content.get(key, default)
|
||||
|
||||
def __str__(self):
|
||||
return "\n".join([f'{k}: {v}' for k, v in self.content.items()])
|
||||
|
||||
|
||||
class MarkdownParser:
|
||||
def __init__(self, content: str):
|
||||
self.content = content
|
||||
self.content_lines = content.split('\n')
|
||||
self.front_matters: FrontMatter = FrontMatter({})
|
||||
|
||||
self._content_list: list[Any] = [self.front_matters]
|
||||
|
||||
self.lineno = 0
|
||||
|
||||
self._parsed = False
|
||||
|
||||
def parse_front_matters(self) -> err:
|
||||
if self.content_lines[self.lineno].strip() != '---':
|
||||
return ValueError('Invalid front matter')
|
||||
while self.lineno < len(self.content_lines):
|
||||
self._next_line()
|
||||
line = self.content_lines[self.lineno]
|
||||
if line.strip() == '---':
|
||||
break
|
||||
if line.strip().startswith('#'):
|
||||
# fm注释
|
||||
continue
|
||||
try:
|
||||
key, value = line.split(':', 1)
|
||||
except ValueError:
|
||||
return Exception(f'Invalid front matter: {line}')
|
||||
self.front_matters[key.strip()] = value.strip()
|
||||
return nil
|
||||
|
||||
def _parse_content(self) -> tuple[list[Any], err]:
|
||||
content: list[Any] = []
|
||||
while self.lineno < len(self.content_lines):
|
||||
item, e = self._parse_line()
|
||||
if e != nil:
|
||||
return nil, e
|
||||
content.append(item)
|
||||
return content, nil
|
||||
|
||||
def _parse_line(self) -> tuple[Any, err]:
|
||||
line = self.content_lines[self.lineno]
|
||||
if line.startswith('#'):
|
||||
# 计算start有几个#
|
||||
start = 0
|
||||
while line[start] == '#':
|
||||
start += 1
|
||||
return Header(start, line[start:].strip()), nil
|
||||
elif line.startswith('-'):
|
||||
start = 0
|
||||
while line[start] == '-':
|
||||
start += 1
|
||||
return List(start, line[start:].strip()), nil
|
||||
|
||||
# 处理<!--注释 continue
|
||||
elif line.strip().startswith('<!--'):
|
||||
while not line.strip().endswith('-->'):
|
||||
self._next_line()
|
||||
line = self.content_lines[self.lineno]
|
||||
return None, nil
|
||||
# 处理[//]: # (注释) continue
|
||||
elif line.strip().startswith('[//]: #'):
|
||||
self._next_line()
|
||||
return None, nil
|
||||
|
||||
self._next_line()
|
||||
return nil, ValueError(f'Invalid line: {line}')
|
||||
|
||||
def _next_line(self):
|
||||
self.lineno += 1
|
||||
|
||||
def parse(self) -> tuple[list[Any] | Nil, err]:
|
||||
if self._parsed:
|
||||
return self._content_list, nil
|
||||
|
||||
e = self.parse_front_matters()
|
||||
if e != nil:
|
||||
return nil, e
|
||||
|
||||
ls, e = self._parse_content()
|
||||
if e != nil:
|
||||
return nil, e
|
||||
|
||||
self._content_list.extend(ls)
|
||||
self._parsed = True
|
||||
|
||||
return self._content_list, nil
|
7
liteyuki_flow/plugin_handler.py
Normal file
7
liteyuki_flow/plugin_handler.py
Normal file
@ -0,0 +1,7 @@
|
||||
"""
|
||||
Module docs
|
||||
"""
|
||||
|
||||
|
||||
def plugin_handler():
|
||||
pass
|
3
liteyuki_flow/requirements.txt
Normal file
3
liteyuki_flow/requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
PyGithub==2.4.0
|
||||
requests==2.31.0
|
||||
pyyaml==6.0.2
|
131
liteyuki_flow/resource_handler.py
Normal file
131
liteyuki_flow/resource_handler.py
Normal file
@ -0,0 +1,131 @@
|
||||
"""
|
||||
Module docs
|
||||
"""
|
||||
import requests # type: ignore
|
||||
import zipfile
|
||||
|
||||
from github import Github, InputGitTreeElement, GitTree
|
||||
from github.Issue import Issue
|
||||
from github.Repository import Repository
|
||||
import json
|
||||
import yaml
|
||||
|
||||
from liteyuki_flow.const import OPENED, EDITED, CLOSED, REOPENED, RESOURCE_JSON
|
||||
from liteyuki_flow.markdown_parser import MarkdownParser
|
||||
from liteyuki_flow.typ import err, nil
|
||||
|
||||
|
||||
# opened: 创建新的资源包,预审核
|
||||
# edited: 编辑资源包信息,需重新审核
|
||||
# closed: 审核通过,修改json并提交
|
||||
# reopened: 重新打开,无操作
|
||||
|
||||
# opened | edited
|
||||
def pre_check(github: Github, issue: Issue, repo: Repository) -> err:
|
||||
parser = MarkdownParser(issue.body)
|
||||
parser.parse_front_matters()
|
||||
name = parser.front_matters.get("name")
|
||||
desc = parser.front_matters.get("desc")
|
||||
url = parser.front_matters.get("url")
|
||||
homepage = parser.front_matters.get("homepage") # optional
|
||||
author = parser.front_matters.get("author")
|
||||
if not all((name, desc, url, author)):
|
||||
issue.create_comment("Name, desc, url, homepage and author are required.")
|
||||
return ValueError("Name, desc, url, homepage and author are required.")
|
||||
|
||||
# 下载并解析资源包
|
||||
r = requests.get(url)
|
||||
if r.status_code != 200:
|
||||
issue.create_comment("Download failed.")
|
||||
return ValueError("Download failed.")
|
||||
|
||||
with open(f"tmp/{name}.zip", "wb") as f:
|
||||
f.write(r.content)
|
||||
# 解压
|
||||
with zipfile.ZipFile(f"tmp/{name}.zip", "r") as z:
|
||||
z.extractall(f"tmp/{name}")
|
||||
|
||||
# 检测包内metadata.yml文件
|
||||
try:
|
||||
data = yaml.load(open(f"tmp/{name}/metadata.yml"), Loader=yaml.SafeLoader)
|
||||
except Exception:
|
||||
issue.create_comment("metadata.yml not found or invalid.")
|
||||
return ValueError("metadata.yml not found or invalid.")
|
||||
|
||||
# 检测必要字段 name,description,version
|
||||
if not all((data.get("name"), data.get("description"), data.get("version"))):
|
||||
issue.create_comment("name, description and version are required in metadata.yml.")
|
||||
return ValueError("name, description and version are required in metadata.yml.")
|
||||
|
||||
# 不检测重复资源包,因为资源包可能有多个版本
|
||||
# 检测通过,编辑原issue
|
||||
metadata_markdown = f"**名称**: {data.get('name')}\n**描述**: {data.get('description')}\n**版本**: {data.get('version')}\n"
|
||||
for k, v in data.items():
|
||||
if k not in ("name", "description", "version"):
|
||||
metadata_markdown += f"**{k}**: {v}\n"
|
||||
|
||||
new_issue_body = f"---\nname: {name}\ndesc: {desc}\nurl: {url}\nhomepage: {homepage}\nauthor: {author}\n---\n"
|
||||
new_issue_body += f"# Resource: {name}\n"
|
||||
new_issue_body += f"## 发布信息\n{desc}\n"
|
||||
new_issue_body += f"**名称**: {name}\n"
|
||||
new_issue_body += f"**描述**: {desc}\n"
|
||||
new_issue_body += f"**作者**: {author}\n"
|
||||
new_issue_body += f"**主页**: {homepage}\n"
|
||||
new_issue_body += f"**下载**: {url}\n"
|
||||
# 遍历其他字段
|
||||
for k, v in data.items():
|
||||
if k not in ("name", "description", "version"):
|
||||
new_issue_body += f"**{k}**: {v}\n"
|
||||
|
||||
issue.edit(new_issue_body)
|
||||
issue.create_comment("✅ 预检查通过\n## 元数据\n" + metadata_markdown)
|
||||
return nil
|
||||
|
||||
|
||||
# closed
|
||||
def add_resource(github: Github, issue: Issue, repo: Repository):
|
||||
parser = MarkdownParser(issue.body)
|
||||
parser.parse_front_matters()
|
||||
name = parser.front_matters.get("name")
|
||||
desc = parser.front_matters.get("desc")
|
||||
url = parser.front_matters.get("url")
|
||||
homepage = parser.front_matters.get("homepage") # optional
|
||||
author = parser.front_matters.get("author")
|
||||
|
||||
# 编辑仓库内的json文件
|
||||
resources = json.load(open(RESOURCE_JSON))
|
||||
resources.append({
|
||||
"name" : name,
|
||||
"desc" : desc,
|
||||
"url" : url,
|
||||
"homepage": homepage,
|
||||
"author" : author
|
||||
})
|
||||
ref = repo.get_git_ref("heads/main")
|
||||
tree = repo.create_git_tree(
|
||||
base_tree=repo.get_git_commit(ref.object.sha).tree,
|
||||
tree=[
|
||||
InputGitTreeElement(
|
||||
path=RESOURCE_JSON,
|
||||
mode="100644",
|
||||
type="blob",
|
||||
content=json.dumps(resources, indent=4, ensure_ascii=False)
|
||||
)
|
||||
]
|
||||
)
|
||||
commit = repo.create_git_commit(
|
||||
message=f":package: 发布资源: {name}",
|
||||
tree=tree,
|
||||
parents=[repo.get_git_commit(ref.object.sha)]
|
||||
)
|
||||
ref.edit(commit.sha)
|
||||
|
||||
|
||||
def handle_resource(github: Github, issue: Issue, repo: Repository, act_type: str):
|
||||
if act_type in (OPENED, EDITED):
|
||||
pre_check(github, issue, repo)
|
||||
|
||||
elif act_type == CLOSED:
|
||||
add_resource(github, issue, repo)
|
||||
else:
|
||||
print("No operation found for the issue: ", act_type)
|
26
liteyuki_flow/ts.md
Normal file
26
liteyuki_flow/ts.md
Normal file
@ -0,0 +1,26 @@
|
||||
---
|
||||
# 请修改此处内容/Please modify the content here
|
||||
# 不要修改正文中的内容/Do not modify the content in the body
|
||||
name: PluginName
|
||||
desc: 插件描述
|
||||
author: 作者名
|
||||
link: 下载链接
|
||||
homepage: 主页链接
|
||||
---
|
||||
<!-- 以下字段自动生成,请勿编辑 -->
|
||||
# Plugin: PluginName
|
||||
|
||||
## 插件信息
|
||||
|
||||
- **包名**: PluginName
|
||||
- **描述**: 插件描述
|
||||
- **作者**: AuthorName
|
||||
- **主页**: https://a.b.c
|
||||
|
||||
## 资源包信息
|
||||
|
||||
- **名称**: PluginName
|
||||
- **描述**: 插件描述
|
||||
- **作者**: AuthorName
|
||||
- **主页**: https://a.b.c
|
||||
- **下载**: https://a.b.c
|
20
liteyuki_flow/typ.py
Normal file
20
liteyuki_flow/typ.py
Normal file
@ -0,0 +1,20 @@
|
||||
"""
|
||||
Module docs
|
||||
"""
|
||||
from typing import TypeAlias
|
||||
|
||||
|
||||
class Nil():
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, Nil):
|
||||
return True
|
||||
return other is None
|
||||
|
||||
# 不等于
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
|
||||
nil = Nil()
|
||||
|
||||
err: TypeAlias = Exception | Nil
|
Loading…
Reference in New Issue
Block a user