mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-02 12:05:54 +00:00
style: introduce ruff as linter and formatter (#1356)
* style: remove necessary imports * style: fix F841 * style: fix F401 * style: fix F811 * style: fix E402 * style: fix E721 * style: fix E722 * style: fix E722 * style: fix F541 * style: ruff format * style: all passed * style: add ruff in deps * style: more ignores in ruff.toml * style: add pre-commit
This commit is contained in:
committed by
GitHub
parent
09e70d70e9
commit
209f16af76
@@ -14,7 +14,7 @@ from ..core import app
|
||||
|
||||
class Announcement(pydantic.BaseModel):
|
||||
"""公告"""
|
||||
|
||||
|
||||
id: int
|
||||
|
||||
time: str
|
||||
@@ -27,11 +27,11 @@ class Announcement(pydantic.BaseModel):
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
return {
|
||||
"id": self.id,
|
||||
"time": self.time,
|
||||
"timestamp": self.timestamp,
|
||||
"content": self.content,
|
||||
"enabled": self.enabled
|
||||
'id': self.id,
|
||||
'time': self.time,
|
||||
'timestamp': self.timestamp,
|
||||
'content': self.content,
|
||||
'enabled': self.enabled,
|
||||
}
|
||||
|
||||
|
||||
@@ -43,30 +43,28 @@ class AnnouncementManager:
|
||||
def __init__(self, ap: app.Application):
|
||||
self.ap = ap
|
||||
|
||||
async def fetch_all(
|
||||
self
|
||||
) -> list[Announcement]:
|
||||
async def fetch_all(self) -> list[Announcement]:
|
||||
"""获取所有公告"""
|
||||
resp = requests.get(
|
||||
url="https://api.github.com/repos/RockChinQ/LangBot/contents/res/announcement.json",
|
||||
url='https://api.github.com/repos/RockChinQ/LangBot/contents/res/announcement.json',
|
||||
proxies=self.ap.proxy_mgr.get_forward_proxies(),
|
||||
timeout=5
|
||||
timeout=5,
|
||||
)
|
||||
obj_json = resp.json()
|
||||
b64_content = obj_json["content"]
|
||||
b64_content = obj_json['content']
|
||||
# 解码
|
||||
content = base64.b64decode(b64_content).decode("utf-8")
|
||||
content = base64.b64decode(b64_content).decode('utf-8')
|
||||
|
||||
return [Announcement(**item) for item in json.loads(content)]
|
||||
|
||||
async def fetch_saved(
|
||||
self
|
||||
) -> list[Announcement]:
|
||||
if not os.path.exists("data/labels/announcement_saved.json"):
|
||||
with open("data/labels/announcement_saved.json", "w", encoding="utf-8") as f:
|
||||
f.write("[]")
|
||||
async def fetch_saved(self) -> list[Announcement]:
|
||||
if not os.path.exists('data/labels/announcement_saved.json'):
|
||||
with open(
|
||||
'data/labels/announcement_saved.json', 'w', encoding='utf-8'
|
||||
) as f:
|
||||
f.write('[]')
|
||||
|
||||
with open("data/labels/announcement_saved.json", "r", encoding="utf-8") as f:
|
||||
with open('data/labels/announcement_saved.json', 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
if not content:
|
||||
@@ -74,19 +72,15 @@ class AnnouncementManager:
|
||||
|
||||
return [Announcement(**item) for item in json.loads(content)]
|
||||
|
||||
async def write_saved(
|
||||
self,
|
||||
content: list[Announcement]
|
||||
):
|
||||
async def write_saved(self, content: list[Announcement]):
|
||||
with open('data/labels/announcement_saved.json', 'w', encoding='utf-8') as f:
|
||||
f.write(
|
||||
json.dumps(
|
||||
[item.to_dict() for item in content], indent=4, ensure_ascii=False
|
||||
)
|
||||
)
|
||||
|
||||
with open("data/labels/announcement_saved.json", "w", encoding="utf-8") as f:
|
||||
f.write(json.dumps([
|
||||
item.to_dict() for item in content
|
||||
], indent=4, ensure_ascii=False))
|
||||
|
||||
async def fetch_new(
|
||||
self
|
||||
) -> list[Announcement]:
|
||||
async def fetch_new(self) -> list[Announcement]:
|
||||
"""获取新公告"""
|
||||
all = await self.fetch_all()
|
||||
saved = await self.fetch_saved()
|
||||
@@ -106,18 +100,15 @@ class AnnouncementManager:
|
||||
await self.write_saved(all)
|
||||
return to_show
|
||||
|
||||
async def show_announcements(
|
||||
self
|
||||
) -> typing.Tuple[str, int]:
|
||||
async def show_announcements(self) -> typing.Tuple[str, int]:
|
||||
"""显示公告"""
|
||||
try:
|
||||
announcements = await self.fetch_new()
|
||||
ann_text = ""
|
||||
ann_text = ''
|
||||
for ann in announcements:
|
||||
ann_text += f"[公告] {ann.time}: {ann.content}\n"
|
||||
ann_text += f'[公告] {ann.time}: {ann.content}\n'
|
||||
|
||||
if announcements:
|
||||
|
||||
await self.ap.ctr_mgr.main.post_announcement_showed(
|
||||
ids=[item.id for item in announcements]
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
semantic_version = "v4.0.0"
|
||||
semantic_version = 'v4.0.0'
|
||||
|
||||
required_database_version = 1
|
||||
"""标记本版本所需要的数据库结构版本,用于判断数据库迁移"""
|
||||
|
||||
debug_mode = False
|
||||
|
||||
edition = 'community'
|
||||
edition = 'community'
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import sys
|
||||
import re
|
||||
import inspect
|
||||
|
||||
@@ -33,18 +32,18 @@ def get_func_schema(function: callable) -> dict:
|
||||
func_doc = function.__doc__
|
||||
# Google Style Docstring
|
||||
if func_doc is None:
|
||||
raise Exception("Function {} has no docstring.".format(function.__name__))
|
||||
func_doc = func_doc.strip().replace(' ','').replace('\t', '')
|
||||
raise Exception('Function {} has no docstring.'.format(function.__name__))
|
||||
func_doc = func_doc.strip().replace(' ', '').replace('\t', '')
|
||||
# extract doc of args from docstring
|
||||
doc_spt = func_doc.split('\n\n')
|
||||
desc = doc_spt[0]
|
||||
args = doc_spt[1] if len(doc_spt) > 1 else ""
|
||||
returns = doc_spt[2] if len(doc_spt) > 2 else ""
|
||||
args = doc_spt[1] if len(doc_spt) > 1 else ''
|
||||
# returns = doc_spt[2] if len(doc_spt) > 2 else ""
|
||||
|
||||
# extract args
|
||||
# delete the first line of args
|
||||
arg_lines = args.split('\n')[1:]
|
||||
arg_doc_list = re.findall(r'(\w+)(\((\w+)\))?:\s*(.*)', args)
|
||||
# arg_doc_list = re.findall(r'(\w+)(\((\w+)\))?:\s*(.*)', args)
|
||||
args_doc = {}
|
||||
for arg_line in arg_lines:
|
||||
doc_tuple = re.findall(r'(\w+)(\(([\w\[\]]+)\))?:\s*(.*)', arg_line)
|
||||
@@ -53,18 +52,16 @@ def get_func_schema(function: callable) -> dict:
|
||||
args_doc[doc_tuple[0][0]] = doc_tuple[0][3]
|
||||
|
||||
# extract returns
|
||||
return_doc_list = re.findall(r'(\w+):\s*(.*)', returns)
|
||||
# return_doc_list = re.findall(r'(\w+):\s*(.*)', returns)
|
||||
|
||||
params = enumerate(inspect.signature(function).parameters.values())
|
||||
parameters = {
|
||||
"type": "object",
|
||||
"required": [],
|
||||
"properties": {},
|
||||
'type': 'object',
|
||||
'required': [],
|
||||
'properties': {},
|
||||
}
|
||||
|
||||
|
||||
for i, param in params:
|
||||
|
||||
# 排除 self, query
|
||||
if param.name in ['self', 'query']:
|
||||
continue
|
||||
@@ -72,24 +69,24 @@ def get_func_schema(function: callable) -> dict:
|
||||
param_type = param.annotation.__name__
|
||||
|
||||
type_name_mapping = {
|
||||
"str": "string",
|
||||
"int": "integer",
|
||||
"float": "number",
|
||||
"bool": "boolean",
|
||||
"list": "array",
|
||||
"dict": "object",
|
||||
'str': 'string',
|
||||
'int': 'integer',
|
||||
'float': 'number',
|
||||
'bool': 'boolean',
|
||||
'list': 'array',
|
||||
'dict': 'object',
|
||||
}
|
||||
|
||||
if param_type in type_name_mapping:
|
||||
param_type = type_name_mapping[param_type]
|
||||
|
||||
parameters['properties'][param.name] = {
|
||||
"type": param_type,
|
||||
"description": args_doc[param.name],
|
||||
'type': param_type,
|
||||
'description': args_doc[param.name],
|
||||
}
|
||||
|
||||
# add schema for array
|
||||
if param_type == "array":
|
||||
if param_type == 'array':
|
||||
# extract type of array, the int of list[int]
|
||||
# use re
|
||||
array_type_tuple = re.findall(r'list\[(\w+)\]', str(param.annotation))
|
||||
@@ -102,15 +99,15 @@ def get_func_schema(function: callable) -> dict:
|
||||
if array_type in type_name_mapping:
|
||||
array_type = type_name_mapping[array_type]
|
||||
|
||||
parameters['properties'][param.name]["items"] = {
|
||||
"type": array_type,
|
||||
parameters['properties'][param.name]['items'] = {
|
||||
'type': array_type,
|
||||
}
|
||||
|
||||
if param.default is inspect.Parameter.empty:
|
||||
parameters["required"].append(param.name)
|
||||
parameters['required'].append(param.name)
|
||||
|
||||
return {
|
||||
"function": function,
|
||||
"description": desc,
|
||||
"parameters": parameters,
|
||||
}
|
||||
'function': function,
|
||||
'description': desc,
|
||||
'parameters': parameters,
|
||||
}
|
||||
|
||||
@@ -8,23 +8,16 @@ import aiohttp
|
||||
import PIL.Image
|
||||
import httpx
|
||||
|
||||
import os
|
||||
import aiofiles
|
||||
import pathlib
|
||||
import asyncio
|
||||
from urllib.parse import urlparse
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async def get_gewechat_image_base64(
|
||||
gewechat_url: str,
|
||||
gewechat_file_url: str,
|
||||
app_id: str,
|
||||
xml_content: str,
|
||||
token: str,
|
||||
image_type: int = 2,
|
||||
gewechat_url: str,
|
||||
gewechat_file_url: str,
|
||||
app_id: str,
|
||||
xml_content: str,
|
||||
token: str,
|
||||
image_type: int = 2,
|
||||
) -> typing.Tuple[str, str]:
|
||||
"""从gewechat服务器获取图片并转换为base64格式
|
||||
|
||||
@@ -43,17 +36,14 @@ async def get_gewechat_image_base64(
|
||||
aiohttp.ClientTimeout: 请求超时(15秒)或连接超时(2秒)
|
||||
Exception: 其他错误
|
||||
"""
|
||||
headers = {
|
||||
'X-GEWE-TOKEN': token,
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
headers = {'X-GEWE-TOKEN': token, 'Content-Type': 'application/json'}
|
||||
|
||||
# 设置超时
|
||||
timeout = aiohttp.ClientTimeout(
|
||||
total=15.0, # 总超时时间15秒
|
||||
connect=2.0, # 连接超时2秒
|
||||
sock_connect=2.0, # socket连接超时2秒
|
||||
sock_read=15.0 # socket读取超时15秒
|
||||
sock_read=15.0, # socket读取超时15秒
|
||||
)
|
||||
|
||||
try:
|
||||
@@ -61,37 +51,37 @@ async def get_gewechat_image_base64(
|
||||
# 获取图片下载链接
|
||||
try:
|
||||
async with session.post(
|
||||
f"{gewechat_url}/v2/api/message/downloadImage",
|
||||
headers=headers,
|
||||
json={
|
||||
"appId": app_id,
|
||||
"type": image_type,
|
||||
"xml": xml_content
|
||||
}
|
||||
f'{gewechat_url}/v2/api/message/downloadImage',
|
||||
headers=headers,
|
||||
json={'appId': app_id, 'type': image_type, 'xml': xml_content},
|
||||
) as response:
|
||||
if response.status != 200:
|
||||
# print(response)
|
||||
raise Exception(f"获取gewechat图片下载失败: {await response.text()}")
|
||||
raise Exception(
|
||||
f'获取gewechat图片下载失败: {await response.text()}'
|
||||
)
|
||||
|
||||
resp_data = await response.json()
|
||||
if resp_data.get("ret") != 200:
|
||||
raise Exception(f"获取gewechat图片下载链接失败: {resp_data}")
|
||||
if resp_data.get('ret') != 200:
|
||||
raise Exception(f'获取gewechat图片下载链接失败: {resp_data}')
|
||||
|
||||
file_url = resp_data['data']['fileUrl']
|
||||
except asyncio.TimeoutError:
|
||||
raise Exception("获取图片下载链接超时")
|
||||
raise Exception('获取图片下载链接超时')
|
||||
except aiohttp.ClientError as e:
|
||||
raise Exception(f"获取图片下载链接网络错误: {str(e)}")
|
||||
raise Exception(f'获取图片下载链接网络错误: {str(e)}')
|
||||
|
||||
# 解析原始URL并替换端口
|
||||
base_url = gewechat_file_url
|
||||
download_url = f"{base_url}/download/{file_url}"
|
||||
download_url = f'{base_url}/download/{file_url}'
|
||||
|
||||
# 下载图片
|
||||
try:
|
||||
async with session.get(download_url) as img_response:
|
||||
if img_response.status != 200:
|
||||
raise Exception(f"下载图片失败: {await img_response.text()}, URL: {download_url}")
|
||||
raise Exception(
|
||||
f'下载图片失败: {await img_response.text()}, URL: {download_url}'
|
||||
)
|
||||
|
||||
image_data = await img_response.read()
|
||||
|
||||
@@ -105,14 +95,11 @@ async def get_gewechat_image_base64(
|
||||
|
||||
return base64_str, image_format
|
||||
except asyncio.TimeoutError:
|
||||
raise Exception(f"下载图片超时, URL: {download_url}")
|
||||
raise Exception(f'下载图片超时, URL: {download_url}')
|
||||
except aiohttp.ClientError as e:
|
||||
raise Exception(f"下载图片网络错误: {str(e)}, URL: {download_url}")
|
||||
raise Exception(f'下载图片网络错误: {str(e)}, URL: {download_url}')
|
||||
except Exception as e:
|
||||
raise Exception(f"获取图片失败: {str(e)}") from e
|
||||
|
||||
|
||||
|
||||
raise Exception(f'获取图片失败: {str(e)}') from e
|
||||
|
||||
|
||||
async def get_wecom_image_base64(pic_url: str) -> tuple[str, str]:
|
||||
@@ -124,22 +111,26 @@ async def get_wecom_image_base64(pic_url: str) -> tuple[str, str]:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(pic_url) as response:
|
||||
if response.status != 200:
|
||||
raise Exception(f"Failed to download image: {response.status}")
|
||||
|
||||
raise Exception(f'Failed to download image: {response.status}')
|
||||
|
||||
# 读取图片数据
|
||||
image_data = await response.read()
|
||||
|
||||
|
||||
# 获取图片格式
|
||||
content_type = response.headers.get('Content-Type', '')
|
||||
image_format = content_type.split('/')[-1] # 例如 'image/jpeg' -> 'jpeg'
|
||||
|
||||
|
||||
# 转换为 base64
|
||||
import base64
|
||||
|
||||
image_base64 = base64.b64encode(image_data).decode('utf-8')
|
||||
|
||||
|
||||
return image_base64, image_format
|
||||
|
||||
async def get_qq_official_image_base64(pic_url:str,content_type:str) -> tuple[str,str]:
|
||||
|
||||
|
||||
async def get_qq_official_image_base64(
|
||||
pic_url: str, content_type: str
|
||||
) -> tuple[str, str]:
|
||||
"""
|
||||
下载QQ官方图片,
|
||||
并且转换为base64格式
|
||||
@@ -149,18 +140,18 @@ async def get_qq_official_image_base64(pic_url:str,content_type:str) -> tuple[st
|
||||
response.raise_for_status() # 确保请求成功
|
||||
image_data = response.content
|
||||
base64_data = base64.b64encode(image_data).decode('utf-8')
|
||||
|
||||
return f"data:{content_type};base64,{base64_data}"
|
||||
|
||||
return f'data:{content_type};base64,{base64_data}'
|
||||
|
||||
|
||||
def get_qq_image_downloadable_url(image_url: str) -> tuple[str, dict]:
|
||||
"""获取QQ图片的下载链接"""
|
||||
parsed = urlparse(image_url)
|
||||
query = parse_qs(parsed.query)
|
||||
return f"http://{parsed.netloc}{parsed.path}", query
|
||||
return f'http://{parsed.netloc}{parsed.path}', query
|
||||
|
||||
|
||||
async def get_qq_image_bytes(image_url: str, query: dict={}) -> tuple[bytes, str]:
|
||||
async def get_qq_image_bytes(image_url: str, query: dict = {}) -> tuple[bytes, str]:
|
||||
"""[弃用]获取QQ图片的bytes"""
|
||||
image_url, query_in_url = get_qq_image_downloadable_url(image_url)
|
||||
query = {**query, **query_in_url}
|
||||
@@ -177,14 +168,12 @@ async def get_qq_image_bytes(image_url: str, query: dict={}) -> tuple[bytes, str
|
||||
elif not content_type.startswith('image/'):
|
||||
pil_img = PIL.Image.open(io.BytesIO(file_bytes))
|
||||
image_format = pil_img.format.lower()
|
||||
else:
|
||||
else:
|
||||
image_format = content_type.split('/')[-1]
|
||||
return file_bytes, image_format
|
||||
|
||||
|
||||
async def qq_image_url_to_base64(
|
||||
image_url: str
|
||||
) -> typing.Tuple[str, str]:
|
||||
async def qq_image_url_to_base64(image_url: str) -> typing.Tuple[str, str]:
|
||||
"""[弃用]将QQ图片URL转为base64,并返回图片格式
|
||||
|
||||
Args:
|
||||
@@ -204,12 +193,13 @@ async def qq_image_url_to_base64(
|
||||
|
||||
return base64_str, image_format
|
||||
|
||||
|
||||
async def extract_b64_and_format(image_base64_data: str) -> typing.Tuple[str, str]:
|
||||
"""提取base64编码和图片格式
|
||||
|
||||
|
||||
data:image/jpeg;base64,xxx
|
||||
提取出base64编码和图片格式
|
||||
"""
|
||||
base64_str = image_base64_data.split(',')[-1]
|
||||
image_format = image_base64_data.split(':')[-1].split(';')[0].split('/')[-1]
|
||||
return base64_str, image_format
|
||||
return base64_str, image_format
|
||||
|
||||
43
pkg/utils/importutil.py
Normal file
43
pkg/utils/importutil.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import importlib
|
||||
import importlib.util
|
||||
import os
|
||||
import typing
|
||||
|
||||
|
||||
def import_modules_in_pkg(pkg: typing.Any) -> None:
|
||||
"""
|
||||
导入一个包内的所有模块
|
||||
Args:
|
||||
pkg: 要导入的包对象
|
||||
"""
|
||||
pkg_path = os.path.dirname(pkg.__file__)
|
||||
import_dir(pkg_path)
|
||||
|
||||
|
||||
def import_modules_in_pkgs(pkgs: typing.List) -> None:
|
||||
for pkg in pkgs:
|
||||
import_modules_in_pkg(pkg)
|
||||
|
||||
|
||||
def import_dot_style_dir(dot_sep_path: str):
|
||||
sec = dot_sep_path.split('.')
|
||||
|
||||
return import_dir(os.path.join(*sec))
|
||||
|
||||
|
||||
def import_dir(path: str):
|
||||
for file in os.listdir(path):
|
||||
if file.endswith('.py') and file != '__init__.py':
|
||||
full_path = os.path.join(path, file)
|
||||
rel_path = full_path.replace(
|
||||
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), ''
|
||||
)
|
||||
rel_path = rel_path[1:]
|
||||
rel_path = rel_path.replace('/', '.')[:-3]
|
||||
importlib.import_module(rel_path)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from pkg.platform import types
|
||||
|
||||
import_modules_in_pkg(types)
|
||||
@@ -1,9 +1,12 @@
|
||||
import aiohttp
|
||||
|
||||
|
||||
async def get_myip() -> str:
|
||||
try:
|
||||
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=10)) as session:
|
||||
async with session.get("https://ip.useragentinfo.com/myip") as response:
|
||||
async with aiohttp.ClientSession(
|
||||
timeout=aiohttp.ClientTimeout(total=10)
|
||||
) as session:
|
||||
async with session.get('https://ip.useragentinfo.com/myip') as response:
|
||||
return await response.text()
|
||||
except Exception as e:
|
||||
return '0.0.0.0'
|
||||
except Exception:
|
||||
return '0.0.0.0'
|
||||
|
||||
@@ -5,8 +5,9 @@ LOG_PAGE_SIZE = 20
|
||||
MAX_CACHED_PAGES = 10
|
||||
|
||||
|
||||
class LogPage():
|
||||
class LogPage:
|
||||
"""日志页"""
|
||||
|
||||
number: int
|
||||
"""页码"""
|
||||
|
||||
@@ -51,12 +52,12 @@ class LogCache:
|
||||
start_offset: int,
|
||||
) -> tuple[str, int, int]:
|
||||
"""获取指定页码和偏移量的日志"""
|
||||
final_logs_str = ""
|
||||
final_logs_str = ''
|
||||
|
||||
for page in self.log_pages:
|
||||
if page.number == start_page_number:
|
||||
final_logs_str += "\n".join(page.logs[start_offset:])
|
||||
final_logs_str += '\n'.join(page.logs[start_offset:])
|
||||
elif page.number > start_page_number:
|
||||
final_logs_str += "\n".join(page.logs)
|
||||
final_logs_str += '\n'.join(page.logs)
|
||||
|
||||
return final_logs_str, page.number, len(page.logs)
|
||||
|
||||
@@ -6,8 +6,17 @@ def install(package):
|
||||
|
||||
|
||||
def install_upgrade(package):
|
||||
pipmain(['install', '--upgrade', package, "-i", "https://pypi.tuna.tsinghua.edu.cn/simple",
|
||||
"--trusted-host", "pypi.tuna.tsinghua.edu.cn"])
|
||||
pipmain(
|
||||
[
|
||||
'install',
|
||||
'--upgrade',
|
||||
package,
|
||||
'-i',
|
||||
'https://pypi.tuna.tsinghua.edu.cn/simple',
|
||||
'--trusted-host',
|
||||
'pypi.tuna.tsinghua.edu.cn',
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def run_pip(params: list):
|
||||
@@ -15,5 +24,15 @@ def run_pip(params: list):
|
||||
|
||||
|
||||
def install_requirements(file, extra_params: list = []):
|
||||
pipmain(['install', '-r', file, "-i", "https://pypi.tuna.tsinghua.edu.cn/simple",
|
||||
"--trusted-host", "pypi.tuna.tsinghua.edu.cn"] + extra_params)
|
||||
pipmain(
|
||||
[
|
||||
'install',
|
||||
'-r',
|
||||
file,
|
||||
'-i',
|
||||
'https://pypi.tuna.tsinghua.edu.cn/simple',
|
||||
'--trusted-host',
|
||||
'pypi.tuna.tsinghua.edu.cn',
|
||||
]
|
||||
+ extra_params
|
||||
)
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from ..core import app
|
||||
|
||||
|
||||
class ProxyManager:
|
||||
"""代理管理器
|
||||
"""
|
||||
"""代理管理器"""
|
||||
|
||||
ap: app.Application
|
||||
|
||||
@@ -21,14 +19,24 @@ class ProxyManager:
|
||||
|
||||
async def initialize(self):
|
||||
self.forward_proxies = {
|
||||
"http://": os.getenv("HTTP_PROXY") or os.getenv("http_proxy"),
|
||||
"https://": os.getenv("HTTPS_PROXY") or os.getenv("https_proxy"),
|
||||
'http://': os.getenv('HTTP_PROXY') or os.getenv('http_proxy'),
|
||||
'https://': os.getenv('HTTPS_PROXY') or os.getenv('https_proxy'),
|
||||
}
|
||||
|
||||
if 'http' in self.ap.instance_config.data['proxy'] and self.ap.instance_config.data['proxy']['http']:
|
||||
self.forward_proxies['http://'] = self.ap.instance_config.data['proxy']['http']
|
||||
if 'https' in self.ap.instance_config.data['proxy'] and self.ap.instance_config.data['proxy']['https']:
|
||||
self.forward_proxies['https://'] = self.ap.instance_config.data['proxy']['https']
|
||||
if (
|
||||
'http' in self.ap.instance_config.data['proxy']
|
||||
and self.ap.instance_config.data['proxy']['http']
|
||||
):
|
||||
self.forward_proxies['http://'] = self.ap.instance_config.data['proxy'][
|
||||
'http'
|
||||
]
|
||||
if (
|
||||
'https' in self.ap.instance_config.data['proxy']
|
||||
and self.ap.instance_config.data['proxy']['https']
|
||||
):
|
||||
self.forward_proxies['https://'] = self.ap.instance_config.data['proxy'][
|
||||
'https'
|
||||
]
|
||||
|
||||
# 设置到环境变量
|
||||
os.environ['HTTP_PROXY'] = self.forward_proxies['http://'] or ''
|
||||
|
||||
@@ -12,41 +12,33 @@ from . import constants
|
||||
|
||||
|
||||
class VersionManager:
|
||||
"""版本管理器
|
||||
"""
|
||||
"""版本管理器"""
|
||||
|
||||
ap: app.Application
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ap: app.Application
|
||||
):
|
||||
def __init__(self, ap: app.Application):
|
||||
self.ap = ap
|
||||
|
||||
async def initialize(
|
||||
self
|
||||
):
|
||||
async def initialize(self):
|
||||
pass
|
||||
|
||||
def get_current_version(
|
||||
self
|
||||
) -> str:
|
||||
|
||||
def get_current_version(self) -> str:
|
||||
current_tag = constants.semantic_version
|
||||
|
||||
return current_tag
|
||||
|
||||
|
||||
async def get_release_list(self) -> list:
|
||||
"""获取发行列表"""
|
||||
rls_list_resp = requests.get(
|
||||
url="https://api.github.com/repos/RockChinQ/LangBot/releases",
|
||||
url='https://api.github.com/repos/RockChinQ/LangBot/releases',
|
||||
proxies=self.ap.proxy_mgr.get_forward_proxies(),
|
||||
timeout=5
|
||||
timeout=5,
|
||||
)
|
||||
|
||||
rls_list = rls_list_resp.json()
|
||||
|
||||
return rls_list
|
||||
|
||||
|
||||
async def update_all(self):
|
||||
"""检查更新并下载源码"""
|
||||
start_time = time.time()
|
||||
@@ -58,10 +50,10 @@ class VersionManager:
|
||||
|
||||
latest_rls = {}
|
||||
rls_notes = []
|
||||
latest_tag_name = ""
|
||||
latest_tag_name = ''
|
||||
for rls in rls_list:
|
||||
rls_notes.append(rls['name']) # 使用发行名称作为note
|
||||
if latest_tag_name == "":
|
||||
if latest_tag_name == '':
|
||||
latest_tag_name = rls['tag_name']
|
||||
|
||||
if rls['tag_name'] == current_tag:
|
||||
@@ -69,56 +61,68 @@ class VersionManager:
|
||||
|
||||
if latest_rls == {}:
|
||||
latest_rls = rls
|
||||
self.ap.logger.info("更新日志: {}".format(rls_notes))
|
||||
self.ap.logger.info('更新日志: {}'.format(rls_notes))
|
||||
|
||||
if latest_rls == {} and not self.is_newer(latest_tag_name, current_tag): # 没有新版本
|
||||
if latest_rls == {} and not self.is_newer(
|
||||
latest_tag_name, current_tag
|
||||
): # 没有新版本
|
||||
return False
|
||||
|
||||
# 下载最新版本的zip到temp目录
|
||||
self.ap.logger.info("开始下载最新版本: {}".format(latest_rls['zipball_url']))
|
||||
self.ap.logger.info('开始下载最新版本: {}'.format(latest_rls['zipball_url']))
|
||||
|
||||
zip_url = latest_rls['zipball_url']
|
||||
zip_resp = requests.get(
|
||||
url=zip_url,
|
||||
proxies=self.ap.proxy_mgr.get_forward_proxies()
|
||||
url=zip_url, proxies=self.ap.proxy_mgr.get_forward_proxies()
|
||||
)
|
||||
zip_data = zip_resp.content
|
||||
|
||||
# 检查temp/updater目录
|
||||
if not os.path.exists("temp"):
|
||||
os.mkdir("temp")
|
||||
if not os.path.exists("temp/updater"):
|
||||
os.mkdir("temp/updater")
|
||||
with open("temp/updater/{}.zip".format(latest_rls['tag_name']), "wb") as f:
|
||||
if not os.path.exists('temp'):
|
||||
os.mkdir('temp')
|
||||
if not os.path.exists('temp/updater'):
|
||||
os.mkdir('temp/updater')
|
||||
with open('temp/updater/{}.zip'.format(latest_rls['tag_name']), 'wb') as f:
|
||||
f.write(zip_data)
|
||||
|
||||
self.ap.logger.info("下载最新版本完成: {}".format("temp/updater/{}.zip".format(latest_rls['tag_name'])))
|
||||
self.ap.logger.info(
|
||||
'下载最新版本完成: {}'.format(
|
||||
'temp/updater/{}.zip'.format(latest_rls['tag_name'])
|
||||
)
|
||||
)
|
||||
|
||||
# 解压zip到temp/updater/<tag_name>/
|
||||
import zipfile
|
||||
|
||||
# 检查目标文件夹
|
||||
if os.path.exists("temp/updater/{}".format(latest_rls['tag_name'])):
|
||||
if os.path.exists('temp/updater/{}'.format(latest_rls['tag_name'])):
|
||||
import shutil
|
||||
shutil.rmtree("temp/updater/{}".format(latest_rls['tag_name']))
|
||||
os.mkdir("temp/updater/{}".format(latest_rls['tag_name']))
|
||||
with zipfile.ZipFile("temp/updater/{}.zip".format(latest_rls['tag_name']), 'r') as zip_ref:
|
||||
zip_ref.extractall("temp/updater/{}".format(latest_rls['tag_name']))
|
||||
|
||||
shutil.rmtree('temp/updater/{}'.format(latest_rls['tag_name']))
|
||||
os.mkdir('temp/updater/{}'.format(latest_rls['tag_name']))
|
||||
with zipfile.ZipFile(
|
||||
'temp/updater/{}.zip'.format(latest_rls['tag_name']), 'r'
|
||||
) as zip_ref:
|
||||
zip_ref.extractall('temp/updater/{}'.format(latest_rls['tag_name']))
|
||||
|
||||
# 覆盖源码
|
||||
source_root = ""
|
||||
source_root = ''
|
||||
# 找到temp/updater/<tag_name>/中的第一个子目录路径
|
||||
for root, dirs, files in os.walk("temp/updater/{}".format(latest_rls['tag_name'])):
|
||||
if root != "temp/updater/{}".format(latest_rls['tag_name']):
|
||||
for root, dirs, files in os.walk(
|
||||
'temp/updater/{}'.format(latest_rls['tag_name'])
|
||||
):
|
||||
if root != 'temp/updater/{}'.format(latest_rls['tag_name']):
|
||||
source_root = root
|
||||
break
|
||||
|
||||
# 覆盖源码
|
||||
import shutil
|
||||
|
||||
for root, dirs, files in os.walk(source_root):
|
||||
# 覆盖所有子文件子目录
|
||||
for file in files:
|
||||
src = os.path.join(root, file)
|
||||
dst = src.replace(source_root, ".")
|
||||
dst = src.replace(source_root, '.')
|
||||
if os.path.exists(dst):
|
||||
os.remove(dst)
|
||||
|
||||
@@ -128,18 +132,18 @@ class VersionManager:
|
||||
# 检查目标文件是否存在
|
||||
if not os.path.exists(dst):
|
||||
# 创建目标文件
|
||||
open(dst, "w").close()
|
||||
open(dst, 'w').close()
|
||||
|
||||
shutil.copy(src, dst)
|
||||
|
||||
# 把current_tag写入文件
|
||||
current_tag = latest_rls['tag_name']
|
||||
with open("current_tag", "w") as f:
|
||||
with open('current_tag', 'w') as f:
|
||||
f.write(current_tag)
|
||||
|
||||
await self.ap.ctr_mgr.main.post_update_record(
|
||||
spent_seconds=int(time.time()-start_time),
|
||||
infer_reason="update",
|
||||
spent_seconds=int(time.time() - start_time),
|
||||
infer_reason='update',
|
||||
old_version=old_tag,
|
||||
new_version=current_tag,
|
||||
)
|
||||
@@ -155,23 +159,22 @@ class VersionManager:
|
||||
current_tag = self.get_current_version()
|
||||
|
||||
# 检查是否有新版本
|
||||
latest_tag_name = ""
|
||||
latest_tag_name = ''
|
||||
for rls in rls_list:
|
||||
if latest_tag_name == "":
|
||||
if latest_tag_name == '':
|
||||
latest_tag_name = rls['tag_name']
|
||||
break
|
||||
|
||||
return self.is_newer(latest_tag_name, current_tag)
|
||||
|
||||
|
||||
def is_newer(self, new_tag: str, old_tag: str):
|
||||
"""判断版本是否更新,忽略第四位版本和第一位版本"""
|
||||
if new_tag == old_tag:
|
||||
return False
|
||||
|
||||
new_tag = new_tag.split(".")
|
||||
old_tag = old_tag.split(".")
|
||||
|
||||
new_tag = new_tag.split('.')
|
||||
old_tag = old_tag.split('.')
|
||||
|
||||
# 判断主版本是否相同
|
||||
if new_tag[0] != old_tag[0]:
|
||||
return False
|
||||
@@ -180,29 +183,28 @@ class VersionManager:
|
||||
return True
|
||||
|
||||
# 合成前三段,判断是否相同
|
||||
new_tag = ".".join(new_tag[:3])
|
||||
old_tag = ".".join(old_tag[:3])
|
||||
new_tag = '.'.join(new_tag[:3])
|
||||
old_tag = '.'.join(old_tag[:3])
|
||||
|
||||
return new_tag != old_tag
|
||||
|
||||
|
||||
def compare_version_str(v0: str, v1: str) -> int:
|
||||
"""比较两个版本号"""
|
||||
|
||||
# 删除版本号前的v
|
||||
if v0.startswith("v"):
|
||||
if v0.startswith('v'):
|
||||
v0 = v0[1:]
|
||||
if v1.startswith("v"):
|
||||
if v1.startswith('v'):
|
||||
v1 = v1[1:]
|
||||
|
||||
v0:list = v0.split(".")
|
||||
v1:list = v1.split(".")
|
||||
v0: list = v0.split('.')
|
||||
v1: list = v1.split('.')
|
||||
|
||||
# 如果两个版本号节数不同,把短的后面用0补齐
|
||||
if len(v0) < len(v1):
|
||||
v0.extend(["0"]*(len(v1)-len(v0)))
|
||||
v0.extend(['0'] * (len(v1) - len(v0)))
|
||||
elif len(v0) > len(v1):
|
||||
v1.extend(["0"]*(len(v0)-len(v1)))
|
||||
v1.extend(['0'] * (len(v0) - len(v1)))
|
||||
|
||||
# 从高位向低位比较
|
||||
for i in range(len(v0)):
|
||||
@@ -210,16 +212,16 @@ class VersionManager:
|
||||
return 1
|
||||
elif int(v0[i]) < int(v1[i]):
|
||||
return -1
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
async def show_version_update(
|
||||
self
|
||||
) -> typing.Tuple[str, int]:
|
||||
async def show_version_update(self) -> typing.Tuple[str, int]:
|
||||
try:
|
||||
|
||||
if await self.ap.ver_mgr.is_new_version_available():
|
||||
return "有新版本可用,根据文档更新:https://docs.langbot.app/deploy/update.html", logging.INFO
|
||||
|
||||
return (
|
||||
'有新版本可用,根据文档更新:https://docs.langbot.app/deploy/update.html',
|
||||
logging.INFO,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return f"检查版本更新时出错: {e}", logging.WARNING
|
||||
return f'检查版本更新时出错: {e}', logging.WARNING
|
||||
|
||||
Reference in New Issue
Block a user