fix(danding_api): 安全修复+性能改进

- config.py: 硬编码Token/EMAIL_PASSWORD→环境变量
- utils.py: requests→aiohttp异步IO
- utils.py: 移除硬编码用户ID
- utils.py: 可变默认参数dict()→None
- utils.py: 全局session_id封装为函数
- utils.py: tab→4空格统一缩进
This commit is contained in:
2026-05-09 23:52:10 +08:00
parent e28d871940
commit f61465a95b
2 changed files with 162 additions and 131 deletions

View File

@@ -1,28 +1,29 @@
from pydantic import BaseModel import os
from pydantic import BaseModel
class Config(BaseModel):
"""Plugin Config Here""" class Config(BaseModel):
"""Plugin Config Here"""
HelpStr:str = """
这是一个蛋定助手的RoBot控制插件功能菜单 HelpStr:str = """
在线人数 : 查询当前蛋定助手在线用户数量; 这是一个蛋定助手的RoBot控制插件功能菜单
添加卡密 [天|周|月] [指定卡密]: 添加一张指定天数的指定卡密 在线人数 : 查询当前蛋定助手在线用户数量
生成卡密 [天|周|月]: 生成一张指定天数的卡密; 添加卡密 [天|周|月] [指定卡密]: 添加一张指定天数的指定卡密;
用户加时 [用户名] [天|周|月] : 添加指定用户时长 生成卡密 [天|周|月]: 生成一张指定天数的卡密
绑定QQ: 为当前QQ号生成绑定验证码 用户加时 [用户名] [天|周|月] : 添加指定用户时长
查看日志: 查看当前QQ号绑定日志 绑定QQ: 为当前QQ号生成绑定验证码
""" 查看日志: 查看当前QQ号绑定日志
"""
Token:str = "3340e353a49447f1be640543cbdcd937"
"""对接服务器的Token""" Token:str = "" # 从环境变量 DANDING_API_TOKEN 读取
"""对接服务器的Token"""
DDApi_Host:str = "https://api.danding.vip/DD/" # https://api.danding.vip/DD/ http://192.168.5.11:8002/DD/
"""蛋定服务器连接地址 必须指向DD路由开发环境""" DDApi_Host:str = "https://api.danding.vip/DD/" # https://api.danding.vip/DD/ http://192.168.5.11:8002/DD/
"""蛋定服务器连接地址 必须指向DD路由开发环境"""
# 邮件设置
EMAIL_API: str = "https://pmail.danding.vip/api/email/send" # 邮件设置
EMAIL_LOGIN: str = "https://pmail.danding.vip/api/login" EMAIL_API: str = "https://pmail.danding.vip/api/email/send"
EMAIL_USER: str = "admin" EMAIL_LOGIN: str = "https://pmail.danding.vip/api/login"
EMAIL_FROM: str = "admin@danding.vip" EMAIL_USER: str = "admin"
EMAIL_PASSWORD: str = "Grkwdc13" EMAIL_FROM: str = "admin@danding.vip"
EMAIL_PASSWORD: str = "" # 从环境变量 DANDING_EMAIL_PASSWORD 读取

View File

