refactor(plugins): comprehensive code review - ~35 fixes across 14 plugins
Phase 1 - Plugin code review (14/14 plugins): - Security: 3x token leak in print→logger.debug, Bearer prefix handling - Bug: bare except→specific exceptions, HorseState type safety, sync→async - Critical: response_model undefined, route dead code, sync blocking event loop - Quality: 11x print()→logger, variable name shadowing, consistent logging Phase 2 - Deep analysis: - Fix: payout int truncation→max(1, round(amount*odds)) - Fix: room_store get_lock race condition→dict.setdefault() - Verify: data_manager f-string SQL is safe (uses ? placeholders) Infrastructure: review reports generated for all plugins.
This commit is contained in:
@@ -1,69 +1,65 @@
|
||||
"""Danding_QqPush 插件初始化模块"""
|
||||
from nonebot import get_driver, get_bots
|
||||
from nonebot.log import logger
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .config import Config
|
||||
from .api import create_routes
|
||||
from .sender import sender
|
||||
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="danding_qqpush",
|
||||
description="通过外部 HTTP API 向 QQ 群定向推送通知",
|
||||
usage="""
|
||||
API 接口:
|
||||
POST /danding/qqpush/{token}
|
||||
|
||||
请求参数:
|
||||
{
|
||||
"group_id": 123456789,
|
||||
"qq": 987654321,
|
||||
"text": "系统告警#数据库连接失败#请立即处理"
|
||||
}
|
||||
|
||||
说明:
|
||||
- text 中的 # 表示换行
|
||||
- 消息会自动渲染为图片并发送到指定群
|
||||
""",
|
||||
config=Config,
|
||||
)
|
||||
|
||||
|
||||
# 加载配置
|
||||
plugin_config = Config.model_validate(get_driver().config.dict())
|
||||
|
||||
|
||||
def register_routes():
|
||||
"""注册 FastAPI 路由"""
|
||||
driver = get_driver()
|
||||
|
||||
# 创建并注册路由
|
||||
routes = create_routes(plugin_config.Token, plugin_config)
|
||||
driver.server_app.include_router(routes)
|
||||
|
||||
logger.info(f"[Danding_QqPush] API 路由已注册: /danding/qqpush/{plugin_config.Token}")
|
||||
|
||||
|
||||
def init_bot():
|
||||
"""初始化 Bot 实例"""
|
||||
try:
|
||||
bots = get_bots()
|
||||
if bots:
|
||||
# 获取第一个可用的 Bot
|
||||
bot = list(bots.values())[0]
|
||||
sender.set_bot(bot)
|
||||
logger.info(f"[Danding_QqPush] Bot 已连接: {bot.self_id}")
|
||||
else:
|
||||
logger.warning("[Danding_QqPush] 未找到可用的 Bot 实例")
|
||||
except Exception as e:
|
||||
logger.warning(f"[Danding_QqPush] 初始化 Bot 失败: {str(e)}")
|
||||
|
||||
|
||||
# 插件加载时注册路由并初始化 Bot
|
||||
try:
|
||||
register_routes()
|
||||
init_bot()
|
||||
logger.info("[Danding_QqPush] 插件加载成功")
|
||||
except Exception as e:
|
||||
logger.error(f"[Danding_QqPush] 插件加载失败: {str(e)}")
|
||||
"""Danding_QqPush 插件初始化模块"""
|
||||
from nonebot import get_driver
|
||||
from nonebot.log import logger
|
||||
from nonebot.plugin import PluginMetadata
|
||||
|
||||
from .config import Config
|
||||
from .api import create_routes
|
||||
from .sender import sender
|
||||
|
||||
|
||||
__plugin_meta__ = PluginMetadata(
|
||||
name="danding_qqpush",
|
||||
description="通过外部 HTTP API 向 QQ 群定向推送通知",
|
||||
usage="""
|
||||
API 接口:
|
||||
POST /danding/qqpush/{token}
|
||||
|
||||
请求参数:
|
||||
{
|
||||
"group_id": 123456789,
|
||||
"qq": 987654321,
|
||||
"text": "系统告警#数据库连接失败#请立即处理"
|
||||
}
|
||||
|
||||
说明:
|
||||
- text 中的 # 表示换行
|
||||
- 消息会自动渲染为图片并发送到指定群
|
||||
""",
|
||||
config=Config,
|
||||
)
|
||||
|
||||
|
||||
# 加载配置
|
||||
plugin_config = Config.model_validate(get_driver().config.model_dump())
|
||||
|
||||
|
||||
def register_routes():
|
||||
"""注册 FastAPI 路由"""
|
||||
driver = get_driver()
|
||||
|
||||
# 创建并注册路由
|
||||
routes = create_routes(plugin_config.Token, plugin_config)
|
||||
driver.server_app.include_router(routes)
|
||||
|
||||
logger.info(f"[Danding_QqPush] API 路由已注册: /danding/qqpush/{plugin_config.Token}")
|
||||
|
||||
|
||||
# 插件加载时注册路由
|
||||
try:
|
||||
register_routes()
|
||||
logger.info("[Danding_QqPush] 插件加载成功")
|
||||
except Exception as e:
|
||||
logger.error(f"[Danding_QqPush] 插件加载失败: {str(e)}")
|
||||
|
||||
|
||||
# Bot 连接时自动初始化 sender
|
||||
driver = get_driver()
|
||||
@driver.on_bot_connect
|
||||
async def _(bot):
|
||||
"""Bot 连接时自动设置 sender"""
|
||||
try:
|
||||
sender.set_bot(bot)
|
||||
logger.info(f"[Danding_QqPush] Bot 已连接: {bot.self_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"[Danding_QqPush] Bot 连接初始化失败: {e}")
|
||||
|
||||
@@ -1,142 +1,143 @@
|
||||
"""API 接口模块 - FastAPI 路由定义"""
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
from nonebot import get_driver, logger
|
||||
|
||||
from .config import Config
|
||||
from .text_parser import TextParser
|
||||
from .image_render import ImageRenderer
|
||||
from .sender import sender
|
||||
|
||||
|
||||
# 请求体模型
|
||||
class PushRequest(BaseModel):
|
||||
"""推送请求模型"""
|
||||
group_id: int
|
||||
"""接收消息的 QQ 群号"""
|
||||
|
||||
qq: int
|
||||
"""被 @ 的 QQ 号"""
|
||||
|
||||
text: str
|
||||
"""通知文本(# 表示换行)"""
|
||||
|
||||
|
||||
# 响应模型
|
||||
class PushResponse(BaseModel):
|
||||
"""推送响应模型"""
|
||||
success: bool
|
||||
"""是否成功"""
|
||||
|
||||
message: str
|
||||
"""响应消息"""
|
||||
|
||||
data: Optional[dict] = None
|
||||
"""返回数据(如有)"""
|
||||
|
||||
|
||||
# 创建路由器
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def create_routes(token: str, config: Config):
|
||||
"""
|
||||
创建 API 路由
|
||||
|
||||
Args:
|
||||
token: 鉴权 Token
|
||||
config: 配置对象
|
||||
"""
|
||||
|
||||
@router.post(f"/danding/qqpush/{token}", response_model=PushResponse)
|
||||
async def qqpush(request: Request, data: PushRequest):
|
||||
"""
|
||||
QQ 消息推送接口
|
||||
|
||||
Args:
|
||||
request: FastAPI 请求对象
|
||||
data: 推送请求数据
|
||||
|
||||
Returns:
|
||||
推送结果
|
||||
"""
|
||||
try:
|
||||
# 1. 验证参数
|
||||
if not data.group_id:
|
||||
raise HTTPException(status_code=400, detail="group_id 不能为空")
|
||||
|
||||
if not data.qq:
|
||||
raise HTTPException(status_code=400, detail="qq 不能为空")
|
||||
|
||||
if not data.text or not isinstance(data.text, str):
|
||||
raise HTTPException(status_code=400, detail="text 不能为空且必须是字符串")
|
||||
|
||||
# 2. 检查 Bot 是否在线
|
||||
bot = sender.get_bot()
|
||||
if not bot:
|
||||
logger.error("Bot 实例未设置,无法发送消息")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Bot 未连接,请检查机器人状态"
|
||||
)
|
||||
|
||||
# 3. 文本处理
|
||||
text_parser = TextParser(max_length=config.MaxTextLength)
|
||||
if not text_parser.validate_text(data.text):
|
||||
raise HTTPException(status_code=400, detail="文本内容无效")
|
||||
|
||||
parsed_text = text_parser.parse(data.text)
|
||||
logger.info(f"解析文本: {parsed_text[:50]}..." if len(parsed_text) > 50 else parsed_text)
|
||||
|
||||
# 4. 生成图片
|
||||
image_renderer = ImageRenderer(
|
||||
width=config.ImageWidth,
|
||||
font_size=config.ImageFontSize,
|
||||
padding=config.ImagePadding,
|
||||
line_spacing=config.ImageLineSpacing,
|
||||
bg_color=config.ImageBgColor,
|
||||
text_color=config.ImageTextColor,
|
||||
font_paths=config.FontPaths
|
||||
)
|
||||
|
||||
image_base64 = image_renderer.render_to_base64(parsed_text)
|
||||
logger.info("图片生成成功")
|
||||
|
||||
# 5. 发送消息
|
||||
send_result = await sender.send_to_group(
|
||||
group_id=data.group_id,
|
||||
qq=data.qq,
|
||||
image_base64=image_base64
|
||||
)
|
||||
|
||||
if not send_result["success"]:
|
||||
logger.error(f"消息发送失败: {send_result['error']}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=send_result["message"]
|
||||
)
|
||||
|
||||
logger.info(f"消息发送成功 - 群: {data.group_id}, @: {data.qq}")
|
||||
|
||||
return PushResponse(
|
||||
success=True,
|
||||
message="推送成功",
|
||||
data={
|
||||
"group_id": data.group_id,
|
||||
"qq": data.qq,
|
||||
"message_id": send_result["data"].get("message_id")
|
||||
}
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"推送接口异常: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"服务器内部错误: {str(e)}"
|
||||
)
|
||||
|
||||
return router
|
||||
"""API 接口模块 - FastAPI 路由定义"""
|
||||
from fastapi import APIRouter, Request, HTTPException
|
||||
from pydantic import BaseModel
|
||||
import asyncio
|
||||
from typing import Optional
|
||||
from nonebot import get_driver, logger
|
||||
|
||||
from .config import Config
|
||||
from .text_parser import TextParser
|
||||
from .image_render import ImageRenderer
|
||||
from .sender import sender
|
||||
|
||||
|
||||
# 请求体模型
|
||||
class PushRequest(BaseModel):
|
||||
"""推送请求模型"""
|
||||
group_id: int
|
||||
"""接收消息的 QQ 群号"""
|
||||
|
||||
qq: int
|
||||
"""被 @ 的 QQ 号"""
|
||||
|
||||
text: str
|
||||
"""通知文本(# 表示换行)"""
|
||||
|
||||
|
||||
# 响应模型
|
||||
class PushResponse(BaseModel):
|
||||
"""推送响应模型"""
|
||||
success: bool
|
||||
"""是否成功"""
|
||||
|
||||
message: str
|
||||
"""响应消息"""
|
||||
|
||||
data: Optional[dict] = None
|
||||
"""返回数据(如有)"""
|
||||
|
||||
|
||||
# 创建路由器
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def create_routes(token: str, config: Config):
|
||||
"""
|
||||
创建 API 路由
|
||||
|
||||
Args:
|
||||
token: 鉴权 Token
|
||||
config: 配置对象
|
||||
"""
|
||||
|
||||
@router.post(f"/danding/qqpush/{token}", response_model=PushResponse)
|
||||
async def qqpush(request: Request, data: PushRequest):
|
||||
"""
|
||||
QQ 消息推送接口
|
||||
|
||||
Args:
|
||||
request: FastAPI 请求对象
|
||||
data: 推送请求数据
|
||||
|
||||
Returns:
|
||||
推送结果
|
||||
"""
|
||||
try:
|
||||
# 1. 验证参数
|
||||
if not data.group_id:
|
||||
raise HTTPException(status_code=400, detail="group_id 不能为空")
|
||||
|
||||
if not data.qq:
|
||||
raise HTTPException(status_code=400, detail="qq 不能为空")
|
||||
|
||||
if not data.text or not isinstance(data.text, str):
|
||||
raise HTTPException(status_code=400, detail="text 不能为空且必须是字符串")
|
||||
|
||||
# 2. 检查 Bot 是否在线
|
||||
bot = sender.get_bot()
|
||||
if not bot:
|
||||
logger.error("Bot 实例未设置,无法发送消息")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="Bot 未连接,请检查机器人状态"
|
||||
)
|
||||
|
||||
# 3. 文本处理
|
||||
text_parser = TextParser(max_length=config.MaxTextLength)
|
||||
if not text_parser.validate_text(data.text):
|
||||
raise HTTPException(status_code=400, detail="文本内容无效")
|
||||
|
||||
parsed_text = text_parser.parse(data.text)
|
||||
logger.info(f"解析文本: {parsed_text[:50]}..." if len(parsed_text) > 50 else parsed_text)
|
||||
|
||||
# 4. 生成图片
|
||||
image_renderer = ImageRenderer(
|
||||
width=config.ImageWidth,
|
||||
font_size=config.ImageFontSize,
|
||||
padding=config.ImagePadding,
|
||||
line_spacing=config.ImageLineSpacing,
|
||||
bg_color=config.ImageBgColor,
|
||||
text_color=config.ImageTextColor,
|
||||
font_paths=config.FontPaths
|
||||
)
|
||||
|
||||
image_base64 = await asyncio.to_thread(image_renderer.render_to_base64, parsed_text)
|
||||
logger.info("图片生成成功")
|
||||
|
||||
# 5. 发送消息
|
||||
send_result = await sender.send_to_group(
|
||||
group_id=data.group_id,
|
||||
qq=data.qq,
|
||||
image_base64=image_base64
|
||||
)
|
||||
|
||||
if not send_result["success"]:
|
||||
logger.error(f"消息发送失败: {send_result['error']}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=send_result["message"]
|
||||
)
|
||||
|
||||
logger.info(f"消息发送成功 - 群: {data.group_id}, @: {data.qq}")
|
||||
|
||||
return PushResponse(
|
||||
success=True,
|
||||
message="推送成功",
|
||||
data={
|
||||
"group_id": data.group_id,
|
||||
"qq": data.qq,
|
||||
"message_id": send_result["data"].get("message_id")
|
||||
}
|
||||
)
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
|
||||
except Exception as e:
|
||||
logger.exception(f"推送接口异常: {str(e)}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"服务器内部错误: {str(e)}"
|
||||
)
|
||||
|
||||
return router
|
||||
|
||||
@@ -1,42 +1,40 @@
|
||||
"""Danding_QqPush 插件配置模块"""
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
"""插件配置"""
|
||||
|
||||
Token: str = "danding-8HkL9xQ2"
|
||||
"""API 访问 Token,用于鉴权"""
|
||||
|
||||
# 图片生成配置
|
||||
ImageWidth: int = 800
|
||||
"""生成的图片宽度(像素)"""
|
||||
|
||||
ImageFontSize: int = 24
|
||||
"""字体大小(像素)"""
|
||||
|
||||
ImagePadding: int = 30
|
||||
"""图片内边距(像素)"""
|
||||
|
||||
ImageLineSpacing: float = 1.4
|
||||
"""行距倍数"""
|
||||
|
||||
ImageBgColor: tuple = (252, 252, 252)
|
||||
"""图片背景颜色 (R, G, B)"""
|
||||
|
||||
ImageTextColor: tuple = (0, 0, 0)
|
||||
"""文本颜色 (R, G, B)"""
|
||||
|
||||
# 文本处理配置
|
||||
MaxTextLength: int = 2000
|
||||
"""最大文本长度(字符数),超过将截断"""
|
||||
|
||||
# 字体路径配置
|
||||
FontPaths: list = [
|
||||
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
||||
"/usr/share/fonts/wqy-microhei/wqy-microhei.ttc",
|
||||
"/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc",
|
||||
"C:/Windows/Fonts/msyh.ttc",
|
||||
"C:/Windows/Fonts/simhei.ttf",
|
||||
]
|
||||
"""字体文件路径列表"""
|
||||
"""Danding_QqPush 插件配置模块"""
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
"""插件配置"""
|
||||
|
||||
Token: str = ""
|
||||
"""API 访问 Token,用于鉴权(必须在 .env 中配置 DANDING_QQPUSH_TOKEN)"""
|
||||
|
||||
# 图片生成配置
|
||||
ImageWidth: int = 800
|
||||
"""生成的图片宽度(像素)"""
|
||||
|
||||
ImageFontSize: int = 24
|
||||
"""字体大小(像素)"""
|
||||
|
||||
ImagePadding: int = 30
|
||||
"""图片内边距(像素)"""
|
||||
|
||||
ImageLineSpacing: float = 1.4
|
||||
"""行距倍数"""
|
||||
|
||||
ImageBgColor: tuple = (252, 252, 252)
|
||||
"""图片背景颜色 (R, G, B)"""
|
||||
|
||||
ImageTextColor: tuple = (0, 0, 0)
|
||||
"""文本颜色 (R, G, B)"""
|
||||
|
||||
# 文本处理配置
|
||||
MaxTextLength: int = 2000
|
||||
"""最大文本长度(字符数),超过将截断"""
|
||||
|
||||
# 字体路径配置
|
||||
FontPaths: list = ("/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
||||
"/usr/share/fonts/wqy-microhei/wqy-microhei.ttc",
|
||||
"/usr/share/fonts/wqy-zenhei/wqy-zenhei.ttc",
|
||||
"C:/Windows/Fonts/msyh.ttc",
|
||||
"C:/Windows/Fonts/simhei.ttf",)
|
||||
"""字体文件路径列表"""
|
||||
|
||||
@@ -1,148 +1,148 @@
|
||||
"""消息发送模块 - 负责向 QQ 群发送消息"""
|
||||
from typing import Optional
|
||||
from nonebot import get_bots
|
||||
from nonebot.adapters.onebot.v11 import Bot, Message, MessageSegment
|
||||
|
||||
|
||||
class MessageSender:
|
||||
"""消息发送器"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化消息发送器"""
|
||||
self.bot: Optional[Bot] = None
|
||||
|
||||
def set_bot(self, bot: Bot):
|
||||
"""
|
||||
设置 Bot 实例
|
||||
|
||||
Args:
|
||||
bot: OneBot V11 Bot 实例
|
||||
"""
|
||||
self.bot = bot
|
||||
|
||||
def get_bot(self) -> Optional[Bot]:
|
||||
"""
|
||||
获取 Bot 实例
|
||||
|
||||
Returns:
|
||||
Bot 实例,如果未设置则尝试从全局获取
|
||||
"""
|
||||
if self.bot:
|
||||
return self.bot
|
||||
|
||||
# 尝试从全局获取 Bot
|
||||
try:
|
||||
bots = get_bots()
|
||||
if bots:
|
||||
bot = list(bots.values())[0]
|
||||
self.bot = bot
|
||||
return bot
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
async def send_to_group(
|
||||
self,
|
||||
group_id: int,
|
||||
qq: int,
|
||||
image_base64: str
|
||||
) -> dict:
|
||||
"""
|
||||
向指定群发送消息(@用户 + 图片)
|
||||
|
||||
Args:
|
||||
group_id: 群号
|
||||
qq: 要 @ 的 QQ 号
|
||||
image_base64: 图片的 base64 编码(格式:base64://...)
|
||||
|
||||
Returns:
|
||||
发送结果字典
|
||||
|
||||
Raises:
|
||||
ValueError: Bot 未设置
|
||||
Exception: 发送失败
|
||||
"""
|
||||
bot = self.get_bot()
|
||||
if not bot:
|
||||
raise ValueError("Bot 实例未设置,无法发送消息")
|
||||
|
||||
try:
|
||||
# 构造消息:@用户 + 图片
|
||||
message = Message()
|
||||
message.append(MessageSegment.at(qq))
|
||||
message.append(MessageSegment.image(image_base64))
|
||||
|
||||
# 发送群消息,添加 __qqpush_source 标记供 auto_recall 识别
|
||||
result = await bot.call_api(
|
||||
"send_group_msg",
|
||||
group_id=group_id,
|
||||
message=message,
|
||||
__qqpush_source="danding_qqpush" # 添加标记
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": result,
|
||||
"message": "消息发送成功"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
# 捕获异常并返回错误信息
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"消息发送失败: {str(e)}"
|
||||
}
|
||||
|
||||
async def send_text_to_group(
|
||||
self,
|
||||
group_id: int,
|
||||
qq: int,
|
||||
text: str
|
||||
) -> dict:
|
||||
"""
|
||||
向指定群发送纯文本消息(@用户 + 文本)
|
||||
|
||||
Args:
|
||||
group_id: 群号
|
||||
qq: 要 @ 的 QQ 号
|
||||
text: 文本内容
|
||||
|
||||
Returns:
|
||||
发送结果字典
|
||||
"""
|
||||
bot = self.get_bot()
|
||||
if not bot:
|
||||
raise ValueError("Bot 实例未设置,无法发送消息")
|
||||
|
||||
try:
|
||||
# 构造消息:@用户 + 文本
|
||||
message = Message()
|
||||
message.append(MessageSegment.at(qq))
|
||||
message.append(MessageSegment.text(text))
|
||||
|
||||
# 发送群消息,添加 __qqpush_source 标记供 auto_recall 识别
|
||||
result = await bot.call_api(
|
||||
"send_group_msg",
|
||||
group_id=group_id,
|
||||
message=message,
|
||||
__qqpush_source="danding_qqpush" # 添加标记
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": result,
|
||||
"message": "消息发送成功"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"消息发送失败: {str(e)}"
|
||||
}
|
||||
|
||||
|
||||
# 全局消息发送器实例
|
||||
sender = MessageSender()
|
||||
"""消息发送模块 - 负责向 QQ 群发送消息"""
|
||||
from typing import Optional
|
||||
from nonebot import get_bots, logger
|
||||
from nonebot.adapters.onebot.v11 import Bot, Message, MessageSegment
|
||||
|
||||
|
||||
class MessageSender:
|
||||
"""消息发送器"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化消息发送器"""
|
||||
self.bot: Optional[Bot] = None
|
||||
|
||||
def set_bot(self, bot: Bot):
|
||||
"""
|
||||
设置 Bot 实例
|
||||
|
||||
Args:
|
||||
bot: OneBot V11 Bot 实例
|
||||
"""
|
||||
self.bot = bot
|
||||
|
||||
def get_bot(self) -> Optional[Bot]:
|
||||
"""
|
||||
获取 Bot 实例
|
||||
|
||||
Returns:
|
||||
Bot 实例,如果未设置则尝试从全局获取
|
||||
"""
|
||||
if self.bot:
|
||||
return self.bot
|
||||
|
||||
# 尝试从全局获取 Bot
|
||||
try:
|
||||
bots = get_bots()
|
||||
if bots:
|
||||
bot = list(bots.values())[0]
|
||||
self.bot = bot
|
||||
return bot
|
||||
except Exception as e:
|
||||
logger.warning(f"[QqPush] 获取全局Bot失败: {e}")
|
||||
|
||||
return None
|
||||
|
||||
async def send_to_group(
|
||||
self,
|
||||
group_id: int,
|
||||
qq: int,
|
||||
image_base64: str
|
||||
) -> dict:
|
||||
"""
|
||||
向指定群发送消息(@用户 + 图片)
|
||||
|
||||
Args:
|
||||
group_id: 群号
|
||||
qq: 要 @ 的 QQ 号
|
||||
image_base64: 图片的 base64 编码(格式:base64://...)
|
||||
|
||||
Returns:
|
||||
发送结果字典
|
||||
|
||||
Raises:
|
||||
ValueError: Bot 未设置
|
||||
Exception: 发送失败
|
||||
"""
|
||||
bot = self.get_bot()
|
||||
if not bot:
|
||||
raise ValueError("Bot 实例未设置,无法发送消息")
|
||||
|
||||
try:
|
||||
# 构造消息:@用户 + 图片
|
||||
message = Message()
|
||||
message.append(MessageSegment.at(qq))
|
||||
message.append(MessageSegment.image(image_base64))
|
||||
|
||||
# 发送群消息,添加 __qqpush_source 标记供 auto_recall 识别
|
||||
result = await bot.call_api(
|
||||
"send_group_msg",
|
||||
group_id=group_id,
|
||||
message=message,
|
||||
__qqpush_source="danding_qqpush" # 添加标记
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": result,
|
||||
"message": "消息发送成功"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
# 捕获异常并返回错误信息
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"消息发送失败: {str(e)}"
|
||||
}
|
||||
|
||||
async def send_text_to_group(
|
||||
self,
|
||||
group_id: int,
|
||||
qq: int,
|
||||
text: str
|
||||
) -> dict:
|
||||
"""
|
||||
向指定群发送纯文本消息(@用户 + 文本)
|
||||
|
||||
Args:
|
||||
group_id: 群号
|
||||
qq: 要 @ 的 QQ 号
|
||||
text: 文本内容
|
||||
|
||||
Returns:
|
||||
发送结果字典
|
||||
"""
|
||||
bot = self.get_bot()
|
||||
if not bot:
|
||||
raise ValueError("Bot 实例未设置,无法发送消息")
|
||||
|
||||
try:
|
||||
# 构造消息:@用户 + 文本
|
||||
message = Message()
|
||||
message.append(MessageSegment.at(qq))
|
||||
message.append(MessageSegment.text(text))
|
||||
|
||||
# 发送群消息,添加 __qqpush_source 标记供 auto_recall 识别
|
||||
result = await bot.call_api(
|
||||
"send_group_msg",
|
||||
group_id=group_id,
|
||||
message=message,
|
||||
__qqpush_source="danding_qqpush" # 添加标记
|
||||
)
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"data": result,
|
||||
"message": "消息发送成功"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": str(e),
|
||||
"message": f"消息发送失败: {str(e)}"
|
||||
}
|
||||
|
||||
|
||||
# 全局消息发送器实例
|
||||
sender = MessageSender()
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
"""工具函数模块"""
|
||||
import secrets
|
||||
import string
|
||||
|
||||
|
||||
def generate_token(length: int = 16, prefix: str = "danding-") -> str:
|
||||
"""
|
||||
生成随机 Token
|
||||
|
||||
Args:
|
||||
length: 随机部分长度
|
||||
prefix: Token 前缀
|
||||
|
||||
Returns:
|
||||
生成的 Token
|
||||
"""
|
||||
# 生成随机字符串(字母和数字)
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
random_part = ''.join(secrets.choice(alphabet) for _ in range(length))
|
||||
|
||||
return f"{prefix}{random_part}"
|
||||
|
||||
|
||||
def validate_token(token: str, expected_token: str) -> bool:
|
||||
"""
|
||||
验证 Token 是否正确
|
||||
|
||||
Args:
|
||||
token: 待验证的 Token
|
||||
expected_token: 期望的 Token
|
||||
|
||||
Returns:
|
||||
是否匹配
|
||||
"""
|
||||
if not token or not expected_token:
|
||||
return False
|
||||
|
||||
return token == expected_token
|
||||
|
||||
|
||||
def format_log_message(message: str, level: str = "INFO") -> str:
|
||||
"""
|
||||
格式化日志消息
|
||||
|
||||
Args:
|
||||
message: 原始消息
|
||||
level: 日志级别
|
||||
|
||||
Returns:
|
||||
格式化后的消息
|
||||
"""
|
||||
return f"[Danding_QqPush] [{level}] {message}"
|
||||
"""工具函数模块"""
|
||||
import secrets
|
||||
import string
|
||||
|
||||
|
||||
def generate_token(length: int = 16, prefix: str = "danding-") -> str:
|
||||
"""
|
||||
生成随机 Token
|
||||
|
||||
Args:
|
||||
length: 随机部分长度
|
||||
prefix: Token 前缀
|
||||
|
||||
Returns:
|
||||
生成的 Token
|
||||
"""
|
||||
# 生成随机字符串(字母和数字)
|
||||
alphabet = string.ascii_letters + string.digits
|
||||
random_part = ''.join(secrets.choice(alphabet) for _ in range(length))
|
||||
|
||||
return f"{prefix}{random_part}"
|
||||
|
||||
|
||||
def validate_token(token: str, expected_token: str) -> bool:
|
||||
"""
|
||||
验证 Token 是否正确
|
||||
|
||||
Args:
|
||||
token: 待验证的 Token
|
||||
expected_token: 期望的 Token
|
||||
|
||||
Returns:
|
||||
是否匹配
|
||||
"""
|
||||
if not token or not expected_token:
|
||||
return False
|
||||
|
||||
return secrets.compare_digest(token.encode(), expected_token.encode())
|
||||
|
||||
|
||||
def format_log_message(message: str, level: str = "INFO") -> str:
|
||||
"""
|
||||
格式化日志消息
|
||||
|
||||
Args:
|
||||
message: 原始消息
|
||||
level: 日志级别
|
||||
|
||||
Returns:
|
||||
格式化后的消息
|
||||
"""
|
||||
return f"[Danding_QqPush] [{level}] {message}"
|
||||
|
||||
Reference in New Issue
Block a user