Skip to content

Commit bd6d11e

Browse files
authored
Merge pull request #91 from ljzloser/master
feat: 添加日志查看器、设置管理器和插件开发技能
2 parents ac8e66c + af60991 commit bd6d11e

32 files changed

+3518
-1072
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,5 @@ old/
169169
src/plugins/*/*.json
170170
src/plugins/*/*.db
171171
.vscode/
172-
data/*
172+
data/*
173+
.iflow/

build.bat

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ xcopy /e /y /i "%SP%\debugpy" "%DEST%\_internal\debugpy" >nul
3737
xcopy /e /y /i "%SP%\msgspec" "%DEST%\_internal\msgspec" >nul 2>nul
3838
xcopy /e /y /i "%SP%\setuptools" "%DEST%\_internal\setuptools" >nul 2>nul
3939

40+
echo [5/5] Copy plugin-dev-tutorial.md
41+
copy /y "plugin-dev-tutorial.md" "%DEST%\" >nul
42+
4043
echo.
4144
echo Done! Both in: %OUT%\
4245
pause

skills/plugin-dev/SKILL.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
name: plugin-dev
3+
description: Meta-Minesweeper 插件开发助手,支持创建、调试和优化插件
4+
---
5+
6+
# Meta-Minesweeper 插件开发助手
7+
8+
你是一个专业的 Meta-Minesweeper 插件开发助手。你熟悉插件系统的架构、API 和最佳实践,能够帮助用户创建、调试和优化插件。
9+
10+
## 核心能力
11+
12+
1. **插件创建** - 支持单文件和包形式两种插件结构
13+
2. **渐进式指导** - 根据用户需求逐步披露相关知识点
14+
3. **代码生成** - 生成符合规范的插件模板代码
15+
4. **问题诊断** - 帮助排查插件加载、运行时的常见问题
16+
17+
## 知识体系
18+
19+
### 架构概览
20+
详见 [references/architecture.md](./references/architecture.md)
21+
22+
### 创建插件
23+
详见 [references/creating.md](./references/creating.md)
24+
25+
### 渐进式知识披露
26+
- [references/level-1-basics.md](./references/level-1-basics.md) - 基础概念
27+
- [references/level-2-events.md](./references/level-2-events.md) - 事件系统
28+
- [references/level-3-threading.md](./references/level-3-threading.md) - 跨线程 GUI
29+
- [references/level-4-control.md](./references/level-4-control.md) - 控制授权
30+
- [references/level-5-service.md](./references/level-5-service.md) - 服务系统
31+
- [references/level-6-config.md](./references/level-6-config.md) - 配置系统
32+
33+
### 问题诊断
34+
详见 [references/troubleshooting.md](./references/troubleshooting.md)
35+
36+
### 最佳实践
37+
详见 [references/best-practices.md](./references/best-practices.md)
38+
39+
## 快速开始
40+
41+
### 第一步:检测环境
42+
43+
**必须首先执行**,检测运行环境并获取可用的类型:
44+
45+
```bash
46+
python scripts/create_plugin.py discover
47+
```
48+
49+
返回 JSON:
50+
```json
51+
{
52+
"environment": "dev", // "dev" 或 "frozen"
53+
"install_dir": "...", // 安装目录
54+
"plugins_dir": "...", // 插件目录
55+
"shared_types_dir": "...", // shared_types 目录
56+
"events": [...], // 可用事件列表
57+
"commands": [...] // 可用命令列表
58+
}
59+
```
60+
61+
根据 `environment` 判断:
62+
- `"dev"` - 开发模式,插件放在 `src/plugins/`
63+
- `"frozen"` - 打包模式,插件放在 `plugins/`
64+
65+
### 第二步:收集信息
66+
67+
使用 `ask_user_question` 工具收集插件信息:
68+
69+
1. 插件形式(单文件/包形式)
70+
2. 插件名称、描述、作者
71+
3. 窗口模式(TAB/DETACHED/CLOSED)
72+
4. 订阅的事件(从 discover 返回的 events 选择)
73+
5. 控制权限(从 discover 返回的 commands 选择)
74+
6. 是否需要配置系统、服务接口
75+
76+
### 第三步:创建插件
77+
78+
调用脚本创建插件:
79+
80+
```bash
81+
python scripts/create_plugin.py create \
82+
--name my_plugin \
83+
--description "描述" \
84+
--window-mode TAB \
85+
--events VideoSaveEvent \
86+
--commands NewGameCommand
87+
```
88+
89+
脚本输出创建结果(JSON 格式)
90+
91+
## 插件模板
92+
93+
模板文件位于 `assets/templates/` 目录:
94+
- `minimal.py` - 最小可行插件
95+
- `with-gui.py` - 带 GUI 的插件
96+
- `with-config.py` - 带配置的插件
97+
- `with-control.py` - 带控制权限的插件
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
"""
2+
{plugin_name} - {description}
3+
4+
一个最小可行插件示例。
5+
"""
6+
from __future__ import annotations
7+
8+
from plugin_sdk import BasePlugin, PluginInfo
9+
from shared_types.events import VideoSaveEvent
10+
11+
12+
class {PluginName}(BasePlugin):
13+
"""{description}"""
14+
15+
@classmethod
16+
def plugin_info(cls) -> PluginInfo:
17+
return PluginInfo(
18+
name="{plugin_name}",
19+
version="1.0.0",
20+
description="{description}",
21+
window_mode=WindowMode.CLOSED, # 无界面
22+
)
23+
24+
def _setup_subscriptions(self) -> None:
25+
self.subscribe(VideoSaveEvent, self._on_video_save)
26+
27+
def on_initialized(self) -> None:
28+
self.logger.info("{PluginName} 已初始化")
29+
30+
def _on_video_save(self, event: VideoSaveEvent):
31+
self.logger.info(f"游戏结束: 用时={event.rtime}s, 3BV={event.bbbv}")
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""
2+
{plugin_name} - {description}
3+
4+
带配置系统的插件示例。
5+
"""
6+
from __future__ import annotations
7+
8+
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel
9+
from PyQt5.QtCore import pyqtSignal
10+
11+
from plugin_sdk import (
12+
BasePlugin, PluginInfo, make_plugin_icon, WindowMode,
13+
OtherInfoBase, BoolConfig, IntConfig,
14+
)
15+
from shared_types.events import VideoSaveEvent
16+
17+
18+
class {PluginName}Config(OtherInfoBase):
19+
"""插件配置"""
20+
21+
enable_logging = BoolConfig(
22+
default=True,
23+
label="启用日志",
24+
description="是否记录游戏数据到日志",
25+
)
26+
27+
max_records = IntConfig(
28+
default=100,
29+
label="最大记录数",
30+
min_value=10,
31+
max_value=1000,
32+
)
33+
34+
35+
class {PluginName}Widget(QWidget):
36+
"""插件 UI"""
37+
38+
_update_signal = pyqtSignal(str)
39+
40+
def __init__(self, parent=None):
41+
super().__init__(parent)
42+
layout = QVBoxLayout(self)
43+
44+
self._label = QLabel("等待游戏数据...")
45+
layout.addWidget(self._label)
46+
47+
self._update_signal.connect(self._on_update)
48+
49+
def _on_update(self, text: str):
50+
self._label.setText(text)
51+
52+
53+
class {PluginName}(BasePlugin):
54+
"""{description}"""
55+
56+
@classmethod
57+
def plugin_info(cls) -> PluginInfo:
58+
return PluginInfo(
59+
name="{plugin_name}",
60+
version="1.0.0",
61+
description="{description}",
62+
window_mode=WindowMode.TAB,
63+
icon=make_plugin_icon("#2196F3", "C"),
64+
other_info={PluginName}Config, # 绑定配置类
65+
)
66+
67+
def _setup_subscriptions(self) -> None:
68+
self.subscribe(VideoSaveEvent, self._on_video_save)
69+
70+
def _create_widget(self) -> QWidget | None:
71+
self._widget = {PluginName}Widget()
72+
return self._widget
73+
74+
def on_initialized(self) -> None:
75+
self.logger.info("{PluginName} 已初始化")
76+
77+
# 连接配置变化信号
78+
self.config_changed.connect(self._on_config_changed)
79+
80+
# 读取配置
81+
if self.other_info:
82+
self.logger.info(f"配置: enable_logging={self.other_info.enable_logging}")
83+
84+
def _on_config_changed(self, name: str, value):
85+
self.logger.info(f"配置变化: {name} = {value}")
86+
87+
def _on_video_save(self, event: VideoSaveEvent):
88+
if self.other_info and self.other_info.enable_logging:
89+
self.logger.info(f"游戏数据: 用时={event.rtime}s")
90+
self._widget._update_signal.emit(f"用时: {event.rtime:.2f}s")
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
"""
2+
{plugin_name} - {description}
3+
4+
带控制权限的插件示例。
5+
"""
6+
from __future__ import annotations
7+
8+
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QLabel, QPushButton
9+
from PyQt5.QtCore import pyqtSignal
10+
11+
from plugin_sdk import BasePlugin, PluginInfo, make_plugin_icon, WindowMode
12+
from shared_types.events import VideoSaveEvent
13+
from shared_types.commands import NewGameCommand
14+
15+
16+
class {PluginName}Widget(QWidget):
17+
"""插件 UI"""
18+
19+
_update_signal = pyqtSignal(str)
20+
_auth_signal = pyqtSignal(bool)
21+
22+
def __init__(self, parent=None):
23+
super().__init__(parent)
24+
layout = QVBoxLayout(self)
25+
26+
self._status = QLabel("控制权限: 未知")
27+
layout.addWidget(self._status)
28+
29+
self._btn_new_game = QPushButton("开始新游戏")
30+
self._btn_new_game.clicked.connect(self._on_new_game_click)
31+
layout.addWidget(self._btn_new_game)
32+
33+
self._update_signal.connect(self._on_update)
34+
self._auth_signal.connect(self._on_auth)
35+
36+
def _on_update(self, text: str):
37+
self._status.setText(text)
38+
39+
def _on_auth(self, granted: bool):
40+
self._status.setText(f"控制权限: {'已授权' if granted else '未授权'}")
41+
self._btn_new_game.setEnabled(granted)
42+
43+
def _on_new_game_click(self):
44+
# 由插件类处理
45+
pass
46+
47+
48+
class {PluginName}(BasePlugin):
49+
"""{description}"""
50+
51+
@classmethod
52+
def plugin_info(cls) -> PluginInfo:
53+
return PluginInfo(
54+
name="{plugin_name}",
55+
version="1.0.0",
56+
description="{description}",
57+
window_mode=WindowMode.TAB,
58+
icon=make_plugin_icon("#FF5722", "G"),
59+
required_controls=[NewGameCommand], # 声明需要的控制权限
60+
)
61+
62+
def _setup_subscriptions(self) -> None:
63+
self.subscribe(VideoSaveEvent, self._on_video_save)
64+
65+
def _create_widget(self) -> QWidget | None:
66+
self._widget = {PluginName}Widget()
67+
# 连接按钮点击
68+
self._widget._btn_new_game.clicked.connect(self._start_new_game)
69+
return self._widget
70+
71+
def on_initialized(self) -> None:
72+
self.logger.info("{PluginName} 已初始化")
73+
74+
# 检查控制权限
75+
has_auth = self.has_control_auth(NewGameCommand)
76+
self._widget._auth_signal.emit(has_auth)
77+
78+
def on_control_auth_changed(self, cmd_type, granted: bool):
79+
"""控制权限变更回调"""
80+
if cmd_type == NewGameCommand:
81+
self.logger.info(f"权限变更: {granted}")
82+
self._widget._auth_signal.emit(granted)
83+
84+
def _start_new_game(self):
85+
"""开始新游戏"""
86+
if self.has_control_auth(NewGameCommand):
87+
self.send_command(NewGameCommand(rows=16, cols=30, mines=99))
88+
self.logger.info("已发送 NewGameCommand")
89+
else:
90+
self.logger.warning("没有控制权限")
91+
92+
def _on_video_save(self, event: VideoSaveEvent):
93+
self._widget._update_signal.emit(f"用时: {event.rtime:.2f}s")

0 commit comments

Comments
 (0)