diff --git a/core/managers/1.py b/core/managers/1.py new file mode 100644 index 0000000..aa631a7 --- /dev/null +++ b/core/managers/1.py @@ -0,0 +1,32 @@ + +class 真鸭子: + def 叫(self): + print("嘎嘎嘎") + + def 跑(self): + print("鸭子摇摇摆摆跑") + +class 玩具鸭子: + def 叫(self): + print("玩具鸭发出嘎嘎声") + + def 跑(self): + print("玩具鸭轮子咕噜噜跑") + +class 小猫: + def 叫(self): + print("喵喵喵") + def 跑(self): + print("猫咪跑跑") + +def 逗鸭子(鸭子一样的东西): + 鸭子一样的东西.叫() + 鸭子一样的东西.跑() + +逗鸭子(真鸭子()) + +逗鸭子(玩具鸭子()) + +逗鸭子(小猫()) + +鸭子 = 1 \ No newline at end of file diff --git a/core/managers/image_manager.py b/core/managers/image_manager.py index 6305cf3..b757ca2 100644 --- a/core/managers/image_manager.py +++ b/core/managers/image_manager.py @@ -71,15 +71,21 @@ class ImageManager: return None try: - # 设置视口 - await page.set_viewport_size({"width": 650, "height": 100}) + width = 1920 + height = 1080 + await page.set_viewport_size({"width": width, "height": height}) # 加载内容 await page.set_content(html_content) await page.wait_for_selector("body") - # 截图 - screenshot_args = {'full_page': True, 'type': image_type} + + screenshot_args = { + 'full_page': True, + 'type': image_type, + 'omit_background': False, + 'scale': 'css' + } if image_type == 'jpeg': screenshot_args['quality'] = quality diff --git a/models/events/message.py b/models/events/message.py index e84381a..b458f08 100644 --- a/models/events/message.py +++ b/models/events/message.py @@ -72,20 +72,7 @@ class MessageEvent(OneBotEvent): def post_type(self) -> str: return EventType.MESSAGE - @property - def ADMIN(self) -> Permission: - """权限级别常量,用于装饰器参数""" - return MESSAGE_EVENT_ADMIN - @property - def OP(self) -> Permission: - """权限级别常量,用于装饰器参数""" - return MESSAGE_EVENT_OP - - @property - def USER(self) -> Permission: - """权限级别常量,用于装饰器参数""" - return MESSAGE_EVENT_USER async def reply(self, message: Union[str, "MessageSegment", List["MessageSegment"]], auto_escape: bool = False): """ @@ -97,6 +84,12 @@ class MessageEvent(OneBotEvent): raise NotImplementedError("reply method must be implemented by subclasses") +# 在类定义之后添加权限常量作为类变量 +MessageEvent.ADMIN = MESSAGE_EVENT_ADMIN +MessageEvent.OP = MESSAGE_EVENT_OP +MessageEvent.USER = MESSAGE_EVENT_USER + + @dataclass(slots=True) class PrivateMessageEvent(MessageEvent): """ diff --git a/plugins/github_parser.py b/plugins/github_parser.py new file mode 100644 index 0000000..36e6378 --- /dev/null +++ b/plugins/github_parser.py @@ -0,0 +1,228 @@ +# -*- coding: utf-8 -*- +import re +import json +import aiohttp +from typing import Optional, Dict, Any, Union +from cachetools import TTLCache + +from core.utils.logger import logger +from core.managers.command_manager import matcher +from core.managers.image_manager import image_manager +from models import MessageEvent, MessageSegment + +# 插件元数据 +__plugin_meta__ = { + "name": "github_parser", + "description": "自动解析GitHub仓库链接,或通过命令查询仓库信息。", + "usage": "(自动触发)当检测到GitHub仓库链接时,自动发送仓库信息。\n(命令触发)/查仓库 作者/仓库名", +} + +# 常量定义 +GITHUB_NICKNAME = "GitHub仓库信息" + +HEADERS = { + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' +} + +# 全局共享的 ClientSession +_session: Optional[aiohttp.ClientSession] = None + +# 缓存GitHub API响应,避免频繁请求 +api_cache = TTLCache(maxsize=100, ttl=3600) # 100个缓存项,1小时过期 + + +def get_session() -> aiohttp.ClientSession: + """ + 获取或创建全局的aiohttp ClientSession + + Returns: + aiohttp.ClientSession: 客户端会话对象 + """ + global _session + if _session is None or _session.closed: + _session = aiohttp.ClientSession(headers=HEADERS) + return _session + + +async def get_github_repo_info(owner: str, repo: str) -> Optional[Dict[str, Any]]: + """ + 通过GitHub API获取仓库信息 + + Args: + owner (str): 仓库所有者用户名 + repo (str): 仓库名称 + + Returns: + Optional[Dict[str, Any]]: 仓库信息字典,如果失败则返回None + """ + cache_key = f"{owner}/{repo}" + if cache_key in api_cache: + logger.info(f"[github_parser] 使用缓存的仓库信息: {cache_key}") + return api_cache[cache_key] + + api_url = f"https://api.github.com/repos/{owner}/{repo}" + try: + session = get_session() + async with session.get(api_url, timeout=10) as response: + response.raise_for_status() + repo_data = await response.json() + + # 将数据存入缓存 + api_cache[cache_key] = repo_data + logger.info(f"[github_parser] 成功获取仓库信息并缓存: {cache_key}") + return repo_data + + except aiohttp.ClientError as e: + logger.error(f"[github_parser] GitHub API请求失败: {e}") + except json.JSONDecodeError as e: + logger.error(f"[github_parser] 解析GitHub API响应失败: {e}") + except Exception as e: + logger.error(f"[github_parser] 获取仓库信息时发生未知错误: {e}") + + return None + + +async def generate_repo_image(repo_data: Dict[str, Any]) -> Optional[str]: + """ + 使用Jinja2模板渲染仓库信息为图片 + + Args: + repo_data (Dict[str, Any]): 仓库信息字典 + + Returns: + Optional[str]: 生成的图片Base64编码,如果失败则返回None + """ + try: + # 准备模板数据 + template_data = { + "full_name": repo_data.get("full_name", ""), + "description": repo_data.get("description", "暂无描述"), + "owner_avatar": repo_data.get("owner", {}).get("avatar_url", ""), + "stargazers_count": repo_data.get("stargazers_count", 0), + "forks_count": repo_data.get("forks_count", 0), + "open_issues_count": repo_data.get("open_issues_count", 0), + "watchers_count": repo_data.get("watchers_count", 0), + } + + # 渲染模板为图片,使用高质量设置 + base64_image = await image_manager.render_template_to_base64( + template_name="github_repo.html", + data=template_data, + output_name=f"github_{repo_data.get('name', 'repo')}.png", + quality=100, # 使用最高质量 + image_type="png" # PNG格式为无损压缩 + ) + + return base64_image + + except Exception as e: + logger.error(f"[github_parser] 生成仓库信息图片失败: {e}") + return None + + +async def process_github_repo(event: MessageEvent, owner: str, repo: str): + """ + 处理GitHub仓库信息查询,获取信息并回复 + + Args: + event (MessageEvent): 消息事件对象 + owner (str): 仓库所有者用户名 + repo (str): 仓库名称 + """ + try: + # 获取仓库信息 + repo_data = await get_github_repo_info(owner, repo) + if not repo_data: + logger.error(f"[github_parser] 无法获取仓库信息: {owner}/{repo}") + await event.reply("无法获取仓库信息,可能是仓库不存在或网络问题。") + return + + # 生成图片 + image_base64 = await generate_repo_image(repo_data) + if image_base64: + # 发送图片 + await event.reply(MessageSegment.image(image_base64)) + else: + # 如果图片生成失败,发送文本信息 + text_message = ( + f"GitHub 仓库信息\n" + f"--------------------\n" + f"仓库: {repo_data.get('full_name', '')}\n" + f"描述: {repo_data.get('description', '暂无描述')}\n" + f"--------------------\n" + f"数据:\n" + f" 星标: {repo_data.get('stargazers_count', 0)}\n" + f" Fork: {repo_data.get('forks_count', 0)}\n" + f" Issues: {repo_data.get('open_issues_count', 0)}\n" + f" 关注: {repo_data.get('watchers_count', 0)}\n" + ) + await event.reply(text_message) + + except Exception as e: + logger.error(f"[github_parser] 处理仓库信息时发生错误: {e}") + await event.reply("处理仓库信息时发生错误,请稍后再试。") + + +# GitHub仓库链接正则表达式 +GITHUB_URL_PATTERN = re.compile(r"https?://(?:www\.)?github\.com/([\w\-]+)/([\w\-\.]+)(?:/[^\s]*)?") + + +# 注册命令处理器 +@matcher.command("查仓库", "github", "github_repo") +async def handle_github_command(bot, event: MessageEvent): + """ + 处理命令调用:/查仓库 作者/仓库名 + + Args: + bot: 机器人对象 + event (MessageEvent): 消息事件对象 + """ + # 提取命令参数 + command_text = event.raw_message + # 移除命令前缀和命令名 + prefix = command_text.split()[0] if command_text.split() else "" + params = command_text[len(prefix):].strip() + + if not params: + await event.reply("请输入仓库地址,格式:/查仓库 作者/仓库名") + return + + # 解析参数格式 + if "/" in params: + owner, repo = params.split("/", 1) + # 移除可能的.git后缀 + repo = repo.replace(".git", "") + await process_github_repo(event, owner, repo) + else: + await event.reply("参数格式错误,请输入:/查仓库 作者/仓库名") + + +# 注册消息处理器 +@matcher.on_message() +async def handle_github_link(event: MessageEvent): + """ + 处理消息,检测GitHub仓库链接并自动解析 + + Args: + event (MessageEvent): 消息事件对象 + """ + # 忽略机器人自己发送的消息,防止无限循环 + if hasattr(event, "user_id") and hasattr(event, "self_id") and event.user_id == event.self_id: + return + + # 提取消息文本 + message_text = "" + for segment in event.message: + if segment.type == "text": + message_text += segment.data.get("text", "") + + # 查找GitHub仓库链接 + match = GITHUB_URL_PATTERN.search(message_text) + if match: + owner = match.group(1) + repo = match.group(2) + # 移除可能的.git后缀 + repo = repo.replace(".git", "") + + logger.info(f"[github_parser] 检测到GitHub仓库链接: {owner}/{repo}") + await process_github_repo(event, owner, repo) diff --git a/templates/github_repo.html b/templates/github_repo.html new file mode 100644 index 0000000..328da70 --- /dev/null +++ b/templates/github_repo.html @@ -0,0 +1,200 @@ + + + + + + GitHub仓库信息 + + + +
+
+
+
GitHub Repository
+

