refactor(managers): 重构单例管理器实现并优化代码结构
feat(ws_pool): 新增 WebSocket 连接池实现 perf(json): 使用 orjson 替代标准 json 库提升性能 style: 清理未使用的导入和冗余代码 docs: 更新架构文档和开发规范 test: 添加 WebSocket 连接池测试用例 fix(plugins): 修复自动审批插件 API 调用参数格式
This commit is contained in:
@@ -1,13 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import json
|
||||
import orjson
|
||||
import abc
|
||||
import aiohttp
|
||||
from typing import Optional, Dict, Any, List, Union
|
||||
from cachetools import TTLCache
|
||||
|
||||
from core.utils.logger import logger
|
||||
from models import MessageEvent, MessageSegment
|
||||
from models import MessageEvent
|
||||
|
||||
|
||||
class BaseParser(metaclass=abc.ABCMeta):
|
||||
@@ -38,6 +37,7 @@ class BaseParser(metaclass=abc.ABCMeta):
|
||||
"""
|
||||
self.name = "Base Parser"
|
||||
self.url_pattern = re.compile(r"https?://[^\s]+")
|
||||
self.processed_messages = {} # 用于存储已处理的消息ID,防止重复处理
|
||||
|
||||
@classmethod
|
||||
def get_session(cls) -> aiohttp.ClientSession:
|
||||
@@ -105,12 +105,12 @@ class BaseParser(metaclass=abc.ABCMeta):
|
||||
if segment.type == "json":
|
||||
logger.info(f"[{self.name}] 检测到JSON CQ码: {segment.data}")
|
||||
try:
|
||||
json_data = json.loads(segment.data.get("data", "{}"))
|
||||
json_data = orjson.loads(segment.data.get("data", "{}"))
|
||||
short_url = json_data.get("meta", {}).get("detail_1", {}).get("qqdocurl")
|
||||
if short_url:
|
||||
logger.success(f"[{self.name}] 成功从JSON卡片中提取到链接: {short_url}")
|
||||
return short_url
|
||||
except (json.JSONDecodeError, KeyError) as e:
|
||||
except (orjson.JSONDecodeError, KeyError) as e:
|
||||
logger.error(f"[{self.name}] 解析JSON失败: {e}")
|
||||
continue
|
||||
return None
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import json
|
||||
import orjson
|
||||
import aiohttp
|
||||
from typing import Optional, Dict, Any, List
|
||||
from typing import Optional, Dict, Any, List, Union
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from core.utils.logger import logger
|
||||
from models import MessageEvent, MessageSegment
|
||||
from ..base import BaseParser
|
||||
from ..utils import format_duration, clean_url
|
||||
from ..utils import format_duration
|
||||
|
||||
from cachetools import TTLCache
|
||||
|
||||
@@ -42,7 +42,7 @@ class BiliParser(BaseParser):
|
||||
clean_url = clean_url.split('#/')[0]
|
||||
|
||||
session = self.get_session()
|
||||
async with session.get(clean_url, headers=self.HEADERS, timeout=5) as response:
|
||||
async with session.get(clean_url, headers=self.HEADERS, timeout=aiohttp.ClientTimeout(total=5)) as response:
|
||||
response.raise_for_status()
|
||||
text = await response.text()
|
||||
soup = BeautifulSoup(text, 'html.parser')
|
||||
@@ -93,14 +93,14 @@ class BiliParser(BaseParser):
|
||||
json_str = json_str.strip().rstrip(';')
|
||||
|
||||
try:
|
||||
data = json.loads(json_str)
|
||||
except json.JSONDecodeError:
|
||||
data = orjson.loads(json_str)
|
||||
except ValueError:
|
||||
# 如果直接解析失败,尝试清理JSON字符串
|
||||
# 移除可能的注释或无效字符
|
||||
cleaned_json = re.sub(r',\s*[}]', '}', json_str) # 移除末尾多余的逗号
|
||||
cleaned_json = re.sub(r'/\*.*?\*/', '', cleaned_json) # 移除注释
|
||||
cleaned_json = re.sub(r'//.*', '', cleaned_json) # 移除行注释
|
||||
data = json.loads(cleaned_json)
|
||||
data = orjson.loads(cleaned_json)
|
||||
|
||||
video_data = data.get('videoData', {})
|
||||
up_data = data.get('upData', {})
|
||||
@@ -134,7 +134,7 @@ class BiliParser(BaseParser):
|
||||
"followers": up_data.get('fans', 0),
|
||||
}
|
||||
|
||||
except (aiohttp.ClientError, KeyError, AttributeError, json.JSONDecodeError) as e:
|
||||
except (aiohttp.ClientError, KeyError, AttributeError, ValueError) as e:
|
||||
logger.error(f"[{self.name}] 解析视频信息失败: {e}")
|
||||
logger.debug(f"失败的URL: {url}")
|
||||
except Exception as e:
|
||||
@@ -155,7 +155,7 @@ class BiliParser(BaseParser):
|
||||
"""
|
||||
try:
|
||||
session = self.get_session()
|
||||
async with session.head(short_url, headers=self.HEADERS, allow_redirects=False, timeout=5) as response:
|
||||
async with session.head(short_url, headers=self.HEADERS, allow_redirects=False, timeout=aiohttp.ClientTimeout(total=5)) as response:
|
||||
if response.status == 302:
|
||||
return response.headers.get('Location')
|
||||
except Exception as e:
|
||||
@@ -175,13 +175,13 @@ class BiliParser(BaseParser):
|
||||
api_url = f"https://api.mir6.com/api/bzjiexi?url={video_url}&type=json"
|
||||
try:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(api_url, headers=self.HEADERS, timeout=10) as response:
|
||||
async with session.get(api_url, headers=self.HEADERS, timeout=aiohttp.ClientTimeout(total=10)) as response:
|
||||
response.raise_for_status()
|
||||
# 使用 content_type=None 来忽略 Content-Type 检查
|
||||
data = await response.json(content_type=None)
|
||||
if data.get("code") == 200 and data.get("data"):
|
||||
return data["data"][0].get("video_url")
|
||||
except (aiohttp.ClientError, json.JSONDecodeError, KeyError, IndexError) as e:
|
||||
except (aiohttp.ClientError, ValueError, KeyError, IndexError) as e:
|
||||
logger.error(f"[{self.name}] 调用第三方API解析视频失败: {e}")
|
||||
return None
|
||||
|
||||
@@ -197,6 +197,7 @@ class BiliParser(BaseParser):
|
||||
List[Any]: 消息段列表
|
||||
"""
|
||||
# 检查视频时长
|
||||
video_message: Union[str, MessageSegment]
|
||||
if data['duration'] > 1200: # 20分钟 = 1200秒
|
||||
video_message = "视频时长超过20分钟,不进行解析。"
|
||||
else:
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import json
|
||||
import aiohttp
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
@@ -40,7 +39,7 @@ class DouyinParser(BaseParser):
|
||||
api_url = f"http://api.xhus.cn/api/douyin?url={url}"
|
||||
|
||||
session = self.get_session()
|
||||
async with session.get(api_url, headers=self.HEADERS, timeout=10) as response:
|
||||
async with session.get(api_url, headers=self.HEADERS, timeout=aiohttp.ClientTimeout(total=10)) as response:
|
||||
if response.status != 200:
|
||||
logger.error(f"[{self.name}] API请求失败,状态码: {response.status}")
|
||||
return None
|
||||
@@ -75,7 +74,7 @@ class DouyinParser(BaseParser):
|
||||
"music": data.get("music", {}),
|
||||
}
|
||||
|
||||
except (aiohttp.ClientError, KeyError, AttributeError, json.JSONDecodeError) as e:
|
||||
except (aiohttp.ClientError, KeyError, AttributeError, ValueError) as e:
|
||||
logger.error(f"[{self.name}] 解析抖音视频信息失败: {e}")
|
||||
logger.debug(f"失败的URL: {url}")
|
||||
except Exception as e:
|
||||
@@ -110,7 +109,7 @@ class DouyinParser(BaseParser):
|
||||
'Referer': 'https://www.douyin.com/'
|
||||
})
|
||||
|
||||
async with session.get(short_url, headers=mobile_headers, allow_redirects=True, timeout=10) as response:
|
||||
async with session.get(short_url, headers=mobile_headers, allow_redirects=True, timeout=aiohttp.ClientTimeout(total=10)) as response:
|
||||
redirected_url = str(response.url)
|
||||
|
||||
# 检查重定向后的URL是否包含视频ID
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import json
|
||||
import aiohttp
|
||||
from typing import Optional, Dict, Any, List
|
||||
from cachetools import TTLCache
|
||||
@@ -60,7 +59,7 @@ class GitHubParser(BaseParser):
|
||||
"""
|
||||
try:
|
||||
session = self.get_session()
|
||||
async with session.head(short_url, headers=self.HEADERS, allow_redirects=False, timeout=5) as response:
|
||||
async with session.head(short_url, headers=self.HEADERS, allow_redirects=False, timeout=aiohttp.ClientTimeout(total=5)) as response:
|
||||
if response.status == 302:
|
||||
return response.headers.get('Location')
|
||||
except Exception as e:
|
||||
@@ -86,7 +85,7 @@ class GitHubParser(BaseParser):
|
||||
api_url = f"https://api.github.com/repos/{owner}/{repo}"
|
||||
try:
|
||||
session = self.get_session()
|
||||
async with session.get(api_url, timeout=10) as response:
|
||||
async with session.get(api_url, timeout=aiohttp.ClientTimeout(total=10)) as response:
|
||||
response.raise_for_status()
|
||||
repo_data = await response.json()
|
||||
|
||||
@@ -97,7 +96,7 @@ class GitHubParser(BaseParser):
|
||||
|
||||
except aiohttp.ClientError as e:
|
||||
logger.error(f"[{self.name}] GitHub API请求失败: {e}")
|
||||
except json.JSONDecodeError as e:
|
||||
except ValueError as e:
|
||||
logger.error(f"[{self.name}] 解析GitHub API响应失败: {e}")
|
||||
except Exception as e:
|
||||
logger.error(f"[{self.name}] 获取仓库信息时发生未知错误: {e}")
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import re
|
||||
import json
|
||||
from typing import Optional, Dict, Any, Union, List
|
||||
from typing import Dict, Any, List
|
||||
|
||||
from core.utils.logger import logger
|
||||
from models import MessageEvent, MessageSegment
|
||||
from models import MessageEvent
|
||||
|
||||
|
||||
def format_duration(seconds: int) -> str:
|
||||
|
||||
Reference in New Issue
Block a user