From 4a944316fec1c1c99bebe698a37746ede7b80506 Mon Sep 17 00:00:00 2001
From: XiaM-Admin <1424473282@qq.com>
Date: Fri, 26 Dec 2025 22:41:42 +0800
Subject: [PATCH] =?UTF-8?q?=E9=A6=96=E6=AC=A1=E6=8F=90=E4=BA=A4?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env.dev | 1 +
.env.prod | 0
.gitignore | 141 +
.vscode/settings.json | 6 +
PLUGINS.md | 175 +
README.md | 62 +
copyback.sh | 3 +
.../plugins/auto_friend_accept/__init__.py | 14 +
.../plugins/auto_friend_accept/auto_accept.py | 48 +
.../plugins/auto_friend_accept/config.py | 9 +
danding_bot/plugins/auto_recall/__init__.py | 53 +
danding_bot/plugins/auto_recall/config.py | 4 +
danding_bot/plugins/chatai/__init__.py | 183 +
danding_bot/plugins/chatai/chrome_manager.py | 26 +
danding_bot/plugins/chatai/config.py | 6 +
danding_bot/plugins/chatai/screenshot.py | 164 +
danding_bot/plugins/chatai/utils/__init__.py | 1 +
.../plugins/chatai/utils/text_image.py | 143 +
danding_bot/plugins/command_list/__init__.py | 4 +
.../plugins/command_list/command_list.py | 50 +
danding_bot/plugins/command_list/config.py | 8 +
.../plugins/damo_balance/AccountSpider.py | 81 +
danding_bot/plugins/damo_balance/__init__.py | 81 +
danding_bot/plugins/damo_balance/config.py | 5 +
.../damo_balance/verification_code.png | Bin 0 -> 193 bytes
danding_bot/plugins/danding_api/__init__.py | 22 +
danding_bot/plugins/danding_api/admin.py | 142 +
danding_bot/plugins/danding_api/config.py | 28 +
danding_bot/plugins/danding_api/utils.py | 155 +
.../plugins/danding_help/HelpCaiDan.md | 15 +
danding_bot/plugins/danding_help/__init__.py | 30 +
danding_bot/plugins/danding_help/config.py | 45 +
danding_bot/plugins/danding_help/help.py | 99 +
.../plugins/danding_help/img/帮助菜单.jpg | Bin 0 -> 113867 bytes
.../plugins/danding_help/img/开软件教程.jpg | Bin 0 -> 54996 bytes
.../plugins/danding_help/img/御魂双开方法.jpg | Bin 0 -> 130990 bytes
danding_bot/plugins/onmyoji_gacha.zip | Bin 0 -> 25155 bytes
danding_bot/plugins/onmyoji_gacha/__init__.py | 771 ++
.../plugins/onmyoji_gacha/api_utils.py | 216 +
danding_bot/plugins/onmyoji_gacha/config.py | 118 +
.../onmyoji_gacha/data/onmyoji_gacha/gacha.db | Bin 0 -> 45056 bytes
.../plugins/onmyoji_gacha/data_manager.py | 616 ++
danding_bot/plugins/onmyoji_gacha/gacha.py | 307 +
.../onmyoji_gacha/templates/admin.html | 799 +++
danding_bot/plugins/onmyoji_gacha/utils.py | 12 +
danding_bot/plugins/onmyoji_gacha/web_api.py | 202 +
.../plugins/welcome_plugin/__init__.py | 14 +
danding_bot/plugins/welcome_plugin/welcome.py | 64 +
data/chatai/output.png | Bin 0 -> 89985 bytes
data/chouka/n/唐纸伞妖.png | Bin 0 -> 7789 bytes
data/chouka/n/天邪鬼绿.png | Bin 0 -> 7405 bytes
data/chouka/n/天邪鬼赤.png | Bin 0 -> 8034 bytes
data/chouka/n/天邪鬼青.png | Bin 0 -> 7965 bytes
data/chouka/n/天邪鬼黄.png | Bin 0 -> 8189 bytes
data/chouka/n/寄生魂.png | Bin 0 -> 8699 bytes
data/chouka/n/帚神.png | Bin 0 -> 5836 bytes
data/chouka/n/提灯小僧.png | Bin 0 -> 6503 bytes
data/chouka/n/涂壁.png | Bin 0 -> 6986 bytes
data/chouka/n/灯笼鬼.png | Bin 0 -> 5474 bytes
data/chouka/n/盗墓小鬼.png | Bin 0 -> 8003 bytes
data/chouka/n/赤舌.png | Bin 0 -> 7934 bytes
data/chouka/r/三尾狐.png | Bin 0 -> 8438 bytes
data/chouka/r/丑时之女.png | Bin 0 -> 8353 bytes
data/chouka/r/九命猫.png | Bin 0 -> 8115 bytes
data/chouka/r/兵俑.png | Bin 0 -> 9450 bytes
data/chouka/r/巫蛊师.png | Bin 0 -> 7787 bytes
data/chouka/r/座敷童子.png | Bin 0 -> 8746 bytes
data/chouka/r/武士之灵.png | Bin 0 -> 8990 bytes
data/chouka/r/河童.png | Bin 0 -> 8038 bytes
data/chouka/r/狸猫.png | Bin 0 -> 9058 bytes
data/chouka/r/童女.png | Bin 0 -> 9426 bytes
data/chouka/r/童男.png | Bin 0 -> 10155 bytes
data/chouka/r/跳跳妹妹.png | Bin 0 -> 7939 bytes
data/chouka/r/跳跳弟弟.png | Bin 0 -> 6985 bytes
data/chouka/r/雨女.png | Bin 0 -> 9911 bytes
data/chouka/r/食发鬼.png | Bin 0 -> 7821 bytes
data/chouka/r/饿鬼.png | Bin 0 -> 9408 bytes
data/chouka/r/鲤鱼精.png | Bin 0 -> 8046 bytes
data/chouka/r/鸦天狗.png | Bin 0 -> 7490 bytes
data/chouka/sp/初翎山风.png | Bin 0 -> 30964 bytes
data/chouka/sp/夜溟彼岸花.png | Bin 0 -> 33402 bytes
data/chouka/sp/天剑韧心鬼切.png | Bin 0 -> 24959 bytes
data/chouka/sp/少羽大天狗.png | Bin 0 -> 6265 bytes
data/chouka/sp/待宵姑获鸟.png | Bin 0 -> 35725 bytes
data/chouka/sp/御怨般若.png | Bin 0 -> 33749 bytes
data/chouka/sp/浮世青行灯.png | Bin 0 -> 33035 bytes
data/chouka/sp/炼狱茨木童子.png | Bin 0 -> 8247 bytes
data/chouka/sp/烬天玉藻前.png | Bin 0 -> 31809 bytes
data/chouka/sp/稻荷神御馔津.png | Bin 0 -> 8528 bytes
data/chouka/sp/缚骨清姬.png | Bin 0 -> 33534 bytes
data/chouka/sp/聆海金鱼姬.png | Bin 0 -> 34202 bytes
data/chouka/sp/苍风一目连.png | Bin 0 -> 6878 bytes
data/chouka/sp/蝉冰雪女.png | Bin 0 -> 26873 bytes
data/chouka/sp/赤影妖刀姬.png | Bin 0 -> 27351 bytes
data/chouka/sp/骁浪荒川之主.png | Bin 0 -> 27368 bytes
data/chouka/sp/鬼王酒吞童子.png | Bin 0 -> 35274 bytes
data/chouka/sp/麓铭大岳丸.png | Bin 0 -> 32883 bytes
data/chouka/sr/傀儡师.png | Bin 0 -> 8692 bytes
data/chouka/sr/凤凰火.png | Bin 0 -> 8246 bytes
data/chouka/sr/判官.png | Bin 0 -> 7554 bytes
data/chouka/sr/吸血姬.png | Bin 0 -> 7939 bytes
data/chouka/sr/妖狐.png | Bin 0 -> 6655 bytes
data/chouka/sr/妖琴师.png | Bin 0 -> 6769 bytes
data/chouka/sr/孟婆.png | Bin 0 -> 8608 bytes
data/chouka/sr/桃花妖.png | Bin 0 -> 8060 bytes
data/chouka/sr/海坊主.png | Bin 0 -> 6899 bytes
data/chouka/sr/清姬.png | Bin 0 -> 8303 bytes
data/chouka/sr/犬神.png | Bin 0 -> 8562 bytes
data/chouka/sr/跳跳哥哥.png | Bin 0 -> 7944 bytes
data/chouka/sr/雪女.png | Bin 0 -> 8748 bytes
data/chouka/sr/食梦貘.png | Bin 0 -> 5927 bytes
data/chouka/sr/骨女.png | Bin 0 -> 8739 bytes
data/chouka/sr/鬼使白.png | Bin 0 -> 8782 bytes
data/chouka/sr/鬼使黑.png | Bin 0 -> 7970 bytes
data/chouka/sr/鬼女红叶.png | Bin 0 -> 8478 bytes
data/chouka/ssr/一目连.png | Bin 0 -> 35880 bytes
data/chouka/ssr/两面佛.png | Bin 0 -> 9980 bytes
data/chouka/ssr/大天狗.png | Bin 0 -> 6343 bytes
data/chouka/ssr/妖刀姬.png | Bin 0 -> 8315 bytes
data/chouka/ssr/小鹿男.png | Bin 0 -> 7075 bytes
data/chouka/ssr/山风.png | Bin 0 -> 8306 bytes
data/chouka/ssr/彼岸花.png | Bin 0 -> 8154 bytes
data/chouka/ssr/御馔津.png | Bin 0 -> 8476 bytes
data/chouka/ssr/玉藻前.png | Bin 0 -> 7801 bytes
data/chouka/ssr/花鸟卷.png | Bin 0 -> 8229 bytes
data/chouka/ssr/茨木童子.png | Bin 0 -> 8754 bytes
data/chouka/ssr/荒.png | Bin 0 -> 9659 bytes
data/chouka/ssr/荒川之主.png | Bin 0 -> 9572 bytes
data/chouka/ssr/辉夜姬.png | Bin 0 -> 9307 bytes
data/chouka/ssr/酒吞童子.png | Bin 0 -> 10191 bytes
data/chouka/ssr/阎魔.png | Bin 0 -> 10312 bytes
data/chouka/ssr/雪童子.png | Bin 0 -> 7957 bytes
data/chouka/ssr/青行灯.png | Bin 0 -> 7424 bytes
data/learning_chat/learning_chat.db | Bin 0 -> 5955584 bytes
data/learning_chat/learning_chat.yml | 114 +
data/onmyoji_gacha/daily_draws.json | 6232 +++++++++++++++++
data/onmyoji_gacha/gacha.db | Bin 0 -> 1622016 bytes
data/onmyoji_gacha/user_stats.json | 5972 ++++++++++++++++
pyproject.toml | 14 +
requirements.txt | 92 +
test_api.py | 73 +
test_routes.py | 47 +
token_test.html | 83 +
143 files changed, 17550 insertions(+)
create mode 100644 .env.dev
create mode 100644 .env.prod
create mode 100644 .gitignore
create mode 100644 .vscode/settings.json
create mode 100644 PLUGINS.md
create mode 100644 README.md
create mode 100755 copyback.sh
create mode 100644 danding_bot/plugins/auto_friend_accept/__init__.py
create mode 100644 danding_bot/plugins/auto_friend_accept/auto_accept.py
create mode 100644 danding_bot/plugins/auto_friend_accept/config.py
create mode 100644 danding_bot/plugins/auto_recall/__init__.py
create mode 100644 danding_bot/plugins/auto_recall/config.py
create mode 100644 danding_bot/plugins/chatai/__init__.py
create mode 100644 danding_bot/plugins/chatai/chrome_manager.py
create mode 100644 danding_bot/plugins/chatai/config.py
create mode 100644 danding_bot/plugins/chatai/screenshot.py
create mode 100644 danding_bot/plugins/chatai/utils/__init__.py
create mode 100644 danding_bot/plugins/chatai/utils/text_image.py
create mode 100644 danding_bot/plugins/command_list/__init__.py
create mode 100644 danding_bot/plugins/command_list/command_list.py
create mode 100644 danding_bot/plugins/command_list/config.py
create mode 100644 danding_bot/plugins/damo_balance/AccountSpider.py
create mode 100644 danding_bot/plugins/damo_balance/__init__.py
create mode 100644 danding_bot/plugins/damo_balance/config.py
create mode 100644 danding_bot/plugins/damo_balance/verification_code.png
create mode 100644 danding_bot/plugins/danding_api/__init__.py
create mode 100644 danding_bot/plugins/danding_api/admin.py
create mode 100644 danding_bot/plugins/danding_api/config.py
create mode 100644 danding_bot/plugins/danding_api/utils.py
create mode 100644 danding_bot/plugins/danding_help/HelpCaiDan.md
create mode 100644 danding_bot/plugins/danding_help/__init__.py
create mode 100644 danding_bot/plugins/danding_help/config.py
create mode 100644 danding_bot/plugins/danding_help/help.py
create mode 100644 danding_bot/plugins/danding_help/img/帮助菜单.jpg
create mode 100644 danding_bot/plugins/danding_help/img/开软件教程.jpg
create mode 100644 danding_bot/plugins/danding_help/img/御魂双开方法.jpg
create mode 100644 danding_bot/plugins/onmyoji_gacha.zip
create mode 100644 danding_bot/plugins/onmyoji_gacha/__init__.py
create mode 100644 danding_bot/plugins/onmyoji_gacha/api_utils.py
create mode 100644 danding_bot/plugins/onmyoji_gacha/config.py
create mode 100644 danding_bot/plugins/onmyoji_gacha/data/onmyoji_gacha/gacha.db
create mode 100644 danding_bot/plugins/onmyoji_gacha/data_manager.py
create mode 100644 danding_bot/plugins/onmyoji_gacha/gacha.py
create mode 100644 danding_bot/plugins/onmyoji_gacha/templates/admin.html
create mode 100644 danding_bot/plugins/onmyoji_gacha/utils.py
create mode 100644 danding_bot/plugins/onmyoji_gacha/web_api.py
create mode 100644 danding_bot/plugins/welcome_plugin/__init__.py
create mode 100644 danding_bot/plugins/welcome_plugin/welcome.py
create mode 100644 data/chatai/output.png
create mode 100644 data/chouka/n/唐纸伞妖.png
create mode 100644 data/chouka/n/天邪鬼绿.png
create mode 100644 data/chouka/n/天邪鬼赤.png
create mode 100644 data/chouka/n/天邪鬼青.png
create mode 100644 data/chouka/n/天邪鬼黄.png
create mode 100644 data/chouka/n/寄生魂.png
create mode 100644 data/chouka/n/帚神.png
create mode 100644 data/chouka/n/提灯小僧.png
create mode 100644 data/chouka/n/涂壁.png
create mode 100644 data/chouka/n/灯笼鬼.png
create mode 100644 data/chouka/n/盗墓小鬼.png
create mode 100644 data/chouka/n/赤舌.png
create mode 100644 data/chouka/r/三尾狐.png
create mode 100644 data/chouka/r/丑时之女.png
create mode 100644 data/chouka/r/九命猫.png
create mode 100644 data/chouka/r/兵俑.png
create mode 100644 data/chouka/r/巫蛊师.png
create mode 100644 data/chouka/r/座敷童子.png
create mode 100644 data/chouka/r/武士之灵.png
create mode 100644 data/chouka/r/河童.png
create mode 100644 data/chouka/r/狸猫.png
create mode 100644 data/chouka/r/童女.png
create mode 100644 data/chouka/r/童男.png
create mode 100644 data/chouka/r/跳跳妹妹.png
create mode 100644 data/chouka/r/跳跳弟弟.png
create mode 100644 data/chouka/r/雨女.png
create mode 100644 data/chouka/r/食发鬼.png
create mode 100644 data/chouka/r/饿鬼.png
create mode 100644 data/chouka/r/鲤鱼精.png
create mode 100644 data/chouka/r/鸦天狗.png
create mode 100644 data/chouka/sp/初翎山风.png
create mode 100644 data/chouka/sp/夜溟彼岸花.png
create mode 100644 data/chouka/sp/天剑韧心鬼切.png
create mode 100644 data/chouka/sp/少羽大天狗.png
create mode 100644 data/chouka/sp/待宵姑获鸟.png
create mode 100644 data/chouka/sp/御怨般若.png
create mode 100644 data/chouka/sp/浮世青行灯.png
create mode 100644 data/chouka/sp/炼狱茨木童子.png
create mode 100644 data/chouka/sp/烬天玉藻前.png
create mode 100644 data/chouka/sp/稻荷神御馔津.png
create mode 100644 data/chouka/sp/缚骨清姬.png
create mode 100644 data/chouka/sp/聆海金鱼姬.png
create mode 100644 data/chouka/sp/苍风一目连.png
create mode 100644 data/chouka/sp/蝉冰雪女.png
create mode 100644 data/chouka/sp/赤影妖刀姬.png
create mode 100644 data/chouka/sp/骁浪荒川之主.png
create mode 100644 data/chouka/sp/鬼王酒吞童子.png
create mode 100644 data/chouka/sp/麓铭大岳丸.png
create mode 100644 data/chouka/sr/傀儡师.png
create mode 100644 data/chouka/sr/凤凰火.png
create mode 100644 data/chouka/sr/判官.png
create mode 100644 data/chouka/sr/吸血姬.png
create mode 100644 data/chouka/sr/妖狐.png
create mode 100644 data/chouka/sr/妖琴师.png
create mode 100644 data/chouka/sr/孟婆.png
create mode 100644 data/chouka/sr/桃花妖.png
create mode 100644 data/chouka/sr/海坊主.png
create mode 100644 data/chouka/sr/清姬.png
create mode 100644 data/chouka/sr/犬神.png
create mode 100644 data/chouka/sr/跳跳哥哥.png
create mode 100644 data/chouka/sr/雪女.png
create mode 100644 data/chouka/sr/食梦貘.png
create mode 100644 data/chouka/sr/骨女.png
create mode 100644 data/chouka/sr/鬼使白.png
create mode 100644 data/chouka/sr/鬼使黑.png
create mode 100644 data/chouka/sr/鬼女红叶.png
create mode 100644 data/chouka/ssr/一目连.png
create mode 100644 data/chouka/ssr/两面佛.png
create mode 100644 data/chouka/ssr/大天狗.png
create mode 100644 data/chouka/ssr/妖刀姬.png
create mode 100644 data/chouka/ssr/小鹿男.png
create mode 100644 data/chouka/ssr/山风.png
create mode 100644 data/chouka/ssr/彼岸花.png
create mode 100644 data/chouka/ssr/御馔津.png
create mode 100644 data/chouka/ssr/玉藻前.png
create mode 100644 data/chouka/ssr/花鸟卷.png
create mode 100644 data/chouka/ssr/茨木童子.png
create mode 100644 data/chouka/ssr/荒.png
create mode 100644 data/chouka/ssr/荒川之主.png
create mode 100644 data/chouka/ssr/辉夜姬.png
create mode 100644 data/chouka/ssr/酒吞童子.png
create mode 100644 data/chouka/ssr/阎魔.png
create mode 100644 data/chouka/ssr/雪童子.png
create mode 100644 data/chouka/ssr/青行灯.png
create mode 100644 data/learning_chat/learning_chat.db
create mode 100644 data/learning_chat/learning_chat.yml
create mode 100644 data/onmyoji_gacha/daily_draws.json
create mode 100644 data/onmyoji_gacha/gacha.db
create mode 100644 data/onmyoji_gacha/user_stats.json
create mode 100644 pyproject.toml
create mode 100644 requirements.txt
create mode 100644 test_api.py
create mode 100644 test_routes.py
create mode 100644 token_test.html
diff --git a/.env.dev b/.env.dev
new file mode 100644
index 0000000..2a1b856
--- /dev/null
+++ b/.env.dev
@@ -0,0 +1 @@
+LOG_LEVEL=DEBUG
diff --git a/.env.prod b/.env.prod
new file mode 100644
index 0000000..e69de29
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..093255f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,141 @@
+
+# Created by https://www.toptal.com/developers/gitignore/api/python
+# Edit at https://www.toptal.com/developers/gitignore?templates=python
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+pytestdebug.log
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+doc/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# End of https://www.toptal.com/developers/gitignore/api/python
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..c35dcc0
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+ "python.languageServer": "None",
+ "python.analysis.typeCheckingMode": "basic",
+ "python-envs.defaultEnvManager": "ms-python.python:system",
+ "python-envs.pythonProjects": []
+}
\ No newline at end of file
diff --git a/PLUGINS.md b/PLUGINS.md
new file mode 100644
index 0000000..671d976
--- /dev/null
+++ b/PLUGINS.md
@@ -0,0 +1,175 @@
+# 蛋定助手插件文档
+
+## 项目概述
+
+蛋定助手是一个基于NoneBot2框架开发的QQ机器人,提供多种功能插件,包括AI聊天、管理API、自动撤回消息等。该机器人主要面向特定用户群体,提供游戏辅助和社群管理功能。
+
+## 插件总览
+
+| 插件名称 | 描述 | 权限要求 |
+|---------|------|---------|
+| chatai | AI聊天功能,对接DeepSeek | 所有用户 |
+| auto_recall | 消息自动撤回 | 系统自动执行 |
+| damo_balance | 大漠账户余额查询 | 特定用户 |
+| danding_api | 蛋定助手管理API | 超级用户 |
+| danding_help | 帮助信息 | 特定群用户 |
+| command_list | 命令列表管理 | 系统使用 |
+
+## 详细插件文档
+
+### 1. chatai - AI聊天插件
+
+#### 功能描述
+基于DeepSeek AI的聊天功能,支持将AI回复转换为图片形式,并在一定时间后自动撤回。
+
+#### 使用方法
+- 发送以 `*` 开头的消息触发AI回复
+- AI回复会自动转为图片显示
+- 回复会在120秒后自动撤回
+
+#### 配置项
+```env
+DEEPSEEK_TOKEN=你的DeepSeek API密钥
+```
+
+#### 技术实现
+- 使用OpenAI客户端连接DeepSeek API
+- 使用Pyppeteer将Markdown转为图片
+- 内置Chrome浏览器实例管理
+
+#### 示例
+用户: *你好,请介绍一下自己
+AI: [图片形式回复] 👋 你好呀!我是蛋定助手,一个活泼可爱的AI助手!😊 很高兴认识你!有什么我能帮到你的吗?✨
+
+---
+
+### 2. auto_recall - 自动撤回插件
+
+#### 功能描述
+监控所有发出的消息,并在指定时间后自动撤回,保持聊天环境整洁。
+
+#### 使用方法
+- 无需手动调用,插件会自动监控并撤回消息
+
+#### 配置项
+```env
+RECALL_DELAY=110 # 撤回延迟时间,单位为秒
+```
+
+#### 技术实现
+- 使用NoneBot的API拦截功能
+- 异步定时任务管理
+- 错误处理与日志记录
+
+---
+
+### 3. damo_balance - 大漠账户余额查询
+
+#### 功能描述
+查询大漠平台账户余额,需要验证码验证。
+
+#### 使用方法
+- 命令:`大漠余额`或`余额查询`
+- 需要验证码验证
+
+#### 权限要求
+- 仅特定用户(ID:1424473282)可使用
+
+---
+
+### 4. danding_api - 蛋定助手管理API
+
+#### 功能描述
+提供管理员操作接口,包括在线人数查询、卡密管理和用户时长管理功能。
+
+#### 使用方法
+主要命令:
+- `在线人数`:查询当前在线用户数
+- `添加卡密 [类型] [卡密]`:添加指定类型的卡密
+- `生成卡密 [类型]`:生成新卡密
+- `用户加时 [用户名] [类型]`:为指定用户增加使用时长
+
+#### 卡密类型
+- 天卡/day/Day/DAY/天
+- 周卡/week/Week/WEEK/周
+- 月卡/month/Month/MONTH/月
+
+#### 权限要求
+- 仅超级用户可使用
+
+#### 配置项
+```env
+SUPERUSERS=["1424473282"] # 超级用户ID列表
+```
+
+#### 示例
+```
+在线人数
+> 当前在线用户数: 42
+
+添加卡密 天卡 ABCD1234
+> 添加卡密成功:天卡 ABCD1234
+
+生成卡密 周卡
+> 生成卡密成功:周卡 XYZ789ABC
+
+用户加时 test_user 月卡
+> 用户加时成功:test_user 增加了30天
+```
+
+---
+
+### 5. danding_help - 帮助信息
+
+#### 功能描述
+提供各种帮助信息和指南,支持图片形式的教程和指引。
+
+#### 使用方法
+主要命令:
+- `帮助`:显示帮助菜单
+- `下载`:显示下载信息
+- `公益版`/`正式版`:显示版本信息
+- `正式版御魂双开`:显示双开教程
+- `正式版如何运行`:显示运行教程
+- `正式版内测计划`:显示内测信息
+
+#### 权限要求
+- 仅在特定群(621016172)可用
+
+#### 技术实现
+- 使用图片回复提供直观的教程
+- 文本与图片混合响应
+
+---
+
+### 6. command_list - 命令列表管理
+
+#### 功能描述
+管理系统命令列表,提供命令过滤和权限控制。
+
+#### 使用方法
+- 系统内部使用,不直接暴露给用户
+
+## 常见问题
+
+### Q1: 如何启动蛋定助手?
+A1: 使用`nb run`命令启动,确保已安装所有依赖。
+
+### Q2: 机器人回复后自动撤回的时间可以修改吗?
+A2: 可以,在`.env`文件中修改`RECALL_DELAY`的值(单位为秒)。
+
+### Q3: 如何成为超级用户?
+A3: 在`.env`文件的`SUPERUSERS`列表中添加您的QQ号。
+
+### Q4: AI聊天功能如何配置?
+A4: 需要在`.env`文件中设置`DEEPSEEK_TOKEN`,填入您的DeepSeek API密钥。
+
+### Q5: 为什么帮助命令在某些群不可用?
+A5: 帮助命令仅在特定群(621016172)内可用,这是一种权限控制机制。
+
+## 技术支持
+
+如有问题,可以:
+1. 在群内@机器人并提问
+2. 访问帮助文档:https://www.danding.icu
+3. 联系超级用户获取支持
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..50bae2f
--- /dev/null
+++ b/README.md
@@ -0,0 +1,62 @@
+# danding-bot
+基于:[NoneBot](https://nonebot.dev/)
+
+## 食用步骤
+- 需要安装的Python版本:3.10.12,建议使用Anaconda虚拟环境
+- `conda create --name bot python=3.10.12`
+- `pip install -r requirements.txt`
+- `nb run`
+
+## 创建插件
+使用 `nb plugin create` 快速创建空插件
+
+## 已经安装的包
+
+## 已安装插件列表
+### 1. nonebot_plugin_withdraw - 消息撤回
+- 回复机器人指定的语句,发送撤回即可
+
+### 2. nonebot_plugin_learning_chat - 群聊学习
+> [项目链接](https://github.com/CMHopeSunshine/nonebot-plugin-learning-chat)
+- 功能启停:@Bot 开启学习\学说话\快学 \关闭学习\别学\闭嘴
+- 禁用回复:@Bot 不可以\达咩\不能说这 [需回复机器人的发言]
+- 后台管理:`http://127.0.0.1:8080/learning_chat/admin`
+- 配置文件:`Bot目录/data/learing_chat/learning_chat.yml`
+
+### 3. auto_recall - 消息自动撤回
+- 自动监控所有发出的消息,在指定时间后撤回
+- 默认配置:110秒后自动撤回
+
+### 4. chatai - AI聊天
+- 对接DeepSeek的聊天AI服务
+- 使用方式:发送以`*`开头的消息触发AI回复
+- 支持切换AI模型(目前仅支持deepseek)
+- AI回复会在100秒后自动撤回
+
+### 5. damo_balance - 大漠账户余额查询
+- 命令:`大漠余额`或`余额查询`
+- 需要验证码验证
+- 仅特定用户(ID:1424473282)可使用
+
+### 6. danding_api - 蛋定助手管理API
+- 提供管理员操作接口
+- 主要命令:
+ - `在线人数`:查询当前在线用户数
+ - `添加卡密`:添加指定类型的卡密
+ - `生成卡密`:生成新卡密
+ - `用户加时`:为指定用户增加使用时长
+- 仅超级用户可用
+
+### 7. danding_help - 帮助信息
+- 主要命令:
+ - `帮助`:显示帮助菜单
+ - `下载`:显示下载信息
+ - `公益版`/`正式版`:显示版本信息
+ - `正式版御魂双开`:显示双开教程
+ - `正式版如何运行`:显示运行教程
+ - `正式版内测计划`:显示内测信息
+- 仅在特定群(621016172)可用
+
+注意:
+- 该项目自带后台ui入口:`http://127.0.0.1:8080/learning_chat/admin`
+- 配置路径在:`Bot目录/data/learing_chat/learning_chat.yml`
\ No newline at end of file
diff --git a/copyback.sh b/copyback.sh
new file mode 100755
index 0000000..d462bee
--- /dev/null
+++ b/copyback.sh
@@ -0,0 +1,3 @@
+# 备份文件
+rm NoneBot_DanDing_3.10.12.zip
+zip -r NoneBot_DanDing_3.10.12.zip *
\ No newline at end of file
diff --git a/danding_bot/plugins/auto_friend_accept/__init__.py b/danding_bot/plugins/auto_friend_accept/__init__.py
new file mode 100644
index 0000000..0944b94
--- /dev/null
+++ b/danding_bot/plugins/auto_friend_accept/__init__.py
@@ -0,0 +1,14 @@
+from nonebot import get_plugin_config
+from nonebot.plugin import PluginMetadata
+from . import auto_accept
+
+__plugin_meta__ = PluginMetadata(
+ name="auto_friend_accept",
+ description="自动同意好友请求插件",
+ usage="""
+# 自动好友请求接受插件
+当收到好友请求时,会自动同意
+
+无需用户操作,插件自动处理所有好友请求
+""",
+)
\ No newline at end of file
diff --git a/danding_bot/plugins/auto_friend_accept/auto_accept.py b/danding_bot/plugins/auto_friend_accept/auto_accept.py
new file mode 100644
index 0000000..669bcc3
--- /dev/null
+++ b/danding_bot/plugins/auto_friend_accept/auto_accept.py
@@ -0,0 +1,48 @@
+from nonebot import on_request, get_plugin_config, logger
+from nonebot.adapters.onebot.v11 import FriendRequestEvent, Bot
+from nonebot.typing import T_State
+from .config import Config
+import asyncio
+import random
+
+# 获取插件配置
+plugin_config = get_plugin_config(Config)
+
+# 注册好友请求事件处理器
+friend_request = on_request(priority=5, block=True)
+
+@friend_request.handle()
+async def handle_friend_request(bot: Bot, event: FriendRequestEvent, state: T_State):
+ """处理好友请求,根据配置自动同意并发送欢迎消息"""
+
+ # 检查是否启用自动同意
+ if not plugin_config.auto_accept_enabled:
+ logger.info(f"收到来自 {event.user_id} 的好友请求,但自动同意功能已禁用")
+ return
+
+ try:
+ # 获取请求的标识信息
+ flag = event.flag
+
+ # 调用OneBot接口处理好友请求(设置为同意)
+ await bot.set_friend_add_request(flag=flag, approve=True)
+
+ logger.info(f"已自动同意来自 {event.user_id} 的好友请求")
+
+ # 如果配置了自动回复消息,则发送欢迎消息
+ if plugin_config.auto_reply_message:
+ # 添加随机延迟,模拟真人回复
+ await asyncio.sleep(random.uniform(2, 5))
+
+ try:
+ # 发送欢迎消息
+ await bot.send_private_msg(
+ user_id=event.user_id,
+ message=plugin_config.auto_reply_message
+ )
+ logger.info(f"已向新好友 {event.user_id} 发送欢迎消息")
+ except Exception as e:
+ logger.error(f"向新好友 {event.user_id} 发送欢迎消息失败: {e}")
+
+ except Exception as e:
+ logger.error(f"处理好友请求失败: {e}")
\ No newline at end of file
diff --git a/danding_bot/plugins/auto_friend_accept/config.py b/danding_bot/plugins/auto_friend_accept/config.py
new file mode 100644
index 0000000..8f87ea9
--- /dev/null
+++ b/danding_bot/plugins/auto_friend_accept/config.py
@@ -0,0 +1,9 @@
+from pydantic import BaseModel, validator
+from typing import Optional
+
+class Config(BaseModel):
+ # 是否启用自动同意好友请求
+ auto_accept_enabled: bool = True
+
+ # 自动回复的消息,如果为空则不发送
+ auto_reply_message: Optional[str] = ""
\ No newline at end of file
diff --git a/danding_bot/plugins/auto_recall/__init__.py b/danding_bot/plugins/auto_recall/__init__.py
new file mode 100644
index 0000000..f046ba0
--- /dev/null
+++ b/danding_bot/plugins/auto_recall/__init__.py
@@ -0,0 +1,53 @@
+import asyncio
+from typing import Optional, Dict, Any
+from nonebot import get_driver, get_plugin_config, logger
+from nonebot.adapters.onebot.v11 import Bot
+from nonebot.plugin import PluginMetadata
+from nonebot.exception import MockApiException
+from nonebot.adapters import Bot
+from nonebot.typing import T_State
+
+from .config import Config
+
+# 插件元信息
+__plugin_meta__ = PluginMetadata(
+ name="auto_recall",
+ description="一个通用的消息撤回插件,监控所有发出的消息并在指定时间后撤回",
+ usage="无需手动调用,插件会自动监控并撤回消息",
+ config=Config,
+)
+
+# 获取插件配置
+plugin_config = get_plugin_config(Config)
+
+# 注册 API 调用后钩子
+@Bot.on_called_api
+async def handle_api_result(
+ bot: Bot, exception: Optional[Exception], api: str, data: Dict[str, Any], result: Any
+):
+ """拦截 send_msg API 调用,监控发出的消息"""
+ if api != "send_msg" or exception:
+ return
+
+ # 获取消息 ID
+ message_id = result.get("message_id")
+ if not message_id:
+ logger.warning("未找到 message_id,无法撤回消息")
+ return
+
+ # 获取撤回延迟时间
+ recall_delay = plugin_config.recall_delay
+
+ # 启动异步任务,延迟撤回消息
+ asyncio.create_task(recall_message_after_delay(bot, message_id, recall_delay))
+
+async def recall_message_after_delay(bot: Bot, message_id: int, delay: int):
+ """在指定时间后撤回消息"""
+ await asyncio.sleep(delay) # 等待指定时间
+ try:
+ await bot.delete_msg(message_id=message_id) # 撤回消息
+ except Exception as e:
+ if "success" in str(e).lower() or "timeout" in str(e).lower():
+ # 忽略成功和超时的错误
+ return
+ logger.error(f"撤回消息失败: {str(e)}")
\ No newline at end of file
diff --git a/danding_bot/plugins/auto_recall/config.py b/danding_bot/plugins/auto_recall/config.py
new file mode 100644
index 0000000..e57a695
--- /dev/null
+++ b/danding_bot/plugins/auto_recall/config.py
@@ -0,0 +1,4 @@
+from pydantic import BaseModel, Field
+
+class Config(BaseModel):
+ recall_delay: int = Field(default=110, env="RECALL_DELAY") # 撤回延迟时间,默认 110 秒
\ No newline at end of file
diff --git a/danding_bot/plugins/chatai/__init__.py b/danding_bot/plugins/chatai/__init__.py
new file mode 100644
index 0000000..9798489
--- /dev/null
+++ b/danding_bot/plugins/chatai/__init__.py
@@ -0,0 +1,183 @@
+import asyncio
+import random
+import os
+import signal
+import sys
+import atexit
+import subprocess
+import threading
+from nonebot import on_message, get_plugin_config, get_driver
+from nonebot.adapters.onebot.v11 import MessageEvent, Bot, MessageSegment
+from nonebot.plugin import PluginMetadata
+from nonebot.exception import FinishedException
+from openai import OpenAI
+from .config import Config
+from .utils.text_image import create_text_image
+from .screenshot import markdown_to_image
+import pyppeteer
+import pyppeteer.launcher
+import types
+
+# 插件元信息
+__plugin_meta__ = PluginMetadata(
+ name="chatai",
+ description="一个对接 DeepSeek 的聊天 AI 插件",
+ usage="发送以 * 开头的消息,AI 会回复你,两分钟后自动撤销",
+ config=Config,
+)
+
+# 获取插件配置
+plugin_config = get_plugin_config(Config)
+
+# 全局浏览器实例
+browser = None
+browser_lock = threading.Lock()
+
+# 注册消息事件处理器
+message_handler = on_message(priority=50, block=True)
+
+# 确保输出目录存在
+os.makedirs("data/chatai", exist_ok=True)
+
+# 获取 NoneBot 驱动器
+driver = get_driver()
+
+# 定义强制终止 Chrome 的函数
+def force_kill_chrome():
+ """强制终止所有 Chrome 进程"""
+ try:
+ if sys.platform == 'win32':
+ subprocess.run(['taskkill', '/F', '/IM', 'chrome.exe'],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ else:
+ subprocess.run(['pkill', '-9', '-f', 'chrome'],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ except:
+ pass
+
+# 在启动时确保没有残留的 Chrome 进程
+force_kill_chrome()
+
+# 注册退出处理函数
+atexit.register(force_kill_chrome)
+
+# 注册信号处理
+def signal_handler(sig, frame):
+ """处理终止信号"""
+ # 直接强制终止 Chrome 进程,不使用 Pyppeteer 的关闭方法
+ force_kill_chrome()
+ # 强制退出程序
+ os._exit(0)
+
+# 注册信号处理器
+signal.signal(signal.SIGINT, signal_handler)
+signal.signal(signal.SIGTERM, signal_handler)
+
+@driver.on_shutdown
+async def close_browser():
+ """在 NoneBot 关闭时关闭浏览器"""
+ global browser
+ with browser_lock:
+ if browser is not None:
+ try:
+ await browser.close()
+ except:
+ pass
+ browser = None
+ # 确保所有 Chrome 进程都被终止
+ force_kill_chrome()
+
+# 替代方案:直接替换信号处理器
+def noop_signal_handler(sig, frame):
+ pass
+
+# 保存原始信号处理器
+original_sigint = signal.getsignal(signal.SIGINT)
+original_sigterm = signal.getsignal(signal.SIGTERM)
+
+# 在启动浏览器前替换信号处理器
+async def init_browser():
+ """初始化浏览器实例"""
+ global browser
+ with browser_lock:
+ if browser is None or not hasattr(browser, 'process') or not browser.process:
+ # 替换信号处理器
+ signal.signal(signal.SIGINT, noop_signal_handler)
+ signal.signal(signal.SIGTERM, noop_signal_handler)
+
+ try:
+ browser = await pyppeteer.launch(
+ headless=True,
+ args=['--no-sandbox', '--disable-setuid-sandbox', '--disable-gpu']
+ )
+ finally:
+ # 恢复我们的信号处理器
+ signal.signal(signal.SIGINT, signal_handler)
+ signal.signal(signal.SIGTERM, signal_handler)
+ return browser
+
+async def call_ai_api(message: str) -> str:
+ """调用 AI 接口"""
+ client = OpenAI(
+ api_key=plugin_config.deepseek_token,
+ base_url="https://api.siliconflow.cn/v1"
+ )
+ response = client.chat.completions.create(
+ model="deepseek-ai/DeepSeek-V3",
+ messages=[
+ {"role": "system", "content": "你是一个活泼可爱的群助手。请在回复中使用丰富的 Emoji 表情(如 😊 😄 🎉 ✨ 💖 等)。你的语气要俏皮活泼,像在和朋友聊天一样自然。在回答问题时要保持专业性的同时,也要让回复显得生动有趣。每条回复都必须包含至少2-3个 Emoji 表情。如果你需要展示代码片段,请确保代码中不包含任何颜文字或表情符号,保持代码的专业性和可读性。"},
+ {"role": "user", "content": message},
+ ],
+ stream=False
+ )
+ return response.choices[0].message.content or ""
+
+@message_handler.handle()
+async def handle_message(event: MessageEvent, bot: Bot):
+ # 获取用户发送的消息内容
+ user_message = event.get_plaintext().strip()
+
+ # 检查消息是否以 * 开头
+ if not user_message.startswith("*"):
+ return # 如果不是以 * 开头,直接返回,不处理
+
+ # 去掉开头的 * 并去除多余空格
+ user_message = user_message[1:].strip()
+
+ # 如果消息为空,直接返回
+ if not user_message:
+ await asyncio.sleep(random.uniform(2, 3))
+ await message_handler.finish("请输入有效内容哦~")
+
+ # 调用模型 API
+ try:
+ # 初始化浏览器
+ browser = await init_browser()
+
+ # 调用 AI API
+ response = await call_ai_api(user_message)
+
+ if response:
+ await asyncio.sleep(random.uniform(2, 3))
+ # 使用 markdown_to_image 生成图片
+ image_path = 'data/chatai/output.png'
+ await markdown_to_image(response, image_path, browser)
+
+ # 发送图片消息
+ sent_message = await bot.send(event, MessageSegment.image(f"file:///{os.path.abspath(image_path)}"))
+
+ # 启动定时任务,两分钟后撤销消息
+ asyncio.create_task(delete_message_after_delay(bot, sent_message["message_id"]))
+ except FinishedException:
+ pass
+ except Exception as e:
+ await asyncio.sleep(random.uniform(2, 3))
+ await message_handler.finish(f"出错了: {str(e)}")
+
+async def delete_message_after_delay(bot: Bot, message_id: int):
+ """两分钟后撤销消息"""
+ await asyncio.sleep(120) # 等待两分钟
+ try:
+ await bot.delete_msg(message_id=message_id)
+ except:
+ pass
\ No newline at end of file
diff --git a/danding_bot/plugins/chatai/chrome_manager.py b/danding_bot/plugins/chatai/chrome_manager.py
new file mode 100644
index 0000000..69e617f
--- /dev/null
+++ b/danding_bot/plugins/chatai/chrome_manager.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python3
+import os
+import sys
+import subprocess
+
+def kill_chrome_processes():
+ """强制终止所有 Chrome 进程"""
+ try:
+ if sys.platform == 'win32':
+ subprocess.run(['taskkill', '/F', '/IM', 'chrome.exe'],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ subprocess.run(['taskkill', '/F', '/IM', 'chromedriver.exe'],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ else:
+ # 使用 pkill 终止所有 Chrome 相关进程
+ subprocess.run(['pkill', '-9', '-f', 'chrome'],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ subprocess.run(['pkill', '-9', '-f', 'chromium'],
+ stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
+ return True
+ except Exception:
+ return False
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1 and sys.argv[1] == "kill":
+ kill_chrome_processes()
\ No newline at end of file
diff --git a/danding_bot/plugins/chatai/config.py b/danding_bot/plugins/chatai/config.py
new file mode 100644
index 0000000..5cb7150
--- /dev/null
+++ b/danding_bot/plugins/chatai/config.py
@@ -0,0 +1,6 @@
+from pydantic import BaseModel, Field
+
+class Config(BaseModel):
+ """Plugin Config Here"""
+ deepseek_token: str = Field(..., env="DEEPSEEK_TOKEN")
+ # grok_token: str = Field(..., env="GROK_TOKEN")
\ No newline at end of file
diff --git a/danding_bot/plugins/chatai/screenshot.py b/danding_bot/plugins/chatai/screenshot.py
new file mode 100644
index 0000000..b3c029e
--- /dev/null
+++ b/danding_bot/plugins/chatai/screenshot.py
@@ -0,0 +1,164 @@
+import asyncio
+import markdown
+from pyppeteer import launch
+
+async def markdown_to_image(markdown_text: str, output_path: str, browser=None):
+ """将 Markdown 转换为 HTML 并使用 Puppeteer 截图。"""
+ try:
+ # 将 Markdown 转换为 HTML
+ html = markdown.markdown(markdown_text)
+
+ # 使用传入的浏览器实例或创建新的
+ should_close_browser = False
+ if browser is None:
+ browser = await launch(headless=True, args=['--no-sandbox', '--disable-setuid-sandbox'])
+ should_close_browser = True
+
+ page = await browser.newPage()
+
+ # 设置页面样式,使内容更美观
+ await page.setContent(f"""
+
+
+
+
+
+
+ {html}
+
+
+
+ """)
+
+ # 等待内容渲染完成
+ await asyncio.sleep(0.5)
+
+ # 获取内容尺寸并设置视口
+ dimensions = await page.evaluate('''() => {
+ const container = document.querySelector('.container');
+ return {
+ width: container.offsetWidth + 60, // 加上 body 的 padding
+ height: container.offsetHeight + 60
+ }
+ }''')
+
+ # 设置视口大小
+ await page.setViewport({
+ 'width': dimensions['width'],
+ 'height': dimensions['height'],
+ 'deviceScaleFactor': 2 # 提高图片清晰度
+ })
+
+ # 截图,使用透明背景
+ await page.screenshot({
+ 'path': output_path,
+ 'omitBackground': True, # 透明背景
+ 'clip': {
+ 'x': 0,
+ 'y': 0,
+ 'width': dimensions['width'],
+ 'height': dimensions['height']
+ }
+ })
+
+ # 关闭页面
+ await page.close()
+
+ # 如果是我们创建的浏览器实例,则关闭它
+ if should_close_browser:
+ await browser.close()
+
+ except Exception as e:
+ # 确保资源被释放
+ if 'page' in locals() and page is not None:
+ await page.close()
+ if should_close_browser and 'browser' in locals() and browser is not None:
+ await browser.close()
+ raise # 重新抛出异常以便上层处理
\ No newline at end of file
diff --git a/danding_bot/plugins/chatai/utils/__init__.py b/danding_bot/plugins/chatai/utils/__init__.py
new file mode 100644
index 0000000..c6b1e12
--- /dev/null
+++ b/danding_bot/plugins/chatai/utils/__init__.py
@@ -0,0 +1 @@
+"""文本处理工具包"""
\ No newline at end of file
diff --git a/danding_bot/plugins/chatai/utils/text_image.py b/danding_bot/plugins/chatai/utils/text_image.py
new file mode 100644
index 0000000..350c6a4
--- /dev/null
+++ b/danding_bot/plugins/chatai/utils/text_image.py
@@ -0,0 +1,143 @@
+from PIL import Image, ImageDraw, ImageFont
+import io
+
+def create_text_image(text: str, width: int = 1000, font_size: int = 25) -> bytes:
+ """将文本转换为图片,智能处理各种字符"""
+ def load_fonts():
+ """加载文本和 Emoji 字体"""
+ # 尝试加载 Emoji 字体
+ emoji_font = None
+ try:
+ emoji_font = ImageFont.truetype("/usr/share/fonts/truetype/noto/NotoColorEmoji.ttf", font_size)
+ print("成功加载 Emoji 字体")
+ except Exception as e:
+ print(f"加载 Emoji 字体失败: {e}")
+
+ # 尝试加载文本字体
+ text_font = None
+ font_paths = [
+ "/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",
+ ]
+
+ for path in font_paths:
+ try:
+ text_font = ImageFont.truetype(path, font_size)
+ print(f"成功加载文本字体: {path}")
+ break
+ except Exception:
+ continue
+
+ if text_font is None:
+ text_font = ImageFont.load_default()
+ print("使用默认字体")
+
+ return text_font, emoji_font
+
+ def is_emoji(char):
+ """判断字符是否为 Emoji"""
+ return len(char.encode('utf-8')) >= 4
+
+ def draw_text_with_fonts(draw, text, x, y, text_font, emoji_font):
+ """使用不同的字体绘制文本和 Emoji"""
+ current_x = x
+ for char in text:
+ # 选择合适的字体
+ font = emoji_font if (is_emoji(char) and emoji_font) else text_font
+
+ # 绘制字符
+ draw.text((current_x, y), char, font=font, fill=(0, 0, 0))
+
+ # 计算字符宽度
+ bbox = draw.textbbox((current_x, y), char, font=font)
+ char_width = bbox[2] - bbox[0]
+ current_x += char_width
+
+ return current_x - x
+
+ def calculate_text_dimensions(text, text_font, emoji_font):
+ """计算文本尺寸"""
+ test_img = Image.new('RGB', (1, 1), color=(255, 255, 255))
+ test_draw = ImageDraw.Draw(test_img)
+
+ total_width = 0
+ max_height = 0
+
+ for char in text:
+ font = emoji_font if (is_emoji(char) and emoji_font) else text_font
+ bbox = test_draw.textbbox((0, 0), char, font=font)
+ char_width = bbox[2] - bbox[0]
+ char_height = bbox[3] - bbox[1]
+ total_width += char_width
+ max_height = max(max_height, char_height)
+
+ return total_width, max_height
+
+ # 加载字体
+ text_font, emoji_font = load_fonts()
+
+ # 基础配置
+ padding = 40
+ effective_width = width - (2 * padding)
+
+ def smart_text_wrap(text):
+ """智能文本换行"""
+ lines = []
+ current_line = ""
+ current_width = 0
+
+ for paragraph in text.split('\n'):
+ if not paragraph:
+ lines.append("")
+ continue
+
+ for char in paragraph:
+ char_width, _ = calculate_text_dimensions(char, text_font, emoji_font)
+
+ if current_width + char_width <= effective_width:
+ current_line += char
+ current_width += char_width
+ else:
+ if current_line:
+ lines.append(current_line)
+ current_line = char
+ current_width = char_width
+
+ if current_line:
+ lines.append(current_line)
+ current_line = ""
+ current_width = 0
+
+ return lines
+
+ # 智能换行处理
+ lines = smart_text_wrap(text)
+
+ # 计算行高
+ _, line_height = calculate_text_dimensions("测试", text_font, emoji_font)
+ line_spacing = int(line_height * 0.5) # 行间距为行高的50%
+ total_line_height = line_height + line_spacing
+
+ # 计算总高度
+ total_height = (len(lines) * total_line_height) + (2 * padding)
+ height = max(total_height, 200) # 最小高度200像素
+
+ # 创建图片
+ image = Image.new('RGB', (width, int(height)), color=(252, 252, 252))
+ draw = ImageDraw.Draw(image)
+
+ # 绘制文本
+ y = padding
+ for line in lines:
+ if line: # 跳过空行
+ draw_text_with_fonts(draw, line, padding, y, text_font, emoji_font)
+ y += total_line_height
+
+ # 转换为字节流
+ img_byte_arr = io.BytesIO()
+ image.save(img_byte_arr, format='PNG', quality=95)
+ img_byte_arr.seek(0)
+ return img_byte_arr.getvalue()
\ No newline at end of file
diff --git a/danding_bot/plugins/command_list/__init__.py b/danding_bot/plugins/command_list/__init__.py
new file mode 100644
index 0000000..6b0369f
--- /dev/null
+++ b/danding_bot/plugins/command_list/__init__.py
@@ -0,0 +1,4 @@
+from . import command_list
+from .config import Config
+
+__plugin_meta__ = Config
\ No newline at end of file
diff --git a/danding_bot/plugins/command_list/command_list.py b/danding_bot/plugins/command_list/command_list.py
new file mode 100644
index 0000000..640707a
--- /dev/null
+++ b/danding_bot/plugins/command_list/command_list.py
@@ -0,0 +1,50 @@
+from nonebot import on_command, get_loaded_plugins, logger
+from nonebot.rule import fullmatch
+from nonebot.adapters.onebot.v11.event import MessageEvent
+from nonebot.plugin import Plugin
+from nonebot_plugin_saa import Text, MessageFactory
+import random
+import asyncio
+
+ALLOWED_USER = 1424473282
+
+async def check_user(event: MessageEvent) -> bool:
+ """检查用户是否有权限使用该命令"""
+ return event.user_id == ALLOWED_USER
+
+cmd = on_command(
+ "指令列表",
+ rule=check_user and fullmatch(("指令列表", "命令列表", "help list", "cmd list")),
+ aliases={"命令列表", "help list", "cmd list"},
+ priority=1,
+ block=True
+)
+
+def format_plugin_info(plugin: Plugin) -> str:
+ """格式化插件信息"""
+ info = []
+ if hasattr(plugin, "metadata") and plugin.metadata:
+ meta = plugin.metadata
+ if hasattr(meta, "name") and meta.name:
+ info.append(f"插件名称: {meta.name}")
+ if hasattr(meta, "description") and meta.description:
+ info.append(f"功能描述: {meta.description}")
+ if hasattr(meta, "usage") and meta.usage:
+ info.append(f"使用方法: {meta.usage}")
+ return "\n".join(info) if info else f"插件: {plugin.name}"
+
+@cmd.handle()
+async def handle_command_list():
+ plugins = get_loaded_plugins()
+ msg_parts = ["当前支持的指令列表:\n"]
+
+ for plugin in plugins:
+ plugin_info = format_plugin_info(plugin)
+ if plugin_info:
+ msg_parts.append(f"\n{plugin_info}\n{'='*30}")
+
+ await asyncio.sleep(random.uniform(1, 2))
+ await MessageFactory([Text("\n".join(msg_parts))]).send(
+ at_sender=True,
+ reply=True
+ )
\ No newline at end of file
diff --git a/danding_bot/plugins/command_list/config.py b/danding_bot/plugins/command_list/config.py
new file mode 100644
index 0000000..59d41d6
--- /dev/null
+++ b/danding_bot/plugins/command_list/config.py
@@ -0,0 +1,8 @@
+from pydantic import BaseModel, Field
+
+class Config(BaseModel):
+ """命令列表插件配置"""
+ plugin_name: str = "命令列表"
+ plugin_description: str = "获取当前机器人支持的所有指令"
+ plugin_usage: str = "发送 '指令列表' 获取所有支持的指令"
+ plugin_author: str = "Assistant"
\ No newline at end of file
diff --git a/danding_bot/plugins/damo_balance/AccountSpider.py b/danding_bot/plugins/damo_balance/AccountSpider.py
new file mode 100644
index 0000000..9cbda6c
--- /dev/null
+++ b/danding_bot/plugins/damo_balance/AccountSpider.py
@@ -0,0 +1,81 @@
+import requests
+from bs4 import BeautifulSoup
+from PIL import Image
+import io
+
+class AccountSpider:
+ def __init__(self):
+ self.base_url = "http://121.204.253.175:8088"
+ self.session = requests.Session()
+ # 设置默认请求头
+ self.session.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'
+ }
+
+ def get_verification_code(self,onlysave = False):
+ """获取并保存验证码图片"""
+ code_url = f"{self.base_url}/code.asp"
+ response = self.session.get(code_url)
+
+ # 保存验证码图片
+ image = Image.open(io.BytesIO(response.content))
+ image.save('/bot/danding-bot/danding_bot/plugins/damo_balance/verification_code.png')
+ print("验证码图片已保存为 verification_code.png")
+ # 仅保存验证码图片
+ if onlysave:
+ return
+ # 等待用户输入验证码
+ return input("请输入验证码: ")
+
+ def login(self, username, password,v_code=""):
+ """执行登录操作"""
+
+ # 获取验证码
+ if v_code:
+ verification_code = v_code
+ else:
+ verification_code = self.get_verification_code()
+
+ # 准备登录数据
+ login_data = {
+ 'login_type': '0',
+ 'f_user': username,
+ 'f_code': password,
+ 'codeOK': verification_code,
+ 'Submit': '%C8%B7%B6%A8'
+ }
+
+ # 发送登录请求
+ login_url = f"{self.base_url}/login_result.asp"
+ response = self.session.post(login_url, data=login_data)
+ response.encoding = 'gb2312' # 设置正确的编码
+
+ # 检查登录是否成功 - 通过检查是否包含重定向到account.asp的脚本
+ if "window.location.href=\"account.asp\"" in response.text:
+ return True
+ return False
+
+ def get_balance(self):
+ """获取账户余额"""
+ account_url = f"{self.base_url}/account.asp"
+ response = self.session.get(account_url)
+ response.encoding = 'gb2312' # 设置正确的编码
+
+ soup = BeautifulSoup(response.text, 'html.parser')
+ balance_text = soup.find_all('span', class_='red')[1].text
+ return float(balance_text)
+
+def main():
+ # 账号密码配置
+ USERNAME = "xsllovemlj"
+ PASSWORD = "xsl1314520mlj"
+
+ spider = AccountSpider()
+
+ # 尝试登录
+ if spider.login(USERNAME, PASSWORD):
+ print("登录成功!")
+ balance = spider.get_balance()
+ print(f"账户余额:{balance}元")
+ else:
+ print("登录失败,请检查账号密码或验证码是否正确")
\ No newline at end of file
diff --git a/danding_bot/plugins/damo_balance/__init__.py b/danding_bot/plugins/damo_balance/__init__.py
new file mode 100644
index 0000000..967331f
--- /dev/null
+++ b/danding_bot/plugins/damo_balance/__init__.py
@@ -0,0 +1,81 @@
+from nonebot import get_plugin_config, on_command
+from nonebot.plugin import PluginMetadata
+from nonebot.rule import to_me
+from nonebot.adapters.onebot.v11 import Message,MessageEvent
+from nonebot.params import ArgPlainText,CommandArg
+from .config import Config
+from nonebot.typing import T_State
+from .AccountSpider import AccountSpider
+from nonebot_plugin_saa import Text, Image, MessageFactory
+import os
+import random
+import asyncio
+
+__plugin_meta__ = PluginMetadata(
+ name="大漠余额查询",
+ description="查询大漠插件平台账户余额的插件",
+ usage="""
+ 指令:
+ - 大漠余额
+ - 余额查询
+
+ 权限:
+ 仅限指定用户(QQ:1424473282)使用
+
+ 使用流程:
+ 1. 发送"大漠余额"或"余额查询"指令
+ 2. 机器人会返回验证码图片
+ 3. 输入验证码完成查询
+ """,
+ config=Config,
+)
+
+config = get_plugin_config(Config)
+
+spider = AccountSpider()
+
+# 指令:大漠余额
+check_balance = on_command("大漠余额", aliases={"余额查询"}, priority=5,block=True)
+
+@check_balance.handle()
+async def handle_first_receive(event: MessageEvent, state: T_State):
+ user_id = event.user_id
+ if user_id not in [1424473282]:
+ await asyncio.sleep(random.uniform(2, 3))
+ await check_balance.finish("你没有权限进行此操作")
+
+ global spider
+ spider = AccountSpider()
+ # 获取验证码并存储
+ spider.get_verification_code(True)
+ # 获取当前脚本所在目录的绝对路径
+ current_dir = os.path.dirname(__file__)
+ # 构造图片的绝对路径
+ image_path = os.path.join(current_dir, "verification_code.png")
+ # 发送图片
+ with open(image_path, "rb") as f:
+ image_bytes = f.read()
+ await asyncio.sleep(random.uniform(2, 3))
+ await MessageFactory([Text("请发送验证码图片中的内容进行验证:"),Image(image_bytes)]).send()
+
+# 验证用户输入的验证码
+@check_balance.got("captcha", prompt="请输入验证码:")
+async def handle_captcha(event: MessageEvent, state: T_State, captcha: str = ArgPlainText("captcha")):
+ user_id = event.user_id
+ if user_id not in [1424473282]:
+ await asyncio.sleep(random.uniform(2, 3))
+ await check_balance.finish("你没有权限进行此操作")
+
+ # 账号密码配置
+ USERNAME = "xsllovemlj"
+ PASSWORD = "xsl1314520mlj"
+
+ global spider
+ if spider.login(USERNAME, PASSWORD, captcha):
+ print("登录成功!")
+ balance = spider.get_balance()
+ await asyncio.sleep(random.uniform(2, 3))
+ await check_balance.finish(f"大漠账户余额:{balance}元")
+ else:
+ await asyncio.sleep(random.uniform(2, 3))
+ await check_balance.reject("获取失败、登录失败,请检查账号密码或验证码是否正确")
diff --git a/danding_bot/plugins/damo_balance/config.py b/danding_bot/plugins/damo_balance/config.py
new file mode 100644
index 0000000..a6f482d
--- /dev/null
+++ b/danding_bot/plugins/damo_balance/config.py
@@ -0,0 +1,5 @@
+from pydantic import BaseModel
+
+
+class Config(BaseModel):
+ """Plugin Config Here"""
diff --git a/danding_bot/plugins/damo_balance/verification_code.png b/danding_bot/plugins/damo_balance/verification_code.png
new file mode 100644
index 0000000000000000000000000000000000000000..d5f73d76e50b41a6d8a285d461a5b6e22a622985
GIT binary patch
literal 193
zcmV;y06zbTP) <卡密> (或 /addkami, /akm) - 添加指定类型的卡密
+/生成卡密 <类型> (或 /createkami, /ckm) - 生成指定类型的卡密
+/用户加时 <用户名> <类型> (或 /addviptime, /avt) - 为指定用户添加会员时间
+/生成QQ验证码 (或 /qqvcode, /gqvc) - 生成QQ绑定验证码
+
+普通用户指令:
+/在线人数 (或 /ddonline, /ddop) - 查看当前在线人数
+""",
+ config=Config,
+)
+
diff --git a/danding_bot/plugins/danding_api/admin.py b/danding_bot/plugins/danding_api/admin.py
new file mode 100644
index 0000000..8834a31
--- /dev/null
+++ b/danding_bot/plugins/danding_api/admin.py
@@ -0,0 +1,142 @@
+from nonebot import on_command, get_plugin_config,logger
+from nonebot.permission import SUPERUSER
+from nonebot.rule import to_me
+from nonebot.adapters.onebot.v11 import PrivateMessageEvent, GroupMessageEvent, MessageSegment
+from nonebot.params import Depends
+from .config import Config
+from .utils import post, get_classes, post_vcode, get_log
+import random
+import asyncio
+import time
+
+plugin_config = get_plugin_config(Config)
+
+
+help = on_command("咸鸭蛋",rule=to_me(),aliases={"apihelp", "sudhelp"},permission=SUPERUSER, priority=0, block=True)
+@help.handle()
+async def _():
+ await asyncio.sleep(random.uniform(2, 3))
+ await help.finish(plugin_config.HelpStr)
+
+ddonline = on_command("在线人数",rule=to_me(),aliases={"ddonline", "ddop"}, priority=0, block=True)
+@ddonline.handle()
+async def _(event:PrivateMessageEvent):
+ id:str = str(event.user_id)
+ msg:str = await post("在线人数",id)
+ await asyncio.sleep(random.uniform(2, 3))
+ await ddonline.finish(msg)
+
+addkami = on_command("添加卡密",rule=to_me(),aliases={"addkami", "akm"},permission=SUPERUSER, priority=0, block=True)
+@addkami.handle()
+async def _(event:PrivateMessageEvent):
+ id:str = str(event.user_id)
+ msg:str = event.get_plaintext()
+ if len(msg.split(' ')) != 3:
+ await asyncio.sleep(random.uniform(2, 3))
+ await ddonline.finish("参数不正确!")
+
+ classes:str = msg.split(' ')[1]
+ classes = get_classes(classes)
+ if classes == '':
+ await ddonline.finish("卡密类型不正确!")
+
+ kami:str = msg.split(' ')[2]
+ msg:str = await post("添加卡密",id,{
+ "classes":classes,
+ "kami":kami
+ })
+ await asyncio.sleep(random.uniform(2, 3))
+ await ddonline.finish(msg)
+
+createkami = on_command("生成卡密",rule=to_me(),aliases={"createkami", "ckm"},permission=SUPERUSER, priority=0, block=True)
+@createkami.handle()
+async def _(event:PrivateMessageEvent):
+ id:str = str(event.user_id)
+ msg:str = event.get_plaintext()
+ if len(msg.split(' ')) != 2:
+ await asyncio.sleep(random.uniform(2, 3))
+ await ddonline.finish("参数不正确!")
+
+ classes:str = msg.split(' ')[1]
+ classes = get_classes(classes)
+ if classes == '':
+ await ddonline.finish("卡密类型不正确!")
+
+ msg:str = await post("生成卡密",id,{
+ "classes":classes
+ })
+ await asyncio.sleep(random.uniform(2, 3))
+ await ddonline.finish(msg)
+
+addviptime = on_command("用户加时",rule=to_me(),aliases={"addviptime", "avt"},permission=SUPERUSER, priority=0, block=True)
+@addviptime.handle()
+async def _(event:PrivateMessageEvent):
+ id:str = str(event.user_id)
+ msg:str = event.get_plaintext()
+ if len(msg.split(' ')) != 3:
+ await asyncio.sleep(random.uniform(2, 3))
+ await ddonline.finish("参数不正确!")
+
+ username:str = msg.split(' ')[1]
+ classes:str = msg.split(' ')[2]
+ classes = get_classes(classes)
+ if classes == '':
+ await ddonline.finish("卡密类型不正确!")
+
+ msg:str = await post("用户加时",id,{
+ "username":username,
+ "classes":classes
+ })
+ await asyncio.sleep(random.uniform(2, 3))
+ await ddonline.finish(msg)
+
+generate_qq_vcode = on_command("绑定QQ",aliases={"bindqq", "绑定qq"}, priority=0, block=True)
+
+# 添加用户使用时间记录字典
+user_last_use_time = {}
+
+@generate_qq_vcode.handle()
+async def _(event: GroupMessageEvent): # GroupMessageEvent PrivateMessageEvent
+ # 检查是否来自指定群组
+ if event.group_id != 621016172:
+ return
+ # if event.user_id != 1424473282:
+ # return
+
+ id:str = str(event.user_id)
+
+ # 限流检查:检查用户上次使用时间
+ current_time = time.time()
+ if id in user_last_use_time:
+ time_diff = current_time - user_last_use_time[id]
+ if time_diff < 60: # 60秒内已使用过
+ await generate_qq_vcode.finish(f"请求过于频繁,请在{int(60 - time_diff)}秒后再试")
+ return
+
+ # 更新用户最后使用时间
+ user_last_use_time[id] = current_time
+
+ msg:str = await post_vcode(id)
+ await asyncio.sleep(random.uniform(2, 3))
+ # 在消息前添加@用户
+ at_user = MessageSegment.at(event.user_id)
+ await generate_qq_vcode.finish(at_user + " " + msg)
+
+
+
+
+view_logs = on_command("查看日志",aliases={"logs", "查询日志"}, priority=0, block=True)
+@view_logs.handle()
+async def _(event:GroupMessageEvent): # GroupMessageEvent PrivateMessageEvent
+ # 检查是否来自指定群组
+ if event.group_id != 621016172:
+ return
+ # if event.user_id != 1424473282:
+ # return
+
+ id:str = str(event.user_id)
+ msg:str = await get_log(id)
+ await asyncio.sleep(random.uniform(2, 3))
+ # 在消息前添加@用户
+ at_user = MessageSegment.at(event.user_id)
+ await view_logs.finish(at_user + " " + msg)
\ No newline at end of file
diff --git a/danding_bot/plugins/danding_api/config.py b/danding_bot/plugins/danding_api/config.py
new file mode 100644
index 0000000..d9ac0e8
--- /dev/null
+++ b/danding_bot/plugins/danding_api/config.py
@@ -0,0 +1,28 @@
+from pydantic import BaseModel
+
+
+class Config(BaseModel):
+ """Plugin Config Here"""
+
+ HelpStr:str = """
+这是一个蛋定助手的RoBot控制插件,功能菜单:
+在线人数 : 查询当前蛋定助手在线用户数量;
+添加卡密 [天|周|月] [指定卡密]: 添加一张指定天数的指定卡密;
+生成卡密 [天|周|月]: 生成一张指定天数的卡密;
+用户加时 [用户名] [天|周|月] : 添加指定用户时长;
+绑定QQ: 为当前QQ号生成绑定验证码;
+查看日志: 查看当前QQ号绑定日志;
+"""
+
+ Token:str = "3340e353a49447f1be640543cbdcd937"
+ """对接服务器的Token"""
+
+ 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_USER: str = "admin"
+ EMAIL_FROM: str = "admin@danding.vip"
+ EMAIL_PASSWORD: str = "Grkwdc13"
\ No newline at end of file
diff --git a/danding_bot/plugins/danding_api/utils.py b/danding_bot/plugins/danding_api/utils.py
new file mode 100644
index 0000000..4ed07eb
--- /dev/null
+++ b/danding_bot/plugins/danding_api/utils.py
@@ -0,0 +1,155 @@
+import requests
+from nonebot import get_plugin_config
+from .config import Config
+from nonebot import logger
+
+plugin_config = get_plugin_config(Config)
+router:dict = {
+ "在线人数":"bot_online_count",
+ "添加卡密":"bot_add_kami",
+ "生成卡密":"bot_create_kami",
+ "用户加时":"bot_add_user_viptime",
+ "生成QQ验证码":"bot_generate_vcode",
+ "获取日志":"bot_get_user_log"
+}
+
+async def post(router_name:str,user:str,data:dict={})->str:
+ _url:str = plugin_config.DDApi_Host + router[router_name]
+ data["user"]=user
+ data["token"]=plugin_config.Token
+ r = requests.post(url = _url,json=data)
+ logger.debug(r)
+ if r.status_code != 200:
+ return '出错啦!'
+ r=r.json()
+ logger.debug(r)
+ return r["message"]
+
+async def post_vcode(user:str)->str:
+ _url:str = plugin_config.DDApi_Host + router["生成QQ验证码"]
+ data:dict={}
+ data["user"]="1424473282"
+ data["token"]=plugin_config.Token
+ data["qq"]=user
+ r = requests.post(url = _url,json=data)
+ logger.debug(r)
+ if r.status_code != 200:
+ return '出错啦!'
+ r=r.json()
+ logger.debug(r)
+ if "验证码生成成功" in r["message"]:
+ resp_data = await send_mail(f'{user}@qq.com',"验证码生成成功",r["message"],"DanDing-Admin")
+ if resp_data is None or resp_data.get("errorNo", -1) != 0:
+ return r["message"]
+ else:
+ return f"生成的绑定验证码已经发送到 {user}@qq.com 邮箱中,请查收!"
+ return r["message"]
+
+async def get_log(user:str)->str:
+ _url:str = plugin_config.DDApi_Host + router["获取日志"]
+ r = requests.get(url = f"{_url}?user=1424473282&token={plugin_config.Token}&qq={user}")
+ logger.debug(r)
+ if r.status_code != 200:
+ return '出错啦!'
+ r=r.json()
+ logger.debug(r)
+ return r["message"]
+
+
+def get_classes(classee:str):
+ """
+ 将口语类型转换为程序可识别的标准卡密类型
+ """
+ cases = {
+ 'day': 'Day',
+ 'DAY': 'Day',
+ '天': 'Day',
+ '天卡': 'Day',
+
+ 'week': 'Week',
+ 'WEEK': 'Week',
+ '周': 'Week',
+ '周卡': 'Week',
+
+ 'month': 'Month',
+ 'MONTH': 'Month',
+ '月': 'Month',
+ '月卡': 'Month',
+ }
+ return cases.get(classee, '')
+
+
+session_id: str = ""
+# 登录pmail邮箱 获取cookie
+login_url = plugin_config.EMAIL_LOGIN
+login_pdata = {
+ "account": plugin_config.EMAIL_USER,
+ "password": plugin_config.EMAIL_PASSWORD
+}
+session = requests.session() # 实例化session对象
+
+
+def login_pmail():
+ global session_id
+ resp_data = None
+ error_msg: str = ""
+ retries = 3 # 设置重试次数
+ for attempt in range(retries):
+ try:
+ resp_data = session.post(login_url, json=login_pdata, headers={'Content-Type': 'application/json'})
+ if resp_data.status_code == 200 and resp_data.json().get('errorNo') == 0:
+ logger.info('PMail App 启动成功!')
+ session_id = resp_data.headers['Set-Cookie']
+ return
+ except ConnectionError:
+ error_msg = "服务器连接失败!"
+ except Exception as e:
+ error_msg = str(e)
+ logger.warning(f'PMail App 登录失败,正在重试... ({attempt + 1}/{retries})')
+
+ # 如果重试次数用尽仍然失败
+ logger.error(f'PMail App 登录失败!无法使用邮件功能!可能网络错误!{error_msg}')
+
+
+async def send_mail(mail_to, subject, content, name):
+ """
+ 发送邮件
+ :param mail_to: 发送到
+ :param subject: 标题
+ :param content: 内容
+ :param name: 用户名
+ :return:
+ """
+ url = plugin_config.EMAIL_API
+
+ pdata = {
+ 'from':
+ {
+ "name": "DanDing-Admin",
+ "email": plugin_config.EMAIL_FROM
+ },
+ 'to':
+ [
+ {
+ "name": name,
+ "email": mail_to
+ }
+ ],
+ 'subject': subject,
+ 'html': content,
+ "text": "text"
+ }
+ if session_id is None or "":
+ logger.error("[error] 邮件发送失败,没有session_id,尝试重新登录邮箱服务!")
+ login_pmail()
+
+ resp_data = session.post(url, json=pdata, headers={"cookie": f"{session_id}"}).json()
+ if resp_data is None or resp_data.get("errorNo", -1) != 0:
+ logger.error("[error] 邮件发送失败,未知的错误,尝试重新登录邮箱服务!")
+ # 重新登录pmail邮箱
+ login_pmail()
+ resp_data = session.post(url, json=pdata, headers={"cookie": f"{session_id}"}).json()
+ if resp_data is None or resp_data.get("errorNo", -1) != 0:
+ return {"errorNo": 0, "errorMsg": "", "data": ""}
+
+ return resp_data
\ No newline at end of file
diff --git a/danding_bot/plugins/danding_help/HelpCaiDan.md b/danding_bot/plugins/danding_help/HelpCaiDan.md
new file mode 100644
index 0000000..013e6ec
--- /dev/null
+++ b/danding_bot/plugins/danding_help/HelpCaiDan.md
@@ -0,0 +1,15 @@
+# 欢迎使用蛋定助手🎇
+## 发送以下关键词获取帮助🤝🏻
+1. 下载(dl)
+2. 帮助文档(wd)
+3. 公益版(gyb)
+4. 正式版(zsb)
+5. 下单(xd)
+
+## 部分教程关键字✨️
+1. 正式版御魂双开(dyh)
+2. 正式版如何运行(htr)
+
+## 活动🔥🔥
+1. 每日试用(mrss) - 永久活动,单设备每日可试用1小时!
+
diff --git a/danding_bot/plugins/danding_help/__init__.py b/danding_bot/plugins/danding_help/__init__.py
new file mode 100644
index 0000000..e8cfd47
--- /dev/null
+++ b/danding_bot/plugins/danding_help/__init__.py
@@ -0,0 +1,30 @@
+from nonebot import get_plugin_config
+from nonebot.plugin import PluginMetadata
+from . import help
+from .config import Config
+
+__plugin_meta__ = PluginMetadata(
+ name="danding_help",
+ description="蛋定助手帮助信息",
+ usage="""
+# 蛋定助手帮助系统
+## 基础指令
+- 帮助 / help:显示帮助菜单图片
+- 下载 / dl / downdload:获取下载相关信息
+- 帮助文档 / wd:获取在线帮助文档链接
+
+## 版本信息
+- 公益版 / free / gyb:查看公益版相关说明
+- 正式版 / pro / zsb:查看正式版相关说明
+
+## 使用教程
+- 正式版御魂双开 / dyh:查看御魂双开教程
+- 正式版如何运行 / htr:查看软件运行教程
+- 正式版内测计划 / zsbnc:查看内测相关信息
+
+注意:本插件仅在特定群组中可用
+""",
+ config=Config,
+)
+
+
diff --git a/danding_bot/plugins/danding_help/config.py b/danding_bot/plugins/danding_help/config.py
new file mode 100644
index 0000000..2fcc6ef
--- /dev/null
+++ b/danding_bot/plugins/danding_help/config.py
@@ -0,0 +1,45 @@
+from pydantic import BaseModel
+
+
+class Config(BaseModel):
+ """Plugin Config Here"""
+ HelpStr:str = """\
+# 欢迎使用蛋定助手
+## 发送以下关键词获取帮助
+1. 下载
+2. 下单
+3. 公益版(暂未进行维护!)
+4. 正式版
+
+## 部分教程关键字
+1. 正式版御魂双开
+2. 正式版如何运行
+
+## 活动
+1. 每日试用"""
+
+ DowndLoadStr:str = """\
+公益版提供群文件下载一种方式;
+正式版下载:https://file.x-tools.top
+后缀为Cx结尾的版本是修正版本,用新不用旧;"""
+
+ FreeStr:str = """公益版(暂时没有精力维护)
+1. 不定时更新,仅维护现有功能;
+2. 不提供技术支持;
+3. 仅供群文件下载;
+若出现初始化闪退问题,请@开发者更新共享注册码;"""
+
+ ProStr:str = """正式版
+1. 全新的框架、全新UI、更稳的功能;
+2. 更新、维护频率快;
+3. 提供技术支持;
+4. 提供多种下载方式;"""
+
+ OrderStr:str = """\
+1. 蛋定の卡铺1:https://shop.danding.vip
+2. 蛋定の卡铺2:https://ka.x-tools.top
+"""
+
+ DailyTrialStr:str = """\
+永久活动-每日试用:单设备支持每日试用1小时的脚本时长!
+请在购买或支持蛋定助手前,一定要先试用,确保自己可以正常使用脚本!"""
\ No newline at end of file
diff --git a/danding_bot/plugins/danding_help/help.py b/danding_bot/plugins/danding_help/help.py
new file mode 100644
index 0000000..a4fce42
--- /dev/null
+++ b/danding_bot/plugins/danding_help/help.py
@@ -0,0 +1,99 @@
+from nonebot import on_command, get_plugin_config,logger
+from nonebot.rule import fullmatch
+from .config import Config
+import os
+from nonebot_plugin_saa import Text, Image, MessageFactory
+from nonebot.adapters.onebot.v11.event import GroupMessageEvent
+import random
+import asyncio
+
+async def rule_fun(e:GroupMessageEvent):
+ id = e.group_id
+ if id in [621016172]:
+ return True
+ return False
+
+plugin_config = get_plugin_config(Config)
+
+help = on_command("帮助", rule=rule_fun and fullmatch(('帮助','help')), aliases={"help"}, priority=1, block=True)
+@help.handle()
+async def _():
+ # 获取当前脚本所在目录的绝对路径
+ current_dir = os.path.dirname(__file__)
+ # 构造图片的绝对路径
+ image_path = os.path.join(current_dir, "img", "帮助菜单.jpg")
+ # 发送图片
+ with open(image_path, "rb") as f:
+ image_bytes = f.read()
+ await asyncio.sleep(random.uniform(2, 3))
+ await MessageFactory([Image(image_bytes)]).send(
+ at_sender=True, reply=True
+ )
+
+downdload = on_command("下载", rule=rule_fun and fullmatch(('下载',"dl","downdload")), aliases={"dl","downdload"}, priority=1, block=True)
+@downdload.handle()
+async def _():
+ await asyncio.sleep(random.uniform(2, 3))
+ await downdload.finish(plugin_config.DowndLoadStr)
+
+wd = on_command("帮助文档", rule=rule_fun and fullmatch(('帮助文档',"wd")), aliases={"wd"}, priority=1, block=True)
+@wd.handle()
+async def _():
+ await asyncio.sleep(random.uniform(2, 3))
+ await wd.finish("https://www.danding.vip")
+
+
+free = on_command("公益版", rule=rule_fun and fullmatch(('公益版',"free","gyb")), aliases={"free","gyb"}, priority=1, block=True)
+@free.handle()
+async def _():
+ await asyncio.sleep(random.uniform(2, 3))
+ await help.finish(plugin_config.FreeStr)
+
+pro = on_command("正式版", rule=rule_fun and fullmatch(('正式版',"pro","zsb")), aliases={"pro","zsb"}, priority=1, block=True)
+@pro.handle()
+async def _():
+ await asyncio.sleep(random.uniform(2, 3))
+ await help.finish(plugin_config.ProStr)
+
+dyh = on_command("正式版御魂双开", rule=rule_fun and fullmatch(('正式版御魂双开',"dyh")), aliases={"dyh"}, priority=1, block=True)
+@dyh.handle()
+async def _():
+ # 获取当前脚本所在目录的绝对路径
+ current_dir = os.path.dirname(__file__)
+ # 构造图片的绝对路径
+ image_path = os.path.join(current_dir, "img", "御魂双开方法.jpg")
+ # 发送图片
+ with open(image_path, "rb") as f:
+ image_bytes = f.read()
+ await asyncio.sleep(random.uniform(2, 3))
+ await MessageFactory([Text("御魂双开方法见下图"),Image(image_bytes)]).send(
+ at_sender=True, reply=True
+ )
+
+
+htr = on_command("正式版如何运行", rule=rule_fun and fullmatch(('正式版如何运行',"htr")), aliases={"htr"}, priority=1, block=True)
+@htr.handle()
+async def _():
+ # 获取当前脚本所在目录的绝对路径
+ current_dir = os.path.dirname(__file__)
+ # 构造图片的绝对路径
+ image_path = os.path.join(current_dir, "img", "开软件教程.jpg")
+ # 发送图片
+ with open(image_path, "rb") as f:
+ image_bytes = f.read()
+ await asyncio.sleep(random.uniform(2, 3))
+ await MessageFactory([Text("How To Run? Look!"),Image(image_bytes)]).send(
+ at_sender=True, reply=True
+ )
+
+order = on_command("下单", rule=rule_fun and fullmatch(('下单',"xd")), aliases={"xd"}, priority=1, block=True)
+@order.handle()
+async def _():
+ await asyncio.sleep(random.uniform(2, 3))
+ await order.finish(plugin_config.OrderStr)
+
+daily_trial = on_command("每日试用", rule=rule_fun and fullmatch(('每日试用',"mrss")), aliases={"mrss"}, priority=1, block=True)
+@daily_trial.handle()
+async def _():
+ await asyncio.sleep(random.uniform(2, 3))
+ await daily_trial.finish(plugin_config.DailyTrialStr)
diff --git a/danding_bot/plugins/danding_help/img/帮助菜单.jpg b/danding_bot/plugins/danding_help/img/帮助菜单.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..36533a4d3e3800abb4206365022a9f5ac65dceaf
GIT binary patch
literal 113867
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