Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
4f62641
Add files via upload
chen-zi-qi123 May 9, 2026
97a626c
Delete qqlinker_framework.zip
chen-zi-qi123 May 9, 2026
040b455
添加 qqlinker_framework 插件
chen-zi-qi123 May 9, 2026
a6797b2
更新 qqlinker_framework 插件,修复了一些已知问题并完善了注释
chen-zi-qi123 May 10, 2026
2af7c04
修复了一些已知问题
chen-zi-qi123 May 10, 2026
5d8a2ad
修复一些已知问题
chen-zi-qi123 May 10, 2026
f9b25a9
修复一些已知问题
chen-zi-qi123 May 10, 2026
77789dd
修复一些已知问题
chen-zi-qi123 May 10, 2026
4e0731a
修复一些已知问题
chen-zi-qi123 May 10, 2026
4f3574d
修复一些已知问题
chen-zi-qi123 May 10, 2026
117051e
补充了一些说明文档
chen-zi-qi123 May 10, 2026
5c72438
修复一些已知问题
chen-zi-qi123 May 10, 2026
cbcc014
修复一些已知问题并添加了新的接口
chen-zi-qi123 May 10, 2026
9f29581
修复一些已知问题
chen-zi-qi123 May 10, 2026
6a98431
修复了一些已知错误: 优化了数据存储结构,修复了配置异常文件重置的错误,优化了配置补全功能等
chen-zi-qi123 May 11, 2026
38c16d3
修复一些已知问题
chen-zi-qi123 May 11, 2026
fefbda7
添加了新的接口,修复了.list命令返回结果空的问题,添加了tps估算功能,修复了一些已知问题
chen-zi-qi123 May 11, 2026
78d155a
修复一些已知问题
chen-zi-qi123 May 11, 2026
02f2e26
修复了一些已知问题,添加了新的模块与接口。
chen-zi-qi123 May 11, 2026
a052dc7
修复一些已知问题
chen-zi-qi123 May 12, 2026
686b478
修复一些已知问题
chen-zi-qi123 May 12, 2026
752f07f
修复一些已知问题
chen-zi-qi123 May 12, 2026
3dcf1b5
修复一些已知问题,添加了新的模块
chen-zi-qi123 May 12, 2026
e7719de
修复一些已知问题
chen-zi-qi123 May 12, 2026
88f8452
修复一些已知问题
chen-zi-qi123 May 12, 2026
51b035b
添加了新的模块,并修复了一些已知问题
chen-zi-qi123 May 12, 2026
d06719f
修复一些已知问题
chen-zi-qi123 May 12, 2026
542fd4a
修复一些已知问题
chen-zi-qi123 May 12, 2026
2a19dc8
修复一些已知问题
chen-zi-qi123 May 12, 2026
a4034f2
添加了调试引擎和对应接口
chen-zi-qi123 May 12, 2026
6bed577
修复一些已知问题
chen-zi-qi123 May 12, 2026
cf0eee7
修复一些已知问题
chen-zi-qi123 May 12, 2026
b434da9
修复一些已知问题
chen-zi-qi123 May 12, 2026
a1d4b46
修复去重误判、Redis 降级失效和权限无提示等问题
chen-zi-qi123 May 12, 2026
e1f94c4
修复 1:去重竞态条件
chen-zi-qi123 May 13, 2026
199aa2a
修复一些已知问题
chen-zi-qi123 May 14, 2026
25892f4
修复一些已知问题
chen-zi-qi123 May 14, 2026
d20db40
修复了一些已知问题
chen-zi-qi123 May 18, 2026
a0d9226
修复一些已知问题
chen-zi-qi123 May 18, 2026
1ab5cdd
修复一些已知问题
chen-zi-qi123 May 18, 2026
12e742b
修复一些已知问题
chen-zi-qi123 May 18, 2026
77d5a70
修复一些已知问题
chen-zi-qi123 May 18, 2026
c3a137c
修复一些已知问题
chen-zi-qi123 May 18, 2026
481481e
修复一些已知问题
chen-zi-qi123 May 18, 2026
62bd03d
修复一些已知问题
chen-zi-qi123 May 18, 2026
830ab62
修复一些已知问题
chen-zi-qi123 May 20, 2026
b15776a
修复一些已知问题
chen-zi-qi123 May 20, 2026
1d67141
Merge branch 'ToolDelta-Basic:main' into main
chen-zi-qi123 May 26, 2026
c5db8f8
修复一些已知问题
chen-zi-qi123 May 26, 2026
5750833
修复一些已知问题
chen-zi-qi123 May 26, 2026
6567242
修复一些已知问题
chen-zi-qi123 May 31, 2026
ee159b9
修复一些已知问题
chen-zi-qi123 Jun 1, 2026
4a6eeab
修复一些已知问题
chen-zi-qi123 Jun 1, 2026
db3db7b
修复一些已知问题
chen-zi-qi123 Jun 1, 2026
e4b268c
修复一些已知问题
chen-zi-qi123 Jun 1, 2026
ece2bf0
修复一些已知问题
chen-zi-qi123 Jun 1, 2026
4040930
修复一些已知问题
chen-zi-qi123 Jun 1, 2026
8817457
新增了配置节修复模块作为内置模块
chen-zi-qi123 Jun 2, 2026
d73d410
修复一些已知问题
chen-zi-qi123 Jun 2, 2026
fa5ef7c
修复一些已知问题
chen-zi-qi123 Jun 3, 2026
05704fe
修复一些已知问题
chen-zi-qi123 Jun 3, 2026
8c9dc67
修复了[.exec]命令无效的问题
chen-zi-qi123 Jun 3, 2026
70f362b
修复一些已知问题
chen-zi-qi123 Jun 3, 2026
0e40e20
修复一些已知问题
chen-zi-qi123 Jun 7, 2026
ebd0c6a
修复了一些已知问题
chen-zi-qi123 Jun 9, 2026
3e79b5d
修复一些已知问题
chen-zi-qi123 Jun 11, 2026
5fd4b04
修复一些卫生问题
chen-zi-qi123 Jun 12, 2026
4da3db3
修复一些已知问题
chen-zi-qi123 Jun 14, 2026
d9a7d39
修复一些已知的问题
chen-zi-qi123 Jun 14, 2026
ec9dac2
修复一些已知问题
chen-zi-qi123 Jun 15, 2026
b52b431
架构优化
chen-zi-qi123 Jun 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
417 changes: 417 additions & 0 deletions qqlinker_framework/__init__.py

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions qqlinker_framework/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""QQLinker 框架入口 v1.6.0 — 纯信道启动。"""
import asyncio
import logging
import os
import sys

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
)


async def main():
from qqlinker_framework.libraries.channel_host import ChannelHost, BootstrapError

data_path = os.environ.get("QQLINKER_DATA", ".")
host = ChannelHost(data_path=data_path)

try:
await host.start()
except BootstrapError as e:
logging.getLogger(__name__).critical("启动失败: %s", e)
sys.exit(1)

# 运行循环
try:
logging.getLogger(__name__).info("框架运行中... (Ctrl+C 停止)")
while True:
await asyncio.sleep(1)
except (KeyboardInterrupt, asyncio.CancelledError):
pass
finally:
await host.stop()
logging.getLogger(__name__).info("框架已停止")


if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
pass
1 change: 1 addition & 0 deletions qqlinker_framework/adapters/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# adapters/__init__.py
197 changes: 197 additions & 0 deletions qqlinker_framework/adapters/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
# adapters/base.py
"""平台适配器抽象接口"""
from abc import ABC, abstractmethod
from typing import Callable, List, Optional, Any, Dict


class IFrameworkAdapter(ABC):
"""平台适配器抽象基类,定义所有需要实现的方法。"""

@abstractmethod
def send_game_command(self, cmd: str) -> None: # noqa: PYL-R0201
"""发送游戏指令。"""

@abstractmethod
def send_game_message(self, target: str, text: str) -> None: # noqa: PYL-R0201
"""向游戏内目标发送消息。"""

@abstractmethod
def get_online_players(self) -> List[str]: # noqa: PYL-R0201
"""获取当前在线玩家列表(纯名字列表)。"""

@abstractmethod
def send_group_msg(self, group_id: int, message: str) -> bool: # noqa: PYL-R0201
"""发送群聊消息。"""

@abstractmethod
def send_private_msg(self, user_id: int, message: str) -> bool: # noqa: PYL-R0201
"""发送私聊消息。"""

@abstractmethod
def listen_game_chat( # noqa: PYL-R0201
self, handler: Callable[[str, str], None]
) -> None:
"""注册游戏聊天监听。"""

@abstractmethod
def listen_group_message( # noqa: PYL-R0201
self, handler: Callable[[Dict[str, Any]], None]
) -> None:
"""注册群消息监听。"""

@abstractmethod
def listen_player_join( # noqa: PYL-R0201
self, handler: Callable[[str], None]
) -> None:
"""注册玩家加入事件监听。"""

@abstractmethod
def listen_player_leave( # noqa: PYL-R0201
self, handler: Callable[[str], None]
) -> None:
"""注册玩家离开事件监听。"""

@abstractmethod
def register_console_command( # noqa: PYL-R0201
self,
triggers: List[str],
hint: str,
usage: str,
func: Callable,
) -> None:
"""注册控制台命令。"""

@abstractmethod
def get_plugin_api(self, name: str) -> Optional[Any]: # noqa: PYL-R0201
"""获取其他插件的 API 实例。"""

@abstractmethod
def is_user_admin(self, user_id: int, config_mgr) -> bool: # noqa: PYL-R0201
"""检查用户是否为平台管理员。"""

@abstractmethod
def send_game_command_with_resp( # noqa: PYL-R0201
self, cmd: str, timeout: float = 5.0
) -> Optional[str]:
"""发送游戏指令并等待响应文本,超时返回 None。"""

@abstractmethod
def send_game_command_full( # noqa: PYL-R0201
self, cmd: str, timeout: float = 5.0
) -> Optional[Dict[str, Any]]:
"""发送游戏指令并返回完整响应。