{{ full_name }}

+

{{ description }}

+
+ {% if owner_avatar %} + Owner Avatar + {% endif %} +
+
+
+ {{ stargazers_count }} +
Stars
+
+
+ {{ forks_count }} +
Forks
+
+
+ {{ open_issues_count }} +
Issues
+
+
+ {{ watchers_count }} +
Watchers
+
+
+ +
+ + \ No newline at end of file diff --git a/templates/help.html b/templates/help.html index 1388304..e3ed19a 100644 --- a/templates/help.html +++ b/templates/help.html @@ -32,19 +32,22 @@ /* 居中布局 */ display: flex; justify-content: center; - padding: 40px; - min-height: auto; + padding: 0; + min-height: 100vh; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } /* 窗口容器 */ .window { - width: 800px; /* 稍微收窄一点,更像手机/卡片比例 */ + width: 100%; + height: 100vh; background: var(--window-bg); backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); - border-radius: 20px; - border: 1px solid var(--border-color); - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6); + border-radius: 0; + border: none; + box-shadow: none; overflow: hidden; display: flex; flex-direction: column; @@ -52,7 +55,7 @@ /* 顶部标题栏 */ .header { - padding: 24px 32px; + padding: 32px 40px; border-bottom: 1px solid var(--border-color); background: rgba(255, 255, 255, 0.02); display: flex; @@ -67,48 +70,50 @@ .green { background: #10b981; } .title { - font-size: 14px; + font-size: 18px; font-weight: 700; - letter-spacing: 1px; + letter-spacing: 1.5px; color: var(--text-desc); text-transform: uppercase; } /* 内容区域 */ .content { - padding: 32px; + padding: 40px; display: flex; flex-direction: column; - gap: 24px; /* 卡片之间的间距 */ + gap: 32px; /* 卡片之间的间距 */ } .page-title { margin-bottom: 10px; } .page-title h1 { - font-size: 32px; + font-size: 48px; background: linear-gradient(to right, #fff, #94a3b8); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .page-title p { color: var(--text-desc); - font-size: 14px; - margin-top: 8px; + font-size: 20px; + margin-top: 12px; } - /* 插件卡片 - 改为单列宽卡片 */ + /* 插件卡片 - 改为三列布局 */ .plugin-card { background: var(--card-bg); border: 1px solid var(--border-color); - border-radius: 12px; - padding: 20px; + border-radius: 14px; + padding: 24px; display: flex; flex-direction: column; /* 垂直排列 */ gap: 16px; transition: transform 0.2s; position: relative; overflow: hidden; + flex: 1; + min-width: 320px; } /* 左侧装饰线 */ @@ -129,41 +134,41 @@ } .plugin-name { - font-size: 18px; + font-size: 24px; font-weight: 700; color: #fff; display: flex; align-items: center; - gap: 10px; + gap: 12px; } /* 装饰性Tag */ .plugin-tag { - font-size: 10px; + font-size: 14px; background: rgba(99, 102, 241, 0.2); color: #818cf8; - padding: 2px 6px; - border-radius: 4px; + padding: 4px 8px; + border-radius: 6px; font-weight: 600; text-transform: uppercase; } .plugin-desc { - font-size: 13px; + font-size: 18px; color: var(--text-desc); - line-height: 1.5; - margin-top: 4px; + line-height: 1.6; + margin-top: 8px; } /* 指令代码块 - 核心修改区域 */ .cmd-block { background: var(--cmd-bg); - border-radius: 8px; - padding: 16px; + border-radius: 10px; + padding: 20px; border: 1px solid var(--border-color); font-family: 'JetBrains Mono', monospace; - font-size: 13px; - line-height: 1.6; + font-size: 16px; + line-height: 1.8; color: var(--text-cmd); /* 处理长文本的关键 CSS */ @@ -179,15 +184,24 @@ color: #64748b; user-select: none; } + + /* 插件网格布局 */ + .plugin-grid { + display: flex; + flex-wrap: wrap; + gap: 24px; + justify-content: flex-start; + width: 100%; + } /* 页脚 */ .footer { - padding: 20px 32px; + padding: 24px 40px; border-top: 1px solid var(--border-color); color: rgba(255, 255, 255, 0.2); - font-size: 12px; + font-size: 16px; text-align: right; - background: rgba(0,0,0,0.1); + letter-spacing: 1px; } @@ -210,7 +224,8 @@

Dashboard & Command List · {{ plugins|length }} Modules Loaded

- + +
{% for plugin in plugins %}
@@ -227,10 +242,11 @@
{{ plugin.usage }}
{% endfor %} +