📦 docs: 资源商店新增发布资源功能

This commit is contained in:
远野千束 2024-09-17 14:37:45 +08:00
parent 72e71124b8
commit b5b15c82f8
10 changed files with 401 additions and 44 deletions

View File

@ -1,44 +0,0 @@
# 问题反馈
## **请确保**
- 已认真阅读[文档]("https://bot.liteyuki.icu"),该问题不是文档提及的或你自己操作不当造成的
- 你的问题是在最新版本的代码上测试的
- 请勿重复提交相同或类似的issue
## **描述问题**
请在此简单描述问题
## **如何复现**
请阐述一下如何重现这个问题
### 预期
描述你期望发生的事情
### 实际
描述实际发生的事情
## **日志或截图**
```
日志内容
```
## **设备信息**
- **系统**: [例如 Ubuntu 22.04]
- **CPU**: [例如 Intel i7-7700K]
- **内存**: [例如 16GB]
- **Python**: [例如CPython 3.10.7]
**补充内容**
可选,推荐提供`pip freeze`的输出,以及其他相关信息,以及你的建议

View File

@ -0,0 +1,3 @@
"""
Module docs
"""

60
liteyuki_flow/__main__.py Normal file
View 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
View 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"

View 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

View File

@ -0,0 +1,7 @@
"""
Module docs
"""
def plugin_handler():
pass

View File

@ -0,0 +1,3 @@
PyGithub==2.4.0
requests==2.31.0
pyyaml==6.0.2

View 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.")
# 检测必要字段 namedescriptionversion
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
View 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
View 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