Returns:
None 表示异常或超时,否则返回字典:
{
"success_count": int,
"output": [{"message": str, "parameters": list}, ...]
}
"""

def resolve_player_names(self, entries: list) -> dict: # noqa: PYL-R0201 (abstract interface — subclasses may need self for platform-specific mappings)
"""将查询条目中的 UUID 映射为玩家名。

默认实现为空映射,子类可覆盖以提供平台特定的 UUID→名字解析。

Args:
entries: 包含 uniqueId 键的条目列表。

Returns:
{uniqueId: player_name} 映射字典。
"""
return {}

# ── 可选扩展: 生命周期事件 ──────────────────────────────

def listen_active(self, handler: Callable[[], None]) -> None: # noqa: PYL-R0201
"""注册框架就绪处理器(可选实现)。"""

def listen_frame_exit(self, handler: Callable[[Any], None]) -> None: # noqa: PYL-R0201
"""注册框架退出处理器(可选实现)。"""

def listen_player_pre_join(self, handler: Callable[[str], None]) -> None: # noqa: PYL-R0201
"""注册玩家预加入处理器(可选实现)。"""

# ── 可选扩展: 数据包监听 ──────────────────────────────────

def listen_dict_packet( # noqa: PYL-R0201
self, packet_id: int, handler: Callable[[dict], bool]
) -> None:
"""注册字典数据包监听,返回 True 拦截数据包。"""

def listen_bytes_packet( # noqa: PYL-R0201
self, packet_id: int, handler: Callable[[bytes], bool]
) -> None:
"""注册二进制数据包监听,返回 True 拦截数据包。"""

# ── 可选扩展: 标题栏消息 ────────────────────────────────

def send_game_title(self, target: str, text: str) -> None: # noqa: PYL-R0201
"""向玩家显示标题栏消息(可选实现)。"""

def send_game_subtitle(self, target: str, text: str) -> None: # noqa: PYL-R0201
"""向玩家显示小标题栏消息(可选实现)。"""

def send_game_actionbar(self, target: str, text: str) -> None: # noqa: PYL-R0201
"""向玩家显示行动栏消息(可选实现)。"""

# ── 可选扩展: 轮询发信 ────────────────────────────────

def send_message_round_robin( # noqa: PYL-R0201 (abstract interface — subclasses may need self for multi-bot round-robin)
self, group_id: int, message: str
) -> bool:
"""轮询式群消息发送(多机器人场景下自动切换机器人)。

