diff --git a/.env.dev b/.env.dev index 127b48910..44f19fe28 100644 --- a/.env.dev +++ b/.env.dev @@ -1,5 +1,3 @@ - - SUPERUSERS=[""] COMMAND_START=[""] @@ -13,6 +11,16 @@ SESSION_EXPIRE_TIMEOUT=30 # 全局图片统一使用bytes发送,当真寻与协议端不在同一服务器上时为True IMAGE_TO_BYTES = False +# 回复消息时自称 +SELF_NICKNAME="小真寻" + +# 数据库配置 +# 示例: "postgres://user:password@127.0.0.1:5432/database" +DB_URL = "" + +# 系统代理 +# SYSTEM_PROXY = "http://127.0.0.1:7890" + PLATFORM_SUPERUSERS = ' { "qq": [""], @@ -22,6 +30,12 @@ PLATFORM_SUPERUSERS = ' DRIVER=~fastapi+~httpx+~websockets + +# LOG_LEVEL=DEBUG +# 服务器和端口 +HOST = 127.0.0.1 +PORT = 8080 + # kook adapter toekn # kaiheila_bots =[{"token": ""}] @@ -51,11 +65,4 @@ DRIVER=~fastapi+~httpx+~websockets # ' # application_commands的{"*": ["*"]}代表将全部应用命令注册为全局应用命令 -# {"admin": ["123", "456"]}则代表将admin命令注册为id是123、456服务器的局部命令,其余命令不注册 - -# LOG_LEVEL=DEBUG -# 服务器和端口 -HOST = 127.0.0.1 -PORT = 8080 - - \ No newline at end of file +# {"admin": ["123", "456"]}则代表将admin命令注册为id是123、456服务器的局部命令,其余命令不注册 \ No newline at end of file diff --git a/.github/workflows/bot_check.yml b/.github/workflows/bot_check.yml new file mode 100644 index 000000000..eba97e85a --- /dev/null +++ b/.github/workflows/bot_check.yml @@ -0,0 +1,43 @@ +name: 检查bot是否运行正常 + +on: + push: + branches: [ "dev", "main"] + pull_request: + branches: [ "dev", "main"] + +jobs: + bot-check: + runs-on: ubuntu-latest + name: bot check + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + id: setup_python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install Poetry + run: pip install poetry + + # Poetry cache depends on OS, Python version and Poetry version. + - name: Cache Poetry cache + uses: actions/cache@v3 + with: + path: ~/.cache/pypoetry + key: poetry-cache-${{ runner.os }}-${{ steps.setup_python.outputs.python-version }} + + - name: Install dependencies + run: | + mv scripts/bot_check.py bot_check.py + rm -rf poetry.lock + poetry source remove ali + poetry install --no-root + poetry run pip install pydantic==1.10 + + - name: Check bot run + id: bot_check_run + run: | + poetry run python3 bot_check.py \ No newline at end of file diff --git a/bot.py b/bot.py index b3009b5c7..915b77786 100644 --- a/bot.py +++ b/bot.py @@ -5,9 +5,10 @@ from nonebot.adapters.kaiheila import Adapter as KaiheilaAdapter from nonebot.adapters.onebot.v11 import Adapter as OneBotV11Adapter +nonebot.init() + from zhenxun.services.db_context import disconnect, init -nonebot.init() driver = nonebot.get_driver() driver.register_adapter(OneBotV11Adapter) driver.register_adapter(KaiheilaAdapter) @@ -18,7 +19,7 @@ driver.on_startup(init) driver.on_shutdown(disconnect) -nonebot.load_builtin_plugins("echo") # 内置插件 +# nonebot.load_builtin_plugins("echo") nonebot.load_plugins("zhenxun/builtin_plugins") nonebot.load_plugins("zhenxun/plugins") diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..f2bca1fe1 Binary files /dev/null and b/requirements.txt differ diff --git a/resources/font/HWXingKai.ttf b/resources/font/HWXingKai.ttf deleted file mode 100644 index 353ba6a9a..000000000 Binary files a/resources/font/HWXingKai.ttf and /dev/null differ diff --git a/resources/font/HWZhongSong.ttf b/resources/font/HWZhongSong.ttf deleted file mode 100644 index 0f167cbde..000000000 Binary files a/resources/font/HWZhongSong.ttf and /dev/null differ diff --git a/resources/font/sarasa-mono-sc-nerd-regular.ttf b/resources/font/sarasa-mono-sc-nerd-regular.ttf deleted file mode 100644 index 636c2f445..000000000 Binary files a/resources/font/sarasa-mono-sc-nerd-regular.ttf and /dev/null differ diff --git a/resources/font/wq.ttf b/resources/font/wq.ttf deleted file mode 100644 index f15cfefc1..000000000 Binary files a/resources/font/wq.ttf and /dev/null differ diff --git a/resources/font/yz.ttf b/resources/font/yz.ttf deleted file mode 100644 index d150b5378..000000000 Binary files a/resources/font/yz.ttf and /dev/null differ diff --git a/resources/font/yzz.ttc b/resources/font/yzz.ttc deleted file mode 100644 index d150b5378..000000000 Binary files a/resources/font/yzz.ttc and /dev/null differ diff --git a/resources/image/wordcloud/default.png b/resources/image/wordcloud/default.png index 7912ce8b0..49ba4f55c 100644 Binary files a/resources/image/wordcloud/default.png and b/resources/image/wordcloud/default.png differ diff --git a/resources/template/bar_chart/main.css b/resources/template/bar_chart/main.css new file mode 100644 index 000000000..8c1cc0396 --- /dev/null +++ b/resources/template/bar_chart/main.css @@ -0,0 +1,7 @@ + +body { + position: absolute; + left: -8px; + top: -8px; +} + diff --git a/resources/template/bar_chart/main.html b/resources/template/bar_chart/main.html new file mode 100644 index 000000000..8728b14b7 --- /dev/null +++ b/resources/template/bar_chart/main.html @@ -0,0 +1,50 @@ + + + + + ECharts Example + + + + + + +
+ + + + \ No newline at end of file diff --git a/resources/template/sign/main.css b/resources/template/sign/main.css new file mode 100644 index 000000000..5d9a7016b --- /dev/null +++ b/resources/template/sign/main.css @@ -0,0 +1,285 @@ +@font-face { + font-family: cr105Font; + /* 导入的字体文件 */ + src: url("./res/font/ChillReunion_105S.otf"); +} + +@font-face { + font-family: cr95Font; + /* 导入的字体文件 */ + src: url("./res/font/ChillReunion_95.otf"); +} + +@font-face { + font-family: cr65sFont; + /* 导入的字体文件 */ + src: url("./res/font/ChillReunion_65S.otf"); +} + +@font-face { + font-family: shFont; + /* 导入的字体文件 */ + src: url("./res/font/SourceHanSansSC-Bold.otf"); +} + +@font-face { + font-family: rxxxtFont; + /* 导入的字体文件 */ + src: url("./res/font/rxxxkat.ttf"); +} + +@font-face { + font-family: kcytFont; + /* 导入的字体文件 */ + src: url("./res/font/jcyt.ttf"); +} + +@font-face { + font-family: yshsFont; + /* 导入的字体文件 */ + src: url("/resources/font/YSHaoShenTi-2.ttf"); +} + +body { + position: absolute; + left: -8px; + top: -8px; +} + +.wrapper{ + height: 926px; + width: 465px; + background-color: #FBE4E4; + position: relative; + font-family: 'cr105Font' +} + +.avatar { + height: 120px; + width: 120px; + border-radius: 50%; + margin-top: 38px; + margin-left: 40px; + box-shadow: 0px 0 10px 3px #D6A7A7; +} + +.avatar-img { + height: 120px; + width: 120px; + border-radius: 50%; +} + +.top-head { + width: 100%; + height: 179px; + color: #D37B8D; +} + +.nickname { + margin-top: 43px; + margin-left: 30px; + font-size: 47px; + height: 57px; + align-items: center; + justify-content: center; + display: flex +} + +.uid { + font-size: 21px; + position: absolute; + width: 255px; + top: 84px; + left: 30px; + font-family: 'cr95Font' +} + +.rl-img { + height: 90px; + width: 90px; + position: absolute; + top: 27px; + left: 30px; +} + +.zx-img { + height:238px; + width: 120px; + left: 306px; + position: absolute; + z-index: 999; +} + +.text-day { + font-size: 25px; + margin-left: 130px; + margin-top: 46px; + font-family: 'shFont'; +} + +.text-zx { + font-family: 'cr65sFont'; + position: absolute; + top: 72px; + left: 130px; +} + +.sign-content{ + width: 100%; + height: 160px; + position: relative; +} + +.sign-top { + background-color: #D47E8F; + width:445px; + height: 150px; + position: absolute; + left: 9px; + color: #FBE4E4; +} + +.sign-bottom { + background-color: #953B50; + width: 445px; + height: 150px; + position: absolute; + top: 10px; + left: 15px +} + +.qian { + position: absolute; + left: 15px; + top: 16px; + height: 132px; +} + +.sign-data{ + width: 100%; + height: 207px; + font-family: 'kcytFont'; + position: relative; + color: #D47E8F; +} + +.today-text { + font-size: 50px; + position: absolute; + top: -40px; + left: 130px; +} + +.abs-text { + position: absolute; + font-size: 30px; + z-index: 2; + width: 465px; +} + +.gift { + text-align: center; + /* float: left; */ + /* margin-top: 150px; */ +} + + + +.sign-text { + font-size: 30px; + position: absolute; + top: 60px; + left: 140px; +} + +.sign-num { + font-size: 30px; + position: absolute; + top: 60px; +} + +.line { + background-color: #D1778A; + width: 435px; + height: 3px; + margin-left: 15px; +} +.bottom-foot{ + width: 100%; + height: 376px; + position: relative; + font-family: 'rxxxtFont'; + color: #D47E8F; +} + +.heart-list { + position: absolute; + top: 76px; + left: 15px; + width: 435px; +} + +.heart-img { + height: 30px; + width: 30px; +} + +.cur-text { + font-size: 32px; + left: 10px; + top: -21px; + position: absolute; +} + +.bot-text { + font-size: 30px; + position: absolute; + top: 85px; + left: 15px; + width: 435px; +} + +.progress-border { + position: absolute; + top: 273px; + left: 19px; + width: 313px; + height: 30px; + border: #DF9DA8 1px solid; +} +.progress-bar { + height: 30px; + background-color: #D47E8F; +} + +.weather-img { + width: 70px; + height: 70px; + position: absolute; + top: 37px; + left: 371px; +} + +.mbl-img { + width: 132px; + height: 164px; + position: absolute; + top: 145px; + left: 333px; +} + +.wd{ + position: absolute; + font-size: 35px; + top: 60px; + left: 377px; +} + +.date { + position: absolute; + font-size: 20px; + top: 350px; + left: 250px; + font-family: 'yshsFont'; + width: 210px; +} \ No newline at end of file diff --git a/resources/template/sign/main.html b/resources/template/sign/main.html new file mode 100644 index 000000000..10c3d40ed --- /dev/null +++ b/resources/template/sign/main.html @@ -0,0 +1,78 @@ + + + + + + + + + test + + + + +
+
+
+
+ +
+
+

{{data.name}}

+

UID:{{data.uid}}

+
+
+
+
+
+
+
+ + +

累计签到{{data.sign_count}}天

+

{{data.message}}

+
+
+
+ +

今日签到

+
+

{{data.impression}}

+
+
+

{{data.gold}}

+
+

{{data.gift}}

+
+
+
+
+

当前好感度: {{data.cur_impression}}

+
+ {% for i in data.heart2 %} + + {% endfor %} + {% for i in data.heart1 %} + + {% endfor %} +
+
+

好感度等级: {{data.level}}

+

{{data.attitude}}

+

距离升级还差{{data.interpolation}}好感度

+
+
+
+
+ +

28℃

+ +
+ {{data.date}} +
+
+
+ + + \ No newline at end of file diff --git a/resources/template/sign/main.js b/resources/template/sign/main.js new file mode 100644 index 000000000..085ce3cfe --- /dev/null +++ b/resources/template/sign/main.js @@ -0,0 +1,17 @@ +let wdDom = document.querySelector(".wd") + +let wd = Math.floor(Math.random() * 40) + 1 + +wdDom.innerHTML = wd + "℃" + +let weatherDom = document.querySelector(".weather-img") + +let r = Math.floor(Math.random() * 12) + +weatherDom.src = "res/img/weather/" + r + ".png" + +let qianDom = document.querySelector(".qian") + +let r1 = Math.floor(Math.random() * 6) + +qianDom.src = "res/img/tag/" + r1 + ".png" diff --git a/resources/template/sign/res/font/ChillReunion_105S.otf b/resources/template/sign/res/font/ChillReunion_105S.otf new file mode 100644 index 000000000..8e7866b29 Binary files /dev/null and b/resources/template/sign/res/font/ChillReunion_105S.otf differ diff --git a/resources/template/sign/res/font/ChillReunion_65S.otf b/resources/template/sign/res/font/ChillReunion_65S.otf new file mode 100644 index 000000000..15a0b6457 Binary files /dev/null and b/resources/template/sign/res/font/ChillReunion_65S.otf differ diff --git a/resources/template/sign/res/font/ChillReunion_95.otf b/resources/template/sign/res/font/ChillReunion_95.otf new file mode 100644 index 000000000..672485c45 Binary files /dev/null and b/resources/template/sign/res/font/ChillReunion_95.otf differ diff --git a/resources/font/SweiSpringCJKtc-Bold.ttf b/resources/template/sign/res/font/SourceHanSansSC-Bold.otf similarity index 64% rename from resources/font/SweiSpringCJKtc-Bold.ttf rename to resources/template/sign/res/font/SourceHanSansSC-Bold.otf index ebfafe8ce..acffb8e85 100644 Binary files a/resources/font/SweiSpringCJKtc-Bold.ttf and b/resources/template/sign/res/font/SourceHanSansSC-Bold.otf differ diff --git a/resources/font/SweiSpringSugarCJKtc-Regular.ttf b/resources/template/sign/res/font/jcyt.ttf similarity index 57% rename from resources/font/SweiSpringSugarCJKtc-Regular.ttf rename to resources/template/sign/res/font/jcyt.ttf index 2028acd55..7b6f8379c 100644 Binary files a/resources/font/SweiSpringSugarCJKtc-Regular.ttf and b/resources/template/sign/res/font/jcyt.ttf differ diff --git a/resources/template/sign/res/font/rxxxkat.ttf b/resources/template/sign/res/font/rxxxkat.ttf new file mode 100644 index 000000000..c88c4c8c6 Binary files /dev/null and b/resources/template/sign/res/font/rxxxkat.ttf differ diff --git a/resources/template/sign/res/img/1.png b/resources/template/sign/res/img/1.png new file mode 100644 index 000000000..318f8d376 Binary files /dev/null and b/resources/template/sign/res/img/1.png differ diff --git a/resources/template/sign/res/img/2.png b/resources/template/sign/res/img/2.png new file mode 100644 index 000000000..348ba08ef Binary files /dev/null and b/resources/template/sign/res/img/2.png differ diff --git a/resources/template/sign/res/img/3.png b/resources/template/sign/res/img/3.png new file mode 100644 index 000000000..75436f39b Binary files /dev/null and b/resources/template/sign/res/img/3.png differ diff --git a/resources/template/sign/res/img/h1.png b/resources/template/sign/res/img/h1.png new file mode 100644 index 000000000..c7e5ab99f Binary files /dev/null and b/resources/template/sign/res/img/h1.png differ diff --git a/resources/template/sign/res/img/h2.png b/resources/template/sign/res/img/h2.png new file mode 100644 index 000000000..75cb95f3f Binary files /dev/null and b/resources/template/sign/res/img/h2.png differ diff --git a/resources/template/sign/res/img/rl.png b/resources/template/sign/res/img/rl.png new file mode 100644 index 000000000..b2ce31d9b Binary files /dev/null and b/resources/template/sign/res/img/rl.png differ diff --git a/resources/template/sign/res/img/tag/0.png b/resources/template/sign/res/img/tag/0.png new file mode 100644 index 000000000..3bfac7490 Binary files /dev/null and b/resources/template/sign/res/img/tag/0.png differ diff --git a/resources/template/sign/res/img/tag/1.png b/resources/template/sign/res/img/tag/1.png new file mode 100644 index 000000000..274e77197 Binary files /dev/null and b/resources/template/sign/res/img/tag/1.png differ diff --git a/resources/template/sign/res/img/tag/2.png b/resources/template/sign/res/img/tag/2.png new file mode 100644 index 000000000..4176ab865 Binary files /dev/null and b/resources/template/sign/res/img/tag/2.png differ diff --git a/resources/template/sign/res/img/tag/3.png b/resources/template/sign/res/img/tag/3.png new file mode 100644 index 000000000..0813cdf77 Binary files /dev/null and b/resources/template/sign/res/img/tag/3.png differ diff --git a/resources/template/sign/res/img/tag/4.png b/resources/template/sign/res/img/tag/4.png new file mode 100644 index 000000000..f0419ad78 Binary files /dev/null and b/resources/template/sign/res/img/tag/4.png differ diff --git a/resources/template/sign/res/img/tag/5.png b/resources/template/sign/res/img/tag/5.png new file mode 100644 index 000000000..1cf9da6ec Binary files /dev/null and b/resources/template/sign/res/img/tag/5.png differ diff --git a/resources/template/sign/res/img/weather/0.png b/resources/template/sign/res/img/weather/0.png new file mode 100644 index 000000000..0a6509a2b Binary files /dev/null and b/resources/template/sign/res/img/weather/0.png differ diff --git a/resources/template/sign/res/img/weather/1.png b/resources/template/sign/res/img/weather/1.png new file mode 100644 index 000000000..58701850b Binary files /dev/null and b/resources/template/sign/res/img/weather/1.png differ diff --git a/resources/template/sign/res/img/weather/10.png b/resources/template/sign/res/img/weather/10.png new file mode 100644 index 000000000..1b8616811 Binary files /dev/null and b/resources/template/sign/res/img/weather/10.png differ diff --git a/resources/template/sign/res/img/weather/11.png b/resources/template/sign/res/img/weather/11.png new file mode 100644 index 000000000..5ff612e03 Binary files /dev/null and b/resources/template/sign/res/img/weather/11.png differ diff --git a/resources/template/sign/res/img/weather/2.png b/resources/template/sign/res/img/weather/2.png new file mode 100644 index 000000000..f2a24105c Binary files /dev/null and b/resources/template/sign/res/img/weather/2.png differ diff --git a/resources/template/sign/res/img/weather/3.png b/resources/template/sign/res/img/weather/3.png new file mode 100644 index 000000000..a00a972aa Binary files /dev/null and b/resources/template/sign/res/img/weather/3.png differ diff --git a/resources/template/sign/res/img/weather/4.png b/resources/template/sign/res/img/weather/4.png new file mode 100644 index 000000000..4c1479da6 Binary files /dev/null and b/resources/template/sign/res/img/weather/4.png differ diff --git a/resources/template/sign/res/img/weather/5.png b/resources/template/sign/res/img/weather/5.png new file mode 100644 index 000000000..1e32e043d Binary files /dev/null and b/resources/template/sign/res/img/weather/5.png differ diff --git a/resources/template/sign/res/img/weather/6.png b/resources/template/sign/res/img/weather/6.png new file mode 100644 index 000000000..16b52db6e Binary files /dev/null and b/resources/template/sign/res/img/weather/6.png differ diff --git a/resources/template/sign/res/img/weather/7.png b/resources/template/sign/res/img/weather/7.png new file mode 100644 index 000000000..77f1257c3 Binary files /dev/null and b/resources/template/sign/res/img/weather/7.png differ diff --git a/resources/template/sign/res/img/weather/8.png b/resources/template/sign/res/img/weather/8.png new file mode 100644 index 000000000..3c7f7f0af Binary files /dev/null and b/resources/template/sign/res/img/weather/8.png differ diff --git a/resources/template/sign/res/img/weather/9.png b/resources/template/sign/res/img/weather/9.png new file mode 100644 index 000000000..53d77cc34 Binary files /dev/null and b/resources/template/sign/res/img/weather/9.png differ diff --git a/scripts/bot_check.py b/scripts/bot_check.py new file mode 100644 index 000000000..2f68c1ed0 --- /dev/null +++ b/scripts/bot_check.py @@ -0,0 +1,43 @@ +import re +import nonebot + +# from nonebot.adapters.discord import Adapter as DiscordAdapter +from nonebot.adapters.dodo import Adapter as DoDoAdapter +from nonebot.adapters.kaiheila import Adapter as KaiheilaAdapter +from nonebot.adapters.onebot.v11 import Adapter as OneBotV11Adapter +from regex import F + +nonebot.init() + +from zhenxun.models.plugin_info import PluginInfo + +driver = nonebot.get_driver() +driver.register_adapter(OneBotV11Adapter) +driver.register_adapter(KaiheilaAdapter) +driver.register_adapter(DoDoAdapter) +# driver.register_adapter(DiscordAdapter) + +# nonebot.load_builtin_plugins("echo") +nonebot.load_plugins("zhenxun/builtin_plugins") +nonebot.load_plugins("zhenxun/plugins") + +all_plugins = [name.replace(":", ".") for name in nonebot.get_available_plugin_names()] +print("所有插件:", all_plugins) +loaded_plugins = tuple(re.sub(r"^zhenxun\.(plugins|builtin_plugins)\.", "", plugin.module_name) for plugin in nonebot.get_loaded_plugins()) +print("已加载插件:", loaded_plugins) + +for plugin in all_plugins.copy(): + if plugin.startswith(("platform")): + print(f"平台插件:{plugin}") + elif plugin.endswith(loaded_plugins): + print(f"已加载插件:{plugin}") + else: + print(f"未加载插件:{plugin}") + continue + all_plugins.remove(plugin) + +if all_plugins: + print("出现未加载的插件:", all_plugins) + exit(1) +else: + print("所有插件均已加载") \ No newline at end of file diff --git a/zhenxun/builtin_plugins/__init__.py b/zhenxun/builtin_plugins/__init__.py index 5e3d6f942..9bce3c1c2 100644 --- a/zhenxun/builtin_plugins/__init__.py +++ b/zhenxun/builtin_plugins/__init__.py @@ -62,8 +62,8 @@ async def _(): group_user = [] try: group_user = await GroupInfoUser.filter(uid__isnull=False).all() - except Exception: - logger.warning("获取GroupInfoUser数据uid失败...") + except Exception as e: + logger.warning("获取GroupInfoUser数据uid失败...", e=e) user2uid = {u.user_id: u.uid for u in group_user} db = Tortoise.get_connection("default") old_sign_list = await db.execute_query_dict(SIGN_SQL) diff --git a/zhenxun/plugins/about.py b/zhenxun/builtin_plugins/about.py similarity index 100% rename from zhenxun/plugins/about.py rename to zhenxun/builtin_plugins/about.py diff --git a/zhenxun/builtin_plugins/admin/group_member_update/__init__.py b/zhenxun/builtin_plugins/admin/group_member_update/__init__.py index 437b3aa92..2c9fbd214 100644 --- a/zhenxun/builtin_plugins/admin/group_member_update/__init__.py +++ b/zhenxun/builtin_plugins/admin/group_member_update/__init__.py @@ -7,7 +7,7 @@ from nonebot_plugin_apscheduler import scheduler from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType @@ -62,7 +62,7 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent): if str(event.user_id) == bot.self_id: await MemberUpdateManage.update(bot, str(event.group_id)) logger.info( - f"{NICKNAME}加入群聊更新群组信息", + f"{BotConfig.self_nickname}加入群聊更新群组信息", "更新群组成员列表", session=event.user_id, group_id=event.group_id, diff --git a/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py b/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py index a24741cdb..7bda88e72 100644 --- a/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py +++ b/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py @@ -102,7 +102,10 @@ async def v11(cls, bot: v11Bot, group_id: str): if role in ["owner", "admin"] and not await LevelUser.is_group_flag( user_id, group_id ): - await LevelUser.set_level(user_id, group_id, default_auth) + if role == "owner": + await LevelUser.set_level(user_id, group_id, default_auth + 1) + else: + await LevelUser.set_level(user_id, group_id, default_auth) if user_id in bot.config.superusers: await LevelUser.set_level(user_id, group_id, 9) join_time = datetime.strptime( diff --git a/zhenxun/builtin_plugins/admin/group_update.py b/zhenxun/builtin_plugins/admin/group_update.py new file mode 100644 index 000000000..9b2637038 --- /dev/null +++ b/zhenxun/builtin_plugins/admin/group_update.py @@ -0,0 +1,45 @@ +from nonebot.adapters import Bot +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Arparma, on_alconna +from nonebot_plugin_session import EventSession + +from zhenxun.configs.utils import PluginExtraData +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils +from zhenxun.utils.platform import PlatformUtils +from zhenxun.utils.rules import admin_check, ensure_group + +__plugin_meta__ = PluginMetadata( + name="更新群组列表", + description="更新群组列表", + usage=""" + 更新群组的基本信息 + 指令: + 更新群组信息 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPER_AND_ADMIN, + admin_level=1, + ).dict(), +) + + +_matcher = on_alconna( + Alconna("更新群组信息"), + rule=admin_check(1) & ensure_group, + priority=5, + block=True, +) + + +@_matcher.handle() +async def _(bot: Bot, session: EventSession, arparma: Arparma): + logger.info("更新群组信息", arparma.header_result, session=session) + try: + await PlatformUtils.update_group(bot) + await MessageUtils.build_message("已经成功更新了群组信息!").send(reply_to=True) + except Exception as e: + await MessageUtils.build_message("更新群组信息失败!").finish(reply_to=True) diff --git a/zhenxun/builtin_plugins/admin/plugin_switch/__init__.py b/zhenxun/builtin_plugins/admin/plugin_switch/__init__.py index 76b102596..0dfb68af4 100644 --- a/zhenxun/builtin_plugins/admin/plugin_switch/__init__.py +++ b/zhenxun/builtin_plugins/admin/plugin_switch/__init__.py @@ -58,7 +58,7 @@ 关闭群被动早晚安 关闭群被动早晚安 -g 12355555 - 开启/关闭所有群被动 -[g ?[group_id]] + 开启/关闭所有群被动 ?[-g [group_id]] 私聊中: 开启/关闭全局或指定群组被动状态 示例: 开启所有群被动: 开启全局所有被动 diff --git a/zhenxun/builtin_plugins/auto_update/__init__.py b/zhenxun/builtin_plugins/auto_update/__init__.py new file mode 100644 index 000000000..1f38d19fb --- /dev/null +++ b/zhenxun/builtin_plugins/auto_update/__init__.py @@ -0,0 +1,69 @@ +from nonebot.adapters import Bot +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot.rule import to_me +from nonebot_plugin_alconna import Alconna, Args, Match, on_alconna +from nonebot_plugin_session import EventSession + +from zhenxun.configs.utils import PluginExtraData, RegisterConfig +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils + +from ._data_source import UpdateManage + +__plugin_meta__ = PluginMetadata( + name="自动更新", + description="就算是真寻也会成长的", + usage=""" + usage: + 检查更新真寻最新版本,包括了自动更新 + 指令: + 检查更新真寻 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPERUSER, + configs=[ + RegisterConfig( + key="UPDATE_REMIND", + value=True, + help="是否检测更新版本", + default_value=True, + ), + RegisterConfig( + key="UPDATE_REMIND", + value=True, + help="是否检测更新版本", + default_value=True, + ), + ], + ).dict(), +) + +_matcher = on_alconna( + Alconna("检查更新", Args["ver_type?", ["main", "dev", "release"]]), + priority=1, + block=True, + permission=SUPERUSER, + rule=to_me(), +) + + +@_matcher.handle() +async def _(bot: Bot, session: EventSession, ver_type: Match[str]): + if not session.id1: + await MessageUtils.build_message("用户id为空...").finish() + if not ver_type.available: + result = await UpdateManage.check_version() + logger.info("查看当前版本...", "检查更新", session=session) + await MessageUtils.build_message(result).finish() + try: + result = await UpdateManage.update(bot, session.id1, ver_type.result) + except Exception as e: + logger.error("版本更新失败...", "检查更新", session=session, e=e) + await MessageUtils.build_message(f"更新版本失败...e: {e}").finish() + if result: + await MessageUtils.build_message(result).finish() + await MessageUtils.build_message("更新版本失败...").finish() diff --git a/zhenxun/builtin_plugins/auto_update/_data_source.py b/zhenxun/builtin_plugins/auto_update/_data_source.py new file mode 100644 index 000000000..6d93255cd --- /dev/null +++ b/zhenxun/builtin_plugins/auto_update/_data_source.py @@ -0,0 +1,254 @@ +import os +import shutil +import subprocess +import tarfile +import zipfile +from pathlib import Path + +from nonebot.adapters import Bot +from nonebot.utils import run_sync + +from zhenxun.services.log import logger +from zhenxun.utils.http_utils import AsyncHttpx +from zhenxun.utils.platform import PlatformUtils + +from .config import ( + BACKUP_PATH, + BASE_PATH, + DEV_URL, + DOWNLOAD_GZ_FILE, + DOWNLOAD_ZIP_FILE, + MAIN_URL, + PYPROJECT_FILE, + PYPROJECT_LOCK_FILE, + RELEASE_URL, + REPLACE_FOLDERS, + REQ_TXT_FILE, + TMP_PATH, + VERSION_FILE, +) + + +def install_requirement(): + requirement_path = (Path() / "requirements.txt").absolute() + + if not requirement_path.exists(): + logger.debug( + f"没有找到zhenxun的requirement.txt,目标路径为{requirement_path}", "插件管理" + ) + return + try: + result = subprocess.run( + ["pip", "install", "-r", str(requirement_path)], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + logger.debug(f"成功安装真寻依赖,日志:\n{result.stdout}", "插件管理") + except subprocess.CalledProcessError as e: + logger.error(f"安装真寻依赖失败,错误:\n{e.stderr}", "插件管理", e=e) + + +@run_sync +def _file_handle(latest_version: str | None): + """文件移动操作 + + 参数: + latest_version: 版本号 + """ + BACKUP_PATH.mkdir(exist_ok=True, parents=True) + logger.debug("开始解压文件压缩包...", "检查更新") + download_file = DOWNLOAD_GZ_FILE + if DOWNLOAD_GZ_FILE.exists(): + tf = tarfile.open(DOWNLOAD_GZ_FILE) + else: + download_file = DOWNLOAD_ZIP_FILE + tf = zipfile.ZipFile(DOWNLOAD_ZIP_FILE) + tf.extractall(TMP_PATH) + logger.debug("解压文件压缩包完成...", "检查更新") + download_file_path = ( + TMP_PATH / [x for x in os.listdir(TMP_PATH) if (TMP_PATH / x).is_dir()][0] + ) + _pyproject = download_file_path / "pyproject.toml" + _lock_file = download_file_path / "poetry.lock" + _req_file = download_file_path / "requirements.txt" + extract_path = download_file_path / "zhenxun" + target_path = BASE_PATH + if PYPROJECT_FILE.exists(): + logger.debug(f"移除备份文件: {PYPROJECT_FILE}", "检查更新") + shutil.move(PYPROJECT_FILE, BACKUP_PATH / "pyproject.toml") + if PYPROJECT_LOCK_FILE.exists(): + logger.debug(f"移除备份文件: {PYPROJECT_LOCK_FILE}", "检查更新") + shutil.move(PYPROJECT_LOCK_FILE, BACKUP_PATH / "poetry.lock") + if REQ_TXT_FILE.exists(): + logger.debug(f"移除备份文件: {REQ_TXT_FILE}", "检查更新") + shutil.move(REQ_TXT_FILE, BACKUP_PATH / "requirements.txt") + if _pyproject.exists(): + logger.debug("移动文件: pyproject.toml", "检查更新") + shutil.move(_pyproject, Path() / "pyproject.toml") + if _lock_file.exists(): + logger.debug("移动文件: poetry.lock", "检查更新") + shutil.move(_lock_file, Path() / "poetry.lock") + if _req_file.exists(): + logger.debug("移动文件: requirements.txt", "检查更新") + shutil.move(_req_file, Path() / "requirements.txt") + for folder in REPLACE_FOLDERS: + """移动指定文件夹""" + _dir = BASE_PATH / folder + _backup_dir = BACKUP_PATH / folder + if _backup_dir.exists(): + logger.debug(f"删除备份文件夹 {_backup_dir}", "检查更新") + shutil.rmtree(_backup_dir) + if _dir.exists(): + logger.debug(f"移动旧文件夹 {_dir}", "检查更新") + shutil.move(_dir, _backup_dir) + else: + logger.warning(f"文件夹 {_dir} 不存在,跳过删除", "检查更新") + for folder in REPLACE_FOLDERS: + src_folder_path = extract_path / folder + dest_folder_path = target_path / folder + if src_folder_path.exists(): + logger.debug( + f"移动文件夹: {src_folder_path} -> {dest_folder_path}", "检查更新" + ) + shutil.move(src_folder_path, dest_folder_path) + else: + logger.debug(f"源文件夹不存在: {src_folder_path}", "检查更新") + if download_file.exists(): + logger.debug(f"删除下载文件: {download_file}", "检查更新") + download_file.unlink() + if extract_path.exists(): + logger.debug(f"删除解压文件夹: {extract_path}", "检查更新") + shutil.rmtree(extract_path) + if tf: + tf.close() + if TMP_PATH.exists(): + shutil.rmtree(TMP_PATH) + if latest_version: + with open(VERSION_FILE, "w", encoding="utf8") as f: + f.write(f"__version__: {latest_version}") + install_requirement() + + +class UpdateManage: + + @classmethod + async def check_version(cls) -> str: + """检查更新版本 + + 返回: + str: 更新信息 + """ + cur_version = cls.__get_version() + data = await cls.__get_latest_data() + if not data: + return "检查更新获取版本失败..." + return f"检测到当前版本更新\n当前版本:{cur_version}\n最新版本:{data.get('name')}\n创建日期:{data.get('created_at')}\n更新内容:\n{data.get('body')}" + + @classmethod + async def update(cls, bot: Bot, user_id: str, version_type: str) -> str | None: + """更新操作 + + 参数: + bot: Bot + user_id: 用户id + version_type: 更新版本类型 + + 返回: + str | None: 返回消息 + """ + logger.info(f"开始下载真寻最新版文件....", "检查更新") + cur_version = cls.__get_version() + if version_type == "dev": + url = DEV_URL + new_version = await cls.__get_version_from_branch("dev") + if new_version: + new_version = new_version.split(":")[-1].strip() + elif version_type == "main": + url = MAIN_URL + new_version = await cls.__get_version_from_branch("main") + if new_version: + new_version = new_version.split(":")[-1].strip() + elif version_type == "release": + data = await cls.__get_latest_data() + if not data: + return "获取更新版本失败..." + url = data.get("tarball_url") + new_version = data.get("name") + url = (await AsyncHttpx.get(url)).headers.get("Location") # type: ignore + if not url: + return "获取版本下载链接失败..." + if TMP_PATH.exists(): + logger.debug(f"删除临时文件夹 {TMP_PATH}", "检查更新") + shutil.rmtree(TMP_PATH) + logger.debug( + f"开始更新版本:{cur_version} -> {new_version} | 下载链接:{url}", + "检查更新", + ) + await PlatformUtils.send_superuser( + bot, + f"检测真寻已更新,版本更新:{cur_version} -> {new_version}\n开始更新...", + user_id, + ) + download_file = ( + DOWNLOAD_GZ_FILE if version_type == "release" else DOWNLOAD_ZIP_FILE + ) + if await AsyncHttpx.download_file(url, download_file): + logger.debug("下载真寻最新版文件完成...", "检查更新") + await _file_handle(new_version) + return f"版本更新完成\n版本: {cur_version} -> {new_version}\n请重新启动真寻以完成更新!" + else: + logger.debug("下载真寻最新版文件失败...", "检查更新") + return None + + @classmethod + def __get_version(cls) -> str: + """获取当前版本 + + 返回: + str: 当前版本号 + """ + _version = "v0.0.0" + if VERSION_FILE.exists(): + text = VERSION_FILE.open(encoding="utf8").readline() + if text: + _version = text.split(":")[-1].strip() + return _version + + @classmethod + async def __get_latest_data(cls) -> dict: + """获取最新版本信息 + + 返回: + dict: 最新版本数据 + """ + for _ in range(3): + try: + res = await AsyncHttpx.get(RELEASE_URL) + if res.status_code == 200: + return res.json() + except TimeoutError: + pass + except Exception as e: + logger.error(f"检查更新真寻获取版本失败", e=e) + return {} + + @classmethod + async def __get_version_from_branch(cls, branch: str) -> str: + """从指定分支获取版本号 + + 参数: + branch: 分支名称 + + 返回: + str: 版本号 + """ + version_url = f"https://raw.githubusercontent.com/HibiKier/zhenxun_bot/{branch}/__version__" + try: + res = await AsyncHttpx.get(version_url) + if res.status_code == 200: + return res.text.strip() + except Exception as e: + logger.error(f"获取 {branch} 分支版本失败", e=e) + return "未知版本" diff --git a/zhenxun/builtin_plugins/auto_update/config.py b/zhenxun/builtin_plugins/auto_update/config.py new file mode 100644 index 000000000..db6c880c6 --- /dev/null +++ b/zhenxun/builtin_plugins/auto_update/config.py @@ -0,0 +1,32 @@ +from pathlib import Path + +from zhenxun.configs.path_config import TEMP_PATH + +DEV_URL = "https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/dev.zip" +MAIN_URL = "https://ghproxy.cc/https://github.com/HibiKier/zhenxun_bot/archive/refs/heads/main.zip" +RELEASE_URL = "https://api.github.com/repos/HibiKier/zhenxun_bot/releases/latest" + + +VERSION_FILE = Path() / "__version__" + +PYPROJECT_FILE = Path() / "pyproject.toml" +PYPROJECT_LOCK_FILE = Path() / "poetry.lock" +REQ_TXT_FILE = Path() / "requirements.txt" + +BASE_PATH = Path() / "zhenxun" + +TMP_PATH = TEMP_PATH / "auto_update" + +BACKUP_PATH = Path() / "backup" + +DOWNLOAD_GZ_FILE = TMP_PATH / "download_latest_file.tar.gz" +DOWNLOAD_ZIP_FILE = TMP_PATH / "download_latest_file.zip" + +REPLACE_FOLDERS = [ + "builtin_plugins", + "plugins", + "services", + "utils", + "models", + "configs", +] diff --git a/zhenxun/builtin_plugins/help/__init__.py b/zhenxun/builtin_plugins/help/__init__.py index 69a981a33..3f89c1172 100644 --- a/zhenxun/builtin_plugins/help/__init__.py +++ b/zhenxun/builtin_plugins/help/__init__.py @@ -67,6 +67,7 @@ async def _( session: EventSession, is_superuser: Query[bool] = AlconnaQuery("superuser.value", False), ): + logger.debug("进入help") _is_superuser = False if is_superuser.available: _is_superuser = is_superuser.result diff --git a/zhenxun/builtin_plugins/hooks/_auth_checker.py b/zhenxun/builtin_plugins/hooks/_auth_checker.py index 4aaa3f19c..efffa2369 100644 --- a/zhenxun/builtin_plugins/hooks/_auth_checker.py +++ b/zhenxun/builtin_plugins/hooks/_auth_checker.py @@ -198,6 +198,8 @@ def is_send_limit_message(self, plugin: PluginInfo, sid: str) -> bool: return False if not self._flmt_s.check(sid): return False + if plugin.module == "ai": + return False return True async def auth( @@ -392,7 +394,7 @@ async def auth_plugin( if not plugin.status and plugin.block_type == BlockType.ALL: """全局状态""" if group_id: - if await GroupConsole.is_super_group(group_id, channel_id): + if await GroupConsole.is_super_group(group_id): raise IsSuperuserException() logger.debug( f"{plugin.name}({plugin.module}) 全局未开启此功能...", @@ -482,11 +484,11 @@ async def auth_group( """群休眠""" if text.strip() != "醒来": logger.debug( - f"功能总开关关闭状态...", + f"群休眠状态...", "HOOK", session=session, ) - raise IgnoredException("功能总开关关闭状态") + raise IgnoredException("群休眠状态") async def auth_cost( self, user: UserConsole, plugin: PluginInfo, session: EventSession diff --git a/zhenxun/builtin_plugins/hooks/auth_hook.py b/zhenxun/builtin_plugins/hooks/auth_hook.py index 4f030d8ca..9df91a4d1 100644 --- a/zhenxun/builtin_plugins/hooks/auth_hook.py +++ b/zhenxun/builtin_plugins/hooks/auth_hook.py @@ -1,6 +1,6 @@ from typing import Optional -from nonebot.adapters.onebot.v11 import Bot, Event, MessageEvent +from nonebot.adapters.onebot.v11 import Bot, Event from nonebot.matcher import Matcher from nonebot.message import run_postprocessor, run_preprocessor from nonebot_plugin_alconna import UniMsg diff --git a/zhenxun/builtin_plugins/hooks/ban_hook.py b/zhenxun/builtin_plugins/hooks/ban_hook.py index b4923eff7..675f04c6f 100644 --- a/zhenxun/builtin_plugins/hooks/ban_hook.py +++ b/zhenxun/builtin_plugins/hooks/ban_hook.py @@ -1,3 +1,5 @@ +import logging + from nonebot.adapters import Bot, Event from nonebot.exception import IgnoredException from nonebot.matcher import Matcher @@ -8,6 +10,7 @@ from zhenxun.configs.config import Config from zhenxun.models.ban_console import BanConsole +from zhenxun.models.group_console import GroupConsole from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils from zhenxun.utils.utils import FreqLimiter @@ -30,7 +33,7 @@ async def _( if plugin := matcher.plugin: if metadata := plugin.metadata: extra = metadata.extra - if extra.get("plugin_type") == PluginType.HIDDEN: + if extra.get("plugin_type") in [PluginType.HIDDEN, PluginType.DEPENDANT]: return user_id = session.id1 group_id = session.id3 or session.id2 @@ -38,7 +41,12 @@ async def _( if user_id in bot.config.superusers: return if await BanConsole.is_ban(None, group_id): + logging.debug("群组处于黑名单中...", "ban_hook") raise IgnoredException("群组处于黑名单中...") + if g := await GroupConsole.get_group(group_id): + if g.level < 0: + logging.debug("群黑名单, 群权限-1...", "ban_hook") + raise IgnoredException("群黑名单, 群权限-1..") if user_id: ban_result = Config.get_config("hook", "BAN_RESULT") if user_id in bot.config.superusers: @@ -59,7 +67,7 @@ async def _( time_str = f"{hours} 小时 {minute}分钟" else: time_str = f"{minute} 分钟" - if ban_result and _flmt.check(user_id): + if time != -1 and ban_result and _flmt.check(user_id): _flmt.start_cd(user_id) await MessageUtils.build_message( [ @@ -67,4 +75,5 @@ async def _( f"{ban_result}\n在..在 {time_str} 后才会理你喔", ] ).send() + logging.debug("用户处于黑名单中...", "ban_hook") raise IgnoredException("用户处于黑名单中...") diff --git a/zhenxun/builtin_plugins/hooks/chkdsk_hook.py b/zhenxun/builtin_plugins/hooks/chkdsk_hook.py index 512465152..5fb66a09e 100644 --- a/zhenxun/builtin_plugins/hooks/chkdsk_hook.py +++ b/zhenxun/builtin_plugins/hooks/chkdsk_hook.py @@ -1,6 +1,7 @@ import time from collections import defaultdict +from nonebot.adapters import Event from nonebot.adapters.onebot.v11 import Bot from nonebot.exception import IgnoredException from nonebot.matcher import Matcher @@ -63,16 +64,24 @@ def check(self, key: str | int | float) -> bool: # 恶意触发命令检测 @run_preprocessor -async def _(matcher: Matcher, bot: Bot, session: EventSession, state: T_State): +async def _( + matcher: Matcher, bot: Bot, session: EventSession, state: T_State, event: Event +): module = None if plugin := matcher.plugin: module = plugin.module_name if metadata := plugin.metadata: extra = metadata.extra - if extra.get("plugin_type") in [PluginType.HIDDEN, PluginType.DEPENDANT]: + if extra.get("plugin_type") in [ + PluginType.HIDDEN, + PluginType.DEPENDANT, + PluginType.ADMIN, + ]: return else: return + if matcher.type == "notice": + return user_id = session.id1 group_id = session.id3 or session.id2 malicious_ban_time = Config.get_config("hook", "MALICIOUS_BAN_TIME") diff --git a/zhenxun/builtin_plugins/init/__init__.py b/zhenxun/builtin_plugins/init/__init__.py index b3bab4dba..0631c9ed0 100644 --- a/zhenxun/builtin_plugins/init/__init__.py +++ b/zhenxun/builtin_plugins/init/__init__.py @@ -23,17 +23,15 @@ async def _(bot: Bot): if PlatformUtils.get_platform(bot) == "qq": logger.debug(f"更新Bot: {bot.self_id} 的群认证...") group_list, _ = await PlatformUtils.get_group_list(bot) - gid_list = [(g.group_id, g.group_name) for g in group_list] db_group_list = await GroupConsole.all().values_list("group_id", flat=True) create_list = [] update_id = [] - for gid, name in gid_list: - if gid not in db_group_list: - create_list.append( - GroupConsole(group_id=gid, group_name=name, group_flag=1) - ) + for group in group_list: + if group.group_id not in db_group_list: + group.group_flag = 1 + create_list.append(group) else: - update_id.append(gid) + update_id.append(group.group_id) if create_list: await GroupConsole.bulk_create(create_list, 10) else: diff --git a/zhenxun/builtin_plugins/init/init_plugin.py b/zhenxun/builtin_plugins/init/init_plugin.py index 8a65e27b8..5650fb476 100644 --- a/zhenxun/builtin_plugins/init/init_plugin.py +++ b/zhenxun/builtin_plugins/init/init_plugin.py @@ -87,14 +87,14 @@ async def _handle_setting( ) if extra_data.tasks: for task in extra_data.tasks: - task_list.append( + task_list.append((task.create_status, TaskInfo( module=task.module, name=task.name, status=task.status, run_time=task.run_time, ) - ) + )) @driver.on_startup @@ -163,18 +163,25 @@ async def _(): } create_list = [] update_list = [] - for task in task_list: + for status, task in task_list: if task.module not in module_dict: - create_list.append(task) + create_list.append((status, task)) else: task.id = module_dict[task.module] update_list.append(task) if create_list: - await TaskInfo.bulk_create(create_list, 10) + _create_list = [t[1] for t in create_list] + await TaskInfo.bulk_create(_create_list, 10) + if block := [t[1].module for t in create_list if not t[0]]: + block_task = ",".join(block) + "," + if group_list := await GroupConsole.all(): + for group in group_list: + group.block_task += block_task + await GroupConsole.bulk_update(group_list, ["block_task"], 10) if update_list: await TaskInfo.bulk_update( update_list, - ["run_time", "status", "name"], + ["run_time", "name"], 10, ) await data_migration() diff --git a/zhenxun/builtin_plugins/nickname.py b/zhenxun/builtin_plugins/nickname.py index f5cce1fb7..60779e8b6 100644 --- a/zhenxun/builtin_plugins/nickname.py +++ b/zhenxun/builtin_plugins/nickname.py @@ -10,7 +10,7 @@ from nonebot_plugin_session import EventSession from nonebot_plugin_userinfo import EventUserInfo, UserInfo -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.models.ban_console import BanConsole from zhenxun.models.friend_user import FriendUser @@ -24,11 +24,11 @@ name="昵称系统", description="区区昵称,才不想叫呢!", usage=f""" - 个人昵称,将替换{NICKNAME}称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称 + 个人昵称,将替换{BotConfig.self_nickname}称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称 指令: 以后叫我 [昵称]: 设置当前群聊/私聊的昵称 全局昵称设置 [昵称]: 设置当前所有群聊和私聊的昵称 - {NICKNAME}我是谁 + {BotConfig.self_nickname}我是谁 """.strip(), extra=PluginExtraData( author="HibiKier", @@ -86,25 +86,25 @@ CALL_NAME = [ "好啦好啦,我知道啦,{},以后就这么叫你吧", - f"嗯嗯,{NICKNAME}" + "记住你的昵称了哦,{}", + f"嗯嗯,{BotConfig.self_nickname}" + "记住你的昵称了哦,{}", "好突然,突然要叫你昵称什么的...{}..", - f"{NICKNAME}" + "会好好记住{}的,放心吧", + f"{BotConfig.self_nickname}" + "会好好记住{}的,放心吧", "好..好.,那窝以后就叫你{}了.", ] REMIND = [ "我肯定记得你啊,你是{}啊", "我不会忘记你的,你也不要忘记我!{}", - f"哼哼,{NICKNAME}" + "记忆力可是很好的,{}", + f"哼哼,{BotConfig.self_nickname}" + "记忆力可是很好的,{}", "嗯?你是失忆了嘛...{}..", - f"不要小看{NICKNAME}" + "的记忆力啊!笨蛋{}!QAQ", + f"不要小看{BotConfig.self_nickname}" + "的记忆力啊!笨蛋{}!QAQ", "哎?{}..怎么了吗..突然这样问..", ] CANCEL = [ - f"呜..{NICKNAME}" + "睡一觉就会忘记的..和梦一样..{}", + f"呜..{BotConfig.self_nickname}" + "睡一觉就会忘记的..和梦一样..{}", "窝知道了..{}..", - f"是{NICKNAME}" + "哪里做的不好嘛..好吧..晚安{}", + f"是{BotConfig.self_nickname}" + "哪里做的不好嘛..好吧..晚安{}", "呃,{},下次我绝对绝对绝对不会再忘记你!", "可..可恶!{}!太可恶了!呜", ] @@ -152,9 +152,9 @@ async def dependency( logger.debug( "昵称设置禁止字符: [{word}]", "昵称设置", session=session ) - await MessageUtils.build_message(f"字符 [{x}] 为禁止字符!").finish( - at_sender=True - ) + await MessageUtils.build_message( + f"字符 [{word}] 为禁止字符!" + ).finish(at_sender=True) return Depends(dependency) diff --git a/zhenxun/builtin_plugins/platform/qq/group_handle.py b/zhenxun/builtin_plugins/platform/qq/group_handle.py index 262a67f10..d2f851e2b 100644 --- a/zhenxun/builtin_plugins/platform/qq/group_handle.py +++ b/zhenxun/builtin_plugins/platform/qq/group_handle.py @@ -18,7 +18,7 @@ from nonebot.plugin import PluginMetadata from nonebot_plugin_alconna import At -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task from zhenxun.models.fg_request import FgRequest @@ -44,7 +44,7 @@ RegisterConfig( module="invite_manager", key="message", - value=f"请不要未经同意就拉{NICKNAME}入群!告辞!", + value=f"请不要未经同意就拉{BotConfig.self_nickname}入群!告辞!", help="强制拉群后进群回复的内容", ), RegisterConfig( @@ -88,8 +88,6 @@ ) -superuser = nonebot.get_driver().config.platform_superusers["qq"][0] - base_config = Config.get("invite_manager") @@ -108,6 +106,7 @@ @group_increase_handle.handle() async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent): + superuser = BotConfig.get_superuser("qq") user_id = str(event.user_id) group_id = str(event.group_id) if user_id == bot.self_id: @@ -231,17 +230,19 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent """群欢迎消息""" _flmt.start_cd(group_id) path = DATA_PATH / "welcome_message" / "qq" / f"{group_id}" - data = json.load((path / "text.json").open(encoding="utf-8")) - message = data["message"] - msg_split = re.split(r"\[image:\d+\]", message) + file = path / "text.json" msg_list = [] - if data["at"]: - msg_list.append(At(flag="user", target=user_id)) - for i, text in enumerate(msg_split): - msg_list.append(text) - img_file = path / f"{i}.png" - if img_file.exists(): - msg_list.append(img_file) + if file.exists(): + data = json.load((path / "text.json").open(encoding="utf-8")) + message = data["message"] + msg_split = re.split(r"\[image:\d+\]", message) + if data["at"]: + msg_list.append(At(flag="user", target=user_id)) + for i, text in enumerate(msg_split): + msg_list.append(text) + img_file = path / f"{i}.png" + if img_file.exists(): + msg_list.append(img_file) if not TaskInfo.is_block("group_welcome", group_id): logger.info(f"发送群欢迎消息...", "入群检测", group_id=group_id) if msg_list: diff --git a/zhenxun/builtin_plugins/plugin_store/__init__.py b/zhenxun/builtin_plugins/plugin_store/__init__.py new file mode 100644 index 000000000..d9c2cc522 --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_store/__init__.py @@ -0,0 +1,135 @@ +from nonebot.permission import SUPERUSER +from nonebot.plugin import PluginMetadata +from nonebot_plugin_alconna import Alconna, Args, Subcommand, on_alconna +from nonebot_plugin_session import EventSession + +from zhenxun.configs.utils import PluginExtraData +from zhenxun.services.log import logger +from zhenxun.utils.enum import PluginType +from zhenxun.utils.message import MessageUtils + +from .data_source import ShopManage + +__plugin_meta__ = PluginMetadata( + name="插件商店", + description="插件商店", + usage=""" + 插件商店 : 查看当前的插件商店 + 添加插件 id : 添加插件 + 移除插件 id : 移除插件 + 搜索插件 name or author : 搜索插件 + 更新插件 id : 更新插件 + """.strip(), + extra=PluginExtraData( + author="HibiKier", + version="0.1", + plugin_type=PluginType.SUPERUSER, + ).dict(), +) + +_matcher = on_alconna( + Alconna( + "插件商店", + Subcommand("add", Args["plugin_id", int]), + Subcommand("remove", Args["plugin_id", int]), + Subcommand("search", Args["plugin_name_or_author", str]), + Subcommand("update", Args["plugin_id", int]), + ), + permission=SUPERUSER, + priority=1, + block=True, +) + +_matcher.shortcut( + r"添加插件", + command="插件商店", + arguments=["add", "{%0}"], + prefix=True, +) + +_matcher.shortcut( + r"移除插件", + command="插件商店", + arguments=["remove", "{%0}"], + prefix=True, +) + +_matcher.shortcut( + r"搜索插件", + command="插件商店", + arguments=["search", "{%0}"], + prefix=True, +) + +_matcher.shortcut( + r"更新插件", + command="插件商店", + arguments=["update", "{%0}"], + prefix=True, +) + +@_matcher.assign("$main") +async def _(session: EventSession): + try: + result = await ShopManage.get_plugins_info() + logger.info("查看插件列表", "插件商店", session=session) + await MessageUtils.build_message(result).send() + except Exception as e: + logger.error(f"查看插件列表失败 e: {e}", "插件商店", session=session, e=e) + await MessageUtils.build_message("获取插件列表失败...").send() + + +@_matcher.assign("add") +async def _(session: EventSession, plugin_id: int): + try: + await MessageUtils.build_message( + f"正在添加插件 Id: {plugin_id}" + ).send() + result = await ShopManage.add_plugin(plugin_id) + except Exception as e: + logger.error(f"添加插件 Id: {plugin_id}失败", "插件商店", session=session, e=e) + await MessageUtils.build_message( + f"添加插件 Id: {plugin_id} 失败 e: {e}" + ).finish() + logger.info(f"添加插件 Id: {plugin_id}", "插件商店", session=session) + await MessageUtils.build_message(result).send() + + +@_matcher.assign("remove") +async def _(session: EventSession, plugin_id: int): + try: + result = await ShopManage.remove_plugin(plugin_id) + except Exception as e: + logger.error(f"移除插件 Id: {plugin_id}失败", "插件商店", session=session, e=e) + await MessageUtils.build_message( + f"移除插件 Id: {plugin_id} 失败 e: {e}" + ).finish() + logger.info(f"移除插件 Id: {plugin_id}", "插件商店", session=session) + await MessageUtils.build_message(result).send() + +@_matcher.assign("search") +async def _(session: EventSession, plugin_name_or_author: str): + try: + result = await ShopManage.search_plugin(plugin_name_or_author) + except Exception as e: + logger.error(f"搜索插件 name: {plugin_name_or_author}失败", "插件商店", session=session, e=e) + await MessageUtils.build_message( + f"搜索插件 name: {plugin_name_or_author} 失败 e: {e}" + ).finish() + logger.info(f"搜索插件 name: {plugin_name_or_author}", "插件商店", session=session) + await MessageUtils.build_message(result).send() + +@_matcher.assign("update") +async def _(session: EventSession, plugin_id: int): + try: + await MessageUtils.build_message( + f"正在更新插件 Id: {plugin_id}" + ).send() + result = await ShopManage.update_plugin(plugin_id) + except Exception as e: + logger.error(f"更新插件 Id: {plugin_id}失败", "插件商店", session=session, e=e) + await MessageUtils.build_message( + f"更新插件 Id: {plugin_id} 失败 e: {e}" + ).finish() + logger.info(f"更新插件 Id: {plugin_id}", "插件商店", session=session) + await MessageUtils.build_message(result).send() diff --git a/zhenxun/builtin_plugins/plugin_store/config.py b/zhenxun/builtin_plugins/plugin_store/config.py new file mode 100644 index 000000000..1b6c83609 --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_store/config.py @@ -0,0 +1,19 @@ +from pathlib import Path + +BASE_PATH = Path() / "zhenxun" +BASE_PATH.mkdir(parents=True, exist_ok=True) + + +CONFIG_URL = "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins/plugins.json" +"""插件信息文件""" + +CONFIG_INDEX_URL = "https://raw.githubusercontent.com/zhenxun-org/zhenxun_bot_plugins_index/index/plugins.json" +"""插件索引库信息文件""" + +CONFIG_INDEX_CDN_URL = "https://cdn.jsdelivr.net/gh/zhenxun-org/zhenxun_bot_plugins_index@index/plugins.json" +"""插件索引库信息文件cdn""" + +DOWNLOAD_URL = ( + "https://api.github.com/repos/zhenxun-org/zhenxun_bot_plugins/contents/{}?ref=main" +) +"""插件下载地址""" diff --git a/zhenxun/builtin_plugins/plugin_store/data_source.py b/zhenxun/builtin_plugins/plugin_store/data_source.py new file mode 100644 index 000000000..6b57e17e3 --- /dev/null +++ b/zhenxun/builtin_plugins/plugin_store/data_source.py @@ -0,0 +1,410 @@ +import re +import shutil +import subprocess +from pathlib import Path + +import ujson as json + +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.services.log import logger +from zhenxun.utils.http_utils import AsyncHttpx +from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle + +from .config import BASE_PATH, CONFIG_URL, CONFIG_INDEX_URL, CONFIG_INDEX_CDN_URL, DOWNLOAD_URL + + +def row_style(column: str, text: str) -> RowStyle: + """被动技能文本风格 + + 参数: + column: 表头 + text: 文本内容 + + 返回: + RowStyle: RowStyle + """ + style = RowStyle() + if column in ["-"]: + if text == "已安装": + style.font_color = "#67C23A" + return style + + +async def recurrence_get_url( + url: str, data_list: list[tuple[str, str]], ignore_list: list[str] = [], api_url: str = None +): + """递归获取目录下所有文件 + + 参数: + url: 信息url + data_list: 数据列表 + + 异常: + ValueError: 访问错误 + """ + logger.debug(f"访问插件下载信息 URL: {url}", "插件管理") + res = await AsyncHttpx.get(url) + if res.status_code != 200: + raise ValueError(f"访问错误, code: {res.status_code}") + json_data = res.json() + if isinstance(json_data, list): + for v in json_data: + data_list.append((v.get("download_url"), v["path"])) + else: + data_list.append((json_data.get("download_url"), json_data["path"])) + for download_url, path in data_list: + if not download_url: + _url = api_url + path if api_url else DOWNLOAD_URL.format(path) + if _url not in ignore_list: + ignore_list.append(_url) + await recurrence_get_url(_url, data_list, ignore_list, api_url) + + +async def download_file(url: str, _is: bool = False, api_url: str = None): + """下载文件 + + 参数: + url: 插件详情url + _is: 是否为第三方插件 + url_start : 第三方插件url + + 异常: + ValueError: 下载失败 + """ + data_list = [] + await recurrence_get_url(url, data_list, api_url=api_url) + for download_url, path in data_list: + if download_url and "." in path: + logger.debug(f"下载文件: {path}", "插件管理") + base_path = "zhenxun/plugins/" if _is else "zhenxun/" + file = Path(f"{base_path}{path}") + file.parent.mkdir(parents=True, exist_ok=True) + print(download_url) + r = await AsyncHttpx.get(download_url) + if r.status_code != 200: + raise ValueError(f"文件下载错误, code: {r.status_code}") + content = r.text.replace("\r\n", "\n") # 统一换行符为 UNIX 风格 + with open(file, "w", encoding="utf8") as f: + logger.debug(f"写入文件: {file}", "插件管理") + f.write(content) + + +def install_requirement(plugin_path: Path): + requirement_files = ["requirement.txt", "requirements.txt"] + requirement_paths = [plugin_path / file for file in requirement_files] + + existing_requirements = next((path for path in requirement_paths if path.exists()), None) + + if not existing_requirements: + logger.debug(f"No requirement.txt found for plugin: {plugin_path.name}", "插件管理") + return + + try: + result = subprocess.run( + ["pip", "install", "-r", str(existing_requirements)], + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + logger.debug( + f"Successfully installed dependencies for plugin: {plugin_path.name}. Output:\n{result.stdout}", + "插件管理", + ) + except subprocess.CalledProcessError as e: + logger.error( + f"Failed to install dependencies for plugin: {plugin_path.name}. Error:\n{e.stderr}" + ) + + +class ShopManage: + type2name = { + "NORMAL": "普通插件", + "ADMIN": "管理员插件", + "SUPERUSER": "超级用户插件", + "ADMIN_SUPERUSER": "管理员/超级用户插件", + "DEPENDANT": "依赖插件", + "HIDDEN": "其他插件", + } + + @classmethod + async def __get_data(cls) -> dict: + """获取插件信息数据 + + 异常: + ValueError: 访问请求失败 + + 返回: + dict: 插件信息数据 + """ + res = await AsyncHttpx.get(CONFIG_URL) + res2 = await AsyncHttpx.get(CONFIG_INDEX_URL) + + if res2.status_code != 200: + logger.info("访问第三方插件信息文件失败,改为进行cdn访问") + res2 = await AsyncHttpx.get(CONFIG_INDEX_CDN_URL) + + # 检查请求结果 + if res.status_code != 200 or res2.status_code != 200: + raise ValueError(f"下载错误, code: {res.status_code}, {res2.status_code}") + + # 解析并合并返回的 JSON 数据 + data1 = json.loads(res.text) + data2 = json.loads(res2.text) + return {**data1, **data2} + + @classmethod + def version_check(cls, plugin_info: dict, suc_plugin: dict[str, str]): + module = plugin_info["module"] + if module in suc_plugin: + if plugin_info["version"] != suc_plugin[module]: + return f"{suc_plugin[module]} (有更新->{plugin_info['version']})" + return plugin_info["version"] + + @classmethod + def get_url_path(cls, module_path: str, is_dir: bool) -> str: + url_path = None + path = BASE_PATH + module_path_split = module_path.split(".") + if len(module_path_split) == 2: + """单个文件或文件夹""" + if is_dir: + url_path = "/".join(module_path_split) + else: + url_path = "/".join(module_path_split) + ".py" + else: + """嵌套文件或文件夹""" + for p in module_path_split[:-1]: + path = path / p + path.mkdir(parents=True, exist_ok=True) + if is_dir: + url_path = f"{'/'.join(module_path_split)}" + else: + url_path = f"{'/'.join(module_path_split)}.py" + return url_path + + @classmethod + async def get_plugins_info(cls) -> BuildImage | str: + """插件列表 + + 返回: + BuildImage | str: 返回消息 + """ + data: dict = await cls.__get_data() + column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"] + for k in data.copy(): + if data[k]["plugin_type"]: + data[k]["plugin_type"] = cls.type2name[data[k]["plugin_type"]] + plugin_list = await PluginInfo.filter(load_status=True).values_list( + "module", "version" + ) + suc_plugin = {p[0]: p[1] for p in plugin_list if p[1]} + data_list = [ + [ + "已安装" if plugin_info[1]["module"] in suc_plugin else "", + id, + plugin_info[0], + plugin_info[1]["description"], + plugin_info[1]["author"], + cls.version_check(plugin_info[1], suc_plugin), + plugin_info[1]["plugin_type"], + ] + for id, plugin_info in enumerate(data.items()) + ] + return await ImageTemplate.table_page( + "插件列表", + f"通过安装/卸载插件 ID 来管理插件", + column_name, + data_list, + text_style=row_style, + ) + + @classmethod + async def add_plugin(cls, plugin_id: int) -> str: + """添加插件 + + 参数: + plugin_id: 插件id + + 返回: + str: 返回消息 + """ + data: dict = await cls.__get_data() + if plugin_id < 0 or plugin_id >= len(data): + return "插件ID不存在..." + plugin_key = list(data.keys())[plugin_id] + plugin_info = data[plugin_key] + module_path_split = plugin_info["module_path"].split(".") + url_path = cls.get_url_path(plugin_info["module_path"], plugin_info["is_dir"]) + if not url_path and plugin_info["module_path"]: + return "插件下载地址构建失败..." + logger.debug(f"尝试下载插件 URL: {url_path}", "插件管理") + github_url = plugin_info.get("github_url") + if github_url: + github_path = re.search(r"github\.com/([^/]+/[^/]+)", github_url).group(1) + api_url = f"https://api.github.com/repos/{github_path}/contents/" + download_url = f"{api_url}{url_path}?ref=main" + else: + download_url = DOWNLOAD_URL.format(url_path) + api_url = None + + await download_file(download_url, bool(github_url), api_url) + + # 安装依赖 + plugin_path = BASE_PATH / "/".join(module_path_split) + if url_path and github_url: + plugin_path = BASE_PATH / "plugins" / "/".join(module_path_split) + res = await AsyncHttpx.get(api_url) + if res.status_code != 200: + return f"访问错误, code: {res.status_code}" + json_data = res.json() + requirement_file = next( + (v for v in json_data if v["name"] in ["requirements.txt", "requirement.txt"]), None + ) + if requirement_file: + r = await AsyncHttpx.get(requirement_file.get("download_url")) + if r.status_code != 200: + raise ValueError(f"文件下载错误, code: {r.status_code}") + requirement_path = plugin_path / requirement_file["name"] + with open(requirement_path, "w", encoding="utf8") as f: + logger.debug(f"写入文件: {requirement_path}", "插件管理") + f.write(r.text) + + install_requirement(plugin_path) + + return f"插件 {plugin_key} 安装成功! 重启后生效" + + @classmethod + async def remove_plugin(cls, plugin_id: int) -> str: + """移除插件 + + 参数: + plugin_id: 插件id + + 返回: + str: 返回消息 + """ + data: dict = await cls.__get_data() + if plugin_id < 0 or plugin_id >= len(data): + return "插件ID不存在..." + plugin_key = list(data.keys())[plugin_id] + plugin_info = data[plugin_key] + path = BASE_PATH + github_url = plugin_info.get("github_url") + if github_url: + path = BASE_PATH / 'plugins' + for p in plugin_info["module_path"].split("."): + path = path / p + if not plugin_info["is_dir"]: + path = Path(f"{path}.py") + if not path.exists(): + return f"插件 {plugin_key} 不存在..." + logger.debug(f"尝试移除插件 {plugin_key} 文件: {path}", "插件管理") + if plugin_info["is_dir"]: + shutil.rmtree(path) + else: + path.unlink() + return f"插件 {plugin_key} 移除成功! 重启后生效" + + @classmethod + async def search_plugin(cls, plugin_name_or_author: str) -> BuildImage | str: + """搜索插件 + + 参数: + plugin_name_or_author: 插件名称或作者 + + 返回: + BuildImage | str: 返回消息 + """ + data: dict = await cls.__get_data() + column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"] + for k in data.copy(): + if data[k]["plugin_type"]: + data[k]["plugin_type"] = cls.type2name[data[k]["plugin_type"]] + plugin_list = await PluginInfo.filter(load_status=True).values_list( + "module", "version" + ) + suc_plugin = {p[0]: p[1] for p in plugin_list if p[1]} + filtered_data = [ + (id, plugin_info) + for id, plugin_info in enumerate(data.items()) + if plugin_name_or_author.lower() in plugin_info[0].lower() + or plugin_name_or_author.lower() in plugin_info[1]["author"].lower() + ] + + data_list = [ + [ + "已安装" if plugin_info[1]["module"] in suc_plugin else "", + id, + plugin_info[0], + plugin_info[1]["description"], + plugin_info[1]["author"], + cls.version_check(plugin_info[1], suc_plugin), + plugin_info[1]["plugin_type"], + ] + for id, plugin_info in filtered_data + ] + if not data_list: + return "未找到相关插件..." + return await ImageTemplate.table_page( + "插件列表", + f"通过安装/卸载插件 ID 来管理插件", + column_name, + data_list, + text_style=row_style, + ) + + @classmethod + async def update_plugin(cls, plugin_id: int) -> str: + """更新插件 + + 参数: + plugin_id: 插件id + + 返回: + str: 返回消息 + """ + data: dict = await cls.__get_data() + if plugin_id < 0 or plugin_id >= len(data): + return "插件ID不存在..." + plugin_key = list(data.keys())[plugin_id] + plugin_info = data[plugin_key] + module_path_split = plugin_info["module_path"].split(".") + url_path = cls.get_url_path(plugin_info["module_path"], plugin_info["is_dir"]) + if not url_path and plugin_info["module_path"]: + return "插件下载地址构建失败..." + logger.debug(f"尝试下载插件 URL: {url_path}", "插件管理") + github_url = plugin_info.get("github_url") + if github_url: + github_path = re.search(r"github\.com/([^/]+/[^/]+)", github_url).group(1) + api_url = f"https://api.github.com/repos/{github_path}/contents/" + download_url = f"{api_url}{url_path}?ref=main" + else: + download_url = DOWNLOAD_URL.format(url_path) + api_url = None + + await download_file(download_url, bool(github_url), api_url) + + # 安装依赖 + plugin_path = BASE_PATH / "/".join(module_path_split) + if url_path and github_url: + plugin_path = BASE_PATH / "plugins" / "/".join(module_path_split) + res = await AsyncHttpx.get(api_url) + if res.status_code != 200: + return f"访问错误, code: {res.status_code}" + json_data = res.json() + requirement_file = next( + (v for v in json_data if v["name"] in ["requirements.txt", "requirement.txt"]), None + ) + if requirement_file: + r = await AsyncHttpx.get(requirement_file.get("download_url")) + if r.status_code != 200: + raise ValueError(f"文件下载错误, code: {r.status_code}") + requirement_path = plugin_path / requirement_file["name"] + with open(requirement_path, "w", encoding="utf8") as f: + logger.debug(f"写入文件: {requirement_path}", "插件管理") + f.write(r.text) + + install_requirement(plugin_path) + + return f"插件 {plugin_key} 更新成功! 重启后生效" diff --git a/zhenxun/builtin_plugins/record_request.py b/zhenxun/builtin_plugins/record_request.py index e894d61e5..8a837e7a8 100644 --- a/zhenxun/builtin_plugins/record_request.py +++ b/zhenxun/builtin_plugins/record_request.py @@ -11,7 +11,7 @@ from nonebot_plugin_apscheduler import scheduler from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.models.fg_request import FgRequest from zhenxun.models.friend_user import FriendUser @@ -69,7 +69,7 @@ def clear(cls): @friend_req.handle() async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSession): - superuser = nonebot.get_driver().config.platform_superusers["qq"][0] + superuser = BotConfig.get_superuser("qq") if event.user_id and Timer.check(event.user_id): logger.debug(f"收录好友请求...", "好友请求", target=event.user_id) user = await bot.get_stranger_info(user_id=event.user_id) @@ -78,7 +78,6 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi # age = str(user["age"]) comment = event.comment if superuser: - superuser = int(superuser) await MessageUtils.build_message( f"*****一份好友申请*****\n" f"昵称:{nickname}({event.user_id})\n" @@ -118,7 +117,7 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi @group_req.handle() async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession): - superuser = nonebot.get_driver().config.platform_superusers["qq"][0] + superuser = BotConfig.get_superuser("qq") # 邀请 if event.sub_type == "invite": if str(event.user_id) in bot.config.superusers: @@ -165,15 +164,14 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio target=event.group_id, ) nickname = await FriendUser.get_user_name(str(event.user_id)) - await Text( - f"*****一份入群申请*****\n" - f"申请人:{nickname}({event.user_id})\n" - f"群聊:{event.group_id}\n" - f"邀请日期:{datetime.now().replace(microsecond=0)}" - ).send_to(target=TargetQQPrivate(user_id=superuser), bot=bot) + await PlatformUtils.send_superuser( + bot, + f"*****一份入群申请*****\n申请人:{nickname}({event.user_id})\n群聊:{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}", + superuser, + ) await bot.send_private_msg( user_id=event.user_id, - message=f"想要邀请我偷偷入群嘛~已经提醒{NICKNAME}的管理员大人了\n" + message=f"想要邀请我偷偷入群嘛~已经提醒{BotConfig.self_nickname}的管理员大人了\n" "请确保已经群主或群管理沟通过!\n" "等待管理员处理吧!", ) diff --git a/zhenxun/plugins/restart/__init__.py b/zhenxun/builtin_plugins/restart/__init__.py similarity index 85% rename from zhenxun/plugins/restart/__init__.py rename to zhenxun/builtin_plugins/restart/__init__.py index eed6fa17f..3c16091f2 100644 --- a/zhenxun/plugins/restart/__init__.py +++ b/zhenxun/builtin_plugins/restart/__init__.py @@ -11,7 +11,7 @@ from nonebot.rule import to_me from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType @@ -48,11 +48,13 @@ @_matcher.got( "flag", - prompt=f"确定是否重启{NICKNAME}?确定请回复[是|好|确定](重启失败咱们将失去联系,请谨慎!)", + prompt=f"确定是否重启{BotConfig.self_nickname}?确定请回复[是|好|确定](重启失败咱们将失去联系,请谨慎!)", ) async def _(bot: Bot, session: EventSession, flag: str = ArgStr("flag")): if flag.lower() in ["true", "是", "好", "确定", "确定是"]: - await MessageUtils.build_message(f"开始重启{NICKNAME}..请稍等...").send() + await MessageUtils.build_message( + f"开始重启{BotConfig.self_nickname}..请稍等..." + ).send() with open(RESTART_MARK, "w", encoding="utf8") as f: f.write(f"{bot.self_id} {session.id1}") logger.info("开始重启真寻...", "重启", session=session) @@ -90,7 +92,7 @@ async def _(bot: Bot): bot_id, session_id = f.read().split() if bot := nonebot.get_bot(bot_id): if target := PlatformUtils.get_target(bot, session_id): - await MessageUtils.build_message(f"{NICKNAME}已成功重启!").send( - target, bot=bot - ) + await MessageUtils.build_message( + f"{BotConfig.self_nickname}已成功重启!" + ).send(target, bot=bot) RESTART_MARK.unlink() diff --git a/zhenxun/builtin_plugins/scheduler/morning.py b/zhenxun/builtin_plugins/scheduler/morning.py index d12e07652..dbb3536ac 100644 --- a/zhenxun/builtin_plugins/scheduler/morning.py +++ b/zhenxun/builtin_plugins/scheduler/morning.py @@ -2,7 +2,7 @@ from nonebot.plugin import PluginMetadata from nonebot_plugin_apscheduler import scheduler -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.path_config import IMAGE_PATH from zhenxun.configs.utils import PluginExtraData, Task from zhenxun.models.task_info import TaskInfo @@ -19,10 +19,7 @@ author="HibiKier", version="0.1", plugin_type=PluginType.HIDDEN, - tasks=[ - Task(module="group_welcome", name="进群欢迎"), - Task(module="refund_group_remind", name="退群提醒"), - ], + tasks=[Task(module="morning_goodnight", name="早晚安")], ).dict(), ) @@ -63,7 +60,10 @@ async def _(): ) async def _(): message = MessageUtils.build_message( - [f"{NICKNAME}要睡觉了,你们也要早点睡呀", IMAGE_PATH / "zhenxun" / "sleep.jpg"] + [ + f"{BotConfig.self_nickname}要睡觉了,你们也要早点睡呀", + IMAGE_PATH / "zhenxun" / "sleep.jpg", + ] ) await broadcast_group( message, diff --git a/zhenxun/builtin_plugins/sign_in/__init__.py b/zhenxun/builtin_plugins/sign_in/__init__.py index ac09256a2..277f883dc 100644 --- a/zhenxun/builtin_plugins/sign_in/__init__.py +++ b/zhenxun/builtin_plugins/sign_in/__init__.py @@ -72,6 +72,12 @@ default_value=0.05, type=float, ), + RegisterConfig( + key="IMAGE_STYLE", + value="zhenxun", + help="签到图片样式, [normal, zhenxun]", + default_value="zhenxun", + ), ], limits=[PluginCdBlock()], ).dict(), diff --git a/zhenxun/builtin_plugins/sign_in/utils.py b/zhenxun/builtin_plugins/sign_in/utils.py index b8fe28538..81f2a6d79 100644 --- a/zhenxun/builtin_plugins/sign_in/utils.py +++ b/zhenxun/builtin_plugins/sign_in/utils.py @@ -7,9 +7,10 @@ import nonebot import pytz from nonebot.drivers import Driver +from nonebot_plugin_htmlrender import template_to_pic -from zhenxun.configs.config import NICKNAME, Config -from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.configs.config import BotConfig, Config +from zhenxun.configs.path_config import IMAGE_PATH, TEMPLATE_PATH from zhenxun.models.sign_log import SignLog from zhenxun.models.sign_user import SignUser from zhenxun.utils.image_utils import BuildImage @@ -25,8 +26,27 @@ lik2relation, ) +AVA_URL = "http://q1.qlogo.cn/g?b=qq&nk={}&s=160" + driver: Driver = nonebot.get_driver() +base_config = Config.get("sign_in") + + +MORNING_MESSAGE = [ + "早上好,希望今天是美好的一天!", + "醒了吗,今天也要元气满满哦!", + "早上好呀,今天也要开心哦!", + "早安,愿你拥有美好的一天!", +] + +LG_MESSAGE = [ + "今天要早点休息哦~", + "可不要熬夜到太晚呀", + "请尽早休息吧!", + "不要熬夜啦!", +] + @driver.on_startup async def init_image(): @@ -73,9 +93,14 @@ async def get_card( if card_file.exists(): return card_file is_card_view = True - return await _generate_card( - user, nickname, add_impression, gold, gift, is_double, is_card_view - ) + if base_config.get("IMAGE_STYLE") == "zhenxun": + return await _generate_html_card( + user, nickname, add_impression, gold, gift, is_double, is_card_view + ) + else: + return await _generate_card( + user, nickname, add_impression, gold, gift, is_double, is_card_view + ) async def _generate_card( @@ -125,7 +150,9 @@ async def _generate_card( level = "8" interpolation = 0 await info_img.text((0, 0), f"· 好感度等级:{level} [{lik2relation[level]}]") - await info_img.text((0, 20), f"· {NICKNAME}对你的态度:{level2attitude[level]}") + await info_img.text( + (0, 20), f"· {BotConfig.self_nickname}对你的态度:{level2attitude[level]}" + ) await info_img.text((0, 40), f"· 距离升级还差 {interpolation:.2f} 好感度") bar_bk = BuildImage(220, 20, background=SIGN_RESOURCE_PATH / "bar_white.png") @@ -189,7 +216,9 @@ async def _generate_card( f"好感度:{user.impression:.2f}", size=30 ) watermark = await BuildImage.build_text_image( - f"{NICKNAME}@{datetime.now().year}", size=15, font_color=(155, 155, 155) + f"{BotConfig.self_nickname}@{datetime.now().year}", + size=15, + font_color=(155, 155, 155), ) today_data = BuildImage(300, 300, color=(255, 255, 255, 0), font_size=20) if is_card_view: @@ -237,8 +266,8 @@ async def _generate_card( _type = "sign" current_date = datetime.now() current_datetime_str = current_date.strftime("%Y-%m-%d %a %H:%M:%S") - data = current_date.date() - data_img = await BuildImage.build_text_image( + date = current_date.date() + date_img = await BuildImage.build_text_image( f"时间:{current_datetime_str}", size=20 ) await bk.paste(nickname_img, (30, 15)) @@ -249,7 +278,7 @@ async def _generate_card( # await bk.paste(sign_day_img, (398, 158)) # await bk.text((_x, 167), "days") await bk.paste(tip_image, (10, 167)) - await bk.paste(data_img, (220, 370)) + await bk.paste(date_img, (220, 370)) await bk.paste(lik_text1_img, (220, 240)) await bk.paste(lik_text2_img, (262, 234)) await bk.paste(bar_bk, (225, 275)) @@ -257,8 +286,8 @@ async def _generate_card( await bk.paste(today_sign_text_img, (550, 180)) await bk.paste(today_data, (580, 220)) await bk.paste(watermark, (15, 400)) - await bk.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{data}.png") - return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{data}.png" + await bk.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{date}.png") + return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{date}.png" async def generate_progress_bar_pic(): @@ -333,3 +362,103 @@ def clear_sign_data_pic(): for file in os.listdir(SIGN_TODAY_CARD_PATH): if str(date) not in file: os.remove(SIGN_TODAY_CARD_PATH / file) + + +async def _generate_html_card( + user: SignUser, + nickname: str, + impression: float, + gold: int | None, + gift: str, + is_double: bool = False, + is_card_view: bool = False, +) -> Path: + """生成签到卡片 + + 参数: + user: SignUser + nickname: 用户昵称 + impression: 新增的好感度 + gold: 金币 + gift: 礼物 + is_double: 是否触发双倍. + is_card_view: 是否展示好感度卡片. + + 返回: + Path: 卡片路径 + """ + user_console = await user.user_console + if user_console and user_console.uid: + uid = f"{user_console.uid}".rjust(12, "0") + uid = uid[:4] + " " + uid[4:8] + " " + uid[8:] + else: + uid = "XXXX XXXX XXXX" + level, next_impression, previous_impression = get_level_and_next_impression( + float(user.impression) + ) + interpolation = next_impression - impression + if level == "9": + level = "8" + interpolation = 0 + message = f"{BotConfig.self_nickname}希望你开心!" + hour = datetime.now().hour + if hour > 6 and hour < 10: + message = random.choice(MORNING_MESSAGE) + elif hour >= 0 and hour < 6: + message = random.choice(LG_MESSAGE) + _impression = impression + if is_double: + _impression = f"{impression}(×2)" + process = 1 - (next_impression - user.impression) / ( + next_impression - previous_impression + ) + if next_impression == 0: + process = 0 + now = datetime.now() + data = { + "ava": AVA_URL.format(user.user_id), + "name": nickname, + "uid": uid, + "sign_count": f"{user.sign_count}", + "message": f"{BotConfig.self_nickname}说: {message}", + "cur_impression": f"{user.impression:.2f}", + "impression": f"好感度+{_impression}", + "gold": f"金币+{gold}", + "gift": gift, + "level": f"{level} [{lik2relation[level]}]", + "attitude": f"对你的态度: {level2attitude[level]}", + "interpolation": f"{interpolation:.2f}", + "heart2": [1 for _ in range(int(level))], + "heart1": [1 for _ in range(9 - int(level))], + "process": process * 100, + "date": str(now.replace(microsecond=0)), + "font_size": 45, + } + if len(nickname) > 6: + data["font_size"] = 27 + _type = "sign" + if is_card_view: + _type = "view" + value_list = ( + await SignUser.annotate() + .order_by("-impression") + .values_list("user_id", flat=True) + ) + index = value_list.index(user.user_id) + 1 # type: ignore + data["impression"] = f"好感度排名第 {index} 位" + data["gold"] = f"总金币:{gold}" + data["gift"] = "" + pic = await template_to_pic( + template_path=str((TEMPLATE_PATH / "sign").absolute()), + template_name="main.html", + templates={"data": data}, + pages={ + "viewport": {"width": 465, "height": 926}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + image = BuildImage.open(pic) + date = now.date() + await image.save(SIGN_TODAY_CARD_PATH / f"{user.user_id}_{_type}_{date}.png") + return IMAGE_PATH / "sign" / "today_card" / f"{user.user_id}_{_type}_{date}.png" diff --git a/zhenxun/plugins/statistics/__init__.py b/zhenxun/builtin_plugins/statistics/__init__.py similarity index 100% rename from zhenxun/plugins/statistics/__init__.py rename to zhenxun/builtin_plugins/statistics/__init__.py diff --git a/zhenxun/plugins/statistics/_data_source.py b/zhenxun/builtin_plugins/statistics/_data_source.py similarity index 93% rename from zhenxun/plugins/statistics/_data_source.py rename to zhenxun/builtin_plugins/statistics/_data_source.py index e83707b1b..9cc1b2aea 100644 --- a/zhenxun/plugins/statistics/_data_source.py +++ b/zhenxun/builtin_plugins/statistics/_data_source.py @@ -6,6 +6,8 @@ from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.models.plugin_info import PluginInfo from zhenxun.models.statistics import Statistics +from zhenxun.utils.echart_utils import ChartUtils +from zhenxun.utils.echart_utils.models import Barh from zhenxun.utils.enum import PluginType from zhenxun.utils.image_utils import BuildImage, BuildMat, MatType @@ -114,17 +116,15 @@ async def get_group_statistics(cls, group_id: str, day: int | None, title: str): @classmethod async def __build_image(cls, data_list: list[tuple[str, int]], title: str): - mat = BuildMat(MatType.BARH) module2count = {x[0]: x[1] for x in data_list} plugin_info = await PluginInfo.filter( - module__in=module2count.keys(), plugin_type=PluginType.NORMAL + module__in=module2count.keys(), + plugin_type=PluginType.NORMAL, ).all() x_index = [] data = [] for plugin in plugin_info: x_index.append(plugin.name) data.append(module2count.get(plugin.module, 0)) - mat.x_index = x_index - mat.data = data - mat.title = title - return await mat.build() + barh = Barh(data=data, category_data=x_index) + return await ChartUtils.barh(barh) diff --git a/zhenxun/plugins/statistics/statistics_handle.py b/zhenxun/builtin_plugins/statistics/statistics_handle.py similarity index 100% rename from zhenxun/plugins/statistics/statistics_handle.py rename to zhenxun/builtin_plugins/statistics/statistics_handle.py diff --git a/zhenxun/plugins/statistics/statistics_hook.py b/zhenxun/builtin_plugins/statistics/statistics_hook.py similarity index 100% rename from zhenxun/plugins/statistics/statistics_hook.py rename to zhenxun/builtin_plugins/statistics/statistics_hook.py diff --git a/zhenxun/builtin_plugins/superuser/exec_sql.py b/zhenxun/builtin_plugins/superuser/exec_sql.py index e99cd8f63..83a116894 100644 --- a/zhenxun/builtin_plugins/superuser/exec_sql.py +++ b/zhenxun/builtin_plugins/superuser/exec_sql.py @@ -7,7 +7,7 @@ from tortoise import Tortoise from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.db_context import TestSQL +from zhenxun.models.ban_console import BanConsole from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType from zhenxun.utils.image_utils import ImageTemplate @@ -60,7 +60,7 @@ async def _(session: EventSession, message: UniMsg): logger.info(f"执行SQL语句: {sql_text}", "exec", session=session) try: if not sql_text.lower().startswith("select"): - await TestSQL.raw(sql_text) + await BanConsole.raw(sql_text) else: db = Tortoise.get_connection("default") res = await db.execute_query_dict(sql_text) @@ -74,10 +74,12 @@ async def _(session: EventSession, message: UniMsg): for c in _column: data.append(r.get(c)) data_list.append(data) + if not data_list: + return await MessageUtils.build_message("查询结果为空!").send() table = await ImageTemplate.table_page( "EXEC", f"总共有 {len(data_list)} 条数据捏", list(_column), data_list ) - await MessageUtils.build_message(table).send() + return await MessageUtils.build_message(table).send() except Exception as e: logger.error("执行 SQL 语句失败...", session=session, e=e) await MessageUtils.build_message(f"执行 SQL 语句失败... {type(e)}").finish() diff --git a/zhenxun/builtin_plugins/superuser/group_manage.py b/zhenxun/builtin_plugins/superuser/group_manage.py index 41044f944..4c228c2a5 100644 --- a/zhenxun/builtin_plugins/superuser/group_manage.py +++ b/zhenxun/builtin_plugins/superuser/group_manage.py @@ -16,7 +16,7 @@ ) from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger @@ -60,18 +60,17 @@ _matcher = on_alconna( Alconna( "group-manage", + Option("--delete", action=store_true, help_text="删除"), Subcommand( "modify-level", Args["level", int]["group_id?", int], help_text="修改群权限" ), Subcommand( "super-handle", - Option("--del", action=store_true, help_text="删除"), Args["group_id", int], help_text="添加/删除群白名单", ), Subcommand( "auth-handle", - Option("--del", action=store_true, help_text="删除"), Args["group_id", int], help_text="添加/删除群认证", ), @@ -99,7 +98,7 @@ _matcher.shortcut( "删除群白名单", command="group-manage", - arguments=["super-handle", "{%0}", "--del"], + arguments=["super-handle", "{%0}", "--delete"], prefix=True, ) @@ -113,7 +112,7 @@ _matcher.shortcut( "删除群认证", command="group-manage", - arguments=["auth-handle", "{%0}", "--del"], + arguments=["auth-handle", "{%0}", "--delete"], prefix=True, ) @@ -167,8 +166,8 @@ async def _(session: EventSession, arparma: Arparma, state: T_State): group = await GroupConsole.get_or_none(group_id=gid) if not group: await MessageUtils.build_message("群组信息不存在, 请更新群组信息...").finish() - s = "删除" if arparma.find("del") else "添加" - group.is_super = not arparma.find("del") + s = "删除" if arparma.find("delete") else "添加" + group.is_super = not arparma.find("delete") await group.save(update_fields=["is_super"]) await MessageUtils.build_message(f"{s}群白名单成功!").send(reply_to=True) logger.info(f"{s}群白名单", arparma.header_result, session=session, target=gid) @@ -178,9 +177,9 @@ async def _(session: EventSession, arparma: Arparma, state: T_State): async def _(session: EventSession, arparma: Arparma, state: T_State): gid = state["group_id"] await GroupConsole.update_or_create( - group_id=gid, defaults={"group_flag": 0 if arparma.find("del") else 1} + group_id=gid, defaults={"group_flag": 0 if arparma.find("delete") else 1} ) - s = "删除" if arparma.find("del") else "添加" + s = "删除" if arparma.find("delete") else "添加" await MessageUtils.build_message(f"{s}群认证成功!").send(reply_to=True) logger.info(f"{s}群白名单", arparma.header_result, session=session, target=gid) @@ -191,11 +190,16 @@ async def _(bot: Bot, session: EventSession, arparma: Arparma, group_id: int): group_list = [g["group_id"] for g in await bot.get_group_list()] if group_id not in group_list: logger.debug("群组不存在", "退群", session=session, target=group_id) - await MessageUtils.build_message(f"{NICKNAME}未在该群组中...").finish() + await MessageUtils.build_message( + f"{BotConfig.self_nickname}未在该群组中..." + ).finish() try: await bot.set_group_leave(group_id=group_id) logger.info( - f"{NICKNAME}退出群组成功", "退群", session=session, target=group_id + f"{BotConfig.self_nickname}退出群组成功", + "退群", + session=session, + target=group_id, ) await MessageUtils.build_message(f"退出群组 {group_id} 成功!").send() await GroupConsole.filter(group_id=group_id).delete() diff --git a/zhenxun/plugins/web_ui/__init__.py b/zhenxun/builtin_plugins/web_ui/__init__.py similarity index 97% rename from zhenxun/plugins/web_ui/__init__.py rename to zhenxun/builtin_plugins/web_ui/__init__.py index 35fcf9fee..d05e55f97 100644 --- a/zhenxun/plugins/web_ui/__init__.py +++ b/zhenxun/builtin_plugins/web_ui/__init__.py @@ -20,6 +20,7 @@ from .api.tabs.plugin_manage import router as plugin_router from .api.tabs.system import router as system_router from .auth import router as auth_router +from .public import init_public __plugin_meta__ = PluginMetadata( name="WebUi", @@ -59,7 +60,7 @@ @driver.on_startup -def _(): +async def _(): try: async def log_sink(message: str): @@ -80,6 +81,7 @@ async def log_sink(message: str): app: FastAPI = nonebot.get_app() app.include_router(BaseApiRouter) app.include_router(WsApiRouter) + await init_public(app) logger.info("API启动成功", "Web UI") except Exception as e: logger.error("API启动失败", "Web UI", e=e) diff --git a/zhenxun/plugins/web_ui/api/__init__.py b/zhenxun/builtin_plugins/web_ui/api/__init__.py similarity index 100% rename from zhenxun/plugins/web_ui/api/__init__.py rename to zhenxun/builtin_plugins/web_ui/api/__init__.py diff --git a/zhenxun/plugins/web_ui/api/logs/__init__.py b/zhenxun/builtin_plugins/web_ui/api/logs/__init__.py similarity index 100% rename from zhenxun/plugins/web_ui/api/logs/__init__.py rename to zhenxun/builtin_plugins/web_ui/api/logs/__init__.py diff --git a/zhenxun/plugins/web_ui/api/logs/log_manager.py b/zhenxun/builtin_plugins/web_ui/api/logs/log_manager.py similarity index 100% rename from zhenxun/plugins/web_ui/api/logs/log_manager.py rename to zhenxun/builtin_plugins/web_ui/api/logs/log_manager.py diff --git a/zhenxun/plugins/web_ui/api/logs/logs.py b/zhenxun/builtin_plugins/web_ui/api/logs/logs.py similarity index 78% rename from zhenxun/plugins/web_ui/api/logs/logs.py rename to zhenxun/builtin_plugins/web_ui/api/logs/logs.py index 01c780967..fcd4cce89 100644 --- a/zhenxun/plugins/web_ui/api/logs/logs.py +++ b/zhenxun/builtin_plugins/web_ui/api/logs/logs.py @@ -8,16 +8,6 @@ router = APIRouter() -@router.get("/logs", response_model=list[str]) -async def system_logs_history(reverse: bool = False): - """历史日志 - - 参数: - reverse: 反转顺序. - """ - return LOG_STORAGE.list(reverse=reverse) # type: ignore - - @router.websocket("/logs") async def system_logs_realtime(websocket: WebSocket): await websocket.accept() diff --git a/zhenxun/plugins/web_ui/api/tabs/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/__init__.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/__init__.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/__init__.py diff --git a/zhenxun/plugins/web_ui/api/tabs/database/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py similarity index 98% rename from zhenxun/plugins/web_ui/api/tabs/database/__init__.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py index 2fd770855..d334c08c6 100644 --- a/zhenxun/plugins/web_ui/api/tabs/database/__init__.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/database/__init__.py @@ -5,7 +5,7 @@ from tortoise.exceptions import OperationalError from zhenxun.models.plugin_info import PluginInfo -from zhenxun.services.db_context import TestSQL +from zhenxun.models.task_info import TaskInfo from ....base_model import BaseResultModel, QueryModel, Result from ....utils import authentication diff --git a/zhenxun/plugins/web_ui/api/tabs/database/models/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/database/models/model.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/database/models/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/database/models/model.py diff --git a/zhenxun/plugins/web_ui/api/tabs/database/models/sql_log.py b/zhenxun/builtin_plugins/web_ui/api/tabs/database/models/sql_log.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/database/models/sql_log.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/database/models/sql_log.py diff --git a/zhenxun/plugins/web_ui/api/tabs/main/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/main/__init__.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/main/__init__.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/main/__init__.py diff --git a/zhenxun/plugins/web_ui/api/tabs/main/data_source.py b/zhenxun/builtin_plugins/web_ui/api/tabs/main/data_source.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/main/data_source.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/main/data_source.py diff --git a/zhenxun/plugins/web_ui/api/tabs/main/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/main/model.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/main/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/main/model.py diff --git a/zhenxun/plugins/web_ui/api/tabs/manage/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/__init__.py similarity index 94% rename from zhenxun/plugins/web_ui/api/tabs/manage/__init__.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/manage/__init__.py index 4f545200f..902b189b4 100644 --- a/zhenxun/plugins/web_ui/api/tabs/manage/__init__.py +++ b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/__init__.py @@ -3,7 +3,7 @@ from nonebot.adapters.onebot.v11 import ActionFailed from tortoise.functions import Count -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.models.ban_console import BanConsole from zhenxun.models.chat_history import ChatHistory from zhenxun.models.fg_request import FgRequest @@ -133,7 +133,7 @@ async def _() -> Result: "friend_count": f_count, "group_count": g_count, } - return Result.ok(data, f"{NICKNAME}带来了最新的数据!") + return Result.ok(data, f"{BotConfig.self_nickname}带来了最新的数据!") @router.get( @@ -177,7 +177,7 @@ async def _() -> Result: except Exception as e: logger.error("调用API错误", "/get_request", e=e) return Result.fail(f"{type(e)}: {e}") - return Result.ok(req_result, f"{NICKNAME}带来了最新的数据!") + return Result.ok(req_result, f"{BotConfig.self_nickname}带来了最新的数据!") @router.post( @@ -227,22 +227,15 @@ async def _(parma: HandleRequest) -> Result: if bot_id not in nonebot.get_bots(): return Result.warning_("指定Bot未连接...") if req := await FgRequest.get_or_none(id=parma.id): - if group := await GroupConsole.get_group(group_id=req.group_id): - group.group_flag = 1 - await group.save(update_fields=["group_flag"]) - else: - group_info = await bots[bot_id].get_group_info( - group_id=req.group_id - ) - await GroupConsole.update_or_create( - group_id=str(group_info["group_id"]), - defaults={ - "group_name": group_info["group_name"], - "max_member_count": group_info["max_member_count"], - "member_count": group_info["member_count"], - "group_flag": 1, - }, - ) + if req.request_type == RequestType.GROUP: + if group := await GroupConsole.get_group(group_id=req.group_id): + group.group_flag = 1 + await group.save(update_fields=["group_flag"]) + else: + await GroupConsole.update_or_create( + group_id=req.group_id, + defaults={"group_flag": 1}, + ) else: return Result.warning_("未找到此Id请求...") try: diff --git a/zhenxun/plugins/web_ui/api/tabs/manage/chat.py b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/chat.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/manage/chat.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/manage/chat.py diff --git a/zhenxun/plugins/web_ui/api/tabs/manage/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/manage/model.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/manage/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/manage/model.py diff --git a/zhenxun/plugins/web_ui/api/tabs/plugin_manage/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/__init__.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/plugin_manage/__init__.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/__init__.py diff --git a/zhenxun/plugins/web_ui/api/tabs/plugin_manage/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/model.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/plugin_manage/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/plugin_manage/model.py diff --git a/zhenxun/plugins/web_ui/api/tabs/system/__init__.py b/zhenxun/builtin_plugins/web_ui/api/tabs/system/__init__.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/system/__init__.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/system/__init__.py diff --git a/zhenxun/plugins/web_ui/api/tabs/system/model.py b/zhenxun/builtin_plugins/web_ui/api/tabs/system/model.py similarity index 100% rename from zhenxun/plugins/web_ui/api/tabs/system/model.py rename to zhenxun/builtin_plugins/web_ui/api/tabs/system/model.py diff --git a/zhenxun/plugins/web_ui/auth/__init__.py b/zhenxun/builtin_plugins/web_ui/auth/__init__.py similarity index 100% rename from zhenxun/plugins/web_ui/auth/__init__.py rename to zhenxun/builtin_plugins/web_ui/auth/__init__.py diff --git a/zhenxun/plugins/web_ui/base_model.py b/zhenxun/builtin_plugins/web_ui/base_model.py similarity index 100% rename from zhenxun/plugins/web_ui/base_model.py rename to zhenxun/builtin_plugins/web_ui/base_model.py diff --git a/zhenxun/plugins/web_ui/config.py b/zhenxun/builtin_plugins/web_ui/config.py similarity index 100% rename from zhenxun/plugins/web_ui/config.py rename to zhenxun/builtin_plugins/web_ui/config.py diff --git a/zhenxun/builtin_plugins/web_ui/public/__init__.py b/zhenxun/builtin_plugins/web_ui/public/__init__.py new file mode 100644 index 000000000..b194ffe7d --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/public/__init__.py @@ -0,0 +1,35 @@ +from fastapi import APIRouter, FastAPI +from fastapi.responses import FileResponse +from fastapi.staticfiles import StaticFiles + +from zhenxun.services.log import logger + +from .config import PUBLIC_PATH +from .data_source import update_webui_assets + +router = APIRouter() + + +@router.get("/") +async def index(): + return FileResponse(PUBLIC_PATH / "index.html") + + +@router.get("/favicon.ico") +async def favicon(): + return FileResponse(PUBLIC_PATH / "favicon.ico") + + +async def init_public(app: FastAPI): + try: + if not PUBLIC_PATH.exists(): + await update_webui_assets() + app.include_router(router) + for pathname in ["css", "js", "fonts", "img"]: + app.mount( + f"/{pathname}", + StaticFiles(directory=PUBLIC_PATH / pathname, check_dir=True), + name=f"public_{pathname}", + ) + except Exception as e: + logger.error(f"初始化 web ui assets 失败", "Web UI assets", e=e) diff --git a/zhenxun/builtin_plugins/web_ui/public/config.py b/zhenxun/builtin_plugins/web_ui/public/config.py new file mode 100644 index 000000000..7c27d38d7 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/public/config.py @@ -0,0 +1,20 @@ +from datetime import datetime +from pydantic import BaseModel +from zhenxun.configs.path_config import DATA_PATH, TEMP_PATH + + +class PublicData(BaseModel): + etag: str + update_time: datetime + + +COMMAND_NAME = "webui_update_assets" + +WEBUI_DATA_PATH = DATA_PATH / "web_ui" +PUBLIC_PATH = WEBUI_DATA_PATH / "public" +TMP_PATH = TEMP_PATH / "web_ui" + +GITHUB_API_COMMITS = "https://api.github.com/repos/HibiKier/zhenxun_bot_webui/commits" +WEBUI_ASSETS_DOWNLOAD_URL = ( + "https://github.com/HibiKier/zhenxun_bot_webui/archive/refs/heads/dist.zip" +) diff --git a/zhenxun/builtin_plugins/web_ui/public/data_source.py b/zhenxun/builtin_plugins/web_ui/public/data_source.py new file mode 100644 index 000000000..eb04d2979 --- /dev/null +++ b/zhenxun/builtin_plugins/web_ui/public/data_source.py @@ -0,0 +1,39 @@ +import os +import shutil +import zipfile +from pathlib import Path + +from nonebot.utils import run_sync + +from zhenxun.services.log import logger +from zhenxun.utils.http_utils import AsyncHttpx + +from .config import COMMAND_NAME, PUBLIC_PATH, TMP_PATH, WEBUI_ASSETS_DOWNLOAD_URL + + +async def update_webui_assets(): + webui_assets_path = TMP_PATH / "webui_assets.zip" + if await AsyncHttpx.download_file( + WEBUI_ASSETS_DOWNLOAD_URL, webui_assets_path, follow_redirects=True + ): + logger.info("下载 webui_assets 成功...", COMMAND_NAME) + return await _file_handle(webui_assets_path) + raise Exception("下载 webui_assets 失败", COMMAND_NAME) + + +@run_sync +def _file_handle(webui_assets_path: Path): + logger.debug("开始解压 webui_assets...", COMMAND_NAME) + if webui_assets_path.exists(): + tf = zipfile.ZipFile(webui_assets_path) + tf.extractall(TMP_PATH) + logger.debug("解压 webui_assets 成功...", COMMAND_NAME) + else: + raise Exception("解压 webui_assets 失败,文件不存在...", COMMAND_NAME) + download_file_path = ( + TMP_PATH / [x for x in os.listdir(TMP_PATH) if (TMP_PATH / x).is_dir()][0] + ) + shutil.rmtree(PUBLIC_PATH, ignore_errors=True) + shutil.copytree(download_file_path / "dist", PUBLIC_PATH, dirs_exist_ok=True) + logger.debug("复制 webui_assets 成功...", COMMAND_NAME) + shutil.rmtree(TMP_PATH, ignore_errors=True) diff --git a/zhenxun/plugins/web_ui/utils.py b/zhenxun/builtin_plugins/web_ui/utils.py similarity index 94% rename from zhenxun/plugins/web_ui/utils.py rename to zhenxun/builtin_plugins/web_ui/utils.py index f39f36ac1..8d4fd25a9 100644 --- a/zhenxun/plugins/web_ui/utils.py +++ b/zhenxun/builtin_plugins/web_ui/utils.py @@ -1,6 +1,7 @@ import os from datetime import datetime, timedelta from pathlib import Path +import secrets import psutil import ujson as json @@ -14,7 +15,6 @@ from .base_model import SystemFolderSize, SystemStatus, User -SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 @@ -28,6 +28,8 @@ token_data = json.load(open(token_file, "r", encoding="utf8")) except json.JSONDecodeError: pass +if not token_data.get("secret"): + token_data["secret"] = secrets.token_hex(64) def get_user(uname: str) -> User | None: @@ -55,7 +57,7 @@ def create_token(user: User, expires_delta: timedelta | None = None): expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) return jwt.encode( claims={"sub": user.username, "exp": expire}, - key=SECRET_KEY, + key=token_data["secret"], algorithm=ALGORITHM, ) @@ -71,7 +73,7 @@ def authentication(): # if token not in token_data["token"]: def inner(token: str = Depends(oauth2_scheme)): try: - payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) + payload = jwt.decode(token, token_data["secret"], algorithms=[ALGORITHM]) username, expire = payload.get("sub"), payload.get("exp") user = get_user(username) # type: ignore if user is None: diff --git a/zhenxun/configs/config.py b/zhenxun/configs/config.py index e1140a88b..56f0fdb92 100644 --- a/zhenxun/configs/config.py +++ b/zhenxun/configs/config.py @@ -1,35 +1,38 @@ -import platform +import random from pathlib import Path +import nonebot +from pydantic import BaseModel + from .utils import ConfigsManager -if platform.system() == "Linux": - import os - hostip = ( - os.popen("cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }'") - .read() - .replace("\n", "") - ) +class BotSetting(BaseModel): + self_nickname: str = "" + """回复时NICKNAME""" + system_proxy: str | None = None + """系统代理""" + db_url: str = "" + """数据库链接""" + platform_superusers: dict[str, list[str]] = {} + """平台超级用户""" -# 回复消息名称 -NICKNAME: str = "小真寻" + def get_superuser(self, platform: str) -> str: + """获取超级用户 -# 数据库(必要) -# 如果填写了bind就不需要再填写后面的字段了#) -# 示例:"bind": "postgres://user:password@127.0.0.1:5432/database" -bind: str = "" # 数据库连接链接 -sql_name: str = "postgres" -user: str = "" # 数据用户名 -password: str = "" # 数据库密码 -address: str = "" # 数据库地址 -port: str = "" # 数据库端口 -database: str = "" # 数据库名称 + 参数: + platform: 对应平台 -# 代理,例如 "http://127.0.0.1:7890" -# 如果是WLS 可以 f"http://{hostip}:7890" 使用寄主机的代理 -SYSTEM_PROXY: str | None = None # 全局代理 + 返回: + str | None: 超级用户id + """ + if self.platform_superusers: + if platform_superuser := self.platform_superusers.get(platform): + return random.choice(platform_superuser) + return "" Config = ConfigsManager(Path() / "data" / "configs" / "plugins2config.yaml") + +BotConfig = nonebot.get_plugin_config(BotSetting) diff --git a/zhenxun/configs/utils/__init__.py b/zhenxun/configs/utils/__init__.py index 4ba9c47cf..7198d93a5 100644 --- a/zhenxun/configs/utils/__init__.py +++ b/zhenxun/configs/utils/__init__.py @@ -137,6 +137,8 @@ class Task(BaseBlock): """被动技能名称""" status: bool = True """全局开关状态""" + create_status: bool = False + """初次加载默认开关状态""" run_time: str | None = None """运行时间""" diff --git a/zhenxun/models/group_console.py b/zhenxun/models/group_console.py index 88959e8be..bbcdb6002 100644 --- a/zhenxun/models/group_console.py +++ b/zhenxun/models/group_console.py @@ -41,7 +41,9 @@ class Meta: unique_together = ("group_id", "channel_id") @classmethod - async def get_group(cls, group_id: str, channel_id: str | None = None) -> Self: + async def get_group( + cls, group_id: str, channel_id: str | None = None + ) -> Self | None: """获取群组 参数: @@ -52,21 +54,20 @@ async def get_group(cls, group_id: str, channel_id: str | None = None) -> Self: Self: GroupConsole """ if channel_id: - return await cls.get(group_id=group_id, channel_id=channel_id) - return await cls.get(group_id=group_id, channel_id__isnull=True) + return await cls.get_or_none(group_id=group_id, channel_id=channel_id) + return await cls.get_or_none(group_id=group_id, channel_id__isnull=True) @classmethod - async def is_super_group(cls, group_id: str, channel_id: str | None = None) -> bool: + async def is_super_group(cls, group_id: str) -> bool: """是否超级用户指定群 参数: group_id: 群组id - channel_id: 频道id. 返回: bool: 是否超级用户指定群 """ - if group := await cls.get_or_none(group_id=group_id): + if group := await cls.get_group(group_id): return group.is_super return False diff --git a/zhenxun/plugins/ai/__init__.py b/zhenxun/plugins/ai/__init__.py index 28e153546..bc5fd5404 100644 --- a/zhenxun/plugins/ai/__init__.py +++ b/zhenxun/plugins/ai/__init__.py @@ -6,7 +6,7 @@ from nonebot_plugin_alconna import UniMsg from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.models.friend_user import FriendUser from zhenxun.models.group_member_info import GroupInfoUser @@ -20,7 +20,7 @@ name="AI", description="屑Ai", usage=f""" - 与{NICKNAME}普普通通的对话吧! + 与{BotConfig.self_nickname}普普通通的对话吧! """.strip(), extra=PluginExtraData( author="HibiKier", diff --git a/zhenxun/plugins/ai/data_source.py b/zhenxun/plugins/ai/data_source.py index 555f38857..8c813c7d6 100644 --- a/zhenxun/plugins/ai/data_source.py +++ b/zhenxun/plugins/ai/data_source.py @@ -5,7 +5,7 @@ import ujson as json from nonebot_plugin_alconna import UniMessage, UniMsg -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH from zhenxun.services.log import logger from zhenxun.utils.http_utils import AsyncHttpx @@ -154,9 +154,9 @@ async def xie_ai(text: str) -> str: if data["result"] == 0: content = data["content"] if "菲菲" in content: - content = content.replace("菲菲", NICKNAME) + content = content.replace("菲菲", BotConfig.self_nickname) if "艳儿" in content: - content = content.replace("艳儿", NICKNAME) + content = content.replace("艳儿", BotConfig.self_nickname) if "公众号" in content: content = "" if "{br}" in content: @@ -188,7 +188,7 @@ def hello() -> UniMessage: ( "哦豁?!", "你好!Ov<", - f"库库库,呼唤{NICKNAME}做什么呢", + f"库库库,呼唤{BotConfig.self_nickname}做什么呢", "我在呢!", "呼呼,叫俺干嘛", ) @@ -206,7 +206,7 @@ def no_result() -> UniMessage: random.choice( [ "你在说啥子?", - f"纯洁的{NICKNAME}没听懂", + f"纯洁的{BotConfig.self_nickname}没听懂", "下次再告诉你(下次一定)", "你觉得我听懂了吗?嗯?", "我!不!知!道!", diff --git a/zhenxun/plugins/ai/utils.py b/zhenxun/plugins/ai/utils.py index 946bc2348..e22d1fde9 100644 --- a/zhenxun/plugins/ai/utils.py +++ b/zhenxun/plugins/ai/utils.py @@ -1,7 +1,7 @@ import random import time -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.models.ban_console import BanConsole @@ -15,14 +15,14 @@ def __init__(self): "你是只会说这一句话吗?", "[*],你发我也发!", "[uname],[*]", - f"救命!有笨蛋一直给{NICKNAME}发一样的话!", + f"救命!有笨蛋一直给{BotConfig.self_nickname}发一样的话!", "这句话你已经给我发了{}次了,再发就生气!", ] self._repeat_message = [ - f"请不要学{NICKNAME}说话", - f"为什么要一直学{NICKNAME}说话?", + f"请不要学{BotConfig.self_nickname}说话", + f"为什么要一直学{BotConfig.self_nickname}说话?", "你再学!你再学我就生气了!", - f"呜呜,你是想欺负{NICKNAME}嘛..", + f"呜呜,你是想欺负{BotConfig.self_nickname}嘛..", "[uname]不要再学我说话了!", "再学我说话,我就把你拉进黑名单(生气", "你再学![uname]是个笨蛋!", diff --git a/zhenxun/plugins/black_word/black_word.py b/zhenxun/plugins/black_word/black_word.py index 8b819c6a1..8164851f0 100644 --- a/zhenxun/plugins/black_word/black_word.py +++ b/zhenxun/plugins/black_word/black_word.py @@ -7,7 +7,7 @@ from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Option, on_alconna from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType @@ -79,7 +79,7 @@ ), RegisterConfig( key="WARNING_RESULT", - value=f"请注意对{NICKNAME}的发言内容", + value=f"请注意对{BotConfig.self_nickname}的发言内容", help="口头警告内容", default_value=None, ), @@ -202,7 +202,7 @@ async def _(): 关于敏感词: - 记住不要骂{NICKNAME}就对了! + 记住不要骂{BotConfig.self_nickname}就对了! """.strip() max_width = 0 for m in text.split("\n"): diff --git a/zhenxun/plugins/dialogue/__init__.py b/zhenxun/plugins/dialogue/__init__.py index 5524cf9aa..a99bc0558 100644 --- a/zhenxun/plugins/dialogue/__init__.py +++ b/zhenxun/plugins/dialogue/__init__.py @@ -8,6 +8,7 @@ from nonebot_plugin_session import EventSession from nonebot_plugin_userinfo import EventUserInfo, UserInfo +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger @@ -58,14 +59,10 @@ async def _( if session.id1: message[0] = Text(str(message[0]).replace("滴滴滴-", "", 1)) platform = PlatformUtils.get_platform(bot) + superuser_id = None try: - superuser_id = config.platform_superusers["qq"][0] - if platform == "dodo": - superuser_id = config.platform_superusers["dodo"][0] - if platform == "kaiheila": - superuser_id = config.platform_superusers["kaiheila"][0] - if platform == "discord": - superuser_id = config.platform_superusers["discord"][0] + if platform: + superuser_id = BotConfig.get_superuser(platform) except IndexError: await MessageUtils.build_message("管理员失联啦...").finish() if not superuser_id: diff --git a/zhenxun/plugins/epic/data_source.py b/zhenxun/plugins/epic/data_source.py index 87bc5a63c..583221fe8 100644 --- a/zhenxun/plugins/epic/data_source.py +++ b/zhenxun/plugins/epic/data_source.py @@ -5,9 +5,7 @@ from nonebot.adapters.onebot.v12 import Bot as v12Bot from nonebot_plugin_alconna import Image, UniMessage -from zhenxun.configs.config import NICKNAME from zhenxun.services.log import logger -from zhenxun.utils._build_image import BuildImage from zhenxun.utils.http_utils import AsyncHttpx from zhenxun.utils.message import MessageUtils diff --git a/zhenxun/plugins/fudu.py b/zhenxun/plugins/fudu.py index b81a07472..bf4555f40 100644 --- a/zhenxun/plugins/fudu.py +++ b/zhenxun/plugins/fudu.py @@ -7,7 +7,7 @@ from nonebot_plugin_alconna import UniMsg from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.path_config import TEMP_PATH from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task from zhenxun.models.task_info import TaskInfo @@ -112,7 +112,7 @@ async def _(message: UniMsg, event: Event, session: EventSession): image_list.append(m.url) if not plain_text and not image_list: return - if plain_text and plain_text.startswith(f"@可爱的{NICKNAME}"): + if plain_text and plain_text.startswith(f"@可爱的{BotConfig.self_nickname}"): await MessageUtils.build_message("复制粘贴的虚空艾特?").send(reply_to=True) if image_list: img_hash = await get_download_image_hash(image_list[0], group_id) diff --git a/zhenxun/plugins/gold_redbag/__init__.py b/zhenxun/plugins/gold_redbag/__init__.py index f46c66399..7e6a1c285 100644 --- a/zhenxun/plugins/gold_redbag/__init__.py +++ b/zhenxun/plugins/gold_redbag/__init__.py @@ -12,7 +12,7 @@ from nonebot_plugin_apscheduler import scheduler from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig from zhenxun.services.log import logger from zhenxun.utils.depends import GetConfig, UserName @@ -298,7 +298,7 @@ async def _( try: await MessageUtils.build_message( [ - f"{NICKNAME}的节日红包过时了,一共开启了 " + f"{BotConfig.self_nickname}的节日红包过时了,一共开启了 " f"{len(festive_red_bag.open_user)}" f" 个红包,共 {sum(festive_red_bag.open_user.values())} 金币\n", rank_image, @@ -314,10 +314,10 @@ async def _( except JobLookupError: pass await group_red_bag.add_red_bag( - f"{NICKNAME}的红包", + f"{BotConfig.self_nickname}的红包", amount, num, - NICKNAME, + BotConfig.self_nickname, FESTIVE_KEY, _uuid, platform=session.platform, @@ -335,7 +335,7 @@ async def _( ) await MessageUtils.build_message( [ - f"{NICKNAME}发起了节日金币红包\n金额: {amount}\n数量: {num}\n", + f"{BotConfig.self_nickname}发起了节日金币红包\n金额: {amount}\n数量: {num}\n", image_result, ] ).send(target=target, bot=bot) diff --git a/zhenxun/plugins/gold_redbag/data_source.py b/zhenxun/plugins/gold_redbag/data_source.py index ec9031005..ecc7dd380 100644 --- a/zhenxun/plugins/gold_redbag/data_source.py +++ b/zhenxun/plugins/gold_redbag/data_source.py @@ -8,7 +8,7 @@ from nonebot.exception import ActionFailed from nonebot_plugin_alconna import UniMessage -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.path_config import IMAGE_PATH from zhenxun.models.user_console import UserConsole from zhenxun.utils.image_utils import BuildImage @@ -58,7 +58,7 @@ async def _auto_end_festive_red_bag(cls, bot: Bot, group_id: str, platform: str) try: await MessageUtils.build_message( [ - f"{NICKNAME}的节日红包过时了,一共开启了 " + f"{BotConfig.self_nickname}的节日红包过时了,一共开启了 " f"{len(red_bag.open_user)}" f" 个红包,共 {sum(red_bag.open_user.values())} 金币\n", rank_image, @@ -92,7 +92,7 @@ async def end_red_bag( rank_image = await festive_red_bag.build_amount_rank(rank_num, platform) return MessageUtils.build_message( [ - f"{NICKNAME}的节日红包过时了,一共开启了 " + f"{BotConfig.self_nickname}的节日红包过时了,一共开启了 " f"{len(festive_red_bag.open_user)}" f" 个红包,共 {sum(festive_red_bag.open_user.values())} 金币\n", rank_image, diff --git a/zhenxun/plugins/mute/_data_source.py b/zhenxun/plugins/mute/_data_source.py index 92bd3dd54..13c1cadf9 100644 --- a/zhenxun/plugins/mute/_data_source.py +++ b/zhenxun/plugins/mute/_data_source.py @@ -47,9 +47,9 @@ def get_group_data(self, group_id: str) -> GroupData: """ if group_id not in self._group_data: self._group_data[group_id] = GroupData( - count=base_config.get("MUTE_DEFAULT_COUNT", 10), - time=base_config.get("MUTE_DEFAULT_TIME", 7), - duration=base_config.get("MUTE_DEFAULT_DURATION", 10), + count=base_config.get("MUTE_DEFAULT_COUNT", 10) or 10, + time=base_config.get("MUTE_DEFAULT_TIME", 7) or 7, + duration=base_config.get("MUTE_DEFAULT_DURATION", 10) or 10, ) return self._group_data[group_id] diff --git a/zhenxun/plugins/mute/mute_message.py b/zhenxun/plugins/mute/mute_message.py index ec655ccf9..19e9326ae 100644 --- a/zhenxun/plugins/mute/mute_message.py +++ b/zhenxun/plugins/mute/mute_message.py @@ -5,7 +5,7 @@ from nonebot_plugin_alconna import UniMsg from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData from zhenxun.models.ban_console import BanConsole from zhenxun.services.log import logger @@ -48,7 +48,7 @@ async def _(bot: Bot, session: EventSession, message: UniMsg): try: await PlatformUtils.ban_user(bot, session.id1, group_id, duration) await MessageUtils.build_message( - f"检测到恶意刷屏,{NICKNAME}要把你关进小黑屋!" + f"检测到恶意刷屏,{BotConfig.self_nickname}要把你关进小黑屋!" ).send(at_sender=True) mute_manage.reset(session.id1, group_id) logger.info(f"检测刷屏 被禁言 {duration} 分钟", "禁言检查", session=session) diff --git a/zhenxun/plugins/mute/mute_setting.py b/zhenxun/plugins/mute/mute_setting.py index f723f0753..b7947ed69 100644 --- a/zhenxun/plugins/mute/mute_setting.py +++ b/zhenxun/plugins/mute/mute_setting.py @@ -2,7 +2,7 @@ from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Option, on_alconna from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType @@ -15,7 +15,7 @@ name="刷屏禁言", description="刷屏禁言相关操作", usage=""" - 刷屏禁言相关操作,需要 {NICKNAME} 有群管理员权限 + 刷屏禁言相关操作,需要 {BotConfig.self_nickname} 有群管理员权限 指令: 设置刷屏: 查看当前设置 -c [count]: 检测最大次数 diff --git a/zhenxun/plugins/roll.py b/zhenxun/plugins/roll.py index 7c953496a..1c21421b6 100644 --- a/zhenxun/plugins/roll.py +++ b/zhenxun/plugins/roll.py @@ -6,7 +6,7 @@ from nonebot_plugin_alconna import UniMsg from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginExtraData from zhenxun.services.log import logger from zhenxun.utils.depends import UserName @@ -56,7 +56,7 @@ async def _( await MessageUtils.build_message( random.choice( [ - f"让{NICKNAME}看看是什么结果!答案是:‘{random_text}’", + f"让{BotConfig.self_nickname}看看是什么结果!答案是:‘{random_text}’", f"根据命运的指引,接下来{user_name} ‘{random_text}’ 会比较好", f"祈愿被回应了!是 ‘{random_text}’!", f"结束了,{user_name},命运之轮停在了 ‘{random_text}’!", diff --git a/zhenxun/plugins/russian/data_source.py b/zhenxun/plugins/russian/data_source.py index eb8381cb2..73cdb078b 100644 --- a/zhenxun/plugins/russian/data_source.py +++ b/zhenxun/plugins/russian/data_source.py @@ -8,7 +8,7 @@ from nonebot_plugin_apscheduler import scheduler from pydantic import BaseModel -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.models.user_console import UserConsole from zhenxun.utils.enum import GoldHandle @@ -278,7 +278,7 @@ async def shoot( random.choice( [ f"不要打扰 {russian.player1[1]} 和 {russian.player2[1]} 的决斗啊!", - f"给我好好做好一个观众!不然{NICKNAME}就要生气了", + f"给我好好做好一个观众!不然{BotConfig.self_nickname}就要生气了", f"不要捣乱啊baka{uname}!", ] ) @@ -429,7 +429,7 @@ async def settlement( f"\t累计败场:{loser.fail_count}\n" f"\t累计输掉金币:{loser.lose_money}\n" f"-------------------\n" - f"哼哼,{NICKNAME}从中收取了 {float(rand)}%({fee}金币) 作为手续费!\n" + f"哼哼,{BotConfig.self_nickname}从中收取了 {float(rand)}%({fee}金币) 作为手续费!\n" f"子弹排列:{russian.bullet_arr}", padding=10, color="#f9f6f2", diff --git a/zhenxun/plugins/search_buff_skin_price/__init__.py b/zhenxun/plugins/search_buff_skin_price/__init__.py index da224aaf6..ca2a320fa 100644 --- a/zhenxun/plugins/search_buff_skin_price/__init__.py +++ b/zhenxun/plugins/search_buff_skin_price/__init__.py @@ -4,7 +4,7 @@ from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig from zhenxun.services.log import logger from zhenxun.utils.message import MessageUtils @@ -83,7 +83,7 @@ async def arg_handle( result, status_code = await get_price(name) except FileNotFoundError: await MessageUtils.build_message( - f'请先对{NICKNAME}说"设置cookie"来设置cookie!' + f'请先对{BotConfig.self_nickname}说"设置cookie"来设置cookie!' ).send(at_sender=True) if status_code in [996, 997, 998]: await MessageUtils.build_message(result).finish() diff --git a/zhenxun/plugins/send_setu_/send_setu/__init__.py b/zhenxun/plugins/send_setu_/send_setu/__init__.py index 3dab91f62..fea1ad41e 100644 --- a/zhenxun/plugins/send_setu_/send_setu/__init__.py +++ b/zhenxun/plugins/send_setu_/send_setu/__init__.py @@ -16,7 +16,7 @@ ) from nonebot_plugin_session import EventSession -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig from zhenxun.models.sign_user import SignUser from zhenxun.models.user_console import UserConsole @@ -173,7 +173,7 @@ async def _( local_id: Match[int], ): _tags = tags.result.split("#") if tags.available else None - if _tags and NICKNAME in _tags: + if _tags and BotConfig.self_nickname in _tags: await MessageUtils.build_message( "咳咳咳,虽然我很可爱,但是我木有自己的色图~~~有的话记得发我一份呀" ).finish() diff --git a/zhenxun/plugins/send_setu_/send_setu/_data_source.py b/zhenxun/plugins/send_setu_/send_setu/_data_source.py index 6bac3d227..f7a15d328 100644 --- a/zhenxun/plugins/send_setu_/send_setu/_data_source.py +++ b/zhenxun/plugins/send_setu_/send_setu/_data_source.py @@ -5,7 +5,7 @@ from asyncpg import UniqueViolationError from nonebot_plugin_alconna import UniMessage -from zhenxun.configs.config import NICKNAME, Config +from zhenxun.configs.config import BotConfig, Config from zhenxun.configs.path_config import IMAGE_PATH, TEMP_PATH from zhenxun.services.log import logger from zhenxun.utils.http_utils import AsyncHttpx @@ -186,7 +186,7 @@ def get_luo(cls, impression: float) -> UniMessage | None: IMAGE_PATH / "luoxiang" / random.choice(os.listdir(IMAGE_PATH / "luoxiang")), - f"\n(快向{NICKNAME}签到提升好感度吧!)", + f"\n(快向{BotConfig.self_nickname}签到提升好感度吧!)", ] ) return None diff --git a/zhenxun/plugins/word_bank/_config.py b/zhenxun/plugins/word_bank/_config.py index 3d074c182..d003b1394 100644 --- a/zhenxun/plugins/word_bank/_config.py +++ b/zhenxun/plugins/word_bank/_config.py @@ -1,24 +1,43 @@ +from enum import Enum +from pickle import GLOBAL from zhenxun.configs.path_config import DATA_PATH data_dir = DATA_PATH / "word_bank" data_dir.mkdir(parents=True, exist_ok=True) +class ScopeType(Enum): + """ + 全局、群聊、私聊 + """ + GLOBAL = 0 + GROUP = 1 + PRIVATE = 2 + scope2int = { - "全局": 0, - "群聊": 1, - "私聊": 2, + "全局": ScopeType.GLOBAL, + "群聊": ScopeType.GROUP, + "私聊": ScopeType.PRIVATE, } +class WordType(Enum): + """ + 精准、模糊、正则、图片 + """ + EXACT = 0 + FUZZY = 1 + REGEX = 2 + IMAGE = 3 + type2int = { - "精准": 0, - "模糊": 1, - "正则": 2, - "图片": 3, + "精准": WordType.EXACT, + "模糊": WordType.FUZZY, + "正则": WordType.REGEX, + "图片": WordType.IMAGE, } int2type = { - 0: "精准", - 1: "模糊", - 2: "正则", - 3: "图片", + WordType.EXACT: "精准", + WordType.FUZZY: "模糊", + WordType.REGEX: "正则", + WordType.IMAGE: "图片", } diff --git a/zhenxun/plugins/word_bank/_model.py b/zhenxun/plugins/word_bank/_model.py index f31620a2b..abccdc618 100644 --- a/zhenxun/plugins/word_bank/_model.py +++ b/zhenxun/plugins/word_bank/_model.py @@ -19,7 +19,7 @@ from zhenxun.utils.image_utils import get_img_hash from zhenxun.utils.message import MessageUtils -from ._config import int2type +from ._config import WordType, ScopeType, int2type path = DATA_PATH / "word_bank" @@ -309,7 +309,7 @@ async def get_answer( data_list = await cls.check_problem(group_id, problem, word_scope, word_type) if data_list: random_answer = random.choice(data_list) - if random_answer.word_type == 2: + if random_answer.word_type == WordType.REGEX: r = re.search(random_answer.problem, problem) has_placeholder = re.search(rf"\$(\d)", random_answer.answer) if r and r.groups() and has_placeholder: @@ -348,7 +348,7 @@ async def get_problem_all_answer( """ if index is not None: # TODO: group_by和order_by不能同时使用 - if group_id: + if group_id and word_scope != ScopeType.GLOBAL: _problem = ( await cls.filter(group_id=group_id).order_by("create_time") # .group_by("problem") @@ -356,7 +356,7 @@ async def get_problem_all_answer( ) else: _problem = ( - await cls.filter(word_scope=(word_scope or 0)).order_by( + await cls.filter(word_scope=(word_scope or ScopeType.GLOBAL)).order_by( "create_time" ) # .group_by("problem") diff --git a/zhenxun/plugins/word_bank/word_handle.py b/zhenxun/plugins/word_bank/word_handle.py index 39894a459..a87e9e75f 100644 --- a/zhenxun/plugins/word_bank/word_handle.py +++ b/zhenxun/plugins/word_bank/word_handle.py @@ -19,7 +19,7 @@ from zhenxun.services.log import logger from zhenxun.utils.message import MessageUtils -from ._config import scope2int, type2int +from ._config import WordType, ScopeType, scope2int, type2int from ._data_source import WordBankManage, get_answer, get_img_and_at_list, get_problem from ._model import WordBank from .command import _add_matcher, _del_matcher, _show_matcher, _update_matcher @@ -172,8 +172,8 @@ async def _( if group_id and (not word_scope or word_scope == "私聊") else "0" ), - scope2int[word_scope] if word_scope else 1, - type2int[word_type] if word_type else 0, + scope2int[word_scope] if word_scope else ScopeType.GROUP, + type2int[word_type] if word_type else WordType.EXACT, problem, answer, nickname[0] if nickname else None, @@ -220,9 +220,9 @@ async def _( await MessageUtils.build_message( "此命令之后需要跟随指定词条或id,通过“显示词条“查看" ).finish(reply_to=True) - word_scope = 1 if session.id3 or session.id2 else 2 + word_scope = ScopeType.GROUP if session.id3 or session.id2 else ScopeType.PRIVATE if all.result: - word_scope = 0 + word_scope = ScopeType.GLOBAL if gid := session.id3 or session.id2: result, _ = await WordBankManage.delete_word( problem.result, @@ -259,9 +259,9 @@ async def _( await MessageUtils.build_message( "此命令之后需要跟随指定词条或id,通过“显示词条“查看" ).finish(reply_to=True) - word_scope = 1 if session.id3 or session.id2 else 2 + word_scope = ScopeType.GROUP if session.id3 or session.id2 else ScopeType.PRIVATE if all.result: - word_scope = 0 + word_scope = ScopeType.GLOBAL if gid := session.id3 or session.id2: result, old_problem = await WordBankManage.update_word( replace, @@ -297,16 +297,16 @@ async def _( arparma: Arparma, all: Query[bool] = AlconnaQuery("all.value", False), ): - word_scope = 1 if session.id3 or session.id2 else 2 + word_scope = ScopeType.GROUP if session.id3 or session.id2 else ScopeType.PRIVATE + group_id = session.id3 or session.id2 if all.result: word_scope = 0 - group_id = session.id3 or session.id2 if gid.available: group_id = gid.result if problem.available: if index.available: if index.result < 0 or index.result > len( - await WordBank.get_problem_by_scope(2) + await WordBank.get_problem_by_scope(word_scope) ): await MessageUtils.build_message("id必须在范围内...").finish( reply_to=True diff --git a/zhenxun/services/db_context.py b/zhenxun/services/db_context.py index 2bf9c1099..a39e71759 100644 --- a/zhenxun/services/db_context.py +++ b/zhenxun/services/db_context.py @@ -1,26 +1,15 @@ -import ujson as json from nonebot.utils import is_coroutine_callable -from tortoise import Tortoise, fields +from tortoise import Tortoise from tortoise.connection import connections from tortoise.models import Model as Model_ -from zhenxun.configs.config import ( - address, - bind, - database, - password, - port, - sql_name, - user, -) +from zhenxun.configs.config import BotConfig from zhenxun.configs.path_config import DATA_PATH from .log import logger -MODELS: list[str] = [] - SCRIPT_METHOD = [] - +MODELS: list[str] = [] DATABASE_SETTING_FILE = DATA_PATH / "database.json" @@ -29,7 +18,7 @@ class Model(Model_): 自动添加模块 Args: - Model_ (_type_): _description_ + Model_: Model """ def __init_subclass__(cls, **kwargs): @@ -39,49 +28,14 @@ def __init_subclass__(cls, **kwargs): SCRIPT_METHOD.append((cls.__module__, func)) -class TestSQL(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - - class Meta: - abstract = True - table = "test_sql" - table_description = "执行SQL命令,不记录任何数据" - - async def init(): - if DATABASE_SETTING_FILE.exists(): - with open(DATABASE_SETTING_FILE, "r", encoding="utf-8") as f: - setting_data = json.load(f) - else: - i_bind = bind - if not i_bind and any([user, password, address, port, database]): - i_bind = f"{sql_name}://{user}:{password}@{address}:{port}/{database}" - setting_data = { - "bind": i_bind, - "sql_name": sql_name, - "user": user, - "password": password, - "address": address, - "port": port, - "database": database, - } - with open(DATABASE_SETTING_FILE, "w", encoding="utf-8") as f: - json.dump(setting_data, f, ensure_ascii=False, indent=4) - i_bind = setting_data.get("bind") - _sql_name = setting_data.get("sql_name") - _user = setting_data.get("user") - _password = setting_data.get("password") - _address = setting_data.get("address") - _port = setting_data.get("port") - _database = setting_data.get("database") - if not i_bind and not any([_user, _password, _address, _port, _database]): - raise ValueError("\n数据库配置未填写...") - if not i_bind: - i_bind = f"{_sql_name}://{_user}:{_password}@{_address}:{_port}/{_database}" + if not BotConfig.db_url: + raise Exception(f"数据库配置为空,请在.env.dev中配置DB_URL...") try: await Tortoise.init( - db_url=i_bind, modules={"models": MODELS}, timezone="Asia/Shanghai" + db_url=BotConfig.db_url, + modules={"models": MODELS}, + timezone="Asia/Shanghai", ) if SCRIPT_METHOD: db = Tortoise.get_connection("default") diff --git a/zhenxun/utils/_build_image.py b/zhenxun/utils/_build_image.py index 33666f7e9..0f12b1e35 100644 --- a/zhenxun/utils/_build_image.py +++ b/zhenxun/utils/_build_image.py @@ -45,7 +45,7 @@ def __init__( mode: ModeType = "RGBA", font: str | Path | FreeTypeFont = "HYWenHei-85W.ttf", font_size: int = 20, - background: str | BytesIO | Path | None = None, + background: str | BytesIO | Path | bytes | None = None, ) -> None: self.uid = uuid.uuid1() self.width = width @@ -57,7 +57,10 @@ def __init__( else font ) if background: - self.markImg = Image.open(background) + if isinstance(background, bytes): + self.markImg = Image.open(BytesIO(background)) + else: + self.markImg = Image.open(background) if width and height: self.markImg = self.markImg.resize((width, height), Image.LANCZOS) else: @@ -74,7 +77,7 @@ def size(self) -> Tuple[int, int]: return self.markImg.size @classmethod - def open(cls, path: str | Path) -> Self: + def open(cls, path: str | Path | bytes) -> Self: """打开图片 参数: diff --git a/zhenxun/utils/_image_template.py b/zhenxun/utils/_image_template.py index 399c054c2..e989accc4 100644 --- a/zhenxun/utils/_image_template.py +++ b/zhenxun/utils/_image_template.py @@ -182,15 +182,20 @@ async def table( _temp["width"] = w build_data_list.append(_temp) column_image_list = [] + column_name_image_list: list[BuildImage] = [] for i, data in enumerate(build_data_list): - width = data["width"] + padding * 2 - height = (base_h + row_space) * (len(data["data"]) + 1) + padding * 2 - background = BuildImage(width, height, (255, 255, 255)) column_name_image = await BuildImage.build_text_image( column_name[i], font, 12, "#C8CCCF" ) + column_name_image_list.append(column_name_image) + max_h = max([c.height for c in column_name_image_list]) + for i, data in enumerate(build_data_list): + width = data["width"] + padding * 2 + height = (base_h + row_space) * (len(data["data"]) + 1) + padding * 2 + background = BuildImage(width, height, (255, 255, 255)) + column_name_image = column_name_image_list[i] await background.paste(column_name_image, (0, 20), center_type="width") - cur_h = column_name_image.height + row_space + 20 + cur_h = max_h + row_space + 20 for item in data["data"]: style = RowStyle(font=font) if text_style: @@ -198,13 +203,15 @@ async def table( if isinstance(item, tuple): """图片""" data, width, height = item + image_ = None if isinstance(data, Path): image_ = BuildImage(width, height, background=data) elif isinstance(data, bytes): image_ = BuildImage(width, height, background=BytesIO(data)) elif isinstance(data, BuildImage): image_ = data - await background.paste(image_, (padding, cur_h)) + if image_: + await background.paste(image_, (padding, cur_h)) else: await background.text( (padding, cur_h), diff --git a/zhenxun/utils/browser.py b/zhenxun/utils/browser.py index fccfa55e7..ca2e77556 100644 --- a/zhenxun/utils/browser.py +++ b/zhenxun/utils/browser.py @@ -5,7 +5,7 @@ from playwright.__main__ import main from playwright.async_api import Browser, Playwright, async_playwright -from zhenxun.configs.config import SYSTEM_PROXY +from zhenxun.configs.config import BotConfig from zhenxun.services.log import logger driver = get_driver() @@ -45,12 +45,12 @@ def set_env_variables(): os.environ["PLAYWRIGHT_DOWNLOAD_HOST"] = ( "https://npmmirror.com/mirrors/playwright/" ) - if SYSTEM_PROXY: - os.environ["HTTPS_PROXY"] = SYSTEM_PROXY + if BotConfig.system_proxy: + os.environ["HTTPS_PROXY"] = BotConfig.system_proxy def restore_env_variables(): os.environ.pop("PLAYWRIGHT_DOWNLOAD_HOST", None) - if SYSTEM_PROXY: + if BotConfig.system_proxy: os.environ.pop("HTTPS_PROXY", None) if original_proxy is not None: os.environ["HTTPS_PROXY"] = original_proxy diff --git a/zhenxun/utils/echart_utils/__init__.py b/zhenxun/utils/echart_utils/__init__.py new file mode 100644 index 000000000..ede8c35e6 --- /dev/null +++ b/zhenxun/utils/echart_utils/__init__.py @@ -0,0 +1,24 @@ +from nonebot_plugin_htmlrender import template_to_pic + +from zhenxun.configs.path_config import TEMPLATE_PATH +from zhenxun.utils._build_image import BuildImage + +from .models import Barh + + +class ChartUtils: + + @classmethod + async def barh(cls, data: Barh) -> BuildImage: + """横向统计图""" + pic = await template_to_pic( + template_path=str((TEMPLATE_PATH / "bar_chart").absolute()), + template_name="main.html", + templates={"data": data}, + pages={ + "viewport": {"width": 1000, "height": 500}, + "base_url": f"file://{TEMPLATE_PATH}", + }, + wait=2, + ) + return BuildImage.open(pic) diff --git a/zhenxun/utils/echart_utils/models.py b/zhenxun/utils/echart_utils/models.py new file mode 100644 index 000000000..8d9f639f6 --- /dev/null +++ b/zhenxun/utils/echart_utils/models.py @@ -0,0 +1,9 @@ +from pydantic import BaseModel + + +class Barh(BaseModel): + + category_data: list[str] + """坐标轴数据""" + data: list[int | float] + """实际数据""" diff --git a/zhenxun/utils/http_utils.py b/zhenxun/utils/http_utils.py index 15da938dd..b4708b391 100644 --- a/zhenxun/utils/http_utils.py +++ b/zhenxun/utils/http_utils.py @@ -13,7 +13,7 @@ from playwright.async_api import Page from retrying import retry -from zhenxun.configs.config import SYSTEM_PROXY +from zhenxun.configs.config import BotConfig from zhenxun.services.log import logger from zhenxun.utils.message import MessageUtils from zhenxun.utils.user_agent import get_user_agent @@ -23,7 +23,7 @@ class AsyncHttpx: - proxy = {"http://": SYSTEM_PROXY, "https://": SYSTEM_PROXY} + proxy = {"http://": BotConfig.system_proxy, "https://": BotConfig.system_proxy} @classmethod @retry(stop_max_attempt_number=3) @@ -305,7 +305,7 @@ async def new_page(cls, **kwargs) -> AsyncGenerator[Page, None]: 参数: user_agent: 请求头 """ - browser = get_browser() + browser = await get_browser() ctx = await browser.new_context(**kwargs) page = await ctx.new_page() try: diff --git a/zhenxun/utils/message.py b/zhenxun/utils/message.py index 54e7f908f..eb6549f22 100644 --- a/zhenxun/utils/message.py +++ b/zhenxun/utils/message.py @@ -5,7 +5,7 @@ from nonebot.adapters.onebot.v11 import Message, MessageSegment from nonebot_plugin_alconna import At, Image, Text, UniMessage -from zhenxun.configs.config import NICKNAME +from zhenxun.configs.config import BotConfig from zhenxun.services.log import logger from zhenxun.utils._build_image import BuildImage @@ -81,7 +81,7 @@ def custom_forward_msg( cls, msg_list: list[str | Message], uin: str, - name: str = f"这里是{NICKNAME}", + name: str = f"这里是{BotConfig.self_nickname}", ) -> list[dict]: """生成自定义合并消息 diff --git a/zhenxun/utils/platform.py b/zhenxun/utils/platform.py index 072132803..cc4811048 100644 --- a/zhenxun/utils/platform.py +++ b/zhenxun/utils/platform.py @@ -61,7 +61,7 @@ async def ban_user(cls, bot: Bot, user_id: str, group_id: str, duration: int): async def send_superuser( cls, bot: Bot, - message: UniMessage, + message: UniMessage | str, superuser_id: str | None = None, ) -> Receipt | None: """发送消息给超级用户 @@ -83,6 +83,8 @@ async def send_superuser( if not platform_superusers: raise NotFindSuperuser() superuser_id = random.choice(platform_superusers) + if isinstance(message, str): + message = MessageUtils.build_message(message) return await cls.send_message(bot, superuser_id, None, message) @classmethod @@ -347,22 +349,37 @@ async def update_group(cls, bot: Bot) -> int: int: 更新个数 """ create_list = [] + update_list = [] group_list, platform = await cls.get_group_list(bot) if group_list: - exists_group_list = await GroupConsole.all().values_list( - "group_id", "channel_id" - ) + db_group = await GroupConsole.all() + db_group_id = [(group.group_id, group.channel_id) for group in db_group] for group in group_list: group.platform = platform - if (group.group_id, group.channel_id) not in exists_group_list: + if (group.group_id, group.channel_id) not in db_group_id: create_list.append(group) logger.debug( "群聊信息更新成功", "更新群信息", target=f"{group.group_id}:{group.channel_id}", ) + else: + _group = [ + g + for g in db_group + if g.group_id == group.group_id + and g.channel_id == group.channel_id + ][0] + _group.group_name = group.group_name + _group.max_member_count = group.max_member_count + _group.member_count = group.member_count + update_list.append(_group) if create_list: await GroupConsole.bulk_create(create_list, 10) + if group_list: + await GroupConsole.bulk_update( + update_list, ["group_name", "max_member_count", "member_count"], 10 + ) return len(create_list) @classmethod