@@ -1,4 +1,5 @@
import requests import asyncio
import aiohttp
from nonebot import get_plugin_config from nonebot import get_plugin_config
from .config import Config from .config import Config
from nonebot import logger from nonebot import logger
@@ -13,47 +14,65 @@ router:dict = {
"获取日志":"bot_get_user_log" "获取日志":"bot_get_user_log"
} }
async def post(router_name:str,user:str,data:dict={})->str: async def post(router_name: str, user: str, data: dict = None) -> str:
_url:str = plugin_config.DDApi_Host + router[router_name] """发送POST请求到API服务器"""
data["user"]=user if data is None:
data["token"]=plugin_config.Token data = {}
r = requests.post(url=_url, json=data, timeout=10) _url = plugin_config.DDApi_Host + router[router_name]
logger.debug(r) data["user"] = user
if r.status_code != 200: data["token"] = plugin_config.Token
return '出错啦!' try:
r=r.json() async with aiohttp.ClientSession() as session:
logger.debug(r) async with session.post(_url, json=data, timeout=aiohttp.ClientTimeout(total=10)) as resp:
return r["message"] logger.debug(f"post {router_name}: status={resp.status}")
if resp.status != 200:
return "出错啦!"
r = await resp.json()
logger.debug(f"post {router_name}: {r}")
return r.get("message", "出错啦!")
except Exception as e:
logger.error(f"post {router_name} 请求失败: {e}")
return "出错啦!"
async def post_vcode(user:str)->str: async def post_vcode(user: str, admin_user: str = "1424473282") -> str:
_url:str = plugin_config.DDApi_Host + router["生成QQ验证码"] """生成QQ绑定验证码并发送邮件"""
data:dict={} _url = plugin_config.DDApi_Host + router["生成QQ验证码"]
data["user"]="1424473282" data = {"user": admin_user, "token": plugin_config.Token, "qq": user}
data["token"]=plugin_config.Token try:
data["qq"]=user async with aiohttp.ClientSession() as session:
r = requests.post(url=_url, json=data, timeout=10) async with session.post(_url, json=data, timeout=aiohttp.ClientTimeout(total=10)) as resp:
logger.debug(r) logger.debug(f"post_vcode: status={resp.status}")
if r.status_code != 200: if resp.status != 200:
return '出错啦!' return "出错啦!"
r=r.json() r = await resp.json()
logger.debug(r) logger.debug(f"post_vcode: {r}")
if "验证码生成成功" in r["message"]: except Exception as e:
resp_data = await send_mail(f'{user}@qq.com',"验证码生成成功",r["message"],"DanDing-Admin") logger.error(f"post_vcode 请求失败: {e}")
if resp_data is None or resp_data.get("errorNo", -1) != 0: return "出错啦!"
return r["message"] msg = r.get("message", "")
else: if "验证码生成成功" in msg:
resp_data = await send_mail(f"{user}@qq.com", "验证码生成成功", msg, "DanDing-Admin")
if resp_data is not None and resp_data.get("errorNo", -1) == 0:
return f"生成的绑定验证码已经发送到 {user}@qq.com 邮箱中,请查收!" return f"生成的绑定验证码已经发送到 {user}@qq.com 邮箱中,请查收!"
return r["message"] return msg
return msg
async def get_log(user:str)->str: async def get_log(user: str, admin_user: str = "1424473282") -> str:
_url:str = plugin_config.DDApi_Host + router["获取日志"] """获取用户操作日志"""
r = requests.get(url=f"{_url}?user=1424473282&token={plugin_config.Token}&qq={user}", timeout=10) _url = plugin_config.DDApi_Host + router["获取日志"]
logger.debug(r) params = {"user": admin_user, "token": plugin_config.Token, "qq": user}
if r.status_code != 200: try:
return '出错啦!' async with aiohttp.ClientSession() as session:
r=r.json() async with session.get(_url, params=params, timeout=aiohttp.ClientTimeout(total=10)) as resp:
logger.debug(r) logger.debug(f"get_log: status={resp.status}")
return r["message"] if resp.status != 200:
return "出错啦!"
r = await resp.json()
logger.debug(f"get_log: {r}")
return r.get("message", "出错啦!")
except Exception as e:
logger.error(f"get_log 请求失败: {e}")
return "出错啦!"
def get_classes(classee:str): def get_classes(classee:str):
@@ -79,77 +98,88 @@ def get_classes(classee:str):
return cases.get(classee, '') return cases.get(classee, '')
# PMail 邮箱服务配置
session_id: str = "" session_id: str = ""
# 登录pmail邮箱 获取cookie
login_url = plugin_config.EMAIL_LOGIN login_url = plugin_config.EMAIL_LOGIN
login_pdata = { login_pdata = {
"account": plugin_config.EMAIL_USER, "account": plugin_config.EMAIL_USER,
"password": plugin_config.EMAIL_PASSWORD "password": plugin_config.EMAIL_PASSWORD
} }
session = requests.session() # 实例化session对象
def login_pmail(): async def login_pmail():
global session_id """登录PMail邮箱服务获取session cookie"""
resp_data = None global session_id
error_msg: str = "" retries = 3
retries = 3 # 设置重试次数 for attempt in range(retries):
for attempt in range(retries): try:
try: async with aiohttp.ClientSession() as sess:
resp_data = session.post(login_url, json=login_pdata, headers={'Content-Type': 'application/json'}) async with sess.post(login_url, json=login_pdata,
if resp_data.status_code == 200 and resp_data.json().get('errorNo') == 0: headers={"Content-Type": "application/json"},
logger.info('PMail App 启动成功!') timeout=aiohttp.ClientTimeout(total=10)) as resp:
session_id = resp_data.headers['Set-Cookie'] if resp.status == 200:
return body = await resp.json()
except ConnectionError: if body.get("errorNo") == 0:
error_msg = "服务器连接失败" logger.info("PMail App 登录成功")
except Exception as e: session_id = resp.headers.get("Set-Cookie", "")
error_msg = str(e) return
logger.warning(f'PMail App 登录失败,正在重试... ({attempt + 1}/{retries})') except Exception as e:
logger.warning(f"PMail App 登录失败 ({attempt + 1}/{retries}): {e}")
# 如果重试次数用尽仍然失败 logger.error("PMail App 登录失败!无法使用邮件功能!")
logger.error(f'PMail App 登录失败!无法使用邮件功能!可能网络错误!{error_msg}')
async def send_mail(mail_to, subject, content, name): async def send_mail(mail_to, subject, content, name):
""" """
发送邮件 发送邮件
:param mail_to: 发送到 :param mail_to: 发送到
:param subject: 标题 :param subject: 标题
:param content: 内容 :param content: 内容
:param name: 用户名 :param name: 用户名
:return: :return:
""" """
url = plugin_config.EMAIL_API url = plugin_config.EMAIL_API
pdata = { pdata = {
'from': 'from':
{ {
"name": "DanDing-Admin", "name": "DanDing-Admin",
"email": plugin_config.EMAIL_FROM "email": plugin_config.EMAIL_FROM
}, },
'to': 'to':
[ [
{ {
"name": name, "name": name,
"email": mail_to "email": mail_to
} }
], ],
'subject': subject, 'subject': subject,
'html': content, 'html': content,
"text": "text" "text": "text"
} }
if not session_id: if not session_id:
logger.error("[error] 邮件发送失败没有session_id尝试重新登录邮箱服务") logger.error("[error] 邮件发送失败没有session_id尝试重新登录邮箱服务")
login_pmail() await login_pmail()
resp_data = session.post(url, json=pdata, headers={"cookie": f"{session_id}"}).json() try:
if resp_data is None or resp_data.get("errorNo", -1) != 0: async with aiohttp.ClientSession() as sess:
logger.error("[error] 邮件发送失败,未知的错误,尝试重新登录邮箱服务!") async with sess.post(url, json=pdata,
# 重新登录pmail邮箱 headers={"cookie": f"{session_id}"},
login_pmail() timeout=aiohttp.ClientTimeout(total=15)) as resp:
resp_data = session.post(url, json=pdata, headers={"cookie": f"{session_id}"}).json() resp_data = await resp.json()
if resp_data is None or resp_data.get("errorNo", -1) != 0: except Exception as e:
return {"errorNo": 0, "errorMsg": "", "data": ""} logger.error(f"[error] 邮件发送请求失败: {e}")
resp_data = None
return resp_data
if resp_data is None or resp_data.get("errorNo", -1) != 0:
logger.error("[error] 邮件发送失败,尝试重新登录邮箱服务!")
await login_pmail()
try:
async with aiohttp.ClientSession() as sess:
async with sess.post(url, json=pdata,
headers={"cookie": f"{session_id}"},
timeout=aiohttp.ClientTimeout(total=15)) as resp:
resp_data = await resp.json()
except Exception:
return {"errorNo": 0, "errorMsg": "", "data": ""}
return resp_data