多机器人模式:
- 如果 send_guard 可用 → 通过 SendGuard.send_with_ack() 发送
- SendGuard 自动选择机器人 → 发送 → 回显确认 → 故障转移

单机器人模式:
降级为 send_group_msg。

Args:
group_id: QQ 群号。
message: 消息文本。

Returns:
是否发送成功。
"""
send_guard = getattr(self, '_send_guard', None)
if send_guard is not None:
try:
return send_guard.send_with_ack(group_id, message, priority=1)
except Exception:
pass
return self.send_group_msg(group_id, message)

# ── 可选扩展: 跨插件 API 代理 ─────────────────────────────

def register_pre_plugin_api( # noqa: PYL-R0201 (abstract interface — subclasses may need self for adapter-specific API registration)
self, api_name: str, min_version: tuple = (0, 0, 0)
) -> bool:
"""注册 datas.json 声明的依赖插件 API。

Args:
api_name: API 名称。
min_version: 最低版本要求。

Returns:
是否成功注册。
"""
return False

def get_pre_plugin_api(self, api_name: str) -> Optional[Any]: # noqa: PYL-R0201 (abstract interface — subclasses may need self for adapter-specific API resolution)
"""获取已注册的前置插件 API 实例。

Args:
api_name: API 名称。

Returns:
API 实例或 None。
"""
return None

def get_pre_plugin_apis(self) -> Dict[str, Any]: # noqa: PYL-R0201 (abstract interface — subclasses may need self for adapter-specific API collection)
"""返回所有已注册的前置插件 API 字典。"""
return {}
111 changes: 111 additions & 0 deletions qqlinker_framework/adapters/standalone.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# adapters/standalone.py
"""QQ 独立模式适配器 — 不连接游戏服务器,纯 QQ 机器人。

所有游戏相关方法返回空值/NOOP,保持接口兼容。
模块可通过 self.adapter 存在性判断是否在游戏模式。
"""
import logging
from typing import Callable, Dict, Any, List, Optional

from .base import IFrameworkAdapter

_log = logging.getLogger(__name__)


class StandaloneAdapter(IFrameworkAdapter):
"""QQ 独立模式适配器。只提供 QQ 消息功能,游戏接口全部空实现。

适用场景:
- 纯 QQ 群机器人(无 Minecraft 服)
- 测试环境(不需要游戏连接)
- 其他 IM 平台(Telegram/Discord/WhatsApp)
"""

def __init__(self, ws_client=None):
self._ws_client = ws_client
self._active = False

# ── QQ 消息(委托给 WS 客户端)──

def send_group_msg(self, group_id: int, message: str) -> bool:
if self._ws_client and self._ws_client.available:
return self._ws_client.send_group_msg(group_id, message)
_log.warning("WS 客户端不可用,群消息未发送")
return False

def send_private_msg(self, user_id: int, message: str) -> bool:
if self._ws_client and self._ws_client.available:
return self._ws_client.send_private_msg(user_id, message)
_log.warning("WS 客户端不可用,私聊消息未发送")
return False

# ── 游戏指令(空实现)──

def send_game_command(self, cmd: str) -> None:
_log.debug("独立模式: 跳过游戏指令 '%s'", cmd[:60])

def send_game_message(self, target: str, text: str) -> None:
_log.debug("独立模式: 跳过游戏消息 → %s", target)

def send_game_title(self, target: str, text: str) -> None:
pass

def send_game_subtitle(self, target: str, text: str) -> None:
pass

def send_game_actionbar(self, target: str, text: str) -> None:
pass

def get_online_players(self) -> List[str]:
return []

def send_game_command_with_resp(
self, cmd: str, timeout: float = 5.0
) -> Optional[str]:
_log.debug("独立模式: 跳过同步指令 '%s'", cmd[:60])
return None

def send_game_command_full(
self, cmd: str, timeout: float = 5.0
) -> Optional[Dict[str, Any]]:
_log.debug("独立模式: 跳过完整指令 '%s'", cmd[:60])
return None

# ── 事件监听(空实现)──

def listen_game_chat(
self, handler: Callable[[str, str], None]
) -> None:
pass

def listen_player_join(self, handler: Callable[[str], None]) -> None:
pass

def listen_player_leave(self, handler: Callable[[str], None]) -> None:
pass

def listen_group_message(
self, handler: Callable[[Dict[str, Any]], None]
) -> None:
pass

def register_console_command(
self, triggers: List[str], hint: str, usage: str, func: Callable
) -> None:
pass

def get_plugin_api(self, name: str) -> Optional[Any]:
return None

def is_user_admin(self, user_id: int, config_mgr=None) -> bool:
if config_mgr is None:
return False
admin_list = config_mgr.get("管理员.管理员QQ", [])
try:
uid_int = int(user_id) if not isinstance(user_id, int) else user_id
return uid_int in [int(q) for q in admin_list]
except (TypeError, ValueError):
return False

def resolve_player_names(self, entries: list) -> dict:
return {}
Loading