From a053c120c46b4e789f7f393c40f866e209c54531 Mon Sep 17 00:00:00 2001 From: yajiwa <839790708@qq.com> Date: Mon, 6 Feb 2023 02:06:36 +0800 Subject: [PATCH 1/5] update gold_redbag --- plugins/gold_redbag/__init__.py | 129 +++++++++++++------------------- 1 file changed, 54 insertions(+), 75 deletions(-) diff --git a/plugins/gold_redbag/__init__.py b/plugins/gold_redbag/__init__.py index 313125f3..8e578fcc 100755 --- a/plugins/gold_redbag/__init__.py +++ b/plugins/gold_redbag/__init__.py @@ -29,7 +29,6 @@ from nonebot.params import CommandArg import random import time - __zx_plugin_name__ = "金币红包" __plugin_usage__ = """ usage: @@ -64,11 +63,16 @@ __plugin_settings__ = { } __plugin_resources__ = {"prts": IMAGE_PATH} + +async def rule(event: GroupMessageEvent) -> bool: + return check_on_gold_red(event) + + gold_redbag = on_command( "塞红包", aliases={"金币红包"}, priority=5, block=True, permission=GROUP ) -open_ = on_command("开", aliases={"抢"}, priority=5, block=True, permission=GROUP) +open_ = on_command("开", aliases={"抢"}, priority=5, block=True, permission=GROUP, rule=rule) poke_ = on_notice(priority=6, block=False) @@ -85,27 +89,11 @@ festive_redbag_data = {} # 阻断其他poke @run_preprocessor -async def _(matcher: Matcher, event: PokeNotifyEvent): +async def _(matcher: Matcher, event: PokeNotifyEvent, ): try: if matcher.type == "notice" and event.self_id == event.target_id: - flag1 = True - flag2 = True - try: - if festive_redbag_data[event.group_id]["user_id"]: - if ( - event.user_id - in festive_redbag_data[event.group_id]["open_user"] - ): - flag1 = False - except KeyError: - flag1 = False - try: - if redbag_data[event.group_id]["user_id"]: - if event.user_id in redbag_data[event.group_id]["open_user"]: - flag2 = False - except KeyError: - flag2 = False - if flag1 or flag2: + flag = check_on_gold_red(event) + if flag: if matcher.plugin_name == "poke": raise IgnoredException("目前正在抢红包...") else: @@ -121,8 +109,8 @@ async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()): try: if time.time() - redbag_data[event.group_id]["time"] > 60: amount = ( - redbag_data[event.group_id]["amount"] - - redbag_data[event.group_id]["open_amount"] + redbag_data[event.group_id]["amount"] + - redbag_data[event.group_id]["open_amount"] ) await return_gold(redbag_data[event.group_id]["user_id"], event.group_id, amount) await gold_redbag.send( @@ -185,43 +173,21 @@ async def _(event: GroupMessageEvent, arg: Message = CommandArg()): msg = arg.extract_plain_text().strip() msg = ( msg.replace("!", "") - .replace("!", "") - .replace(",", "") - .replace(",", "") - .replace(".", "") - .replace("。", "") + .replace("!", "") + .replace(",", "") + .replace(",", "") + .replace(".", "") + .replace("。", "") ) if msg: if "红包" not in msg: return - flag1 = True - flag2 = True - open_flag1 = True - open_flag2 = True try: - if festive_redbag_data[event.group_id]["user_id"]: - if event.user_id in festive_redbag_data[event.group_id]["open_user"]: - open_flag1 = False - except KeyError: - open_flag1 = False - flag1 = False - try: - if redbag_data[event.group_id]["user_id"]: - if event.user_id in redbag_data[event.group_id]["open_user"]: - open_flag2 = False - except KeyError: - flag2 = False - if not flag1 and not flag2: - await open_.finish("目前没有红包可以开...", at_sender=True) - if open_flag1 or open_flag2: - try: - await open_.send( - image(b64=await get_redbag_img(event.user_id, event.group_id)), - at_sender=True, + await open_.send( + image(b64=await get_redbag_img(event.user_id, event.group_id)), + at_sender=True, ) - except KeyError: - await open_.finish("真贪心,明明已经开过这个红包了的说...", at_sender=True) - else: + except KeyError: await open_.finish("真贪心,明明已经开过这个红包了的说...", at_sender=True) @@ -229,19 +195,8 @@ async def _(event: GroupMessageEvent, arg: Message = CommandArg()): async def _poke_(event: PokeNotifyEvent): global redbag_data, festive_redbag_data if event.self_id == event.target_id: - flag1 = True - flag2 = True - try: - if event.user_id in festive_redbag_data[event.group_id]["open_user"]: - flag1 = False - except KeyError: - flag1 = False - try: - if event.user_id in redbag_data[event.group_id]["open_user"]: - flag2 = False - except KeyError: - flag2 = False - if not flag1 and not flag2: + flag = check_on_gold_red(event) + if not flag: return await poke_.send( image(b64=await get_redbag_img(event.user_id, event.group_id)), @@ -331,7 +286,7 @@ async def _(bot: Bot, arg: Message = CommandArg()): await bot.send_group_msg( group_id=g, message=f"{NICKNAME}发起了金币红包\n金额:{amount}\n数量:{num}\n" - + image( + + image( b64=await generate_send_redbag_pic(int(bot.self_id), greetings) ), ) @@ -342,13 +297,13 @@ async def _(bot: Bot, arg: Message = CommandArg()): # 红包数据初始化 def init_redbag( - user_id: int, - group_id: int, - nickname: str, - amount: int, - num: int, - bot_self_id: int, - mode: int = 0, + user_id: int, + group_id: int, + nickname: str, + amount: int, + num: int, + bot_self_id: int, + mode: int = 0, ): global redbag_data, festive_redbag_data data = redbag_data if mode == 0 else festive_redbag_data @@ -428,3 +383,27 @@ async def end_festive_redbag(bot: Bot, group_id: int): ) await bot.send_group_msg(group_id=group_id, message=message) festive_redbag_data[group_id] = {} + + +def check_on_gold_red(event) -> bool: + flag1 = True + flag2 = True + try: + if festive_redbag_data[event.group_id]["user_id"]: + if ( + event.user_id + in festive_redbag_data[event.group_id]["open_user"] + ): + flag1 = False + except KeyError: + flag1 = False + try: + if redbag_data[event.group_id]["user_id"]: + if event.user_id in redbag_data[event.group_id]["open_user"]: + flag2 = False + except KeyError: + flag2 = False + if flag1 or flag2: + return True + else: + return False From 9feea3ba1d3855714baccf14a3e179ecf9b819b3 Mon Sep 17 00:00:00 2001 From: CRAZYShimakaze <674015283@qq.com> Date: Thu, 16 Feb 2023 22:59:53 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=8F=92=E4=BB=B6?= =?UTF-8?q?=E5=B8=AE=E5=8A=A9=E5=91=BD=E4=BB=A4=E4=B8=8D=E7=94=9F=E6=95=88?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- basic_plugins/init_plugin_config/init_plugins_settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/basic_plugins/init_plugin_config/init_plugins_settings.py b/basic_plugins/init_plugin_config/init_plugins_settings.py index ef47798b..4165d1ca 100755 --- a/basic_plugins/init_plugin_config/init_plugins_settings.py +++ b/basic_plugins/init_plugin_config/init_plugins_settings.py @@ -27,6 +27,8 @@ def init_plugins_settings(): else: if plugin_data := plugin_data_manager.get(matcher.plugin_name): if plugin_settings := plugin_data.plugin_setting: + if (name := _module.__getattribute__("__zx_plugin_name__")) not in plugin_settings.cmd: + plugin_settings.cmd.append(name) # 管理员命令 if plugin_data.plugin_type == PluginType.ADMIN: admin_manager.add_admin_plugin_settings( From a6105e8a1b2b3517a16622a13cbd7f2e58a3d6a2 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sat, 18 Feb 2023 17:51:22 +0800 Subject: [PATCH 3/5] modified: .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9a886520..f56858ea 100644 --- a/.gitignore +++ b/.gitignore @@ -156,8 +156,9 @@ data/ .env.dev /resources/text/ /resources/image/ +/resources/temp/ !/resources/template/ -configs/config.py +/configs/config.py configs/config.yaml ./.env ./.env.dev @@ -165,3 +166,4 @@ plugins/csgo_server/ plugins/activity/ !/resources/image/genshin/alc/back.png !/data/genshin_alc/ +.vscode/launch.json \ No newline at end of file From 7685b95031e27dab947a2bf08b37a47958682402 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sat, 18 Feb 2023 18:46:54 +0800 Subject: [PATCH 4/5] modified: basic_plugins/admin_bot_manage/_data_source.py modified: basic_plugins/admin_bot_manage/admin_config.py modified: basic_plugins/admin_bot_manage/custom_welcome_message.py modified: basic_plugins/admin_bot_manage/timing_task.py modified: basic_plugins/apscheduler/__init__.py modified: basic_plugins/ban/__init__.py modified: basic_plugins/ban/data_source.py modified: basic_plugins/chat_history/chat_message.py modified: basic_plugins/chat_history/chat_message_handle.py modified: basic_plugins/group_handle/__init__.py modified: basic_plugins/hooks/_utils.py modified: basic_plugins/hooks/ban_hook.py modified: basic_plugins/hooks/chkdsk_hook.py modified: basic_plugins/init_plugin_config/__init__.py deleted: basic_plugins/init_plugin_config/init_group_manager.py modified: basic_plugins/invite_manager/__init__.py new file: basic_plugins/invite_manager/utils.py modified: basic_plugins/nickname.py modified: basic_plugins/plugin_shop/__init__.py modified: basic_plugins/plugin_shop/data_source.py modified: basic_plugins/scripts.py modified: basic_plugins/shop/__init__.py modified: basic_plugins/shop/buy.py modified: basic_plugins/shop/gold.py modified: basic_plugins/shop/my_props/__init__.py deleted: basic_plugins/shop/reset_today_gold.py modified: basic_plugins/shop/shop_handle/__init__.py modified: basic_plugins/shop/shop_handle/data_source.py modified: basic_plugins/shop/use/__init__.py modified: basic_plugins/shop/use/data_source.py modified: basic_plugins/super_cmd/__init__.py modified: basic_plugins/super_cmd/bot_friend_group.py modified: basic_plugins/super_cmd/clear_data.py modified: basic_plugins/super_cmd/exec_sql.py modified: basic_plugins/super_cmd/manager_group.py modified: basic_plugins/super_cmd/reload_setting.py modified: basic_plugins/super_cmd/set_admin_permissions.py deleted: basic_plugins/super_cmd/super_task_switch.py modified: basic_plugins/super_cmd/update_friend_group_info.py modified: basic_plugins/super_help/__init__.py modified: basic_plugins/update_info.py modified: configs/config.py modified: configs/utils/__init__.py modified: models/bag_user.py modified: models/ban_user.py modified: models/chat_history.py modified: models/friend_user.py modified: models/goods_info.py modified: models/group_info.py modified: models/group_member_info.py modified: models/level_user.py modified: models/sign_group_user.py modified: models/user_shop_gold_log.py modified: plugins/aconfig/__init__.py modified: plugins/ai/__init__.py modified: plugins/ai/data_source.py modified: plugins/bilibili_sub/__init__.py modified: plugins/bilibili_sub/data_source.py modified: plugins/bilibili_sub/model.py modified: plugins/black_word/__init__.py modified: plugins/black_word/model.py modified: plugins/black_word/utils.py modified: plugins/bt/data_source.py modified: plugins/genshin/almanac/__init__.py modified: plugins/genshin/material_remind/__init__.py modified: plugins/genshin/query_user/_models/__init__.py modified: plugins/genshin/query_user/_utils/__init__.py modified: plugins/genshin/query_user/bind/__init__.py modified: plugins/genshin/query_user/genshin_sign/__init__.py modified: plugins/genshin/query_user/genshin_sign/data_source.py modified: plugins/genshin/query_user/genshin_sign/init_task.py modified: plugins/genshin/query_user/mihoyobbs_sign/__init__.py modified: plugins/genshin/query_user/query_memo/__init__.py modified: plugins/genshin/query_user/query_memo/data_source.py modified: plugins/genshin/query_user/query_role/__init__.py modified: plugins/genshin/query_user/query_role/data_source.py modified: plugins/genshin/query_user/reset_today_query_user_data/__init__.py modified: plugins/genshin/query_user/resin_remind/__init__.py modified: plugins/genshin/query_user/resin_remind/init_task.py modified: plugins/gold_redbag/model.py modified: plugins/image_management/send_image/__init__.py modified: plugins/my_info/__init__.py modified: plugins/open_cases/models/buff_prices.py modified: plugins/open_cases/models/open_cases_user.py modified: plugins/open_cases/open_cases_c.py modified: plugins/open_cases/utils.py modified: plugins/parse_bilibili_json.py modified: plugins/pid_search.py modified: plugins/pix_gallery/__init__.py modified: plugins/pix_gallery/_data_source.py modified: plugins/pix_gallery/_model/omega_pixiv_illusts.py modified: plugins/pix_gallery/_model/pixiv.py modified: plugins/pix_gallery/_model/pixiv_keyword_user.py modified: plugins/pix_gallery/pix_add_keyword.py modified: plugins/pix_gallery/pix_pass_del_keyword.py modified: plugins/pix_gallery/pix_show_info.py modified: plugins/pix_gallery/pix_update.py modified: plugins/pixiv_rank_search/data_source.py modified: plugins/poke/__init__.py modified: plugins/russian/__init__.py modified: plugins/russian/data_source.py modified: plugins/russian/model.py modified: plugins/send_dinggong_voice/__init__.py modified: plugins/send_setu_/_model.py modified: plugins/send_setu_/send_setu/__init__.py modified: plugins/send_setu_/send_setu/data_source.py modified: plugins/send_setu_/update_setu/data_source.py modified: plugins/sign_in/goods_register.py modified: plugins/sign_in/group_user_checkin.py modified: plugins/sign_in/random_event.py modified: plugins/sign_in/utils.py modified: plugins/statistics/_model.py modified: plugins/statistics/statistics_handle.py modified: plugins/statistics/statistics_hook.py modified: plugins/update_picture.py modified: plugins/web_ui/api/request.py modified: plugins/word_bank/_model.py deleted: plugins/word_bank/_old_model.py modified: plugins/word_bank/_rule.py modified: plugins/word_bank/word_handle.py modified: plugins/word_clouds/data_source.py modified: resources/image/sign/sign_res/bar.png modified: resources/image/sign/sign_res/bar_white.png modified: services/db_context.py modified: services/log.py modified: utils/browser.py modified: utils/data_utils.py modified: utils/depends/__init__.py modified: utils/http_utils.py modified: utils/image_utils.py modified: utils/manager/admin_manager.py modified: utils/message_builder.py modified: utils/utils.py --- .../admin_bot_manage/_data_source.py | 118 ++--- .../admin_bot_manage/admin_config.py | 18 +- .../custom_welcome_message.py | 20 +- basic_plugins/admin_bot_manage/timing_task.py | 19 +- basic_plugins/apscheduler/__init__.py | 125 +++-- basic_plugins/ban/__init__.py | 136 ++--- basic_plugins/ban/data_source.py | 67 ++- basic_plugins/chat_history/chat_message.py | 41 +- .../chat_history/chat_message_handle.py | 33 +- basic_plugins/group_handle/__init__.py | 99 ++-- basic_plugins/hooks/_utils.py | 64 ++- basic_plugins/hooks/ban_hook.py | 19 +- basic_plugins/hooks/chkdsk_hook.py | 6 +- basic_plugins/init_plugin_config/__init__.py | 30 +- .../init_plugin_config/init_group_manager.py | 73 --- basic_plugins/invite_manager/__init__.py | 192 +++---- basic_plugins/invite_manager/utils.py | 87 +++ basic_plugins/nickname.py | 261 +++++---- basic_plugins/plugin_shop/__init__.py | 43 +- basic_plugins/plugin_shop/data_source.py | 32 +- basic_plugins/scripts.py | 280 +++++----- basic_plugins/shop/__init__.py | 37 +- basic_plugins/shop/buy.py | 94 ++-- basic_plugins/shop/gold.py | 17 +- basic_plugins/shop/my_props/__init__.py | 16 +- basic_plugins/shop/reset_today_gold.py | 26 - basic_plugins/shop/shop_handle/__init__.py | 65 ++- basic_plugins/shop/shop_handle/data_source.py | 154 ++++-- basic_plugins/shop/use/__init__.py | 40 +- basic_plugins/shop/use/data_source.py | 3 +- basic_plugins/super_cmd/__init__.py | 8 +- basic_plugins/super_cmd/bot_friend_group.py | 48 +- basic_plugins/super_cmd/clear_data.py | 43 +- basic_plugins/super_cmd/exec_sql.py | 110 ++-- basic_plugins/super_cmd/manager_group.py | 210 +++++--- basic_plugins/super_cmd/reload_setting.py | 38 +- .../super_cmd/set_admin_permissions.py | 63 ++- basic_plugins/super_cmd/super_task_switch.py | 58 -- .../super_cmd/update_friend_group_info.py | 65 ++- basic_plugins/super_help/__init__.py | 8 +- basic_plugins/update_info.py | 5 +- configs/config.py | 15 +- configs/utils/__init__.py | 7 +- models/bag_user.py | 222 +++----- models/ban_user.py | 111 ++-- models/chat_history.py | 202 ++----- models/friend_user.py | 99 +--- models/goods_info.py | 197 ++++--- models/group_info.py | 121 +---- models/group_member_info.py | 200 ++----- models/level_user.py | 103 ++-- models/sign_group_user.py | 129 ++--- models/user_shop_gold_log.py | 76 +-- plugins/aconfig/__init__.py | 29 +- plugins/ai/__init__.py | 18 +- plugins/ai/data_source.py | 39 +- plugins/bilibili_sub/__init__.py | 63 +-- plugins/bilibili_sub/data_source.py | 236 ++++----- plugins/bilibili_sub/model.py | 298 ++++------- plugins/black_word/__init__.py | 66 ++- plugins/black_word/model.py | 118 ++--- plugins/black_word/utils.py | 60 ++- plugins/bt/data_source.py | 15 +- plugins/genshin/almanac/__init__.py | 2 +- plugins/genshin/material_remind/__init__.py | 29 +- .../genshin/query_user/_models/__init__.py | 501 ++---------------- plugins/genshin/query_user/_utils/__init__.py | 13 +- plugins/genshin/query_user/bind/__init__.py | 109 ++-- .../query_user/genshin_sign/__init__.py | 73 +-- .../query_user/genshin_sign/data_source.py | 78 +-- .../query_user/genshin_sign/init_task.py | 47 +- .../query_user/mihoyobbs_sign/__init__.py | 59 ++- .../genshin/query_user/query_memo/__init__.py | 26 +- .../query_user/query_memo/data_source.py | 160 +++--- .../genshin/query_user/query_role/__init__.py | 49 +- .../query_user/query_role/data_source.py | 155 +++--- .../reset_today_query_user_data/__init__.py | 7 +- .../query_user/resin_remind/__init__.py | 47 +- .../query_user/resin_remind/init_task.py | 86 +-- plugins/gold_redbag/model.py | 97 ++-- .../image_management/send_image/__init__.py | 37 +- plugins/my_info/__init__.py | 23 +- plugins/open_cases/models/buff_prices.py | 42 +- plugins/open_cases/models/open_cases_user.py | 101 ++-- plugins/open_cases/open_cases_c.py | 376 ++++++------- plugins/open_cases/utils.py | 249 +++++---- plugins/parse_bilibili_json.py | 71 +-- plugins/pid_search.py | 20 +- plugins/pix_gallery/__init__.py | 37 +- plugins/pix_gallery/_data_source.py | 239 +++++---- .../pix_gallery/_model/omega_pixiv_illusts.py | 177 +++---- plugins/pix_gallery/_model/pixiv.py | 185 ++----- .../pix_gallery/_model/pixiv_keyword_user.py | 133 ++--- plugins/pix_gallery/pix_add_keyword.py | 73 ++- plugins/pix_gallery/pix_pass_del_keyword.py | 69 ++- plugins/pix_gallery/pix_show_info.py | 24 +- plugins/pix_gallery/pix_update.py | 65 +-- plugins/pixiv_rank_search/data_source.py | 27 +- plugins/poke/__init__.py | 24 +- plugins/russian/__init__.py | 50 +- plugins/russian/data_source.py | 39 +- plugins/russian/model.py | 176 +++--- plugins/send_dinggong_voice/__init__.py | 23 +- plugins/send_setu_/_model.py | 209 ++------ plugins/send_setu_/send_setu/__init__.py | 90 ++-- plugins/send_setu_/send_setu/data_source.py | 105 ++-- plugins/send_setu_/update_setu/data_source.py | 77 +-- plugins/sign_in/goods_register.py | 38 +- plugins/sign_in/group_user_checkin.py | 106 ++-- plugins/sign_in/random_event.py | 24 +- plugins/sign_in/utils.py | 83 +-- plugins/statistics/_model.py | 47 +- plugins/statistics/statistics_handle.py | 24 +- plugins/statistics/statistics_hook.py | 21 +- plugins/update_picture.py | 48 +- plugins/web_ui/api/request.py | 34 +- plugins/word_bank/_model.py | 292 +++++----- plugins/word_bank/_old_model.py | 20 - plugins/word_bank/_rule.py | 27 +- plugins/word_bank/word_handle.py | 30 +- plugins/word_clouds/data_source.py | 30 +- resources/image/sign/sign_res/bar.png | Bin 755 -> 2535 bytes resources/image/sign/sign_res/bar_white.png | Bin 584 -> 1431 bytes services/db_context.py | 57 +- services/log.py | 147 ++++- utils/browser.py | 37 +- utils/data_utils.py | 25 +- utils/depends/__init__.py | 79 ++- utils/game_utils.py | 192 +++++++ utils/http_utils.py | 161 +++--- utils/image_utils.py | 54 +- utils/manager/admin_manager.py | 13 +- utils/message_builder.py | 88 ++- utils/utils.py | 184 +++++-- 134 files changed, 5402 insertions(+), 6033 deletions(-) delete mode 100755 basic_plugins/init_plugin_config/init_group_manager.py create mode 100644 basic_plugins/invite_manager/utils.py delete mode 100644 basic_plugins/shop/reset_today_gold.py delete mode 100755 basic_plugins/super_cmd/super_task_switch.py delete mode 100644 plugins/word_bank/_old_model.py create mode 100644 utils/game_utils.py diff --git a/basic_plugins/admin_bot_manage/_data_source.py b/basic_plugins/admin_bot_manage/_data_source.py index 50a56879..750141ef 100644 --- a/basic_plugins/admin_bot_manage/_data_source.py +++ b/basic_plugins/admin_bot_manage/_data_source.py @@ -1,29 +1,29 @@ +import asyncio +import os +import time +from datetime import datetime +from pathlib import Path from typing import List + +import ujson as json from nonebot.adapters.onebot.v11.message import MessageSegment -from services.log import logger + +from configs.config import Config from configs.path_config import DATA_PATH, IMAGE_PATH +from models.group_member_info import GroupInfoUser +from models.level_user import LevelUser +from services.log import logger +from utils.http_utils import AsyncHttpx +from utils.image_utils import BuildImage +from utils.manager import group_manager, plugins2settings_manager, plugins_manager from utils.message_builder import image from utils.utils import get_bot, get_matchers -from pathlib import Path -from models.group_member_info import GroupInfoUser -from datetime import datetime -from services.db_context import db -from models.level_user import LevelUser -from configs.config import Config -from utils.manager import group_manager, plugins2settings_manager, plugins_manager -from utils.image_utils import BuildImage -from utils.http_utils import AsyncHttpx -import asyncio -import time -import os -import ujson as json - custom_welcome_msg_json = ( Path() / "data" / "custom_welcome_msg" / "custom_welcome_msg.json" ) -ICON_PATH = IMAGE_PATH / 'other' +ICON_PATH = IMAGE_PATH / "other" async def group_current_status(group_id: int) -> str: @@ -38,7 +38,9 @@ async def group_current_status(group_id: int) -> str: for i, task in enumerate(_data): name = _data[task] name_image = BuildImage(0, 0, plain_text=f"{i+1}.{name}", font_size=20) - bk = BuildImage(name_image.w + 200, name_image.h + 20, color=(103, 177, 109), font_size=15) + bk = BuildImage( + name_image.w + 200, name_image.h + 20, color=(103, 177, 109), font_size=15 + ) await bk.apaste(name_image, (10, 0), True, "by_height") a_icon = BuildImage(40, 40, background=ICON_PATH / "btn_false.png") if group_manager.check_group_task_status(group_id, task): @@ -123,7 +125,7 @@ def change_global_task_status(cmd: str) -> str: task_data = group_manager.get_task_data() status = cmd[:2] _cmd = cmd[4:] - if '全部被动' in cmd: + if "全部被动" in cmd: for task in task_data: if status == "开启": group_manager.open_global_task(task) @@ -134,7 +136,7 @@ def change_global_task_status(cmd: str) -> str: else: modules = [x for x in task_data if task_data[x].lower() == _cmd.lower()] if not modules: - return '未查询到该被动任务' + return "未查询到该被动任务" if status == "开启": group_manager.open_global_task(modules[0]) else: @@ -301,50 +303,40 @@ async def update_member_info(group_id: int, remind_superuser: bool = False) -> b # try: for user_info in _group_user_list: nickname = user_info["card"] or user_info["nickname"] - async with db.transaction(): - # 更新权限 - if user_info["role"] in [ - "owner", - "admin", - ] and not await LevelUser.is_group_flag(user_info["user_id"], group_id): - await LevelUser.set_level( - user_info["user_id"], - user_info["group_id"], - Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH"), - ) - if str(user_info["user_id"]) in bot.config.superusers: - await LevelUser.set_level( - user_info["user_id"], user_info["group_id"], 9 - ) - user = await GroupInfoUser.get_member_info( - user_info["user_id"], user_info["group_id"] - ) - if user: - if user.user_name != nickname: - await user.update(user_name=nickname).apply() - logger.info( - f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新群昵称成功" - ) - _exist_member_list.append(int(user_info["user_id"])) - continue - join_time = datetime.strptime( - time.strftime( - "%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"]) - ), - "%Y-%m-%d %H:%M:%S", - ) - if await GroupInfoUser.add_member_info( + # 更新权限 + if user_info["role"] in [ + "owner", + "admin", + ] and not await LevelUser.is_group_flag(user_info["user_id"], group_id): + await LevelUser.set_level( user_info["user_id"], user_info["group_id"], - nickname, - join_time, - ): - _exist_member_list.append(int(user_info["user_id"])) - logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功") - else: - _error_member_list.append( - f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新失败\n" + Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH"), + ) + if str(user_info["user_id"]) in bot.config.superusers: + await LevelUser.set_level(user_info["user_id"], user_info["group_id"], 9) + user = await GroupInfoUser.filter( + user_qq=user_info["user_id"], group_id=user_info["group_id"] + ).first() + if user: + if user.user_name != nickname: + await user.update(user_name=nickname).apply() + logger.info( + f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新群昵称成功" ) + _exist_member_list.append(int(user_info["user_id"])) + continue + join_time = datetime.strptime( + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])), + "%Y-%m-%d %H:%M:%S", + ) + await GroupInfoUser.update_or_create( + user_qq=user_info["user_id"], + group_id=user_info["group_id"], + defaults={"user_name": nickname, "user_join_time": join_time}, + ) + _exist_member_list.append(int(user_info["user_id"])) + logger.info("更新成功", "更新成员信息", user_info["user_id"], user_info["group_id"]) _del_member_list = list( set(_exist_member_list).difference( set(await GroupInfoUser.get_group_member_id_list(group_id)) @@ -352,10 +344,8 @@ async def update_member_info(group_id: int, remind_superuser: bool = False) -> b ) if _del_member_list: for del_user in _del_member_list: - if await GroupInfoUser.delete_member_info(del_user, group_id): - logger.info(f"退群用户{del_user} 所属{group_id} 已删除") - else: - logger.info(f"退群用户{del_user} 所属{group_id} 删除失败") + await GroupInfoUser.filter(user_qq=del_user, group_id=group_id).delete() + logger.info(f"退群用户{del_user} 所属{group_id} 已删除") if _error_member_list and remind_superuser: result = "" for error_user in _error_member_list: diff --git a/basic_plugins/admin_bot_manage/admin_config.py b/basic_plugins/admin_bot_manage/admin_config.py index a1bc200a..72f685df 100755 --- a/basic_plugins/admin_bot_manage/admin_config.py +++ b/basic_plugins/admin_bot_manage/admin_config.py @@ -1,10 +1,10 @@ from nonebot import on_notice -from services.log import logger from nonebot.adapters.onebot.v11 import GroupAdminNoticeEvent -from models.level_user import LevelUser -from models.group_member_info import GroupInfoUser -from configs.config import Config +from configs.config import Config +from models.group_member_info import GroupInfoUser +from models.level_user import LevelUser +from services.log import logger __zx_plugin_name__ = "群管理员变动监测 [Hidden]" __plugin_version__ = 0.1 @@ -16,11 +16,11 @@ admin_notice = on_notice(priority=5) @admin_notice.handle() async def _(event: GroupAdminNoticeEvent): - try: - nickname = ( - await GroupInfoUser.get_member_info(event.user_id, event.group_id) - ).user_name - except AttributeError: + if user := await GroupInfoUser.filter( + user_qq=event.user_id, group_id=event.group_id + ).first(): + nickname = user.nickname + else: nickname = event.user_id if event.sub_type == "set": await LevelUser.set_level( diff --git a/basic_plugins/admin_bot_manage/custom_welcome_message.py b/basic_plugins/admin_bot_manage/custom_welcome_message.py index 607e13c1..dd1b1594 100755 --- a/basic_plugins/admin_bot_manage/custom_welcome_message.py +++ b/basic_plugins/admin_bot_manage/custom_welcome_message.py @@ -1,14 +1,14 @@ from typing import List +from configs.config import Config from nonebot import on_command from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message -from nonebot.params import CommandArg -from ._data_source import custom_group_welcome from nonebot.adapters.onebot.v11.permission import GROUP -from configs.config import Config +from nonebot.params import CommandArg from services.log import logger from utils.depends import ImageList +from ._data_source import custom_group_welcome __zx_plugin_name__ = "自定义进群欢迎消息 [Admin]" __plugin_usage__ = """ @@ -19,12 +19,14 @@ usage: Note:可以通过[at]来确认是否艾特新成员 示例:自定义进群欢迎消息 欢迎你[at] """.strip() -__plugin_des__ = '简易的自定义群欢迎消息' -__plugin_cmd__ = ['自定义群欢迎消息 ?[文本] ?[图片]'] +__plugin_des__ = "简易的自定义群欢迎消息" +__plugin_cmd__ = ["自定义群欢迎消息 ?[文本] ?[图片]"] __plugin_version__ = 0.1 -__plugin_author__ = 'HibiKier' +__plugin_author__ = "HibiKier" __plugin_settings__ = { - "admin_level": Config.get_config("admin_bot_manage", "SET_GROUP_WELCOME_MESSAGE_LEVEL"), + "admin_level": Config.get_config( + "admin_bot_manage", "SET_GROUP_WELCOME_MESSAGE_LEVEL" + ), } custom_welcome = on_command( @@ -37,7 +39,9 @@ custom_welcome = on_command( @custom_welcome.handle() -async def _(event: GroupMessageEvent, arg: Message = CommandArg(), img: List[str] = ImageList()): +async def _( + event: GroupMessageEvent, arg: Message = CommandArg(), img: List[str] = ImageList() +): msg = arg.extract_plain_text().strip() if not msg and not img: await custom_welcome.finish(__plugin_usage__) diff --git a/basic_plugins/admin_bot_manage/timing_task.py b/basic_plugins/admin_bot_manage/timing_task.py index 840a44ad..198eef5e 100755 --- a/basic_plugins/admin_bot_manage/timing_task.py +++ b/basic_plugins/admin_bot_manage/timing_task.py @@ -1,15 +1,16 @@ -from utils.utils import scheduler, get_bot -from ._data_source import update_member_info -from services.log import logger -from models.group_info import GroupInfo from asyncpg.exceptions import ConnectionDoesNotExistError, UndefinedColumnError +from models.group_info import GroupInfo +from services.log import logger +from utils.utils import get_bot, scheduler -__zx_plugin_name__ = '管理方面定时任务 [Hidden]' -__plugin_usage__ = '无' -__plugin_des__ = '成员信息和管理权限的定时更新' +from ._data_source import update_member_info + +__zx_plugin_name__ = "管理方面定时任务 [Hidden]" +__plugin_usage__ = "无" +__plugin_des__ = "成员信息和管理权限的定时更新" __plugin_version__ = 0.1 -__plugin_author__ = 'HibiKier' +__plugin_author__ = "HibiKier" # 自动更新群员信息 @@ -42,7 +43,7 @@ async def _(): if bot: gl = await bot.get_group_list() gl = [g["group_id"] for g in gl] - all_group = [x.group_id for x in await GroupInfo.get_all_group()] + all_group = [x.group_id for x in await GroupInfo.all()] for g in gl: if g not in all_group: await update_member_info(g, False) diff --git a/basic_plugins/apscheduler/__init__.py b/basic_plugins/apscheduler/__init__.py index 5b6d2370..70ddf922 100755 --- a/basic_plugins/apscheduler/__init__.py +++ b/basic_plugins/apscheduler/__init__.py @@ -1,18 +1,22 @@ -from utils.message_builder import image -from utils.utils import scheduler, get_bot -from nonebot import on_message -from services.log import logger -from models.group_info import GroupInfo -from models.friend_user import FriendUser -from nonebot.adapters.onebot.v11 import ActionFailed -from configs.config import NICKNAME, Config -from pathlib import Path import shutil +from pathlib import Path + +import nonebot +from nonebot import on_message +from nonebot.adapters.onebot.v11 import ActionFailed + +from configs.config import NICKNAME, Config +from configs.path_config import IMAGE_PATH +from models.friend_user import FriendUser +from models.group_info import GroupInfo +from services.log import logger +from utils.message_builder import image +from utils.utils import broadcast_group, get_bot, scheduler __zx_plugin_name__ = "定时任务相关 [Hidden]" __plugin_version__ = 0.1 __plugin_author__ = "HibiKier" -__plugin_task__ = {'zwa': '早晚安'} +__plugin_task__ = {"zwa": "早晚安"} Config.add_plugin_config( @@ -24,20 +28,23 @@ Config.add_plugin_config( ) Config.add_plugin_config( - "_backup", - "BACKUP_FLAG", - True, - help_="是否开启文件备份", - default_value=True + "_backup", "BACKUP_FLAG", True, help_="是否开启文件备份", default_value=True ) Config.add_plugin_config( "_backup", "BACKUP_DIR_OR_FILE", - ['data/black_word', 'data/configs', 'data/statistics', 'data/word_bank', 'data/manager', 'configs'], + [ + "data/black_word", + "data/configs", + "data/statistics", + "data/word_bank", + "data/manager", + "configs", + ], name="文件备份", help_="备份的文件夹或文件", - default_value=[] + default_value=[], ) @@ -51,18 +58,9 @@ cx = on_message(priority=9999, block=False, rule=lambda: False) minute=1, ) async def _(): - try: - bot = get_bot() - gl = await bot.get_group_list() - gl = [g["group_id"] for g in gl] - for g in gl: - result = image("zao.jpg", "zhenxun") - try: - await bot.send_group_msg(group_id=g, message="[[_task|zwa]]早上好" + result) - except ActionFailed: - logger.warning(f"{g} 发送早安失败") - except Exception as e: - logger.error(f"早晚安错误 e:{e}") + img = image(IMAGE_PATH / "zhenxun" / "zao.jpg") + await broadcast_group("[[_task|zwa]]早上好" + img, log_cmd="被动早晚安") + logger.info("每日早安发送") # 睡觉了 @@ -72,20 +70,9 @@ async def _(): minute=59, ) async def _(): - try: - bot = get_bot() - gl = await bot.get_group_list() - gl = [g["group_id"] for g in gl] - for g in gl: - result = image("sleep.jpg", "zhenxun") - try: - await bot.send_group_msg( - group_id=g, message=f"[[_task|zwa]]{NICKNAME}要睡觉了,你们也要早点睡呀" + result - ) - except ActionFailed: - logger.warning(f"{g} 发送晚安失败") - except Exception as e: - logger.error(f"早晚安错误 e:{e}") + img = image(IMAGE_PATH / "zhenxun" / "sleep.jpg") + await broadcast_group("[[_task|zwa]]{NICKNAME}要睡觉了,你们也要早点睡呀" + img, log_cmd="被动早晚安") + logger.info("每日晚安发送") # 自动更新群组信息 @@ -95,22 +82,27 @@ async def _(): minute=1, ) async def _(): - try: - bot = get_bot() - gl = await bot.get_group_list() - gl = [g["group_id"] for g in gl] - for g in gl: - group_info = await bot.get_group_info(group_id=g) - await GroupInfo.add_group_info( - group_info["group_id"], - group_info["group_name"], - group_info["max_member_count"], - group_info["member_count"], - 1 - ) - logger.info(f"自动更新群组 {g} 信息成功") - except Exception as e: - logger.error(f"自动更新群组信息错误 e:{e}") + bots = nonebot.get_bots() + _used_group = [] + for bot in bots.values(): + try: + group_list = await bot.get_group_list() + gl = [g["group_id"] for g in group_list if g["group_id"] not in _used_group] + for g in gl: + _used_group.append(g) + group_info = await bot.get_group_info(group_id=g) + await GroupInfo.update_or_create( + group_id=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, + }, + ) + logger.info(f"自动更新群组信息成功", group_id=g) + except Exception as e: + logger.error(f"Bot: {bot.self_id} 自动更新群组信息", e=e) # 自动更新好友信息 @@ -124,10 +116,10 @@ async def _(): bot = get_bot() fl = await bot.get_friend_list() for f in fl: - if await FriendUser.add_friend_info(f["user_id"], f["nickname"]): - logger.info(f'自动更新好友 {f["user_id"]} 信息成功') - else: - logger.warning(f'自动更新好友 {f["user_id"]} 信息失败') + await FriendUser.create(user_id=f["user_id"], user_name=f["nickname"]) + logger.info(f'自动更新好友 {f["user_id"]} 信息成功') + # else: + # logger.warning(f'自动更新好友 {f["user_id"]} 信息失败') except Exception as e: logger.error(f"自动更新群组信息错误 e:{e}") @@ -140,7 +132,7 @@ async def _(): ) async def _(): if Config.get_config("_backup", "BACKUP_FLAG"): - _backup_path = Path() / 'backup' + _backup_path = Path() / "backup" _backup_path.mkdir(exist_ok=True, parents=True) for x in Config.get_config("_backup", "BACKUP_DIR_OR_FILE"): try: @@ -155,12 +147,13 @@ async def _(): if _p.exists(): _p.unlink() shutil.copy(x, _p) - logger.info(f'已完成自动备份:{x}') + logger.info(f"已完成自动备份:{x}") except Exception as e: logger.error(f"自动备份文件 {x} 发生错误 {type(e)}:{e}") - # 一次性任务 + + # 固定时间触发,仅触发一次: # # from datetime import datetime diff --git a/basic_plugins/ban/__init__.py b/basic_plugins/ban/__init__.py index b5b37942..a9a3318c 100755 --- a/basic_plugins/ban/__init__.py +++ b/basic_plugins/ban/__init__.py @@ -1,16 +1,22 @@ -from typing import Tuple, List +from typing import List + +from nonebot import on_command +from nonebot.adapters.onebot.v11 import ( + Bot, + GroupMessageEvent, + Message, + MessageEvent, + PrivateMessageEvent, +) +from nonebot.params import CommandArg +from nonebot.permission import SUPERUSER from configs.config import NICKNAME, Config from models.ban_user import BanUser from models.level_user import LevelUser -from nonebot import on_command -from nonebot.adapters.onebot.v11 import (Bot, GroupMessageEvent, Message, - MessageEvent, PrivateMessageEvent) -from nonebot.params import Command, CommandArg -from nonebot.permission import SUPERUSER from services.log import logger -from utils.depends import AtList -from utils.utils import get_message_at, is_number +from utils.depends import AtList, OneCommand +from utils.utils import is_number from .data_source import a_ban, parse_ban_time @@ -68,46 +74,38 @@ super_ban = on_command("b了", permission=SUPERUSER, priority=5, block=True) async def _( bot: Bot, event: GroupMessageEvent, - cmd: Tuple[str, ...] = Command(), + cmd: str = OneCommand(), arg: Message = CommandArg(), - qq: List[int] = AtList() + at_list: List[int] = AtList(), ): - cmd = cmd[0] result = "" - if qq: - qq = qq[0] - user_name = await bot.get_group_member_info(group_id=event.group_id, user_id=qq) - user_name = user_name["card"] or user_name["nickname"] + if at_list: + qq = at_list[0] + user = await bot.get_group_member_info(group_id=event.group_id, user_id=qq) + user_name = user["card"] or user["nickname"] msg = arg.extract_plain_text().strip() time = parse_ban_time(msg) if isinstance(time, str): await ban.finish(time, at_sender=True) + user_level = await LevelUser.get_user_level(event.user_id, event.group_id) + is_not_superuser = str(event.user_id) not in bot.config.superusers if cmd in [".ban", "/ban"]: - if ( - await LevelUser.get_user_level(event.user_id, event.group_id) - <= await LevelUser.get_user_level(qq, event.group_id) - and str(event.user_id) not in bot.config.superusers - ): + at_user_level = await LevelUser.get_user_level(qq, event.group_id) + if user_level <= at_user_level and is_not_superuser: await ban.finish( f"您的权限等级比对方低或相等, {NICKNAME}不能为您使用此功能!", at_sender=True, ) + logger.info(f"用户封禁 时长: {time}", cmd, event.user_id, event.group_id, qq) result = await a_ban(qq, time, user_name, event) else: - if ( - await BanUser.check_ban_level( - qq, await LevelUser.get_user_level(event.user_id, event.group_id) - ) - and str(event.user_id) not in bot.config.superusers - ): + if await BanUser.check_ban_level(qq, user_level) and is_not_superuser: await ban.finish( f"ban掉 {user_name} 的管理员权限比您高,无法进行unban", at_sender=True ) if await BanUser.unban(qq): - logger.info( - f"USER {event.user_id} GROUP {event.group_id} 将 USER {qq} 解禁" - ) - result = f"已经把 {user_name} 从黑名单中删除了!" + logger.info(f"解除用户封禁", cmd, event.user_id, event.group_id, qq) + result = f"已经将 {user_name} 从黑名单中删除了!" else: result = f"{user_name} 不在黑名单!" else: @@ -119,54 +117,62 @@ async def _( async def _( bot: Bot, event: PrivateMessageEvent, - cmd: Tuple[str, ...] = Command(), + cmd: str = OneCommand(), arg: Message = CommandArg(), ): - cmd = cmd[0] msg = arg.extract_plain_text().strip() - if msg: - if str(event.user_id) in bot.config.superusers: - msg_splt = msg.split() - if is_number(msg_splt[0]): - qq = int(msg_splt[0]) - msg = msg_splt[1:] - if cmd in [".ban", "/ban"]: - time = parse_ban_time(" ".join(msg)) - if isinstance(time, str): - await ban.finish(time) - result = await a_ban(qq, time, str(qq), event, 9) - else: - if await BanUser.unban(qq): - logger.info(f"USER {event.user_id} 将 USER {qq} 解禁") - result = f"已经把 {qq} 从黑名单中删除了!" - else: - result = f"{qq} 不在黑名单!" - await ban.send(result) + if msg and str(event.user_id) in bot.config.superusers: + msg_split = msg.split() + if msg_split and is_number(msg_split[0]): + qq = int(msg_split[0]) + param = msg_split[1:] + if cmd in [".ban", "/ban"]: + time = parse_ban_time(" ".join(param)) + if isinstance(time, str): + logger.info(time, cmd, event.user_id, target=qq) + await ban.finish(time) + result = await a_ban(qq, time, str(qq), event, 9) else: - await ban.finish( - "qq号必须是数字!\n格式:.ban [qq] [hour]? [minute]?", at_sender=True - ) + if await BanUser.unban(qq): + result = f"已经把 {qq} 从黑名单中删除了!" + else: + result = f"{qq} 不在黑名单!" + await ban.send(result) + logger.info(result, cmd, event.user_id, target=qq) + else: + await ban.send("参数不正确!\n格式:.ban [qq] [hour]? [minute]?", at_sender=True) @super_ban.handle() -async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): +async def _( + bot: Bot, + event: MessageEvent, + cmd: str = OneCommand(), + arg: Message = CommandArg(), + at_list: List[int] = AtList(), +): user_name = "" + qq = None if isinstance(event, GroupMessageEvent): - qq = get_message_at(event.json()) - if qq: - qq = qq[0] + if at_list: + qq = at_list[0] user = await bot.get_group_member_info(group_id=event.group_id, user_id=qq) user_name = user["card"] or user["nickname"] else: - qq = arg.extract_plain_text().strip() - if not is_number(qq): + msg = arg.extract_plain_text().strip() + if not is_number(msg): await super_ban.finish("对象qq必须为纯数字...") - qq = int(qq) - user_name = qq + qq = int(msg) + user_name = msg if qq: - if not await BanUser.ban(qq, 10, 99999999): - await BanUser.unban(qq) - await BanUser.ban(qq, 10, 99999999) + await BanUser.ban(qq, 10, 99999999) await ban.send(f"已将 {user_name} 拉入黑名单!") + logger.info( + f"已将 {user_name} 拉入黑名单!", + cmd, + event.user_id, + event.group_id if isinstance(event, GroupMessageEvent) else None, + qq, + ) else: - await super_ban.send("需要添加被super ban的对象,可以使用at或者指定qq..") + await super_ban.send("需要提供被super ban的对象,可以使用at或者指定qq...") diff --git a/basic_plugins/ban/data_source.py b/basic_plugins/ban/data_source.py index 4121d2e9..6878a8b9 100644 --- a/basic_plugins/ban/data_source.py +++ b/basic_plugins/ban/data_source.py @@ -1,10 +1,12 @@ -from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent +from typing import Optional, Union + +from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent + from configs.config import NICKNAME -from models.level_user import LevelUser -from utils.utils import is_number from models.ban_user import BanUser +from models.level_user import LevelUser from services.log import logger -from typing import Union +from utils.utils import is_number def parse_ban_time(msg: str) -> Union[int, str]: @@ -12,21 +14,31 @@ def parse_ban_time(msg: str) -> Union[int, str]: 解析ban时长 :param msg: 文本消息 """ - if not msg: - return -1 - msg = msg.split() - if len(msg) == 1: - if not is_number(msg[0].strip()): - return "参数必须是数字!" - return int(msg[0]) * 60 * 60 - else: - if not is_number(msg[0].strip()) or not is_number(msg[1].strip()): - return "参数必须是数字!" - return int(msg[0]) * 60 * 60 + int(msg[1]) * 60 + try: + if not msg: + return -1 + msg_split = msg.split() + if len(msg_split) == 1: + if not is_number(msg_split[0].strip()): + return "参数必须是数字!" + return int(msg_split[0]) * 60 * 60 + else: + if not is_number(msg_split[0].strip()) or not is_number( + msg_split[1].strip() + ): + return "参数必须是数字!" + return int(msg_split[0]) * 60 * 60 + int(msg_split[1]) * 60 + except ValueError as e: + logger.error("解析ban时长错误", ".ban", e=e) + return "时长不可以带小数点!" async def a_ban( - qq: int, time: int, user_name: str, event: MessageEvent, ban_level: int = None + qq: int, + time: int, + user_name: str, + event: MessageEvent, + ban_level: Optional[int] = None, ) -> str: """ ban @@ -36,12 +48,15 @@ async def a_ban( :param event: event :param ban_level: ban级别 """ + group_id = None if isinstance(event, GroupMessageEvent): + group_id = event.group_id ban_level = await LevelUser.get_user_level(event.user_id, event.group_id) + if not ban_level: + return "未查询到ban级用户权限" if await BanUser.ban(qq, ban_level, time): logger.info( - f"USER {event.user_id} GROUP" - f" {event.group_id if isinstance(event, GroupMessageEvent) else ''} 将 USER {qq} 封禁 时长 {time / 60} 分钟" + f"将 [Target]({qq})封禁 时长 {time / 60} 分钟", ".ban", event.user_id, group_id ) result = f"已经将 {user_name} 加入{NICKNAME}的黑名单了!" if time != -1: @@ -49,14 +64,14 @@ async def a_ban( else: result += f"将在 ∞ 分钟后解封" else: - time = await BanUser.check_ban_time(qq) - if is_number(time): - time = abs(int(time)) - if time < 60: - time = str(time) + " 秒" + ban_time = await BanUser.check_ban_time(qq) + if isinstance(ban_time, int): + ban_time = abs(float(ban_time)) + if ban_time < 60: + ban_time = str(ban_time) + " 秒" else: - time = str(int(time / 60)) + " 分钟" + ban_time = str(int(ban_time / 60)) + " 分钟" else: - time += " 分钟" - result = f"{user_name} 已在黑名单!预计 {time}后解封" + ban_time += " 分钟" + result = f"{user_name} 已在黑名单!预计 {ban_time}后解封" return result diff --git a/basic_plugins/chat_history/chat_message.py b/basic_plugins/chat_history/chat_message.py index e8bfbdf6..fbfa1d6a 100644 --- a/basic_plugins/chat_history/chat_message.py +++ b/basic_plugins/chat_history/chat_message.py @@ -1,8 +1,11 @@ -from configs.config import Config -from models.chat_history import ChatHistory from nonebot import on_message from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent + +from configs.config import Config +from models.chat_history import ChatHistory +from services.log import logger from utils.depends import PlaintText +from utils.utils import scheduler from ._rule import rule @@ -19,14 +22,38 @@ Config.add_plugin_config( chat_history = on_message(rule=rule, priority=1, block=False) +TEMP_LIST = [] + + @chat_history.handle() async def _(event: MessageEvent, msg: str = PlaintText()): + group_id = None if isinstance(event, GroupMessageEvent): - await ChatHistory.add_chat_msg( - event.user_id, event.group_id, str(event.get_message()), msg - ) - else: - await ChatHistory.add_chat_msg(event.user_id, None, str(event.get_message()), msg) + group_id = event.group_id + TEMP_LIST.append( + { + "user_qq": event.user_id, + "group_id": group_id, + "text": str(event.get_message()), + "plain_text": msg, + } + ) + + +@scheduler.scheduled_job( + "interval", + minutes=1, +) +async def _(): + try: + message_list = TEMP_LIST.copy() + TEMP_LIST.clear() + if message_list: + model_list = [ChatHistory(**x) for x in message_list] + await ChatHistory.bulk_create(model_list) + logger.debug(f"批量添加聊天记录 {len(message_list)} 条", "定时任务") + except Exception as e: + logger.error(f"定时批量添加聊天记录", "定时任务", e=e) # @test.handle() diff --git a/basic_plugins/chat_history/chat_message_handle.py b/basic_plugins/chat_history/chat_message_handle.py index b050490f..3c1c2ddb 100644 --- a/basic_plugins/chat_history/chat_message_handle.py +++ b/basic_plugins/chat_history/chat_message_handle.py @@ -1,16 +1,16 @@ from datetime import datetime, timedelta +from typing import Any, Tuple import pytz -from models.chat_history import ChatHistory -from models.group_member_info import GroupInfoUser from nonebot import on_regex from nonebot.adapters.onebot.v11 import GroupMessageEvent from nonebot.params import RegexGroup -from utils.image_utils import BuildImage, text2image -from utils.utils import is_number -from utils.message_builder import image -from typing import Tuple, Any +from models.chat_history import ChatHistory +from models.group_member_info import GroupInfoUser +from utils.image_utils import BuildImage, text2image +from utils.message_builder import image +from utils.utils import is_number __zx_plugin_name__ = "消息统计" __plugin_usage__ = """ @@ -29,12 +29,7 @@ usage: 消息统计n=15 """.strip() __plugin_des__ = "发言消息排行" -__plugin_cmd__ = [ - "消息统计", - "周消息统计", - "月消息统计", - "日消息统计" -] +__plugin_cmd__ = ["消息统计", "周消息统计", "月消息统计", "日消息统计"] __plugin_type__ = ("数据统计", 1) __plugin_version__ = 0.1 __plugin_author__ = "HibiKier" @@ -44,7 +39,9 @@ __plugin_settings__ = { } -msg_handler = on_regex(r"^(周|月|日)?消息统计(des|DES)?(n=[0-9]{1,2})?$", priority=5, block=True) +msg_handler = on_regex( + r"^(周|月|日)?消息统计(des|DES)?(n=[0-9]{1,2})?$", priority=5, block=True +) @msg_handler.handle() @@ -56,7 +53,9 @@ async def _(event: GroupMessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()) if num and is_number(num) and 10 < int(num) < 50: num = int(num) time_now = datetime.now() - zero_today = time_now - timedelta(hours=time_now.hour, minutes=time_now.minute, seconds=time_now.second) + zero_today = time_now - timedelta( + hours=time_now.hour, minutes=time_now.minute, seconds=time_now.second + ) if date in ["日"]: date_scope = (zero_today, time_now) elif date in ["周"]: @@ -70,9 +69,9 @@ async def _(event: GroupMessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()) num_str = "发言次数:\n\n" idx = 1 for uid, num in rank_data: - try: - user_name = (await GroupInfoUser.get_member_info(uid, gid)).user_name - except AttributeError: + if user := await GroupInfoUser.filter(user_qq=uid, group_id=gid).first(): + user_name = user.user_name + else: user_name = uid name += f"\t{idx}.{user_name} \n\n" num_str += f"\t{num}\n\n" diff --git a/basic_plugins/group_handle/__init__.py b/basic_plugins/group_handle/__init__.py index b5eaacc6..d30fcdf7 100755 --- a/basic_plugins/group_handle/__init__.py +++ b/basic_plugins/group_handle/__init__.py @@ -1,26 +1,27 @@ -from nonebot import on_notice, on_request -from configs.path_config import IMAGE_PATH, DATA_PATH -from models.level_user import LevelUser -from utils.message_builder import image -from models.group_member_info import GroupInfoUser -from datetime import datetime -from services.log import logger -from nonebot.adapters.onebot.v11 import ( - Bot, - ActionFailed, - GroupIncreaseNoticeEvent, - GroupDecreaseNoticeEvent, -) -from utils.manager import group_manager, plugins2settings_manager, requests_manager -from configs.config import NICKNAME -from models.group_info import GroupInfo -from utils.utils import FreqLimiter -from configs.config import Config -from pathlib import Path -import random import os -import ujson as json +import random +from datetime import datetime +from pathlib import Path +import ujson as json +from nonebot import on_notice, on_request +from nonebot.adapters.onebot.v11 import ( + ActionFailed, + Bot, + GroupDecreaseNoticeEvent, + GroupIncreaseNoticeEvent, +) + +from configs.config import NICKNAME, Config +from configs.path_config import DATA_PATH, IMAGE_PATH +from models.group_info import GroupInfo +from models.group_member_info import GroupInfoUser +from models.level_user import LevelUser +from services.log import logger +from utils.depends import GetConfig +from utils.manager import group_manager, plugins2settings_manager, requests_manager +from utils.message_builder import image +from utils.utils import FreqLimiter __zx_plugin_name__ = "群事件处理 [Hidden]" __plugin_version__ = 0.1 @@ -65,7 +66,7 @@ add_group = on_request(priority=1, block=False) @group_increase_handle.handle() async def _(bot: Bot, event: GroupIncreaseNoticeEvent): if event.user_id == int(bot.self_id): - group = await GroupInfo.get_group_info(event.group_id) + group = await GroupInfo.filter(group_id=event.group_id).first() # 群聊不存在或被强制拉群,退出该群 if (not group or group.group_flag == 0) and Config.get_config( "invite_manager", "flag" @@ -115,15 +116,12 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent): user_info = await bot.get_group_member_info( group_id=event.group_id, user_id=event.user_id ) - if await GroupInfoUser.add_member_info( - user_info["user_id"], - user_info["group_id"], - user_info["nickname"], - join_time, - ): - logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功") - else: - logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新失败") + await GroupInfoUser.update_or_create( + user_qq=user_info["user_id"], + group_id=user_info["group_id"], + defaults={"user_name": user_info["nickname"], "user_join_time": join_time}, + ) + logger.info(f"用户{user_info['user_id']} 所属{user_info['group_id']} 更新成功") # 群欢迎消息 if _flmt.check(event.group_id): @@ -152,7 +150,11 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent): else: await group_increase_handle.send( "[[_task|group_welcome]]新人快跑啊!!本群现状↓(快使用自定义!)" - + image(random.choice(os.listdir(IMAGE_PATH / "qxz")), "qxz") + + image( + IMAGE_PATH + / "qxz" + / random.choice(os.listdir(IMAGE_PATH / "qxz")) + ) ) @@ -162,13 +164,13 @@ async def _(bot: Bot, event: GroupDecreaseNoticeEvent): if event.sub_type == "kick_me": group_id = event.group_id operator_id = event.operator_id - try: - operator_name = ( - await GroupInfoUser.get_member_info(event.operator_id, event.group_id) - ).user_name - except AttributeError: + if user := await GroupInfoUser.get_or_none( + user_qq=event.operator_id, group_id=event.group_id + ): + operator_name = user.user_name + else: operator_name = "None" - group = await GroupInfo.get_group_info(group_id) + group = await GroupInfo.filter(group_id=group_id).first() group_name = group.group_name if group else "" coffee = int(list(bot.config.superusers)[0]) await bot.send_private_msg( @@ -182,16 +184,19 @@ async def _(bot: Bot, event: GroupDecreaseNoticeEvent): if event.user_id == int(bot.self_id): group_manager.delete_group(event.group_id) return - try: - user_name = ( - await GroupInfoUser.get_member_info(event.user_id, event.group_id) - ).user_name - except AttributeError: - user_name = str(event.user_id) - if await GroupInfoUser.delete_member_info(event.user_id, event.group_id): - logger.info(f"用户{user_name}, qq={event.user_id} 所属{event.group_id} 删除成功") + if user := await GroupInfoUser.get_or_none( + user_qq=event.user_id, group_id=event.group_id + ): + user_name = user.user_name else: - logger.info(f"用户{user_name}, qq={event.user_id} 所属{event.group_id} 删除失败") + user_name = f"{event.user_id}" + await GroupInfoUser.filter(user_qq=event.user_id, group_id=event.group_id).delete() + logger.info( + f"名称: {user_name} 退出群聊", + "group_decrease_handle", + event.user_id, + event.group_id, + ) rst = "" if event.sub_type == "leave": rst = f"{user_name}离开了我们..." diff --git a/basic_plugins/hooks/_utils.py b/basic_plugins/hooks/_utils.py index a5ded1e5..fcf6cce0 100644 --- a/basic_plugins/hooks/_utils.py +++ b/basic_plugins/hooks/_utils.py @@ -1,39 +1,40 @@ +import time from typing import Optional import nonebot from nonebot.adapters.onebot.v11 import ( - GroupMessageEvent, - PrivateMessageEvent, Bot, Event, - MessageEvent, + GroupMessageEvent, Message, + MessageEvent, PokeNotifyEvent, + PrivateMessageEvent, ) from nonebot.exception import ActionFailed, IgnoredException from nonebot.internal.matcher import Matcher +from configs.config import Config from models.bag_user import BagUser from models.ban_user import BanUser from models.friend_user import FriendUser from models.group_member_info import GroupInfoUser from models.level_user import LevelUser from models.user_shop_gold_log import UserShopGoldLog +from services.log import logger from utils.decorator import Singleton from utils.manager import ( - plugins2block_manager, StaticData, - plugins2settings_manager, - group_manager, admin_manager, - plugins_manager, + group_manager, + plugins2block_manager, plugins2cd_manager, plugins2count_manager, + plugins2settings_manager, + plugins_manager, ) from utils.message_builder import at from utils.utils import FreqLimiter -from configs.config import Config -import time ignore_rst_module = ["ai", "poke", "dialogue"] @@ -97,11 +98,11 @@ async def send_msg(msg: str, bot: Bot, event: MessageEvent): msg = msg.replace("[uname]", uname) if "[nickname]" in msg: if isinstance(event, GroupMessageEvent): - nickname = await GroupInfoUser.get_group_member_nickname( + nickname = await GroupInfoUser.get_user_nickname( event.user_id, event.group_id ) else: - nickname = await FriendUser.get_friend_nickname(event.user_id) + nickname = await FriendUser.get_user_nickname(event.user_id) msg = msg.replace("[nickname]", nickname) if "[at]" in msg and isinstance(event, GroupMessageEvent): msg = msg.replace("[at]", str(at(event.user_id))) @@ -142,16 +143,19 @@ class AuthChecker: :param event: event """ try: - plugin_name = matcher.plugin_name - cost_gold = await self.auth_cost(plugin_name, bot, event) - if hasattr(event, "user_id") and str(event.user_id) not in bot.config.superusers: - await self.auth_basic(plugin_name, bot, event) - self.auth_group(plugin_name, bot, event) - await self.auth_admin(plugin_name, matcher, bot, event) - await self.auth_plugin(plugin_name, matcher, bot, event) - await self.auth_limit(plugin_name, bot, event) - if cost_gold: - await BagUser.spend_gold(event.user_id, event.group_id, cost_gold) + if plugin_name := matcher.plugin_name: + cost_gold = await self.auth_cost(plugin_name, bot, event) + user_id = getattr(event, "user_id", None) + if user_id and str(user_id) not in bot.config.superusers: + await self.auth_basic(plugin_name, bot, event) + self.auth_group(plugin_name, bot, event) + await self.auth_admin(plugin_name, matcher, bot, event) + await self.auth_plugin(plugin_name, matcher, bot, event) + await self.auth_limit(plugin_name, bot, event) + if cost_gold: + await BagUser.spend_gold( + event.user_id, event.group_id, cost_gold + ) except IsSuperuserException: return @@ -221,7 +225,9 @@ class AuthChecker: else: plugins2count_manager.increase(plugin_name, count_type_) - async def auth_plugin(self, plugin_name: str, matcher: Matcher, bot: Bot, event: Event): + async def auth_plugin( + self, plugin_name: str, matcher: Matcher, bot: Bot, event: Event + ): """ 说明: 插件状态 @@ -345,6 +351,7 @@ class AuthChecker: and plugin_name not in ignore_rst_module ): self._flmt_c.start_cd(event.group_id) + logger.info(f"{event.user_id} ||XXXXXX: {matcher.module}") await bot.send_group_msg( group_id=event.group_id, message="此功能正在维护..." ) @@ -364,7 +371,9 @@ class AuthChecker: set_block_limit_false(event, plugin_name) raise IgnoredException("此功能正在维护...") - async def auth_admin(self, plugin_name: str, matcher: Matcher, bot: Bot, event: Event): + async def auth_admin( + self, plugin_name: str, matcher: Matcher, bot: Bot, event: Event + ): """ 说明: 管理员命令 个人权限 @@ -504,8 +513,13 @@ class AuthChecker: await BagUser.spend_gold( event.user_id, event.group_id, psm.cost_gold ) - await UserShopGoldLog.add_shop_log( - event.user_id, event.group_id, 2, plugin_name, psm.cost_gold, 1 + await UserShopGoldLog.create( + user_qq=event.user_id, + group_id=event.group_id, + type=2, + name=plugin_name, + num=1, + spend_gold=psm.cost_gold, ) cost_gold = psm.cost_gold return cost_gold diff --git a/basic_plugins/hooks/ban_hook.py b/basic_plugins/hooks/ban_hook.py index 27f1a012..3f53684f 100755 --- a/basic_plugins/hooks/ban_hook.py +++ b/basic_plugins/hooks/ban_hook.py @@ -1,20 +1,21 @@ -from nonebot.matcher import Matcher -from nonebot.message import run_preprocessor, IgnoredException -from nonebot.typing import T_State from nonebot.adapters.onebot.v11 import ( + ActionFailed, Bot, Event, - MessageEvent, - ActionFailed, - PokeNotifyEvent, GroupMessageEvent, + MessageEvent, + PokeNotifyEvent, ) +from nonebot.matcher import Matcher +from nonebot.message import IgnoredException, run_preprocessor +from nonebot.typing import T_State + from configs.config import Config from models.ban_user import BanUser -from utils.utils import is_number, static_flmt, FreqLimiter from utils.message_builder import at -from ._utils import ignore_rst_module, other_limit_plugins +from utils.utils import FreqLimiter, is_number, static_flmt +from ._utils import ignore_rst_module, other_limit_plugins Config.add_plugin_config( "hook", @@ -49,7 +50,7 @@ async def _(matcher: Matcher, bot: Bot, event: Event, state: T_State): and str(event.user_id) not in bot.config.superusers ): time = await BanUser.check_ban_time(event.user_id) - if is_number(time): + if isinstance(time, int): time = abs(int(time)) if time < 60: time = str(time) + " 秒" diff --git a/basic_plugins/hooks/chkdsk_hook.py b/basic_plugins/hooks/chkdsk_hook.py index 055ffa1c..46c53410 100755 --- a/basic_plugins/hooks/chkdsk_hook.py +++ b/basic_plugins/hooks/chkdsk_hook.py @@ -28,12 +28,12 @@ async def _(matcher: Matcher, bot: Bot, event: GroupMessageEvent, state: T_State if matcher.type == "message" and matcher.priority not in [1, 999]: if state["_prefix"]["raw_command"]: if _blmt.check(f'{event.user_id}{state["_prefix"]["raw_command"]}'): - if await BanUser.ban( + await BanUser.ban( event.user_id, 9, Config.get_config("hook", "MALICIOUS_BAN_TIME") * 60, - ): - logger.info(f"USER {event.user_id} 触发了恶意触发检测") + ) + logger.info(f"USER {event.user_id} 触发了恶意触发检测") if isinstance(event, GroupMessageEvent): try: await bot.send_group_msg( diff --git a/basic_plugins/init_plugin_config/__init__.py b/basic_plugins/init_plugin_config/__init__.py index 4e5a5b15..355239fe 100755 --- a/basic_plugins/init_plugin_config/__init__.py +++ b/basic_plugins/init_plugin_config/__init__.py @@ -1,23 +1,23 @@ +import nonebot +from nonebot import Driver +from nonebot.adapters.onebot.v11 import Bot + from configs.path_config import DATA_PATH -from .init_group_manager import init_group_manager, group_manager +from services.log import logger + +from .check_plugin_status import check_plugin_status +from .init import init +from .init_none_plugin_count_manager import init_none_plugin_count_manager +from .init_plugin_info import init_plugin_info from .init_plugins_config import init_plugins_config from .init_plugins_data import init_plugins_data, plugins_manager -from .init_none_plugin_count_manager import init_none_plugin_count_manager -from .init_plugins_resources import init_plugins_resources -from .init_plugins_settings import init_plugins_settings -from .init_plugin_info import init_plugin_info from .init_plugins_limit import ( init_plugins_block_limit, - init_plugins_count_limit, init_plugins_cd_limit, + init_plugins_count_limit, ) -from .init import init -from .check_plugin_status import check_plugin_status -from nonebot.adapters.onebot.v11 import Bot -from services.log import logger -from nonebot import Driver -import nonebot - +from .init_plugins_resources import init_plugins_resources +from .init_plugins_settings import init_plugins_settings __zx_plugin_name__ = "初始化插件数据 [Hidden]" __plugin_version__ = 0.1 @@ -44,10 +44,6 @@ async def _(): init_plugins_config() init_plugins_resources() init_none_plugin_count_manager() - # x = group_manager.get_super_old_data() - # if x: - # for key in x.keys(): - # plugins_manager.block_plugin(key, block_type=x[key]) if _flag: raise Exception("首次运行,已在configs目录下生成配置文件config.yaml,修改后重启即可...") logger.info("初始化数据完成...") diff --git a/basic_plugins/init_plugin_config/init_group_manager.py b/basic_plugins/init_plugin_config/init_group_manager.py deleted file mode 100755 index 58a1a744..00000000 --- a/basic_plugins/init_plugin_config/init_group_manager.py +++ /dev/null @@ -1,73 +0,0 @@ -from pathlib import Path -from utils.manager import group_manager -from services.db_context import db -from asyncpg.exceptions import DuplicateColumnError -from services.log import logger - -try: - import ujson as json -except ModuleNotFoundError: - import json -try: - from models.group_remind import GroupRemind -except ModuleNotFoundError: - pass - - -async def init_group_manager(): - """ - 旧数据格式替换为新格式 - 初始化数据 - """ - old_group_level_file = Path() / "data" / "manager" / "group_level.json" - old_plugin_list_file = Path() / "data" / "manager" / "plugin_list.json" - if old_group_level_file.exists(): - data = json.load(open(old_group_level_file, "r", encoding="utf8")) - for key in data.keys(): - group = key - level = data[key] - group_manager.set_group_level(group, level) - old_group_level_file.unlink() - group_manager.save() - - if old_plugin_list_file.exists(): - data = json.load(open(old_plugin_list_file, "r", encoding="utf8")) - for plugin in data.keys(): - for group in data[plugin].keys(): - if group == "default" and not data[plugin]["default"]: - group_manager.block_plugin(plugin) - elif not data[plugin][group]: - group_manager.block_plugin(plugin, group) - old_plugin_list_file.unlink() - old_data_table = Path() / "models" / "group_remind.py" - try: - if old_data_table.exists(): - b = { - "hy": "group_welcome", - "kxcz": "open_case_reset_remind", - "zwa": "zwa", - "blpar": "bilibili_parse", - "epic": "epic_free_game", - "pa": "pa", - "almanac": "genshin_alc", - } - for group in group_manager.get_data()["group_manager"]: - for remind in b: - try: - status = await GroupRemind.get_status(int(group), remind) - if status is not None: - if status: - await group_manager.open_group_task(group, b[remind]) - logger.info(f"读取旧数据-->{group} 开启 {b[remind]}") - else: - await group_manager.close_group_task(group, b[remind]) - logger.info(f"读取旧数据-->{group} 关闭 {b[remind]}") - except Exception as e: - pass - query = db.text("DROP TABLE group_reminds;") - await db.first(query) - old_data_table.unlink() - logger.info("旧数据读取完毕,删除了舍弃表 group_reminds...") - except (ModuleNotFoundError, DuplicateColumnError): - pass - group_manager.save() diff --git a/basic_plugins/invite_manager/__init__.py b/basic_plugins/invite_manager/__init__.py index 502f8812..4a07128f 100755 --- a/basic_plugins/invite_manager/__init__.py +++ b/basic_plugins/invite_manager/__init__.py @@ -1,20 +1,25 @@ -from nonebot import on_request, on_message +import asyncio +import re +import time +from datetime import datetime + +from nonebot import on_message, on_request from nonebot.adapters.onebot.v11 import ( - Bot, ActionFailed, + Bot, FriendRequestEvent, GroupRequestEvent, MessageEvent, ) -from models.friend_user import FriendUser -from datetime import datetime + from configs.config import NICKNAME, Config -from utils.manager import requests_manager +from models.friend_user import FriendUser from models.group_info import GroupInfo +from services.log import logger +from utils.manager import requests_manager from utils.utils import scheduler -import asyncio -import time -import re + +from .utils import time_manager __zx_plugin_name__ = "好友群聊处理请求 [Hidden]" __plugin_version__ = 0.1 @@ -27,101 +32,109 @@ friend_req = on_request(priority=5, block=True) group_req = on_request(priority=5, block=True) x = on_message(priority=999, block=False, rule=lambda: False) -exists_data = {"private": {}, "group": {}} - @friend_req.handle() async def _(bot: Bot, event: FriendRequestEvent): - global exists_data - if exists_data["private"].get(event.user_id): - if time.time() - exists_data["private"][event.user_id] < 60 * 5: - return - exists_data["private"][event.user_id] = time.time() - user = await bot.get_stranger_info(user_id=event.user_id) - nickname = user["nickname"] - sex = user["sex"] - age = str(user["age"]) - comment = event.comment - await bot.send_private_msg( - user_id=int(list(bot.config.superusers)[0]), - message=f"*****一份好友申请*****\n" - f"昵称:{nickname}({event.user_id})\n" - f"自动同意:{'√' if Config.get_config('invite_manager', 'AUTO_ADD_FRIEND') else '×'}\n" - f"日期:{str(datetime.now()).split('.')[0]}\n" - f"备注:{event.comment}", - ) - if Config.get_config("invite_manager", "AUTO_ADD_FRIEND"): - await bot.set_friend_add_request(flag=event.flag, approve=True) - await FriendUser.add_friend_info(user["user_id"], user["nickname"]) - else: - requests_manager.add_request( - event.user_id, - "private", - event.flag, - nickname=nickname, - sex=sex, - age=age, - comment=comment, + if time_manager.add_user_request(event.user_id): + logger.debug(f"收录 用户[{event.user_id}] 好友请求", "好友请求") + user = await bot.get_stranger_info(user_id=event.user_id) + nickname = user["nickname"] + sex = user["sex"] + age = str(user["age"]) + comment = event.comment + await bot.send_private_msg( + user_id=int(list(bot.config.superusers)[0]), + message=f"*****一份好友申请*****\n" + f"昵称:{nickname}({event.user_id})\n" + f"自动同意:{'√' if Config.get_config('invite_manager', 'AUTO_ADD_FRIEND') else '×'}\n" + f"日期:{str(datetime.now()).split('.')[0]}\n" + f"备注:{event.comment}", ) + if Config.get_config("invite_manager", "AUTO_ADD_FRIEND"): + logger.debug(f"已开启好友请求自动同意,成功通过该请求", "好友请求", target=event.user_id) + await bot.set_friend_add_request(flag=event.flag, approve=True) + await FriendUser.create(user_id=user["user_id"], user_name=user["nickname"]) + else: + requests_manager.add_request( + event.user_id, + "private", + event.flag, + nickname=nickname, + sex=sex, + age=age, + comment=comment, + ) + else: + logger.debug(f"好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id) @group_req.handle() async def _(bot: Bot, event: GroupRequestEvent): - global exists_data + # 邀请 if event.sub_type == "invite": if str(event.user_id) in bot.config.superusers: try: - if await GroupInfo.get_group_info(event.group_id): - await GroupInfo.set_group_flag(event.group_id, 1) - else: - group_info = await bot.get_group_info(group_id=event.group_id) - await GroupInfo.add_group_info( - group_info["group_id"], - group_info["group_name"], - group_info["max_member_count"], - group_info["member_count"], - 1, - ) + logger.debug( + f"超级用户自动同意加入群聊", "群聊请求", event.user_id, target=event.group_id + ) await bot.set_group_add_request( flag=event.flag, sub_type="invite", approve=True ) - except ActionFailed: - pass + group_info = await bot.get_group_info(group_id=event.group_id) + await GroupInfo.update_or_create( + group_id=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, + }, + ) + except ActionFailed as e: + logger.error( + "超级用户自动同意加入群聊发生错误", + "群聊请求", + event.user_id, + target=event.group_id, + e=e, + ) else: - user = await bot.get_stranger_info(user_id=event.user_id) - sex = user["sex"] - age = str(user["age"]) - if exists_data["group"].get(f"{event.user_id}:{event.group_id}"): - if ( - time.time() - - exists_data["group"][f"{event.user_id}:{event.group_id}"] - < 60 * 5 - ): - return - exists_data["group"][f"{event.user_id}:{event.group_id}"] = time.time() - nickname = await FriendUser.get_user_name(event.user_id) - await bot.send_private_msg( - user_id=int(list(bot.config.superusers)[0]), - message=f"*****一份入群申请*****\n" - f"申请人:{nickname}({event.user_id})\n" - f"群聊:{event.group_id}\n" - f"邀请日期:{str(datetime.now()).split('.')[0]}", - ) - await bot.send_private_msg( - user_id=event.user_id, - message=f"想要邀请我偷偷入群嘛~已经提醒{NICKNAME}的管理员大人了\n" - "请确保已经群主或群管理沟通过!\n" - "等待管理员处理吧!", - ) - requests_manager.add_request( - event.user_id, - "group", - event.flag, - invite_group=event.group_id, - nickname=nickname, - sex=sex, - age=age, - ) + if time_manager.add_group_request(event.user_id, event.group_id): + logger.debug( + f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求", "群聊请求" + ) + user = await bot.get_stranger_info(user_id=event.user_id) + sex = user["sex"] + age = str(user["age"]) + nickname = await FriendUser.get_user_name(event.user_id) + await bot.send_private_msg( + user_id=int(list(bot.config.superusers)[0]), + message=f"*****一份入群申请*****\n" + f"申请人:{nickname}({event.user_id})\n" + f"群聊:{event.group_id}\n" + f"邀请日期:{datetime.now().replace(microsecond=0)}", + ) + await bot.send_private_msg( + user_id=event.user_id, + message=f"想要邀请我偷偷入群嘛~已经提醒{NICKNAME}的管理员大人了\n" + "请确保已经群主或群管理沟通过!\n" + "等待管理员处理吧!", + ) + requests_manager.add_request( + event.user_id, + "group", + event.flag, + invite_group=event.group_id, + nickname=nickname, + sex=sex, + age=age, + ) + else: + logger.debug( + f"群聊请求五分钟内重复, 已忽略", + "群聊请求", + target=f"{event.user_id}:{event.group_id}", + ) @x.handle() @@ -145,5 +158,4 @@ async def _(event: MessageEvent): minutes=5, ) async def _(): - global exists_data - exists_data = {"private": {}, "group": {}} + time_manager.clear() diff --git a/basic_plugins/invite_manager/utils.py b/basic_plugins/invite_manager/utils.py new file mode 100644 index 00000000..aee7c6f1 --- /dev/null +++ b/basic_plugins/invite_manager/utils.py @@ -0,0 +1,87 @@ +import time +from dataclasses import dataclass +from typing import Dict + + +@dataclass +class PrivateRequest: + + """ + 好友请求 + """ + + user_id: int + time: float = time.time() + + +@dataclass +class GroupRequest: + + """ + 群聊请求 + """ + + user_id: int + group_id: int + time: float = time.time() + + +class RequestTimeManage: + + """ + 过滤五分钟以内的重复请求 + """ + + def __init__(self): + + self._group: Dict[str, GroupRequest] = {} + self._user: Dict[int, PrivateRequest] = {} + + def add_user_request(self, user_id: int) -> bool: + """ + 添加请求时间 + + Args: + user_id (int): 用户id + + Returns: + bool: 是否满足时间 + """ + if user := self._user.get(user_id): + if time.time() - user.time < 60 * 5: + return False + self._user[user_id] = PrivateRequest(user_id) + return True + + def add_group_request(self, user_id: int, group_id: int) -> bool: + """ + 添加请求时间 + + Args: + user_id (int): 用户id + group_id (int): 邀请群聊 + + Returns: + bool: 是否满足时间 + """ + key = f"{user_id}:{group_id}" + if group := self._group.get(key): + if time.time() - group.time < 60 * 5: + return False + self._group[key] = GroupRequest(user_id=user_id, group_id=group_id) + return True + + def clear(self): + """ + 清理过期五分钟请求 + """ + now = time.time() + for user_id in self._user: + if now - self._user[user_id].time < 60 * 5: + del self._user[user_id] + for key in self._group: + if now - self._group[key].time < 60 * 5: + del self._group[key] + + +time_manager = RequestTimeManage() diff --git a/basic_plugins/nickname.py b/basic_plugins/nickname.py index c46fc79a..74ed4542 100755 --- a/basic_plugins/nickname.py +++ b/basic_plugins/nickname.py @@ -1,121 +1,162 @@ -from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, PrivateMessageEvent, Message, MessageEvent -from nonebot import on_command -from nonebot.typing import T_State -from nonebot.rule import to_me -from models.group_member_info import GroupInfoUser -from models.friend_user import FriendUser -from models.ban_user import BanUser -from services.log import logger -from configs.config import NICKNAME, Config -from nonebot.params import CommandArg import random +from typing import Any, Tuple + +from nonebot import on_command, on_regex +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent +from nonebot.internal.matcher import Matcher +from nonebot.internal.params import Depends +from nonebot.params import CommandArg, RegexGroup +from nonebot.rule import to_me + +from configs.config import NICKNAME +from models.ban_user import BanUser +from models.friend_user import FriendUser +from models.group_member_info import GroupInfoUser +from services.log import logger +from utils.depends import GetConfig __zx_plugin_name__ = "昵称系统" __plugin_usage__ = f""" usage: - 个人昵称系统,群聊 与 私聊 昵称相互独立 + 个人昵称,将替换真寻称呼你的名称,群聊 与 私聊 昵称相互独立,全局昵称设置将更改您目前所有群聊中及私聊的昵称 指令: - 以后叫我 [昵称] + 以后叫我 [昵称]: 设置当前群聊/私聊的昵称 + 全局昵称设置 [昵称]: 设置当前所有群聊和私聊的昵称 {NICKNAME}我是谁 """.strip() __plugin_des__ = "区区昵称,才不想叫呢!" -__plugin_cmd__ = ["以后叫我 [昵称]", f"{NICKNAME}我是谁"] +__plugin_cmd__ = ["以后叫我 [昵称]", f"{NICKNAME}我是谁", "全局昵称设置 [昵称]"] __plugin_version__ = 0.1 __plugin_author__ = "HibiKier" __plugin_settings__ = { "level": 5, "default_status": True, "limit_superuser": False, - "cmd": ["昵称", "昵称系统"], + "cmd": ["昵称"], } __plugin_configs__ = { "BLACK_WORD": { - "value": ["爸", "爹", "爷", "父亲"], - "help": "昵称所屏蔽的关键词,会被替换为 *", - "default_value": None + "value": ["爸", "爹", "爷", "父"], + "help": "昵称所屏蔽的关键词,已设置的昵称会被替换为 *,未设置的昵称会在设置时提示", + "default_value": None, } } -nickname = on_command( - "nickname", - aliases={"以后叫我", "以后请叫我", "称呼我", "以后请称呼我", "以后称呼我", "叫我", "请叫我"}, +nickname = on_regex( + "(?:以后)?(?:叫我|请叫我|称呼我)(.*)", rule=to_me(), priority=5, block=True, ) my_nickname = on_command( - "my_name", aliases={"我叫什么", "我是谁", "我的名字"}, rule=to_me(), priority=5, block=True + "我叫什么", aliases={"我是谁", "我的名字"}, rule=to_me(), priority=5, block=True ) +global_nickname = on_regex("设置全局昵称(.*)", rule=to_me(), priority=5, block=True) + cancel_nickname = on_command("取消昵称", rule=to_me(), priority=5, block=True) -@nickname.handle() -async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): - msg = arg.extract_plain_text().strip() - if not msg: - await nickname.finish("叫你空白?叫你虚空?叫你无名??", at_sender=True) - if len(msg) > 10: - await nickname.finish("昵称可不能超过10个字!", at_sender=True) - if msg in bot.config.superusers: - await nickname.finish("笨蛋!休想占用我的名字!#", at_sender=True) - _tmp = "" - black_word = Config.get_config("nickname", "BLACK_WORD") - if black_word: - for x in msg: - _tmp += "*" if x in black_word else x - msg = _tmp +def CheckNickname(): + """ + 说明: + 检查名称是否合法 + """ + + async def dependency( + bot: Bot, + matcher: Matcher, + event: MessageEvent, + reg_group: Tuple[Any, ...] = RegexGroup(), + black_word: Any = GetConfig(config="BLACK_WORD"), + ): + (msg,) = reg_group + logger.debug(f"昵称检查: {msg}", "昵称设置", event.user_id) + if not msg: + await matcher.finish("叫你空白?叫你虚空?叫你无名??", at_sender=True) + if str(event.user_id) in bot.config.superusers: + logger.debug(f"超级用户设置昵称, 跳过合法检测: {msg}", "昵称设置", event.user_id) + return + if len(msg) > 20: + await nickname.finish("昵称可不能超过20个字!", at_sender=True) + if msg in bot.config.nickname: + await nickname.finish("笨蛋!休想占用我的名字!#", at_sender=True) + if black_word: + for x in msg: + if x in black_word: + logger.debug("昵称设置禁止字符: [{x}]", "昵称设置", event.user_id) + await nickname.finish(f"字符 [{x}] 为禁止字符!", at_sender=True) + for word in black_word: + if word in msg: + logger.debug("昵称设置禁止字符: [{word}]", "昵称设置", event.user_id) + await nickname.finish(f"字符 [{word}] 为禁止字符!", at_sender=True) + + return Depends(dependency) + + +@global_nickname.handle(parameterless=[CheckNickname()]) +async def _( + event: MessageEvent, + reg_group: Tuple[Any, ...] = RegexGroup(), +): + (msg,) = reg_group + await FriendUser.set_user_nickname(event.user_id, msg) + await GroupInfoUser.filter(user_qq=event.user_id).update(nickname=msg) + logger.info(f"设置全局昵称成功: {msg}", "设置全局昵称", event.user_id) + await global_nickname.send(f"设置全局昵称成功!亲爱的{msg}") + + +@nickname.handle(parameterless=[CheckNickname()]) +async def _( + event: MessageEvent, + reg_group: Tuple[Any, ...] = RegexGroup(), +): + (msg,) = reg_group if isinstance(event, GroupMessageEvent): - if await GroupInfoUser.set_group_member_nickname( - event.user_id, event.group_id, msg - ): - if len(msg) < 5: - if random.random() < 0.3: - msg = "~".join(msg) - await nickname.send( - random.choice( - [ - f"好啦好啦,我知道啦,{msg},以后就这么叫你吧", - f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}", - f"好突然,突然要叫你昵称什么的...{msg}..", - f"{NICKNAME}会好好记住{msg}的,放心吧", - f"好..好.,那窝以后就叫你{msg}了.", - ] - ) + await GroupInfoUser.set_user_nickname(event.user_id, event.group_id, msg) + if len(msg) < 5: + if random.random() < 0.3: + msg = "~".join(msg) + await nickname.send( + random.choice( + [ + f"好啦好啦,我知道啦,{msg},以后就这么叫你吧", + f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}", + f"好突然,突然要叫你昵称什么的...{msg}..", + f"{NICKNAME}会好好记住{msg}的,放心吧", + f"好..好.,那窝以后就叫你{msg}了.", + ] ) - logger.info(f"USER {event.user_id} GROUP {event.group_id} 设置群昵称 {msg}") - else: - await nickname.send("设置昵称失败,请更新群组成员信息!", at_sender=True) - logger.warning(f"USER {event.user_id} GROUP {event.group_id} 设置群昵称 {msg} 失败") + ) + logger.info(f"设置群昵称成功: {msg}", "昵称设置", event.user_id, event.group_id) else: - if await FriendUser.set_friend_nickname(event.user_id, msg): - await nickname.send( - random.choice( - [ - f"好啦好啦,我知道啦,{msg},以后就这么叫你吧", - f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}", - f"好突然,突然要叫你昵称什么的...{msg}..", - f"{NICKNAME}会好好记住{msg}的,放心吧", - f"好..好.,那窝以后就叫你{msg}了.", - ] - ) + await FriendUser.set_user_nickname(event.user_id, msg) + await nickname.send( + random.choice( + [ + f"好啦好啦,我知道啦,{msg},以后就这么叫你吧", + f"嗯嗯,{NICKNAME}记住你的昵称了哦,{msg}", + f"好突然,突然要叫你昵称什么的...{msg}..", + f"{NICKNAME}会好好记住{msg}的,放心吧", + f"好..好.,那窝以后就叫你{msg}了.", + ] ) - logger.info(f"USER {event.user_id} 设置昵称 {msg}") - else: - await nickname.send("设置昵称失败了,明天再来试一试!或联系管理员更新好友!", at_sender=True) - logger.warning(f"USER {event.user_id} 设置昵称 {msg} 失败") + ) + logger.info(f"设置私聊昵称成功: {msg}", "昵称设置", event.user_id) @my_nickname.handle() -async def _(event: GroupMessageEvent): - try: - nickname_ = await GroupInfoUser.get_group_member_nickname( - event.user_id, event.group_id - ) - except AttributeError: - nickname_ = "" +async def _(event: MessageEvent): + nickname_ = None + card = None + if isinstance(event, GroupMessageEvent): + nickname_ = await GroupInfoUser.get_user_nickname(event.user_id, event.group_id) + card = event.sender.card or event.sender.nickname + else: + nickname_ = await FriendUser.get_user_nickname(event.user_id) + card = event.sender.nickname if nickname_: await my_nickname.send( random.choice( @@ -130,44 +171,20 @@ async def _(event: GroupMessageEvent): ) ) else: - nickname_ = event.sender.card or event.sender.nickname await my_nickname.send( random.choice( ["没..没有昵称嘛,{}", "啊,你是{}啊,我想叫你的昵称!", "是{}啊,有什么事吗?", "你是{}?"] - ).format(nickname_) - ) - - -@my_nickname.handle() -async def _(bot: Bot, event: PrivateMessageEvent, state: T_State): - nickname_ = await FriendUser.get_friend_nickname(event.user_id) - if nickname_: - await my_nickname.send( - random.choice( - [ - f"我肯定记得你啊,你是{nickname_}啊", - f"我不会忘记你的,你也不要忘记我!{nickname_}", - f"哼哼,{NICKNAME}记忆力可是很好的,{nickname_}", - f"嗯?你是失忆了嘛...{nickname_}..", - f"不要小看{NICKNAME}的记忆力啊!笨蛋{nickname_}!QAQ", - f"哎?{nickname_}..怎么了吗..突然这样问..", - ] - ) - ) - else: - nickname_ = (await bot.get_stranger_info(user_id=event.user_id))["nickname"] - await my_nickname.send( - random.choice( - ["没..没有昵称嘛,{}", "啊,你是{}啊,我想叫你的昵称!", "是{}啊,有什么事吗?", "你是{}?"] - ).format(nickname_) + ).format(card) ) @cancel_nickname.handle() -async def _(event: GroupMessageEvent): - nickname_ = await GroupInfoUser.get_group_member_nickname( - event.user_id, event.group_id - ) +async def _(event: MessageEvent): + nickname_ = None + if isinstance(event, GroupMessageEvent): + nickname_ = await GroupInfoUser.get_user_nickname(event.user_id, event.group_id) + else: + nickname_ = await FriendUser.get_user_nickname(event.user_id) if nickname_: await cancel_nickname.send( random.choice( @@ -180,28 +197,10 @@ async def _(event: GroupMessageEvent): ] ) ) - await GroupInfoUser.set_group_member_nickname(event.user_id, event.group_id, "") - await BanUser.ban(event.user_id, 9, 60) - else: - await cancel_nickname.send("你在做梦吗?你没有昵称啊", at_sender=True) - - -@cancel_nickname.handle() -async def _(event: PrivateMessageEvent): - nickname_ = await FriendUser.get_friend_nickname(event.user_id) - if nickname_: - await cancel_nickname.send( - random.choice( - [ - f"呜..{NICKNAME}睡一觉就会忘记的..和梦一样..{nickname_}", - f"窝知道了..{nickname_}..", - f"是{NICKNAME}哪里做的不好嘛..好吧..晚安{nickname_}", - f"呃,{nickname_},下次我绝对绝对绝对不会再忘记你!", - f"可..可恶!{nickname_}!太可恶了!呜", - ] - ) - ) - await FriendUser.get_user_name(event.user_id) + if isinstance(event, GroupMessageEvent): + await GroupInfoUser.set_user_nickname(event.user_id, event.group_id, "") + else: + await FriendUser.set_user_nickname(event.user_id, "") await BanUser.ban(event.user_id, 9, 60) else: await cancel_nickname.send("你在做梦吗?你没有昵称啊", at_sender=True) diff --git a/basic_plugins/plugin_shop/__init__.py b/basic_plugins/plugin_shop/__init__.py index 4af777d5..0ef17cb9 100644 --- a/basic_plugins/plugin_shop/__init__.py +++ b/basic_plugins/plugin_shop/__init__.py @@ -1,13 +1,17 @@ from nonebot import on_command, on_regex - -from .data_source import show_plugin_repo, install_plugin, uninstall_plugin, download_json -from services.log import logger from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent from nonebot.params import CommandArg - -from utils.message_builder import image from nonebot.permission import SUPERUSER +from services.log import logger +from utils.message_builder import image + +from .data_source import ( + download_json, + install_plugin, + show_plugin_repo, + uninstall_plugin, +) __zx_plugin_name__ = "插件商店 [Superuser]" __plugin_usage__ = """ @@ -28,23 +32,29 @@ show_repo = on_regex("^查看插件仓库$", priority=1, block=True, permission= update_repo = on_regex("^更新插件仓库$", priority=1, block=True, permission=SUPERUSER) -install_plugin_matcher = on_command("安装插件", priority=1, block=True, permission=SUPERUSER) +install_plugin_matcher = on_command( + "安装插件", priority=1, block=True, permission=SUPERUSER +) -uninstall_plugin_matcher = on_command("卸载插件", priority=1, block=True, permission=SUPERUSER) +uninstall_plugin_matcher = on_command( + "卸载插件", priority=1, block=True, permission=SUPERUSER +) @install_plugin_matcher.handle() async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): - arg = arg.extract_plain_text().strip() - msg = await install_plugin(arg) + name = arg.extract_plain_text().strip() + msg = await install_plugin(name) await install_plugin_matcher.send(msg) + logger.info(f"安装插件: {name}", "安装插件", event.user_id) @uninstall_plugin_matcher.handle() async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): - arg = arg.extract_plain_text().strip() - msg = await uninstall_plugin(arg) + name = arg.extract_plain_text().strip() + msg = await uninstall_plugin(name) await install_plugin_matcher.send(msg) + logger.info(f"卸载插件: {name}", "卸载插件", event.user_id) @update_repo.handle() @@ -53,16 +63,13 @@ async def _(bot: Bot, event: MessageEvent): if code == 200: await update_repo.finish("更新插件仓库信息成功!") await update_repo.send("更新插件仓库信息失败!") - + logger.info("更新插件仓库信息", "更新插件仓库信息", event.user_id) + @show_repo.handle() async def _(bot: Bot, event: MessageEvent): msg = await show_plugin_repo() if isinstance(msg, int): await show_repo.finish("文件下载失败或解压失败..") - await show_repo.send(image(b64=msg)) - logger.info( - f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" - f" 查看插件仓库" - ) - + await show_repo.send(image(msg)) + logger.info("查看插件仓库", "查看插件仓库", event.user_id) diff --git a/basic_plugins/plugin_shop/data_source.py b/basic_plugins/plugin_shop/data_source.py index c73d3d7d..e582ded5 100644 --- a/basic_plugins/plugin_shop/data_source.py +++ b/basic_plugins/plugin_shop/data_source.py @@ -2,15 +2,15 @@ import os import shutil import zipfile from pathlib import Path -from typing import Union, Tuple -from utils.manager import plugins_manager +from typing import Tuple, Union import ujson as json +from configs.path_config import DATA_PATH, TEMP_PATH from services import logger from utils.http_utils import AsyncHttpx -from configs.path_config import TEMP_PATH, DATA_PATH -from utils.image_utils import text2image, BuildImage +from utils.image_utils import BuildImage, text2image +from utils.manager import plugins_manager from utils.utils import is_number path = DATA_PATH / "plugin_shop" @@ -48,7 +48,7 @@ async def install_plugin(name: str) -> str: if zip_file.exists(): zip_file.unlink() if await AsyncHttpx.download_file(url, zip_file): - logger.debug("开始解压插件压缩包...") + logger.debug("开始解压插件压缩包...", "安装插件", target=name) # 解压 zf = zipfile.ZipFile(zip_file, "r") extract_path = TEMP_PATH / f"{name}" @@ -58,32 +58,34 @@ async def install_plugin(name: str) -> str: for file in zf.namelist(): zf.extract(file, extract_path) zf.close() - logger.debug("解压插件压缩包完成...") - logger.debug("开始移动插件文件夹...") + logger.debug("解压插件压缩包完成...", "安装插件", target=name) + logger.debug("开始移动插件文件夹...", "安装插件", target=name) if (extensive_plugin_path / f"{name}").exists(): - logger.debug("extensive_plugin目录下文件夹已存在,删除该目录插件文件夹...") + logger.debug( + "extensive_plugin目录下文件夹已存在,删除该目录插件文件夹...", "安装插件", target=name + ) shutil.rmtree( (extensive_plugin_path / f"{name}").absolute(), ignore_errors=True ) extract_path.rename(extensive_plugin_path / f"{name}") - tmp = "" + prompt = "" if "pyproject.toml" in os.listdir(extensive_plugin_path / f"{name}"): - tmp = "检测到该插件含有额外依赖,当前安装无法保证依赖完全安装成功。" + prompt = "检测到该插件含有额外依赖,当前安装无法保证依赖完全安装成功。" os.system( f"poetry run pip install -r {(extensive_plugin_path / f'{name}' / 'pyproject.toml').absolute()}" ) elif "requirements.txt" in os.listdir(extensive_plugin_path / f"{name}"): - tmp = "检测到该插件含有额外依赖,当前安装无法保证依赖完全安装成功。" + prompt = "检测到该插件含有额外依赖,当前安装无法保证依赖完全安装成功。" os.system( f"poetry run pip install -r {(extensive_plugin_path / f'{name}' / 'requirements.txt').absolute()}" ) with open(extensive_plugin_path / f"{name}" / "plugin_info.json", "w") as f: json.dump(data[name], f, ensure_ascii=False, indent=4) - logger.debug("移动插件文件夹完成...") - logger.info(f"成功安装插件 {name} 成功!\n{tmp}") + logger.debug("移动插件文件夹完成...", "安装插件", target=name) + logger.info(f"成功安装插件 {name} 成功!\n{prompt}", "安装插件", target=name) return f"成功安装插件 {name},请重启真寻!" except Exception as e: - logger.error(f"安装插件 {name} 失败 {type(e)}:{e}") + logger.error(f"安装插失败", "安装插件", target=name, e=e) return f"安装插件 {name} 失败 {type(e)}:{e}" @@ -138,7 +140,7 @@ async def show_plugin_repo() -> Union[int, str]: version = f"[{plugins_data[key].version}]" s = ( f'id:{i+1}\n名称:{plugin_info[key]["plugin_name"]}' - f' \t\t{status}\n' + f" \t\t{status}\n" f"模块:{key}\n" f'作者:{plugin_info[key]["author"]}\n' f'版本:{plugin_info[key]["version"]} \t\t{version}\n' diff --git a/basic_plugins/scripts.py b/basic_plugins/scripts.py index 3e711d4b..1a16d7a1 100755 --- a/basic_plugins/scripts.py +++ b/basic_plugins/scripts.py @@ -1,23 +1,16 @@ import random - -from asyncpg.exceptions import ( - DuplicateColumnError, - UndefinedColumnError, - PostgresSyntaxError, -) -from nonebot import Driver -from services.db_context import db -from models.group_info import GroupInfo -from models.bag_user import BagUser -from nonebot.adapters.onebot.v11 import Bot -from services.log import logger -from configs.path_config import TEXT_PATH from asyncio.exceptions import TimeoutError -from typing import List -from utils.http_utils import AsyncHttpx -from utils.utils import GDict -from utils.utils import scheduler + import nonebot +from nonebot.adapters.onebot.v11 import Bot +from nonebot.drivers import Driver + +from configs.path_config import TEXT_PATH +from models.bag_user import BagUser +from models.group_info import GroupInfo +from services.log import logger +from utils.http_utils import AsyncHttpx +from utils.utils import GDict, scheduler try: import ujson as json @@ -38,6 +31,7 @@ async def update_city(): data = {} if not china_city.exists(): try: + logger.debug("开始更新城市列表...") res = await AsyncHttpx.get( "http://www.weather.com.cn/data/city3jdata/china.html", timeout=5 ) @@ -56,103 +50,97 @@ async def update_city(): with open(china_city, "w", encoding="utf8") as f: json.dump(data, f, indent=4, ensure_ascii=False) logger.info("自动更新城市列表完成.....") - except TimeoutError: - logger.warning("自动更新城市列表超时.....") - except ValueError: - logger.warning("自动城市列表失败.....") + except TimeoutError as e: + logger.warning("自动更新城市列表超时...", e=e) + except ValueError as e: + logger.warning("自动城市列表失败.....", e=e) except Exception as e: - logger.error(f"自动城市列表未知错误 {type(e)}:{e}") + logger.error(f"自动城市列表未知错误", e=e) -@driver.on_startup -async def _(): - """ - 数据库表结构变换 - """ - _flag = [] - sql_str = [ - ( - "ALTER TABLE group_info ADD group_flag Integer NOT NULL DEFAULT 0;", - "group_info", - ), # group_info表添加一个group_flag - ( - "ALTER TABLE bag_users rename belonging_group To group_id;", - "bag_users", - ), # 将 bag_users 的 belonging_group 改为 group_id - ( - "ALTER TABLE group_info_users rename belonging_group To group_id;", - "group_info_users", - ), - ( - "ALTER TABLE sign_group_users rename belonging_group To group_id;", - "sign_group_users", - ), - ( - "ALTER TABLE open_cases_users rename belonging_group To group_id;", - "open_cases_users", - ), - ( - "ALTER TABLE bag_users ADD property json NOT NULL DEFAULT '{}';", - "bag_users", - ), # bag_users 新增字段 property 替代 props - ( - "ALTER TABLE genshin ADD auto_sign_time timestamp with time zone;", - "genshin" - ), # 新增原神自动签到字段 - ( - "ALTER TABLE genshin ADD resin_remind boolean DEFAULT False;", - "genshin" - ), # 新增原神自动签到字段 - ( - "ALTER TABLE genshin ADD resin_recovery_time timestamp with time zone;", - "genshin" - ), # 新增原神自动签到字段 - ( - "ALTER TABLE genshin ADD bind_group Integer;", - "genshin" - ), # 新增原神群号绑定字段 - ( - "ALTER TABLE genshin ADD login_ticket VARCHAR(255) DEFAULT '';", - "genshin" - ), # 新增米游社login_ticket绑定字段 - ( - "ALTER TABLE genshin ADD stuid VARCHAR(255) DEFAULT '';", - "genshin" - ), # 新增米游社stuid绑定字段 - ( - "ALTER TABLE genshin ADD stoken VARCHAR(255) DEFAULT '';", - "genshin" - ), # 新增米游社stoken绑定字段 - ( - "ALTER TABLE chat_history ADD plain_text Text;", - "chat_history" - ), # 新增纯文本 - ( - "ALTER TABLE goods_info ADD daily_limit Integer DEFAULT 0;", - "goods_info" - ), # 新增纯文本 - ( - "ALTER TABLE goods_info ADD daily_purchase_limit Json DEFAULT '{}';", - "goods_info" - ), # 新增纯文本 - ] - for sql in sql_str + GDict.get('run_sql', []): - try: - if isinstance(sql, str): - flag = f'{random.randint(1, 10000)}' - else: - flag = sql[1] - sql = sql[0] - query = db.text(sql) - await db.first(query) - logger.info(f"完成sql操作:{sql}") - _flag.append(flag) - except (DuplicateColumnError, UndefinedColumnError): - pass - except PostgresSyntaxError: - logger.error(f"语法错误:执行sql失败:{sql}") - # bag_user 将文本转为字典格式 - await __database_script(_flag) +# @driver.on_startup +# async def _(): +# """ +# 数据库表结构变换 +# """ +# _flag = [] +# sql_str = [ +# ( +# "ALTER TABLE group_info ADD group_flag Integer NOT NULL DEFAULT 0;", +# "group_info", +# ), # group_info表添加一个group_flag +# ( +# "ALTER TABLE bag_users rename belonging_group To group_id;", +# "bag_users", +# ), # 将 bag_users 的 belonging_group 改为 group_id +# ( +# "ALTER TABLE group_info_users rename belonging_group To group_id;", +# "group_info_users", +# ), +# ( +# "ALTER TABLE sign_group_users rename belonging_group To group_id;", +# "sign_group_users", +# ), +# ( +# "ALTER TABLE open_cases_users rename belonging_group To group_id;", +# "open_cases_users", +# ), +# ( +# "ALTER TABLE bag_users ADD property json NOT NULL DEFAULT '{}';", +# "bag_users", +# ), # bag_users 新增字段 property 替代 props +# ( +# "ALTER TABLE genshin ADD auto_sign_time timestamp with time zone;", +# "genshin", +# ), # 新增原神自动签到字段 +# ( +# "ALTER TABLE genshin ADD resin_remind boolean DEFAULT False;", +# "genshin", +# ), # 新增原神自动签到字段 +# ( +# "ALTER TABLE genshin ADD resin_recovery_time timestamp with time zone;", +# "genshin", +# ), # 新增原神自动签到字段 +# ("ALTER TABLE genshin ADD bind_group Integer;", "genshin"), # 新增原神群号绑定字段 +# ( +# "ALTER TABLE genshin ADD login_ticket VARCHAR(255) DEFAULT '';", +# "genshin", +# ), # 新增米游社login_ticket绑定字段 +# ( +# "ALTER TABLE genshin ADD stuid VARCHAR(255) DEFAULT '';", +# "genshin", +# ), # 新增米游社stuid绑定字段 +# ( +# "ALTER TABLE genshin ADD stoken VARCHAR(255) DEFAULT '';", +# "genshin", +# ), # 新增米游社stoken绑定字段 +# ("ALTER TABLE chat_history ADD plain_text Text;", "chat_history"), # 新增纯文本 +# ( +# "ALTER TABLE goods_info ADD daily_limit Integer DEFAULT 0;", +# "goods_info", +# ), # 新增纯文本 +# ( +# "ALTER TABLE goods_info ADD daily_purchase_limit Json DEFAULT '{}';", +# "goods_info", +# ), # 新增纯文本 +# ] +# for sql in sql_str + GDict.get("run_sql", []): +# try: +# if isinstance(sql, str): +# flag = f"{random.randint(1, 10000)}" +# else: +# flag = sql[1] +# sql = sql[0] +# query = db.text(sql) +# await db.first(query) +# logger.info(f"完成sql操作:{sql}") +# _flag.append(flag) +# except (DuplicateColumnError, UndefinedColumnError): +# pass +# except PostgresSyntaxError: +# logger.error(f"语法错误:执行sql失败:{sql}") +# bag_user 将文本转为字典格式 +# await __database_script(_flag) @driver.on_bot_connect @@ -161,51 +149,57 @@ async def _(bot: Bot): 版本某些需要的变换 """ # 清空不存在的群聊信息,并将已所有已存在的群聊group_flag设置为1(认证所有已存在的群) - if not await GroupInfo.get_group_info(114514): + if not await GroupInfo.get_or_none(group_id=114514): # 标识符,该功能只需执行一次 - await GroupInfo.add_group_info(114514, "114514", 114514, 114514, 1) + await GroupInfo.create( + group_id=114514, + group_name="114514", + max_member_count=114514, + member_count=114514, + group_flag=1, + ) group_list = await bot.get_group_list() group_list = [g["group_id"] for g in group_list] - _gl = [x.group_id for x in await GroupInfo.get_all_group()] + _gl = [x.group_id for x in await GroupInfo.all()] if 114514 in _gl: _gl.remove(114514) for group_id in _gl: if group_id in group_list: - if await GroupInfo.get_group_info(group_id): - await GroupInfo.set_group_flag(group_id, 1) + if group := await GroupInfo.get_or_none(group_id=group_id): + await group.update_or_create(group_flag=1) else: group_info = await bot.get_group_info(group_id=group_id) - await GroupInfo.add_group_info( - group_info["group_id"], - group_info["group_name"], - group_info["max_member_count"], - group_info["member_count"], - 1, + await GroupInfo.create( + group_id=group_info["group_id"], + group_name=group_info["group_name"], + max_member_count=group_info["max_member_count"], + member_count=group_info["member_count"], + group_flag=1, ) - logger.info(f"已将群聊 {group_id} 添加认证...") + logger.info(f"已添加群认证...", group_id=group_id) else: - await GroupInfo.delete_group_info(group_id) - logger.info(f"移除不存在的群聊信息:{group_id}") + await GroupInfo.filter(group_id=group_id).delete() + logger.info(f"移除不存在的群聊信息", group_id=group_id) -async def __database_script(_flag: List[str]): - # bag_user 将文本转为字典格式 - if "bag_users" in _flag: - for x in await BagUser.get_all_users(): - props = {} - if x.props: - for prop in [p for p in x.props.split(",") if p]: - if props.get(prop): - props[prop] += 1 - else: - props[prop] = 1 - logger.info( - f"__database_script USER {x.user_qq} GROUP {x.group_id} 更新数据 {props}" - ) - await x.update( - property=props, - props="", - ).apply() +# async def __database_script(_flag: List[str]): +# # bag_user 将文本转为字典格式 +# if "bag_users" in _flag: +# for x in await BagUser.get_all_users(): +# props = {} +# if x.props: +# for prop in [p for p in x.props.split(",") if p]: +# if props.get(prop): +# props[prop] += 1 +# else: +# props[prop] = 1 +# logger.info( +# f"__database_script USER {x.user_qq} GROUP {x.group_id} 更新数据 {props}" +# ) +# await x.update( +# property=props, +# props="", +# ).apply() # 自动更新城市列表 diff --git a/basic_plugins/shop/__init__.py b/basic_plugins/shop/__init__.py index 501ebfa4..58dac39a 100644 --- a/basic_plugins/shop/__init__.py +++ b/basic_plugins/shop/__init__.py @@ -1,21 +1,40 @@ -from configs.config import Config -from nonebot import Driver -from utils.decorator.shop import shop_register +from pathlib import Path + import nonebot +from nonebot.drivers import Driver + +from configs.config import Config +from utils.decorator.shop import shop_register driver: Driver = nonebot.get_driver() Config.add_plugin_config( - "shop", - "IMPORT_DEFAULT_SHOP_GOODS", - True, - help_="导入商店自带的三个商品", - default_value=True + "shop", "IMPORT_DEFAULT_SHOP_GOODS", True, help_="导入商店自带的三个商品", default_value=True ) -nonebot.load_plugins("basic_plugins/shop") +nonebot.load_plugins(str(Path(__file__).parent.resolve())) + + +@shop_register( + name=("好感度双倍加持卡Ⅰ", "好感度双倍加持卡Ⅱ", "好感度双倍加持卡Ⅲ"), + price=(30, 150, 250), + des=( + "下次签到双倍好感度概率 + 10%(谁才是真命天子?)(同类商品将覆盖)", + "下次签到双倍好感度概率 + 20%(平平庸庸)(同类商品将覆盖)", + "下次签到双倍好感度概率 + 30%(金币才是真命天子!)(同类商品将覆盖)", + ), + load_status=bool(Config.get_config("shop", "IMPORT_DEFAULT_SHOP_GOODS")), + icon=( + "favorability_card_1.png", + "favorability_card_2.png", + "favorability_card_3.png", + ), + **{"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3}, +) +async def sign_card(user_id: int, group_id: int): + pass @driver.on_bot_connect diff --git a/basic_plugins/shop/buy.py b/basic_plugins/shop/buy.py index 99592d1c..a89031f8 100644 --- a/basic_plugins/shop/buy.py +++ b/basic_plugins/shop/buy.py @@ -1,16 +1,15 @@ -from nonebot import on_command - -from models.user_shop_gold_log import UserShopGoldLog -from services.log import logger -from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message -from nonebot.params import CommandArg -from utils.utils import is_number -from models.bag_user import BagUser -from services.db_context import db -from nonebot.adapters.onebot.v11.permission import GROUP -from models.goods_info import GoodsInfo import time +from nonebot import on_command +from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message +from nonebot.adapters.onebot.v11.permission import GROUP +from nonebot.params import CommandArg + +from models.bag_user import BagUser +from models.goods_info import GoodsInfo +from models.user_shop_gold_log import UserShopGoldLog +from services.log import logger +from utils.utils import is_number __zx_plugin_name__ = "商店 - 购买道具" __plugin_usage__ = """ @@ -71,39 +70,40 @@ async def _(event: GroupMessageEvent, arg: Message = CommandArg()): await buy.finish("请输入正确的商品名称!") else: await buy.finish("请输入正确的商品名称!", at_sender=True) - async with db.transaction(): - if ( - await BagUser.get_gold(event.user_id, event.group_id) - ) < goods.goods_price * num * goods.goods_discount: - await buy.finish("您的金币好像不太够哦", at_sender=True) - flag, n = await GoodsInfo.check_user_daily_purchase( - goods, event.user_id, event.group_id, num - ) - if flag: - await buy.finish(f"该次购买将超过每日次数限制,目前该道具还可以购买{n}次哦", at_sender=True) - if await BagUser.buy_property(event.user_id, event.group_id, goods, num): - await GoodsInfo.add_user_daily_purchase( - goods, event.user_id, event.group_id, num - ) - await buy.send( - f"花费 {goods.goods_price * num * goods.goods_discount} 金币购买 {goods.goods_name} ×{num} 成功!", - at_sender=True, - ) - logger.info( - f"USER {event.user_id} GROUP {event.group_id} " - f"花费 {goods.goods_price*num} 金币购买 {goods.goods_name} ×{num} 成功!" - ) - await UserShopGoldLog.add_shop_log( - event.user_id, - event.group_id, - 0, - goods.goods_name, - num, - goods.goods_price * num * goods.goods_discount, - ) - else: - await buy.send(f"{goods.goods_name} 购买失败!", at_sender=True) - logger.info( - f"USER {event.user_id} GROUP {event.group_id} " - f"花费 {goods.goods_price * num * goods.goods_discount} 金币购买 {goods.goods_name} ×{num} 失败!" - ) + if ( + await BagUser.get_gold(event.user_id, event.group_id) + ) < goods.goods_price * num * goods.goods_discount: + await buy.finish("您的金币好像不太够哦", at_sender=True) + flag, n = await GoodsInfo.check_user_daily_purchase( + goods, event.user_id, event.group_id, num + ) + if flag: + await buy.finish(f"该次购买将超过每日次数限制,目前该道具还可以购买{n}次哦", at_sender=True) + spend_gold = int(goods.goods_discount * goods.goods_price * num) + await BagUser.spend_gold(event.user_id, event.group_id, spend_gold) + await BagUser.add_property(event.user_id, event.group_id, goods.goods_name, num) + await GoodsInfo.add_user_daily_purchase(goods, event.user_id, event.group_id, num) + await buy.send( + f"花费 {goods.goods_price * num * goods.goods_discount} 金币购买 {goods.goods_name} ×{num} 成功!", + at_sender=True, + ) + logger.info( + f"花费 {goods.goods_price*num} 金币购买 {goods.goods_name} ×{num} 成功!", + "购买道具", + event.user_id, + event.group_id, + ) + await UserShopGoldLog.create( + user_qq=event.user_id, + group_id=event.group_id, + type=0, + name=goods.goods_name, + num=num, + spend_gold=goods.goods_price * num * goods.goods_discount, + ) + # else: + # await buy.send(f"{goods.goods_name} 购买失败!", at_sender=True) + # logger.info( + # f"USER {event.user_id} GROUP {event.group_id} " + # f"花费 {goods.goods_price * num * goods.goods_discount} 金币购买 {goods.goods_name} ×{num} 失败!" + # ) diff --git a/basic_plugins/shop/gold.py b/basic_plugins/shop/gold.py index e3635338..e761361c 100644 --- a/basic_plugins/shop/gold.py +++ b/basic_plugins/shop/gold.py @@ -1,9 +1,10 @@ from nonebot import on_command -from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, ActionFailed -from nonebot.params import CommandArg +from nonebot.adapters.onebot.v11 import ActionFailed, GroupMessageEvent, Message from nonebot.adapters.onebot.v11.permission import GROUP -from utils.data_utils import init_rank +from nonebot.params import CommandArg + from models.bag_user import BagUser +from utils.data_utils import init_rank from utils.image_utils import text2image from utils.message_builder import image from utils.utils import is_number @@ -39,7 +40,9 @@ async def _(event: GroupMessageEvent): try: await my_gold.send(msg) except ActionFailed: - await my_gold.send(image(b64=(await text2image(msg, color="#f9f6f2", padding=10)).pic2bs4())) + await my_gold.send( + image(b64=(await text2image(msg, color="#f9f6f2", padding=10)).pic2bs4()) + ) @gold_rank.handle() @@ -49,9 +52,11 @@ async def _(event: GroupMessageEvent, arg: Message = CommandArg()): num = int(num) else: num = 10 - all_users = await BagUser.get_all_users(event.group_id) + all_users = await BagUser.filter(group_id=event.group_id) all_user_id = [user.user_qq for user in all_users] all_user_data = [user.gold for user in all_users] - rank_image = await init_rank("金币排行", all_user_id, all_user_data, event.group_id, num) + rank_image = await init_rank( + "金币排行", all_user_id, all_user_data, event.group_id, num + ) if rank_image: await gold_rank.finish(image(b64=rank_image.pic2bs4())) diff --git a/basic_plugins/shop/my_props/__init__.py b/basic_plugins/shop/my_props/__init__.py index 4f5e9827..fc3bd6cf 100644 --- a/basic_plugins/shop/my_props/__init__.py +++ b/basic_plugins/shop/my_props/__init__.py @@ -1,12 +1,12 @@ from nonebot import on_command - -from utils.message_builder import image -from ._data_source import create_bag_image -from services.log import logger from nonebot.adapters.onebot.v11 import GroupMessageEvent -from models.bag_user import BagUser from nonebot.adapters.onebot.v11.permission import GROUP +from models.bag_user import BagUser +from services.log import logger +from utils.message_builder import image + +from ._data_source import create_bag_image __zx_plugin_name__ = "商店 - 我的道具" __plugin_usage__ = """ @@ -17,7 +17,7 @@ usage: """.strip() __plugin_des__ = "商店 - 我的道具" __plugin_cmd__ = ["我的道具"] -__plugin_type__ = ('商店',) +__plugin_type__ = ("商店",) __plugin_version__ = 0.1 __plugin_author__ = "HibiKier" __plugin_settings__ = { @@ -36,10 +36,6 @@ async def _(event: GroupMessageEvent): props = await BagUser.get_property(event.user_id, event.group_id) if props: await my_props.send(image(b64=await create_bag_image(props))) - # rst = "" - # for i, p in enumerate(props.keys()): - # rst += f"{i+1}.{p}\t×{props[p]}\n" - # await my_props.send("\n" + rst[:-1], at_sender=True) logger.info(f"USER {event.user_id} GROUP {event.group_id} 查看我的道具") else: await my_props.finish("您的背包里没有任何的道具噢~", at_sender=True) diff --git a/basic_plugins/shop/reset_today_gold.py b/basic_plugins/shop/reset_today_gold.py deleted file mode 100644 index 7097f043..00000000 --- a/basic_plugins/shop/reset_today_gold.py +++ /dev/null @@ -1,26 +0,0 @@ -from utils.utils import scheduler -from models.bag_user import BagUser -from services.log import logger - - -__zx_plugin_name__ = "每日金币重置 [Hidden]" -__plugin_version__ = 0.1 -__plugin_author__ = "HibiKier" - - -# 重置每日金币 -@scheduler.scheduled_job( - "cron", - hour=0, - minute=1, -) -async def _(): - try: - user_list = await BagUser.get_all_users() - for user in user_list: - await user.update( - get_today_gold=0, - spend_today_gold=0, - ).apply() - except Exception as e: - logger.error(f"重置每日金币错误 e:{e}") diff --git a/basic_plugins/shop/shop_handle/__init__.py b/basic_plugins/shop/shop_handle/__init__.py index 6323f8af..9748330a 100644 --- a/basic_plugins/shop/shop_handle/__init__.py +++ b/basic_plugins/shop/shop_handle/__init__.py @@ -1,14 +1,24 @@ -from .data_source import create_shop_help, delete_goods, update_goods, register_goods, parse_goods_info, GoodsInfo -from nonebot.adapters.onebot.v11 import MessageEvent, Message -from nonebot import on_command -from configs.path_config import IMAGE_PATH -from utils.message_builder import image -from nonebot.permission import SUPERUSER -from utils.utils import is_number, scheduler -from nonebot.params import CommandArg -from services.log import logger import os +from nonebot import on_command +from nonebot.adapters.onebot.v11 import Message, MessageEvent +from nonebot.params import CommandArg +from nonebot.permission import SUPERUSER + +from configs.path_config import IMAGE_PATH +from models.bag_user import BagUser +from services.log import logger +from utils.message_builder import image +from utils.utils import is_number, scheduler + +from .data_source import ( + GoodsInfo, + create_shop_help, + delete_goods, + parse_goods_info, + register_goods, + update_goods, +) __zx_plugin_name__ = "商店" __plugin_usage__ = """ @@ -37,7 +47,7 @@ __plugin_cmd__ = [ "删除商品 [名称或序号] [_superuser]", "修改商品 name:[名称或序号] price:[价格] des:[描述] discount:[折扣] limit_time:[限时] [_superuser]", ] -__plugin_type__ = ('商店',) +__plugin_type__ = ("商店",) __plugin_version__ = 0.1 __plugin_author__ = "HibiKier" __plugin_settings__ = { @@ -46,9 +56,7 @@ __plugin_settings__ = { "limit_superuser": False, "cmd": ["商店"], } -__plugin_block_limit__ = { - "limit_type": "group" -} +__plugin_block_limit__ = {"limit_type": "group"} shop_help = on_command("商店", priority=5, block=True) @@ -75,12 +83,15 @@ async def _(event: MessageEvent, arg: Message = CommandArg()): if not data.get("name") or not data.get("price") or not data.get("des"): await shop_add_goods.finish("name:price:des 参数不可缺少!") if await register_goods(**data): - await shop_add_goods.send(f"添加商品 {data['name']} 成功!\n" - f"名称:{data['name']}\n" - f"价格:{data['price']}金币\n" - f"简介:{data['des']}\n" - f"折扣:{data.get('discount')}\n" - f"限时:{data.get('limit_time')}", at_sender=True) + await shop_add_goods.send( + f"添加商品 {data['name']} 成功!\n" + f"名称:{data['name']}\n" + f"价格:{data['price']}金币\n" + f"简介:{data['des']}\n" + f"折扣:{data.get('discount')}\n" + f"限时:{data.get('limit_time')}", + at_sender=True, + ) logger.info(f"USER {event.user_id} 添加商品 {msg} 成功") else: await shop_add_goods.send(f"添加商品 {msg} 失败了...", at_sender=True) @@ -133,7 +144,19 @@ async def _(event: MessageEvent, arg: Message = CommandArg()): ) async def _(): try: - await GoodsInfo.reset_daily_purchase() + await GoodsInfo.all().update(daily_purchase_limit={}) logger.info("商品每日限购次数重置成功...") except Exception as e: - logger.error(f"商品每日限购次数重置发生错误 {type(e)}:{e}") + logger.error(f"商品每日限购次数重置出错 {type(e)}:{e}") + + +@scheduler.scheduled_job( + "cron", + hour=0, + minute=1, +) +async def _(): + try: + await BagUser.all().update(get_today_gold=0, spend_today_gold=0) + except Exception as e: + logger.error(f"重置每日金币", "定时任务", e=e) diff --git a/basic_plugins/shop/shop_handle/data_source.py b/basic_plugins/shop/shop_handle/data_source.py index a1e9dd27..a4b90e23 100644 --- a/basic_plugins/shop/shop_handle/data_source.py +++ b/basic_plugins/shop/shop_handle/data_source.py @@ -1,18 +1,18 @@ +import time +from typing import Optional, Tuple, Union + from PIL import Image +from configs.path_config import IMAGE_PATH from models.goods_info import GoodsInfo from utils.image_utils import BuildImage, text2image -from utils.utils import is_number -from configs.path_config import IMAGE_PATH -from typing import Optional, Union, Tuple -from utils.utils import GDict -import time +from utils.utils import GDict, is_number -icon_path = IMAGE_PATH / 'shop_icon' +icon_path = IMAGE_PATH / "shop_icon" -GDict['run_sql'].append("ALTER TABLE goods_info ADD is_passive boolean DEFAULT False;") -GDict['run_sql'].append("ALTER TABLE goods_info ADD icon VARCHAR(255);") +GDict["run_sql"].append("ALTER TABLE goods_info ADD is_passive boolean DEFAULT False;") +GDict["run_sql"].append("ALTER TABLE goods_info ADD icon VARCHAR(255);") # 创建商店界面 @@ -43,17 +43,31 @@ async def create_shop_help() -> str: await name_image.atext((390, 0), "售价:", center_type="by_height") if goods.goods_discount != 1: discount_price = int(goods.goods_discount * goods.goods_price) - old_price_image = BuildImage(0, 0, plain_text=str(goods.goods_price), font_color=(194, 194, 194), font="CJGaoDeGuo.otf", font_size=15) - await old_price_image.aline((0, int(old_price_image.h / 2), old_price_image.w + 1, int(old_price_image.h / 2)), (0, 0, 0)) - await name_image.apaste( - old_price_image, (440, 0), True + old_price_image = BuildImage( + 0, + 0, + plain_text=str(goods.goods_price), + font_color=(194, 194, 194), + font="CJGaoDeGuo.otf", + font_size=15, ) - await name_image.atext( - (440, 15), str(discount_price), (255, 255, 255) + await old_price_image.aline( + ( + 0, + int(old_price_image.h / 2), + old_price_image.w + 1, + int(old_price_image.h / 2), + ), + (0, 0, 0), ) + await name_image.apaste(old_price_image, (440, 0), True) + await name_image.atext((440, 15), str(discount_price), (255, 255, 255)) else: await name_image.atext( - (440, 0), str(goods.goods_price), (255, 255, 255), center_type="by_height" + (440, 0), + str(goods.goods_price), + (255, 255, 255), + center_type="by_height", ) await name_image.atext( ( @@ -68,38 +82,48 @@ async def create_shop_help() -> str: font_img = BuildImage( 600, 80, font_size=20, color="#a29ad6", font="CJGaoDeGuo.otf" ) - p = font_img.getsize('简介:')[0] + 20 + p = font_img.getsize("简介:")[0] + 20 if goods.goods_description: - des_list = goods.goods_description.split('\n') - desc = '' + des_list = goods.goods_description.split("\n") + desc = "" for des in des_list: if font_img.getsize(des)[0] > font_img.w - p - 20: - msg = '' - tmp = '' + msg = "" + tmp = "" for i in range(len(des)): if font_img.getsize(tmp)[0] < font_img.w - p - 20: tmp += des[i] else: - msg += tmp + '\n' + msg += tmp + "\n" tmp = des[i] desc += msg if tmp: desc += tmp else: - desc += des + '\n' - if desc[-1] == '\n': + desc += des + "\n" + if desc[-1] == "\n": desc = desc[:-1] des_image = await text2image(desc, color="#a29ad6") goods_image = BuildImage( - 600, (50 + des_image.h) if des_image else 50, font_size=20, color="#a29ad6", font="CJGaoDeGuo.otf" + 600, + (50 + des_image.h) if des_image else 50, + font_size=20, + color="#a29ad6", + font="CJGaoDeGuo.otf", ) if des_image: - await goods_image.atext((15, 50), '简介:') + await goods_image.atext((15, 50), "简介:") await goods_image.apaste(des_image, (p, 50)) await name_image.acircle_corner(5) await goods_image.apaste(name_image, (0, 5), True, center_type="by_width") await goods_image.acircle_corner(20) - bk = BuildImage(1180, (50 + des_image.h) if des_image else 50, font_size=15, color="#f9f6f2", font="CJGaoDeGuo.otf") + bk = BuildImage( + 1180, + (50 + des_image.h) if des_image else 50, + font_size=15, + color="#f9f6f2", + font="CJGaoDeGuo.otf", + ) if goods.icon and (icon_path / goods.icon).exists(): icon = BuildImage(70, 70, background=icon_path / goods.icon) await bk.apaste(icon) @@ -129,7 +153,9 @@ async def create_shop_help() -> str: _w += 140 if goods.goods_discount != 1: n += 140 - _discount_logo = BuildImage(30, 30, background=f"{IMAGE_PATH}/other/discount.png") + _discount_logo = BuildImage( + 30, 30, background=f"{IMAGE_PATH}/other/discount.png" + ) await bk.apaste(_discount_logo, (_w + 50, 10), True) await bk.apaste( BuildImage(0, 0, plain_text="折扣!", font_size=23, font="CJGaoDeGuo.otf"), @@ -137,14 +163,23 @@ async def create_shop_help() -> str: True, ) await bk.apaste( - BuildImage(0, 0, plain_text=f"{10 * goods.goods_discount:.1f} 折", font_size=30, font="CJGaoDeGuo.otf", font_color=(85, 156, 75)), + BuildImage( + 0, + 0, + plain_text=f"{10 * goods.goods_discount:.1f} 折", + font_size=30, + font="CJGaoDeGuo.otf", + font_color=(85, 156, 75), + ), (_w + 50, 44), True, ) _w += 140 if goods.daily_limit != 0: n += 140 - _daily_limit_logo = BuildImage(35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png") + _daily_limit_logo = BuildImage( + 35, 35, background=f"{IMAGE_PATH}/other/daily_limit.png" + ) await bk.apaste(_daily_limit_logo, (_w + 50, 10), True) await bk.apaste( BuildImage(0, 0, plain_text="限购!", font_size=23, font="CJGaoDeGuo.otf"), @@ -152,7 +187,13 @@ async def create_shop_help() -> str: True, ) await bk.apaste( - BuildImage(0, 0, plain_text=f"{goods.daily_limit}", font_size=30, font="CJGaoDeGuo.otf"), + BuildImage( + 0, + 0, + plain_text=f"{goods.daily_limit}", + font_size=30, + font="CJGaoDeGuo.otf", + ), (_w + 72, 45), True, ) @@ -220,22 +261,30 @@ async def register_goods( :param icon: 图标 :return: 是否添加成功 """ - if not await GoodsInfo.get_goods_info(name): - limit_time = float(limit_time) if limit_time else limit_time + if not await GoodsInfo.get_or_none(goods_name=name): + limit_time_ = float(limit_time) if limit_time else limit_time discount = discount if discount is not None else 1 - limit_time = ( - int(time.time() + limit_time * 60 * 60) - if limit_time is not None and limit_time != 0 + limit_time_ = ( + int(time.time() + limit_time_ * 60 * 60) + if limit_time_ is not None and limit_time_ != 0 else 0 ) - return await GoodsInfo.add_goods( - name, int(price), des, float(discount), limit_time, daily_limit, is_passive, icon + await GoodsInfo.create( + goods_name=name, + goods_price=int(price), + goods_description=des, + goods_discount=float(discount), + goods_limit_time=limit_time_, + daily_limit=daily_limit, + is_passive=is_passive, + icon=icon, ) + return True return False # 删除商品 -async def delete_goods(name: str, id_: int) -> "str, str, int": +async def delete_goods(name: str, id_: int) -> Tuple[str, str, int]: """ 删除商品 :param name: 商品名称 @@ -256,6 +305,7 @@ async def delete_goods(name: str, id_: int) -> "str, str, int": return f"删除商品 {name} 成功!", name, 200 else: return f"删除商品 {name} 失败!", name, 999 + return "获取商品失败", "", 999 # 更新商品信息 @@ -272,7 +322,7 @@ async def update_goods(**kwargs) -> Tuple[bool, str, str]: return False, "序号错误,没有该序号的商品...", "" goods = goods_lst[int(kwargs["name"]) - 1] else: - goods = await GoodsInfo.get_goods_info(kwargs["name"]) + goods = await GoodsInfo.filter(goods_name=kwargs["name"]).first() if not goods: return False, "名称错误,没有该名称的商品...", "" name: str = goods.goods_name @@ -295,14 +345,22 @@ async def update_goods(**kwargs) -> Tuple[bool, str, str]: discount = kwargs["discount"] if kwargs.get("limit_time"): kwargs["limit_time"] = float(kwargs["limit_time"]) - new_time = time.strftime( - "%Y-%m-%d %H:%M:%S", - time.localtime(time.time() + kwargs["limit_time"] * 60 * 60), - ) if kwargs["limit_time"] != 0 else 0 + new_time = ( + time.strftime( + "%Y-%m-%d %H:%M:%S", + time.localtime(time.time() + kwargs["limit_time"] * 60 * 60), + ) + if kwargs["limit_time"] != 0 + else 0 + ) tmp += f"限时至: {new_time}\n" if new_time else "取消了限时\n" limit_time = kwargs["limit_time"] if kwargs.get("daily_limit"): - tmp += f'每日购买限制:{daily_limit} --> {kwargs["daily_limit"]}\n' if daily_limit else "取消了购买限制\n" + tmp += ( + f'每日购买限制:{daily_limit} --> {kwargs["daily_limit"]}\n' + if daily_limit + else "取消了购买限制\n" + ) daily_limit = int(kwargs["daily_limit"]) if kwargs.get("is_passive"): tmp += f'被动道具:{is_passive} --> {kwargs["is_passive"]}\n' @@ -318,9 +376,13 @@ async def update_goods(**kwargs) -> Tuple[bool, str, str]: else 0 ), daily_limit, - is_passive + is_passive, + ) + return ( + True, + name, + tmp[:-1], ) - return True, name, tmp[:-1], def parse_goods_info(msg: str) -> Union[dict, str]: diff --git a/basic_plugins/shop/use/__init__.py b/basic_plugins/shop/use/__init__.py index 1c3ffca0..57cc5c00 100644 --- a/basic_plugins/shop/use/__init__.py +++ b/basic_plugins/shop/use/__init__.py @@ -1,19 +1,17 @@ from typing import Any, Tuple from nonebot import on_command, on_regex - -from models.user_shop_gold_log import UserShopGoldLog -from services.log import logger from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message +from nonebot.adapters.onebot.v11.permission import GROUP from nonebot.params import CommandArg, RegexGroup +from models.bag_user import BagUser +from models.user_shop_gold_log import UserShopGoldLog +from services.log import logger from utils.decorator.shop import NotMeetUseConditionsException from utils.utils import is_number -from models.bag_user import BagUser -from nonebot.adapters.onebot.v11.permission import GROUP -from services.db_context import db -from .data_source import effect, register_use, func_manager, build_params +from .data_source import build_params, effect, func_manager, register_use __zx_plugin_name__ = "商店 - 使用道具" __plugin_usage__ = """ @@ -75,17 +73,25 @@ async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()): if prop_n not in property_.keys(): await use_props.finish("道具名称错误!", at_sender=True) name = prop_n + if not name: + await use_props.finish("未获取到道具名称", at_sender=True) _user_prop_count = property_[name] if num > _user_prop_count: await use_props.finish(f"道具数量不足,无法使用{num}次!") if num > (n := func_manager.get_max_num_limit(name)): await use_props.finish(f"该道具单次只能使用 {n} 个!") - model, kwargs = build_params(bot, event, name, num, text) try: - await func_manager.run_handle(type_="before_handle", param=model, **kwargs) - except NotMeetUseConditionsException as e: - await use_props.finish(e.get_info(), at_sender=True) - async with db.transaction(): + model, kwargs = build_params(bot, event, name, num, text) + except KeyError: + logger.warning(f"{name} 未注册使用函数") + await use_props.finish(f"{name} 未注册使用方法") + else: + try: + await func_manager.run_handle( + type_="before_handle", param=model, **kwargs + ) + except NotMeetUseConditionsException as e: + await use_props.finish(e.get_info(), at_sender=True) if await BagUser.delete_property(event.user_id, event.group_id, name, num): if func_manager.check_send_success_message(name): await use_props.send(f"使用道具 {name} {num} 次成功!", at_sender=True) @@ -94,14 +100,18 @@ async def _(bot: Bot, event: GroupMessageEvent, arg: Message = CommandArg()): logger.info( f"USER {event.user_id} GROUP {event.group_id} 使用道具 {name} {num} 次成功" ) - await UserShopGoldLog.add_shop_log( - event.user_id, event.group_id, 1, name, num + await UserShopGoldLog.create( + user_qq=event.user_id, + group_id=event.group_id, + type=1, + name=name, + num=num, ) else: await use_props.send(f"使用道具 {name} {num} 次失败!", at_sender=True) logger.info( f"USER {event.user_id} GROUP {event.group_id} 使用道具 {name} {num} 次失败" ) - await func_manager.run_handle(type_="after_handle", param=model, **kwargs) + await func_manager.run_handle(type_="after_handle", param=model, **kwargs) else: await use_props.send("您的背包里没有任何的道具噢", at_sender=True) diff --git a/basic_plugins/shop/use/data_source.py b/basic_plugins/shop/use/data_source.py index 7b8dafbf..b52d2c52 100644 --- a/basic_plugins/shop/use/data_source.py +++ b/basic_plugins/shop/use/data_source.py @@ -108,7 +108,7 @@ class GoodsUseFuncManager: ) async def run_handle(self, goods_name: str, type_: str, param: ShopParam, **kwargs): - if self._data[goods_name].get(type_): + if self._data.get(goods_name) and self._data[goods_name].get(type_): for func in self._data[goods_name].get(type_): args = inspect.signature(func).parameters if args and list(args.keys())[0] != "kwargs": @@ -170,7 +170,6 @@ def build_params( :param goods_name: 商品名称 :param num: 数量 :param text: 其他信息 - :return: """ _kwargs = func_manager.get_kwargs(goods_name) return ( diff --git a/basic_plugins/super_cmd/__init__.py b/basic_plugins/super_cmd/__init__.py index 2d381421..87ae4077 100755 --- a/basic_plugins/super_cmd/__init__.py +++ b/basic_plugins/super_cmd/__init__.py @@ -1,11 +1,5 @@ -import nonebot from pathlib import Path +import nonebot nonebot.load_plugins(str(Path(__file__).parent.resolve())) - - - - - - diff --git a/basic_plugins/super_cmd/bot_friend_group.py b/basic_plugins/super_cmd/bot_friend_group.py index ab313a97..1470a9d8 100755 --- a/basic_plugins/super_cmd/bot_friend_group.py +++ b/basic_plugins/super_cmd/bot_friend_group.py @@ -1,14 +1,17 @@ -from nonebot import on_command -from nonebot.permission import SUPERUSER -from nonebot.adapters.onebot.v11 import Bot, Message -from nonebot.params import Command, CommandArg from typing import Tuple + +from nonebot import on_command +from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent +from nonebot.params import Command, CommandArg +from nonebot.permission import SUPERUSER from nonebot.rule import to_me -from utils.utils import is_number + +from models.group_info import GroupInfo +from services.log import logger +from utils.depends import OneCommand from utils.manager import requests_manager from utils.message_builder import image -from models.group_info import GroupInfo - +from utils.utils import is_number __zx_plugin_name__ = "显示所有好友群组 [Superuser]" __plugin_usage__ = """ @@ -76,8 +79,7 @@ async def _(bot: Bot): @friend_handle.handle() -async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()): - cmd = cmd[0] +async def _(bot: Bot, cmd: str = OneCommand(), arg: Message = CommandArg()): id_ = arg.extract_plain_text().strip() if is_number(id_): id_ = int(id_) @@ -97,8 +99,9 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr @group_handle.handle() -async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()): - cmd = cmd[0] +async def _( + bot: Bot, event: MessageEvent, cmd: str = OneCommand(), arg: Message = CommandArg() +): id_ = arg.extract_plain_text().strip() flag = None if is_number(id_): @@ -106,20 +109,21 @@ async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandAr if cmd[:2] == "同意": rid = requests_manager.get_group_id(id_) if rid: - if await GroupInfo.get_group_info(rid): - await GroupInfo.set_group_flag(rid, 1) + if group := await GroupInfo.filter(group_id=rid).first(): + await group.update_or_create(group_flag=1) else: group_info = await bot.get_group_info(group_id=rid) - await GroupInfo.add_group_info( - rid, - group_info["group_name"], - group_info["max_member_count"], - group_info["member_count"], - 1 + await GroupInfo.create( + group_id=rid, + group_name=group_info["group_name"], + max_member_count=group_info["max_member_count"], + member_count=group_info["member_count"], + group_flag=1, ) flag = await requests_manager.approve(bot, id_, "group") else: await group_handle.send("同意群聊请求失败,未找到此id的请求..") + logger.info("同意群聊请求失败,未找到此id的请求..", cmd, event.user_id) else: flag = await requests_manager.refused(bot, id_, "group") if flag == 1: @@ -139,15 +143,15 @@ async def _(): for type_ in ["private", "group"]: msg = await requests_manager.show(type_) if msg: - _str += image(b64=msg) + _str += image(msg) else: _str += "没有任何好友请求.." if type_ == "private" else "没有任何群聊请求.." if type_ == "private": - _str += '\n--------------------\n' + _str += "\n--------------------\n" await cls_request.send(Message(_str)) @clear_request.handle() async def _(): requests_manager.clear() - await clear_request.send("已清空所有好友/群聊请求..") \ No newline at end of file + await clear_request.send("已清空所有好友/群聊请求..") diff --git a/basic_plugins/super_cmd/clear_data.py b/basic_plugins/super_cmd/clear_data.py index 8fcd7bfc..effca772 100755 --- a/basic_plugins/super_cmd/clear_data.py +++ b/basic_plugins/super_cmd/clear_data.py @@ -1,13 +1,15 @@ +import asyncio +import os +import time + from nonebot import on_command from nonebot.permission import SUPERUSER -from configs.path_config import TEMP_PATH from nonebot.rule import to_me -from utils.utils import scheduler + +from configs.path_config import TEMP_PATH from services.log import logger from utils.manager import resources_manager -import asyncio -import time -import os +from utils.utils import scheduler __zx_plugin_name__ = "清理临时数据 [Superuser]" __plugin_usage__ = """ @@ -37,22 +39,29 @@ async def _(): await clear_data.send("开始清理临时数据....") size = await asyncio.get_event_loop().run_in_executor(None, _clear_data) await clear_data.send("共清理了 {:.2f}MB 的数据...".format(size / 1024 / 1024)) + logger.info("清理临时数据完成," + "共清理了 {:.2f}MB 的数据...".format(size / 1024 / 1024)) def _clear_data() -> float: + logger.debug("开始清理临时文件...") size = 0 - for dir_ in resources_manager.get_temp_data_dir(): - if dir_.exists(): - for file in os.listdir(dir_): - file = dir_ / file - if file.is_file(): - try: - if time.time() - os.path.getatime(file) > 300: - file_size = os.path.getsize(file) - file.unlink() - size += file_size - except Exception as e: - logger.error(f"清理临时数据错误...{type(e)}:{e}") + dir_list = [dir_ for dir_ in resources_manager.get_temp_data_dir() if dir_.exists()] + for dir_ in dir_list: + logger.debug(f"尝试清理文件夹: {dir_.absolute()}", "清理临时数据") + dir_size = 0 + for file in os.listdir(dir_): + file = dir_ / file + if file.is_file(): + try: + if time.time() - os.path.getatime(file) > 10: + file_size = os.path.getsize(file) + file.unlink() + size += file_size + dir_size += file_size + logger.debug(f"移除临时文件: {file.absolute()}", "清理临时数据") + except Exception as e: + logger.error(f"清理临时数据错误,临时文件夹: {dir_.absolute()}...", "清理临时数据", e=e) + logger.debug("清理临时文件夹大小: {:.2f}MB".format(size / 1024 / 1024), "清理临时数据") return float(size) diff --git a/basic_plugins/super_cmd/exec_sql.py b/basic_plugins/super_cmd/exec_sql.py index 2914046a..26763de9 100644 --- a/basic_plugins/super_cmd/exec_sql.py +++ b/basic_plugins/super_cmd/exec_sql.py @@ -1,13 +1,15 @@ -from nonebot.adapters.onebot.v11 import Message, Bot, MessageEvent +import asyncio + from nonebot import on_command +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent +from nonebot.params import CommandArg from nonebot.permission import SUPERUSER from nonebot.rule import to_me -from utils.message_builder import custom_forward_msg -from services.db_context import db -from nonebot.params import CommandArg -from services.log import logger +from tortoise import Tortoise -import asyncio +from services.db_context import TestSQL +from services.log import logger +from utils.message_builder import custom_forward_msg __zx_plugin_name__ = "执行sql [Superuser]" __plugin_usage__ = """ @@ -18,9 +20,7 @@ usage: 查看所有表 """.strip() __plugin_des__ = "执行一段sql语句" -__plugin_cmd__ = [ - "exec [sql语句]", -] +__plugin_cmd__ = ["exec [sql语句]", "查看所有表"] __plugin_version__ = 0.2 __plugin_author__ = "HibiKier" @@ -32,55 +32,61 @@ tables = on_command("查看所有表", rule=to_me(), permission=SUPERUSER, prior @exec_.handle() async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): sql = arg.extract_plain_text().strip() - async with db.transaction(): + if not sql: + await exec_.finish("未接受到sql语句") + db = Tortoise.get_connection("default") + # try: + # 判断是否为SELECT语句 + if sql.lower().startswith("select"): + pass + # # 分割语句 try: - # 判断是否为SELECT语句 - if sql.lower().startswith("select"): - # 分割语句 - try: - page = int(sql.split(" ")[-1]) - 1 - sql_list = sql.split(" ")[:-1] - except ValueError: - page = 0 - sql_list = sql.split(" ") - # 拼接语句 - sql = " ".join(sql_list) - query = db.text(sql) - res = await db.all(query) - msg_list = [f"第{page+1}页查询结果:"] - # logger.info(res) - # 获取所有字段 - keys = res[0].keys() - # 每页10条 - for i in res[page * 10 : (page + 1) * 10]: - msg = "" - for key in keys: - msg += f"{key}: {i[key]}\n" - msg += f"第{page+1}页第{res.index(i)+1}条" - msg_list.append(msg) - # 检查是私聊还是群聊 - try: - forward_msg_list = custom_forward_msg(msg_list, bot.self_id) - await bot.send_group_forward_msg(group_id=event.group_id, messages=forward_msg_list) - except: - for msg in msg_list: - await exec_.send(msg) - await asyncio.sleep(0.2) - return - query = db.text(sql) - await db.first(query) - await exec_.send("执行 sql 语句成功.") - except Exception as e: - await exec_.send(f"执行 sql 语句失败 {type(e)}:{e}") - logger.error(f"执行 sql 语句失败 {type(e)}:{e}") + page = int(sql.split(" ")[-1]) - 1 + sql_list = sql.split(" ")[:-1] + except ValueError: + page = 0 + sql_list = sql.split(" ") + # 拼接语句 + sql = " ".join(sql_list) + res = await db.execute_query_dict(sql) + msg_list = [f"第{page+1}页查询结果:"] + # logger.info(res) + # 获取所有字段 + keys = res[0].keys() + # 每页10条 + for i in res[page * 10 : (page + 1) * 10]: + msg = "" + for key in keys: + msg += f"{key}: {i[key]}\n" + msg += f"第{page+1}页第{res.index(i)+1}条" + msg_list.append(msg) + # 检查是私聊还是群聊 + if isinstance(event, GroupMessageEvent): + forward_msg_list = custom_forward_msg(msg_list, bot.self_id) + await bot.send_group_forward_msg( + group_id=event.group_id, messages=forward_msg_list + ) + else: + for msg in msg_list: + await exec_.send(msg) + await asyncio.sleep(0.2) + return + else: + await TestSQL.raw(sql) + await exec_.send("执行 sql 语句成功.") + # except Exception as e: + # await exec_.send(f"执行 sql 语句失败 {type(e)}:{e}") + # logger.error(f"执行 sql 语句失败 {type(e)}:{e}") @tables.handle() async def _(bot: Bot, event: MessageEvent): # 获取所有表 - query = db.text("select tablename from pg_tables where schemaname = 'public'") - res = await db.all(query) + db = Tortoise.get_connection("default") + query = await db.execute_query_dict( + "select tablename from pg_tables where schemaname = 'public'" + ) msg = "数据库中的所有表名:\n" - for tablename in res: + for tablename in query: msg += str(tablename["tablename"]) + "\n" await tables.finish(msg) diff --git a/basic_plugins/super_cmd/manager_group.py b/basic_plugins/super_cmd/manager_group.py index f74a32fd..b1add260 100755 --- a/basic_plugins/super_cmd/manager_group.py +++ b/basic_plugins/super_cmd/manager_group.py @@ -1,35 +1,39 @@ -from nonebot.adapters.onebot.v11 import ( - Bot, - MessageEvent, - GROUP, - GroupMessageEvent, - Message, - ActionFailed, -) -from nonebot import on_command, on_regex -from nonebot.permission import SUPERUSER -from nonebot.typing import T_State -from nonebot.rule import to_me -from utils.utils import is_number -from utils.manager import group_manager, plugins2settings_manager -from models.group_info import GroupInfo -from services.log import logger -from configs.config import NICKNAME -from nonebot.params import Command, CommandArg from typing import Tuple +from nonebot import on_command, on_regex +from nonebot.adapters.onebot.v11 import ( + GROUP, + Bot, + GroupMessageEvent, + Message, + MessageEvent, +) +from nonebot.params import Command, CommandArg +from nonebot.permission import SUPERUSER +from nonebot.rule import to_me + +from configs.config import NICKNAME +from models.group_info import GroupInfo +from services.log import logger +from utils.depends import OneCommand +from utils.image_utils import text2image +from utils.manager import group_manager, plugins2settings_manager +from utils.message_builder import image +from utils.utils import is_number __zx_plugin_name__ = "管理群操作 [Superuser]" __plugin_usage__ = """ usage: 群权限 | 群白名单 | 退出群 操作 - 指令: - 退群 [group_id] + 退群,添加/删除群白名单,添加/删除群认证,当在群聊中这五个命令且没有指定群号时,默认指定当前群聊 + 指令: + 退群 ?[group_id] 修改群权限 [group_id] [等级] - 添加群白名单 *[group_id] - 删除群白名单 *[group_id] - 添加群认证 *[group_id] - 删除群认证 *[group_id] + 修改群权限 [等级]: 该命令仅在群聊时生效,默认修改当前群聊 + 添加群白名单 ?*[group_id] + 删除群白名单 ?*[group_id] + 添加群认证 ?*[group_id] + 删除群认证 ?*[group_id] 查看群白名单 """.strip() __plugin_des__ = "管理群操作" @@ -53,7 +57,7 @@ my_group_level = on_command( "查看群权限", aliases={"群权限"}, priority=5, permission=GROUP, block=True ) what_up_group_level = on_regex( - ".*?(提高|提升|升高|增加|加上)(.*?)群权限.*?", + "(:?提高|提升|升高|增加|加上).*?群权限", rule=to_me(), priority=5, permission=GROUP, @@ -73,47 +77,57 @@ group_auth = on_command( @del_group.handle() -async def _(bot: Bot, arg: Message = CommandArg()): +async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): group_id = arg.extract_plain_text().strip() + if not group_id and isinstance(event, GroupMessageEvent): + group_id = event.group_id if group_id: if is_number(group_id): + group_list = [x["group_id"] for x in await bot.get_group_list()] + group_id = int(group_id) + if group_id not in group_list: + logger.debug("群聊不存在", "退群", event.user_id, target=group_id) + await del_group.finish(f"{NICKNAME}未在该群聊中...") try: - await bot.set_group_leave(group_id=int(group_id)) - logger.info(f"退出群聊 {group_id} 成功") + await bot.set_group_leave(group_id=group_id) + logger.info(f"{NICKNAME}退出群聊成功", "退群", event.user_id, target=group_id) await del_group.send(f"退出群聊 {group_id} 成功", at_sender=True) - group_manager.delete_group(int(group_id)) - await GroupInfo.delete_group_info(int(group_id)) + group_manager.delete_group(group_id) + await GroupInfo.filter(group_id=group_id).delete() except Exception as e: - logger.info(f"退出群聊 {group_id} 失败 e:{e}") + logger.error(f"退出群聊失败", "退群", event.user_id, target=group_id, e=e) + await del_group.send(f"退出群聊 {group_id} 失败", at_sender=True) else: - await del_group.finish(f"请输入正确的群号", at_sender=True) + await del_group.send(f"请输入正确的群号", at_sender=True) else: - await del_group.finish(f"请输入群号", at_sender=True) + await del_group.send(f"请输入群号", at_sender=True) @add_group_level.handle() async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): msg = arg.extract_plain_text().strip() + msg = msg.split() group_id = 0 level = 0 + if isinstance(event, GroupMessageEvent) and len(msg) == 1: + msg = [event.group_id, msg[0]] if not msg: - await add_group_level.finish("用法:修改群权限 [group] [level]") - msg = msg.split() + await add_group_level.finish("缺失参数...") if len(msg) < 2: - await add_group_level.finish("参数不完全..[group] [level]") + await add_group_level.finish("缺失参数...") if is_number(msg[0]) and is_number(msg[1]): - group_id = msg[0] + group_id = int(msg[0]) level = int(msg[1]) else: - await add_group_level.finish("参数错误...group和level必须是数字..") + await add_group_level.finish("参数错误...群号和等级必须是数字..") old_level = group_manager.get_group_level(group_id) group_manager.set_group_level(group_id, level) await add_group_level.send("修改成功...", at_sender=True) if level > -1: await bot.send_group_msg( - group_id=int(group_id), message=f"管理员修改了此群权限:{old_level} -> {level}" + group_id=group_id, message=f"管理员修改了此群权限:{old_level} -> {level}" ) - logger.info(f"{event.user_id} 修改了 {group_id} 的权限:{level}") + logger.info(f"修改群权限:{level}", "修改群权限", event.user_id, target=group_id) @my_group_level.handle() @@ -127,78 +141,104 @@ async def _(event: GroupMessageEvent): if plugin_name == "pixiv": plugin_name = "搜图 p站排行" tmp += f"{plugin_name}\n" - if tmp: - tmp = "\n目前无法使用的功能:\n" + tmp - await my_group_level.finish(f"当前群权限:{level}{tmp}") + if not tmp: + await my_group_level.finish(f"当前群权限:{level}") + await my_group_level.finish( + f"当前群权限:{level}\n目前无法使用的功能:\n" + + image(await text2image(tmp, padding=10, color="#f9f6f2")) + ) @what_up_group_level.handle() async def _(): await what_up_group_level.finish( - f"[此功能用于防止内鬼,如果引起不便那真是抱歉了]\n" f"目前提高群权限的方法:\n" f"\t1.管理员修改权限" + f"[此功能用于防止内鬼,如果引起不便那真是抱歉了]\n" f"目前提高群权限的方法:\n" f"\t1.超级管理员修改权限" ) @manager_group_whitelist.handle() -async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()): - cmd = cmd[0] +async def _( + bot: Bot, event: MessageEvent, cmd: str = OneCommand(), arg: Message = CommandArg() +): msg = arg.extract_plain_text().strip().split() + if not msg and isinstance(event, GroupMessageEvent): + msg = [event.group_id] + if not msg: + await manager_group_whitelist.finish("请输入群号") all_group = [g["group_id"] for g in await bot.get_group_list()] + error_group = [] group_list = [] - for group in msg: - if is_number(group) and int(group) in all_group: - group_list.append(int(group)) + for group_id in msg: + if is_number(group_id) and int(group_id) in all_group: + group_list.append(int(group_id)) + else: + logger.debug(f"群号不合法或不存在", cmd, target=group_id) + error_group.append(group_id) if group_list: - for group in group_list: + for group_id in group_list: if cmd in ["添加群白名单"]: - group_manager.add_group_white_list(group) + group_manager.add_group_white_list(group_id) else: - group_manager.delete_group_white_list(group) + group_manager.delete_group_white_list(group_id) group_list = [str(x) for x in group_list] await manager_group_whitelist.send("已成功将 " + "\n".join(group_list) + " " + cmd) - else: - await manager_group_whitelist.send(f"添加失败,请检查{NICKNAME}是否已加入这些群聊或重复添加/删除群白单名") + group_manager.save() + if error_group: + await manager_group_whitelist.send("以下群聊不合法或不存在:\n" + "\n".join(error_group)) @show_group_whitelist.handle() async def _(): - x = group_manager.get_group_white_list() - x = [str(g) for g in x] - if x: - await show_group_whitelist.send("目前的群白名单:\n" + "\n".join(x)) - else: - await show_group_whitelist.send("没有任何群在群白名单...") + group = [str(g) for g in group_manager.get_group_white_list()] + if not group: + await show_group_whitelist.finish("没有任何群在群白名单...") + await show_group_whitelist.send("目前的群白名单:\n" + "\n".join(group)) @group_auth.handle() -async def _(bot: Bot, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()): - cmd = cmd[0] +async def _( + bot: Bot, event: MessageEvent, cmd: str = OneCommand(), arg: Message = CommandArg() +): msg = arg.extract_plain_text().strip().split() + if isinstance(event, GroupMessageEvent) and not msg: + msg = [event.group_id] + if not msg: + await manager_group_whitelist.finish("请输入群号") + error_group = [] + all_group = [g["group_id"] for g in await bot.get_group_list()] for group_id in msg: - if not is_number(group_id): - await group_auth.send(f"{group_id}非纯数字,已跳过该项..") group_id = int(group_id) - if cmd[:2] == "添加": - if await GroupInfo.get_group_info(group_id): - await GroupInfo.set_group_flag(group_id, 1) - else: + if is_number(group_id) and group_id in all_group: + if cmd[:2] == "添加": try: group_info = await bot.get_group_info(group_id=group_id) - except ActionFailed: - group_info = { - "group_id": group_id, - "group_name": "_", - "max_member_count": -1, - "member_count": -1, - } - await GroupInfo.add_group_info( - group_info["group_id"], - group_info["group_name"], - group_info["max_member_count"], - group_info["member_count"], - 1, - ) + await GroupInfo.update_or_create( + group_id=group_info["group_id"], + defaults={ + "group_flag": 1, + "group_name": group_info["group_name"], + "max_member_count": group_info["max_member_count"], + "member_count": group_info["member_count"], + }, + ) + except Exception as e: + await group_auth.send(f"添加群认证 {group_id} 发生错误!") + logger.error(f"添加群认证发生错误", cmd, target=group_id, e=e) + else: + await group_auth.send(f"已为 {group_id} {cmd[:2]}群认证..") + logger.info(f"添加群认证成功", cmd, target=group_id) + else: + if group := await GroupInfo.filter(group_id=group_id).first(): + await group.update_or_create( + group_id=group_id, defaults={"group_flag": 0} + ) + await group_auth.send(f"已删除 {group_id} 群认证..") + logger.info(f"删除群认证成功", cmd, target=group_id) + else: + await group_auth.send(f"未查找到群聊: {group_id}") + logger.info(f"未找到群聊", cmd, target=group_id) else: - if await GroupInfo.get_group_info(group_id): - await GroupInfo.set_group_flag(group_id, 0) - await group_auth.send(f"已为 {group_id} {cmd[:2]}群认证..") + logger.debug(f"群号不合法或不存在", cmd, target=group_id) + error_group.append(str(group_id)) + if error_group: + await manager_group_whitelist.send("以下群聊不合法或不存在:\n" + "\n".join(error_group)) diff --git a/basic_plugins/super_cmd/reload_setting.py b/basic_plugins/super_cmd/reload_setting.py index acbd616e..7ff020c6 100755 --- a/basic_plugins/super_cmd/reload_setting.py +++ b/basic_plugins/super_cmd/reload_setting.py @@ -1,21 +1,21 @@ from nonebot import on_command from nonebot.permission import SUPERUSER from nonebot.rule import to_me -from utils.manager import ( - plugins2cd_manager, - plugins2settings_manager, - plugins2block_manager, - group_manager, -) + from configs.config import Config from services.log import logger +from utils.manager import ( + group_manager, + plugins2block_manager, + plugins2cd_manager, + plugins2settings_manager, +) from utils.utils import scheduler - -__zx_plugin_name__ = "重载插件配置 [Superuser]" +__zx_plugin_name__ = "重载配置 [Superuser]" __plugin_usage__ = """ usage: - 重载插件配置 + 重载配置 plugins2settings, plugins2cd plugins2block @@ -23,23 +23,15 @@ usage: 指令: 重载插件配置 """.strip() -__plugin_des__ = "重载插件配置" +__plugin_des__ = "重载配置" __plugin_cmd__ = [ - "重载插件配置", + "重载配置", ] -__plugin_version__ = 0.1 +__plugin_version__ = 0.2 __plugin_author__ = "HibiKier" __plugin_configs__ = { - "AUTO_RELOAD": { - "value": False, - "help": "自动重载配置文件", - "default_value": False - }, - "AUTO_RELOAD_TIME": { - "value": 180, - "help": "控制自动重载配置文件时长", - "default_value": 180 - } + "AUTO_RELOAD": {"value": False, "help": "自动重载配置文件", "default_value": False}, + "AUTO_RELOAD_TIME": {"value": 180, "help": "控制自动重载配置文件时长", "default_value": 180}, } @@ -59,7 +51,7 @@ async def _(): @scheduler.scheduled_job( - 'interval', + "interval", seconds=Config.get_config("reload_setting", "AUTO_RELOAD_TIME", 180), ) async def _(): diff --git a/basic_plugins/super_cmd/set_admin_permissions.py b/basic_plugins/super_cmd/set_admin_permissions.py index c64028d0..03835f76 100755 --- a/basic_plugins/super_cmd/set_admin_permissions.py +++ b/basic_plugins/super_cmd/set_admin_permissions.py @@ -1,12 +1,16 @@ +from typing import List, Tuple + from nonebot import on_command +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent +from nonebot.exception import ActionFailed +from nonebot.params import CommandArg from nonebot.permission import SUPERUSER + from models.level_user import LevelUser -from nonebot.adapters.onebot.v11 import Bot, MessageEvent, Message, GroupMessageEvent -from utils.utils import get_message_at, is_number from services.log import logger +from utils.depends import AtList, OneCommand from utils.message_builder import at -from nonebot.params import Command, CommandArg -from typing import Tuple +from utils.utils import is_number __zx_plugin_name__ = "用户权限管理 [Superuser]" __plugin_usage__ = """ @@ -16,12 +20,14 @@ usage: 添加权限 [at] [权限] 添加权限 [qq] [group_id] [权限] 删除权限 [at] + 删除权限 [qq] [group_id] """.strip() __plugin_des__ = "增删改用户的权限" __plugin_cmd__ = [ "添加权限 [at] [权限]", "添加权限 [qq] [group_id] [权限]", "删除权限 [at]", + "删除权限 [qq] [group_id]", ] __plugin_version__ = 0.1 __plugin_author__ = "HibiKier" @@ -40,21 +46,22 @@ super_cmd = on_command( async def _( bot: Bot, event: MessageEvent, - cmd: Tuple[str, ...] = Command(), + cmd: str = OneCommand(), arg: Message = CommandArg(), + at_list: List[int] = AtList(), ): group_id = event.group_id if isinstance(event, GroupMessageEvent) else -1 level = None args = arg.extract_plain_text().strip().split() - qq = get_message_at(event.json()) flag = 2 + qq = None try: - if qq: - qq = qq[0] - if cmd[0][:2] == "添加" and args and is_number(args[0]): + if at_list: + qq = at_list[0] + if cmd[:2] == "添加" and args and is_number(args[0]): level = int(args[0]) else: - if cmd[0][:2] == "添加": + if cmd[:2] == "添加": if ( len(args) > 2 and is_number(args[0]) @@ -69,33 +76,39 @@ async def _( qq = int(args[0]) group_id = int(args[1]) flag = 1 - level = -1 if cmd[0][:2] == "删除" else level + level = -1 if cmd[:2] == "删除" else level if group_id == -1 or not level or not qq: raise IndexError() except IndexError: await super_cmd.finish(__plugin_usage__) + if not qq: + await super_cmd.finish("未指定对象...") try: - if cmd[0][:2] == "添加": - if await LevelUser.set_level(qq, group_id, level, 1): - result = f"添加管理成功, 权限: {level}" - else: - result = f"管理已存在, 更新权限: {level}" + if cmd[:2] == "添加": + await LevelUser.set_level(qq, group_id, level, 1) + result = f"设置权限成功, 权限: {level}" else: - if await LevelUser.delete_level(qq, event.group_id): + if await LevelUser.delete_level(qq, group_id): result = "删除管理成功!" else: result = "该账号无管理权限!" if flag == 2: await super_cmd.send(result) elif flag == 1: - await bot.send_group_msg( - group_id=group_id, - message=Message( - f"{at(qq)}管理员修改了你的权限" - f"\n--------\n你当前的权限等级:{level if level != -1 else 0}" - ), - ) + try: + await bot.send_group_msg( + group_id=group_id, + message=Message( + f"{at(qq)}管理员修改了你的权限" + f"\n--------\n你当前的权限等级:{level if level != -1 else 0}" + ), + ) + except ActionFailed: + pass await super_cmd.send("修改成功") + logger.info( + f"修改权限: {level if level != -1 else 0}", cmd, event.user_id, group_id, qq + ) except Exception as e: await super_cmd.send("执行指令失败!") - logger.error(f"执行指令失败 e:{e}") + logger.error(f"执行指令失败", cmd, event.user_id, group_id, qq, e=e) diff --git a/basic_plugins/super_cmd/super_task_switch.py b/basic_plugins/super_cmd/super_task_switch.py deleted file mode 100755 index ba1df382..00000000 --- a/basic_plugins/super_cmd/super_task_switch.py +++ /dev/null @@ -1,58 +0,0 @@ -from nonebot import on_command -from nonebot.permission import SUPERUSER -from nonebot.adapters.onebot.v11 import Bot, MessageEvent, Message -from nonebot.rule import to_me -from utils.utils import is_number -from services.log import logger -from utils.manager import group_manager -from nonebot.params import Command, CommandArg -from typing import Tuple - - -__zx_plugin_name__ = "超级用户被动开关 [Superuser]" -__plugin_usage__ = """ -usage: - 超级用户被动开关 - 指令: - 开启/关闭广播通知 -""".strip() -__plugin_des__ = "超级用户被动开关" -__plugin_cmd__ = [ - "开启/关闭广播通知", -] -__plugin_version__ = 0.1 -__plugin_author__ = "HibiKier" - - -oc_gb = on_command( - "开启广播通知", - aliases={"关闭广播通知"}, - rule=to_me(), - permission=SUPERUSER, - priority=1, - block=True, -) - - -@oc_gb.handle() -async def _(bot: Bot, event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()): - cmd = cmd[0] - group = arg.extract_plain_text().strip() - if group: - if is_number(group): - group = int(group) - for g in await bot.get_group_list(): - if g["group_id"] == group: - break - else: - await oc_gb.finish("没有加入这个群...", at_sender=True) - if cmd == "开启广播通知": - logger.info(f"USER {event.user_id} 开启了 GROUP {group} 的广播") - await oc_gb.finish(await group_manager.open_group_task(group, "broadcast",), at_sender=True) - else: - logger.info(f"USER {event.user_id} 关闭了 GROUP {group} 的广播") - await oc_gb.finish(await group_manager.close_group_task(group, "broadcast"), at_sender=True) - else: - await oc_gb.finish("请输入正确的群号", at_sender=True) - else: - await oc_gb.finish("请输入要关闭广播的群号", at_sender=True) \ No newline at end of file diff --git a/basic_plugins/super_cmd/update_friend_group_info.py b/basic_plugins/super_cmd/update_friend_group_info.py index 8f528c78..f56c4ae6 100755 --- a/basic_plugins/super_cmd/update_friend_group_info.py +++ b/basic_plugins/super_cmd/update_friend_group_info.py @@ -1,11 +1,11 @@ from nonebot import on_command +from nonebot.adapters.onebot.v11 import Bot, MessageEvent from nonebot.permission import SUPERUSER from nonebot.rule import to_me -from utils.utils import get_bot -from services.log import logger -from models.group_info import GroupInfo -from models.friend_user import FriendUser +from models.friend_user import FriendUser +from models.group_info import GroupInfo +from services.log import logger __zx_plugin_name__ = "更新群/好友信息 [Superuser]" __plugin_usage__ = """ @@ -32,39 +32,46 @@ update_friend_info = on_command( @update_group_info.handle() -async def _(): - bot = get_bot() +async def _(bot: Bot, event: MessageEvent): gl = await bot.get_group_list() gl = [g["group_id"] for g in gl] num = 0 - rst = "" for g in gl: - group_info = await bot.get_group_info(group_id=g) - if await GroupInfo.add_group_info( - group_info["group_id"], - group_info["group_name"], - group_info["max_member_count"], - group_info["member_count"], - 1 - ): + try: + group_info = await bot.get_group_info(group_id=g) + await GroupInfo.update_or_create( + group_id=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"], + }, + ) num += 1 - logger.info(f"自动更新群组 {g} 信息成功") - else: - logger.info(f"自动更新群组 {g} 信息失败") - rst += f"{g} 更新失败\n" - await update_group_info.send(f"成功更新了 {num} 个群的信息\n{rst[:-1]}") + logger.debug( + "群聊信息更新成功", "更新群信息", event.user_id, target=group_info["group_id"] + ) + except Exception as e: + logger.error(f"更新群聊信息失败", "更新群信息", event.user_id, target=g, e=e) + await update_group_info.send(f"成功更新了 {len(gl)} 个群的信息") + logger.info(f"更新群聊信息完成,共更新了 {len(gl)} 个群的信息", "更新群信息", event.user_id) @update_friend_info.handle() -async def _(): +async def _(bot: Bot, event: MessageEvent): num = 0 - rst = "" - fl = await get_bot().get_friend_list() + error_list = [] + fl = await bot.get_friend_list() for f in fl: - if await FriendUser.add_friend_info(f["user_id"], f["nickname"]): - logger.info(f'自动更新好友 {f["user_id"]} 信息成功') + try: + await FriendUser.update_or_create( + user_id=f["user_id"], defaults={"nickname": f["nickname"]} + ) + logger.debug(f"更新好友信息成功", "更新好友信息", event.user_id, target=f["user_id"]) num += 1 - else: - logger.warning(f'自动更新好友 {f["user_id"]} 信息失败') - rst += f'{f["user_id"]} 更新失败\n' - await update_friend_info.send(f"成功更新了 {num} 个好友的信息\n{rst[:-1]}") + except Exception as e: + logger.error(f"更新好友信息失败", "更新好友信息", event.user_id, target=f["user_id"], e=e) + await update_friend_info.send(f"成功更新了 {num} 个好友的信息") + if error_list: + await update_friend_info.send(f"以下好友更新失败:\n" + "\n".join(error_list)) + logger.info(f"更新好友信息完成,共更新了 {num} 个群的信息", "更新好友信息", event.user_id) diff --git a/basic_plugins/super_help/__init__.py b/basic_plugins/super_help/__init__.py index b6c29cee..574b3d1b 100755 --- a/basic_plugins/super_help/__init__.py +++ b/basic_plugins/super_help/__init__.py @@ -1,11 +1,12 @@ from nonebot import on_command from nonebot.permission import SUPERUSER from nonebot.rule import to_me + from utils.message_builder import image -from .data_source import create_help_image, SUPERUSER_HELP_IMAGE +from .data_source import SUPERUSER_HELP_IMAGE, create_help_image -__zx_plugin_name__ = '超级用户帮助 [Superuser]' +__zx_plugin_name__ = "超级用户帮助 [Superuser]" if SUPERUSER_HELP_IMAGE.exists(): @@ -20,5 +21,4 @@ super_help = on_command( async def _(): if not SUPERUSER_HELP_IMAGE.exists(): await create_help_image() - x = image(SUPERUSER_HELP_IMAGE) - await super_help.finish(x) + await super_help.finish(image(SUPERUSER_HELP_IMAGE)) diff --git a/basic_plugins/update_info.py b/basic_plugins/update_info.py index 7c19d86c..074d72a9 100755 --- a/basic_plugins/update_info.py +++ b/basic_plugins/update_info.py @@ -1,6 +1,6 @@ from nonebot import on_command -from utils.message_builder import image +from utils.message_builder import image __zx_plugin_name__ = "更新信息" __plugin_usage__ = """ @@ -26,8 +26,7 @@ update_info = on_command("更新信息", aliases={"更新日志"}, priority=5, b @update_info.handle() async def _(): - img = image("update_info.png") - if img: + if img := image("update_info.png"): await update_info.finish(image("update_info.png")) else: await update_info.finish("目前没有更新信息哦") diff --git a/configs/config.py b/configs/config.py index 6528e3a8..9aff1fcb 100644 --- a/configs/config.py +++ b/configs/config.py @@ -1,11 +1,18 @@ -from typing import Optional +import platform from pathlib import Path +from typing import Optional + from .utils import ConfigsManager -import platform if platform.system() == "Linux": import os - hostip = os.popen("cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }'").read().replace("\n","") + + hostip = ( + os.popen("cat /etc/resolv.conf | grep nameserver | awk '{ print $2 }'") + .read() + .replace("\n", "") + ) + # 回复消息名称 NICKNAME: str = "小真寻" @@ -14,7 +21,7 @@ NICKNAME: str = "小真寻" # 如果填写了bind就不需要再填写后面的字段了#) # 示例:"bind": "postgresql://user:password@127.0.0.1:5432/database" bind: str = "" # 数据库连接链接 -sql_name: str = "postgresql" +sql_name: str = "postgres" user: str = "" # 数据用户名 password: str = "" # 数据库密码 address: str = "" # 数据库地址 diff --git a/configs/utils/__init__.py b/configs/utils/__init__.py index 10de0138..79641dd7 100644 --- a/configs/utils/__init__.py +++ b/configs/utils/__init__.py @@ -1,8 +1,9 @@ import copy -from typing import Optional, Any, Union from pathlib import Path -from ruamel.yaml import YAML +from typing import Any, Optional, Union + from ruamel import yaml +from ruamel.yaml import YAML from ruamel.yaml.scanner import ScannerError @@ -50,7 +51,7 @@ class ConfigsManager: *, name: Optional[str] = None, help_: Optional[str] = None, - default_value: Optional[str] = None, + default_value: Optional[Any] = None, _override: bool = False, ): """ diff --git a/models/bag_user.py b/models/bag_user.py index 0027d0d6..9bbce1c0 100755 --- a/models/bag_user.py +++ b/models/bag_user.py @@ -1,24 +1,37 @@ -from services.db_context import db from typing import Dict -from typing import Optional, List -from services.log import logger + +from tortoise import fields + +from services.db_context import Model + from .goods_info import GoodsInfo -class BagUser(db.Model): - __tablename__ = "bag_users" - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger(), nullable=False) - gold = db.Column(db.Integer(), default=100) - props = db.Column(db.TEXT(), nullable=False, default="") # 旧道具字段(废弃) - spend_total_gold = db.Column(db.Integer(), default=0) - get_total_gold = db.Column(db.Integer(), default=0) - get_today_gold = db.Column(db.Integer(), default=0) - spend_today_gold = db.Column(db.Integer(), default=0) - property = db.Column(db.JSON(), nullable=False, default={}) # 新道具字段 +class BagUser(Model): - _idx1 = db.Index("bag_group_users_idx1", "user_qq", "group_id", unique=True) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField() + """群聊id""" + gold = fields.IntField(default=100) + """金币数量""" + spend_total_gold = fields.IntField(default=0) + """花费金币总数""" + get_total_gold = fields.IntField(default=0) + """获取金币总数""" + get_today_gold = fields.IntField(default=0) + """今日获取金币""" + spend_today_gold = fields.IntField(default=0) + """今日获取金币""" + property: Dict[str, int] = fields.JSONField(default={}) + """道具""" + + class Meta: + table = "bag_users" + table_description = "用户道具数据表" + unique_together = ("user_qq", "group_id") @classmethod async def get_user_total_gold(cls, user_qq: int, group_id: int) -> str: @@ -29,13 +42,7 @@ class BagUser(db.Model): :param user_qq: qq号 :param group_id: 所在群号 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - user = await query.gino.first() - if not user: - user = await cls.create( - user_qq=user_qq, - group_id=group_id, - ) + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) return ( f"当前金币:{user.gold}\n今日获取金币:{user.get_today_gold}\n今日花费金币:{user.spend_today_gold}" f"\n今日收益:{user.get_today_gold - user.spend_today_gold}" @@ -51,19 +58,13 @@ class BagUser(db.Model): :param user_qq: qq号 :param group_id: 所在群号 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - user = await query.gino.first() - if user: - return user.gold - else: - await cls.create( - user_qq=user_qq, - group_id=group_id, - ) - return 100 + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + return user.gold @classmethod - async def get_property(cls, user_qq: int, group_id: int, only_active: bool = False) -> Dict[str, int]: + async def get_property( + cls, user_qq: int, group_id: int, only_active: bool = False + ) -> Dict[str, int]: """ 说明: 获取当前道具 @@ -72,22 +73,18 @@ class BagUser(db.Model): :param group_id: 所在群号 :param only_active: 仅仅获取主动使用的道具 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - user = await query.gino.first() - if user: - if only_active and user.property: - data = {} - name_list = [x.goods_name for x in await GoodsInfo.get_all_goods() if not x.is_passive] - for key in [x for x in user.property.keys() if x in name_list]: - data[key] = user.property[key] - return data - return user.property - else: - await cls.create( - user_qq=user_qq, - group_id=group_id, - ) - return {} + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + if only_active and user.property: + data = {} + name_list = [ + x.goods_name + for x in await GoodsInfo.get_all_goods() + if not x.is_passive + ] + for key in [x for x in user.property if x in name_list]: + data[key] = user.property[key] + return data + return user.property @classmethod async def add_gold(cls, user_qq: int, group_id: int, num: int): @@ -99,23 +96,11 @@ class BagUser(db.Model): :param group_id: 所在群号 :param num: 金币数量 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - query = query.with_for_update() - user = await query.gino.first() - if user: - await user.update( - gold=user.gold + num, - get_total_gold=user.get_total_gold + num, - get_today_gold=user.get_today_gold + num, - ).apply() - else: - await cls.create( - user_qq=user_qq, - group_id=group_id, - gold=100 + num, - get_total_gold=num, - get_today_gold=num, - ) + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + user.gold = user.gold + num + user.get_total_gold = user.get_total_gold + num + user.get_today_gold = user.get_today_gold + num + await user.save(update_fields=["gold", "get_today_gold", "get_total_gold"]) @classmethod async def spend_gold(cls, user_qq: int, group_id: int, num: int): @@ -127,26 +112,14 @@ class BagUser(db.Model): :param group_id: 所在群号 :param num: 金币数量 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - query = query.with_for_update() - user = await query.gino.first() - if user: - await user.update( - gold=user.gold - num, - spend_total_gold=user.spend_total_gold + num, - spend_today_gold=user.spend_today_gold + num, - ).apply() - else: - await cls.create( - user_qq=user_qq, - group_id=group_id, - gold=100 - num, - spend_total_gold=num, - spend_today_gold=num, - ) + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + user.gold = user.gold - num + user.spend_total_gold = user.spend_total_gold + num + user.spend_today_gold = user.spend_today_gold + num + await user.save(update_fields=["gold", "spend_total_gold", "spend_today_gold"]) @classmethod - async def add_property(cls, user_qq: int, group_id: int, name: str): + async def add_property(cls, user_qq: int, group_id: int, name: str, num: int = 1): """ 说明: 增加道具 @@ -154,19 +127,15 @@ class BagUser(db.Model): :param user_qq: qq号 :param group_id: 所在群号 :param name: 道具名称 + :param num: 道具数量 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - query = query.with_for_update() - user = await query.gino.first() - if user: - p = user.property - if p.get(name) is None: - p[name] = 1 - else: - p[name] += 1 - await user.update(property=p).apply() - else: - await cls.create(user_qq=user_qq, group_id=group_id, property={name: 1}) + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + property_ = user.property + if property_.get(name) is None: + property_[name] = 0 + property_[name] += num + user.property = property_ + await user.save(update_fields=["property"]) @classmethod async def delete_property( @@ -181,54 +150,15 @@ class BagUser(db.Model): :param name: 道具名称 :param num: 使用个数 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - query = query.with_for_update() - user = await query.gino.first() - if user: - property_ = user.property - if name in property_: - if property_.get(name) == num: - del property_[name] - else: - property_[name] -= num - await user.update(property=property_).apply() - return True - return False - - @classmethod - async def buy_property( - cls, user_qq: int, group_id: int, goods: "GoodsInfo", goods_num: int - ) -> bool: - """ - 说明: - 购买道具 - 参数: - :param user_qq: 用户qq - :param group_id: 所在群聊 - :param goods: 商品 - :param goods_num: 商品数量 - """ - try: - # 折扣后金币 - spend_gold = goods.goods_discount * goods.goods_price * goods_num - await BagUser.spend_gold(user_qq, group_id, spend_gold) - for _ in range(goods_num): - await BagUser.add_property(user_qq, group_id, goods.goods_name) + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + property_ = user.property + if name in property_: + if (n := property_.get(name, 0)) < num: + return False + if n == num: + del property_[name] + else: + property_[name] -= num + await user.save(update_fields=["property"]) return True - except Exception as e: - logger.error(f"buy_property 发生错误 {type(e)}:{e}") return False - - @classmethod - async def get_all_users(cls, group_id: Optional[int] = None) -> List["BagUser"]: - """ - 说明: - 获取所有用户数据 - 参数: - :param group_id: 群号 - """ - if not group_id: - query = await cls.query.gino.all() - else: - query = await cls.query.where((cls.group_id == group_id)).gino.all() - return query diff --git a/models/ban_user.py b/models/ban_user.py index 6ed406d4..e092e26a 100755 --- a/models/ban_user.py +++ b/models/ban_user.py @@ -1,16 +1,26 @@ -from services.db_context import db import time +from typing import Union + +from tortoise import fields + +from services.db_context import Model +from services.log import logger -class BanUser(db.Model): - __tablename__ = "ban_users" +class BanUser(Model): - user_qq = db.Column(db.BigInteger(), nullable=False, primary_key=True) - ban_level = db.Column(db.Integer(), nullable=False) - ban_time = db.Column(db.BigInteger()) - duration = db.Column(db.BigInteger()) + user_qq = fields.IntField(pk=True) + """用户id""" + ban_level = fields.IntField() + """使用ban命令的用户等级""" + ban_time = fields.BigIntField() + """ban开始的时间""" + duration = fields.BigIntField() + """ban时长""" - _idx1 = db.Index("ban_group_users_idx1", "user_qq", unique=True) + class Meta: + table = "ban_users" + table_description = ".ban/b了 封禁人员数据表" @classmethod async def check_ban_level(cls, user_qq: int, level: int) -> bool: @@ -21,30 +31,34 @@ class BanUser(db.Model): :param user_qq: unban用户的qq号 :param level: ban掉目标用户的权限等级 """ - user = await cls.query.where((cls.user_qq == user_qq)).gino.first() - if not user: - return False - if user.ban_level > level: - return True + user = await cls.filter(user_qq=user_qq).first() + if user: + logger.debug( + f"检测用户被ban等级,user_level: {user.ban_level},level: {level}", + target=user_qq, + ) + return bool(user and user.ban_level > level) return False @classmethod - async def check_ban_time(cls, user_qq: int) -> str: + async def check_ban_time(cls, user_qq: int) -> Union[str, int]: """ 说明: 检测用户被ban时长 参数: :param user_qq: qq号 """ - query = cls.query.where((cls.user_qq == user_qq)) - user = await query.gino.first() - if not user: - return "" - if time.time() - (user.ban_time + user.duration) > 0 and user.duration != -1: - return "" - if user.duration == -1: - return "∞" - return time.time() - user.ban_time - user.duration + logger.debug(f"获取用户ban时长", target=user_qq) + if user := await cls.filter(user_qq=user_qq).first(): + if ( + time.time() - (user.ban_time + user.duration) > 0 + and user.duration != -1 + ): + return "" + if user.duration == -1: + return "∞" + return int(time.time() - user.ban_time - user.duration) + return "" @classmethod async def is_ban(cls, user_qq: int) -> bool: @@ -54,28 +68,29 @@ class BanUser(db.Model): 参数: :param user_qq: qq号 """ + logger.debug(f"检测是否被ban", target=user_qq) if await cls.check_ban_time(user_qq): return True else: await cls.unban(user_qq) - return False + return False @classmethod async def is_super_ban(cls, user_qq: int) -> bool: """ 说明: - 判断用户是否被ban + 判断用户是否被超级用户ban / b了 参数: :param user_qq: qq号 """ - user = await cls.query.where((cls.user_qq == user_qq)).gino.first() - if not user: - return False - if user.ban_level == 10: - return True + logger.debug(f"检测是否被超级用户权限封禁", target=user_qq) + if user := await cls.filter(user_qq=user_qq).first(): + if user.ban_level == 10: + return True + return False @classmethod - async def ban(cls, user_qq: int, ban_level: int, duration: int) -> bool: + async def ban(cls, user_qq: int, ban_level: int, duration: int): """ 说明: ban掉目标用户 @@ -84,22 +99,15 @@ class BanUser(db.Model): :param ban_level: 使用ban命令用户的权限 :param duration: ban时长,秒 """ - query = cls.query.where((cls.user_qq == user_qq)) - query = query.with_for_update() - user = await query.gino.first() - if not await cls.check_ban_time(user_qq): + logger.debug(f"封禁用户,等级:{ban_level},时长: {duration}", target=user_qq) + if await cls.filter(user_qq=user_qq).first(): await cls.unban(user_qq) - user = None - if user is None: - await cls.create( - user_qq=user_qq, - ban_level=ban_level, - ban_time=time.time(), - duration=duration, - ) - return True - else: - return False + await cls.create( + user_qq=user_qq, + ban_level=ban_level, + ban_time=time.time(), + duration=duration, + ) @classmethod async def unban(cls, user_qq: int) -> bool: @@ -109,11 +117,8 @@ class BanUser(db.Model): 参数: :param user_qq: qq号 """ - query = cls.query.where((cls.user_qq == user_qq)) - query = query.with_for_update() - user = await query.gino.first() - if user is None: - return False - else: - await cls.delete.where((cls.user_qq == user_qq)).gino.status() + logger.debug("解除封禁", target=user_qq) + if user := await cls.filter(user_qq=user_qq).first(): + await user.delete() return True + return False diff --git a/models/chat_history.py b/models/chat_history.py index 9eb6cafc..9816fa5b 100644 --- a/models/chat_history.py +++ b/models/chat_history.py @@ -1,81 +1,30 @@ from datetime import datetime, timedelta -from typing import List, Literal, Optional, Tuple, Union +from typing import Any, List, Literal, Optional, Tuple, Union -from services.db_context import db +from tortoise import fields +from tortoise.functions import Count + +from services.db_context import Model -class ChatHistory(db.Model): - __tablename__ = "chat_history" +class ChatHistory(Model): - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger()) - text = db.Column(db.Text()) - plain_text = db.Column(db.Text()) - create_time = db.Column(db.DateTime(timezone=True), nullable=False) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField() + """群聊id""" + text = fields.TextField() + """文本内容""" + plain_text = fields.TextField() + """纯文本""" + create_time = fields.DatetimeField(auto_now_add=True) + """创建时间""" - @classmethod - async def add_chat_msg(cls, user_qq: int, group_id: Optional[int], text: str, plain_text: str): - await cls.create( - user_qq=user_qq, group_id=group_id, text=text, plain_text=plain_text, create_time=datetime.now() - ) - - @classmethod - async def get_user_msg( - cls, - uid: int, - msg_type: Optional[Literal["private", "group"]], - days: Optional[int] = None, - ) -> List["ChatHistory"]: - """ - 说明: - 获取用户消息 - 参数: - :param uid: 用户qq - :param msg_type: 消息类型,私聊或群聊 - :param days: 限制日期 - """ - return await cls._get_msg(uid, None, "user", msg_type, days).gino.all() - - @classmethod - async def get_group_user_msg( - cls, - uid: int, - gid: int, - limit: int = 10, - date_scope: Tuple[datetime, datetime] = None, - ) -> List["ChatHistory"]: - """ - 说明: - 获取群聊指定用户聊天记录 - 参数: - :param uid: qq - :param gid: 群号 - :param limit: 获取数量 - :param date_scope: 日期范围,默认None为全搜索 - """ - return ( - await cls._get_msg(uid, gid, "group", days=date_scope) - .limit(limit) - .gino.all() - ) - - @classmethod - async def get_group_user_msg_count(cls, uid: int, gid: int) -> Optional[int]: - """ - 说明: - 查询群聊指定用户的聊天记录数量 - 参数: - :param uid: qq - :param gid: 群号 - """ - if x := await db.first( - db.text( - f"SELECT COUNT(id) as sum FROM public.chat_history WHERE user_qq = {uid} AND group_id = {gid}" - ) - ): - return x[0] - return None + class Meta: + table = "chat_history" + table_description = "聊天记录数据表" @classmethod async def get_group_msg_rank( @@ -84,7 +33,7 @@ class ChatHistory(db.Model): limit: int = 10, order: str = "DESC", date_scope: Optional[Tuple[datetime, datetime]] = None, - ) -> Optional[Tuple[int, int]]: + ) -> List["ChatHistory"]: """ 说明: 获取排行数据 @@ -94,89 +43,39 @@ class ChatHistory(db.Model): :param order: 排序类型,desc,des :param date_scope: 日期范围 """ - sql = f"SELECT user_qq, COUNT(id) as sum FROM public.chat_history WHERE group_id = {gid} " - if date_scope: - sql += f"AND create_time BETWEEN '{date_scope[0]}' AND '{date_scope[1]}' " - sql += f"GROUP BY user_qq ORDER BY sum {order if order and order.upper() != 'DES' else ''} LIMIT {limit}" - return await db.all(db.text(sql)) + return list( + await cls.filter(group_id=gid, create_time__range=date_scope) + .annotate(count=Count("user_qq")) + .order_by(order) + .group_by("user_qq") + .limit(limit) + .values_list("user_qq", "count") + ) @classmethod - async def get_group_first_msg_datetime(cls, gid: int) -> Optional[datetime]: + async def get_group_first_msg_datetime(cls, group_id: int) -> Optional[datetime]: """ 说明: 获取群第一条记录消息时间 参数: - :param gid: + :param group_id: 群聊id """ if ( - msg := await cls.query.where(cls.group_id == gid) - .order_by(cls.create_time) - .gino.first() + message := await cls.filter(group_id=group_id) + .order_by("create_time") + .first() ): - return msg.create_time - return None + return message.create_time @classmethod - async def get_user_msg_count( - cls, - uid: int, - msg_type: Optional[Literal["private", "group"]], - days: Optional[int] = None, - ) -> int: - """ - 说明: - 获取用户消息数量 - 参数: - :param uid: 用户qq - :param msg_type: 消息类型,私聊或群聊 - :param days: 限制日期 - """ - return ( - await cls._get_msg(uid, None, "user", msg_type, days, True).gino.first() - )[0] - - @classmethod - async def get_group_msg( - cls, - gid: int, - days: Optional[int] = None, - ) -> List["ChatHistory"]: - """ - 说明: - 获取群聊消息 - 参数: - :param gid: 用户qq - :param days: 限制日期 - """ - return await cls._get_msg(None, gid, "group", None, days).gino.all() - - @classmethod - async def get_group_msg_count( - cls, - gid: int, - days: Optional[int] = None, - ) -> List["ChatHistory"]: - """ - 说明: - 获取群聊消息数量 - 参数: - :param gid: 用户qq - :param days: 限制日期 - """ - return (await cls._get_msg(None, gid, "group", None, days, True).gino.first())[ - 0 - ] - - @classmethod - def _get_msg( + async def get_message( cls, uid: Optional[int], gid: Optional[int], type_: Literal["user", "group"], msg_type: Optional[Literal["private", "group"]] = None, days: Optional[Union[int, Tuple[datetime, datetime]]] = None, - is_select_count: bool = False, - ): + ) -> List["ChatHistory"]: """ 说明: 获取消息查询query @@ -187,28 +86,21 @@ class ChatHistory(db.Model): :param msg_type: 消息类型,用户或群聊 :param days: 限制日期 """ - if is_select_count: - setattr(ChatHistory, "count", db.func.count(cls.id).label("count")) - query = cls.select("count") - else: - query = cls.query if type_ == "user": - query = query.where(cls.user_qq == uid) + query = cls.filter(user_qq=uid) if msg_type == "private": - query = query.where(cls.group_id == None) + query = query.filter(group_id__isnull=True) elif msg_type == "group": - query = query.where(cls.group_id != None) + query = query.filter(group_id__not_isnull=True) else: - query = query.where(cls.group_id == gid) + query = cls.filter(group_id=gid) if uid: - query = query.where(cls.user_qq == uid) + query = query.filter(user_qq=uid) if days: if isinstance(days, int): - query = query.where( - cls.create_time >= datetime.now() - timedelta(days=days) + query = query.filter( + create_time__gte=datetime.now() - timedelta(days=days) ) elif isinstance(days, tuple): - query = query.where(cls.create_time >= days[0]).where( - cls.create_time <= days[1] - ) - return query + query = query.filter(create_at__range=days) + return await query.all() diff --git a/models/friend_user.py b/models/friend_user.py index 0881041c..3b41259a 100755 --- a/models/friend_user.py +++ b/models/friend_user.py @@ -1,16 +1,23 @@ -from services.db_context import db +from tortoise import fields + from configs.config import Config +from services.db_context import Model -class FriendUser(db.Model): - __tablename__ = "friend_users" +class FriendUser(Model): - id = db.Column(db.Integer(), primary_key=True) - user_id = db.Column(db.BigInteger(), nullable=False) - user_name = db.Column(db.Unicode(), nullable=False, default="") - nickname = db.Column(db.Unicode()) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_id = fields.BigIntField(unique=True) + """用户id""" + user_name = fields.CharField(max_length=255, default="") + """用户名称""" + nickname = fields.CharField(max_length=255, null=True) + """私聊下自定义昵称""" - _idx1 = db.Index("friend_users_idx1", "user_id", unique=True) + class Meta: + table = "friend_users" + table_description = "好友信息数据表" @classmethod async def get_user_name(cls, user_id: int) -> str: @@ -20,77 +27,29 @@ class FriendUser(db.Model): 参数: :param user_id: qq号 """ - query = cls.query.where(cls.user_id == user_id) - user = await query.gino.first() - if user: + if user := await cls.get_or_none(user_id=user_id): return user.user_name - else: - return "" + return "" @classmethod - async def add_friend_info(cls, user_id: int, user_name: str) -> bool: - """ - 说明: - 添加好友信息 - 参数: - :param user_id: qq号 - :param user_name: 用户名称 - """ - try: - query = cls.query.where(cls.user_id == user_id) - user = await query.with_for_update().gino.first() - if not user: - await cls.create( - user_id=user_id, - user_name=user_name, - ) - else: - await user.update( - user_name=user_name, - ).apply() - return True - except Exception: - return False - - @classmethod - async def delete_friend_info(cls, user_id: int) -> bool: - """ - 说明: - 删除好友信息 - 参数: - :param user_id: qq号 - """ - try: - query = cls.query.where(cls.user_id == user_id) - user = await query.with_for_update().gino.first() - if user: - await user.delete() - return True - except Exception: - return False - - @classmethod - async def get_friend_nickname(cls, user_id: int) -> str: + async def get_user_nickname(cls, user_id: int) -> str: """ 说明: 获取用户昵称 参数: :param user_id: qq号 """ - query = cls.query.where(cls.user_id == user_id) - user = await query.gino.first() - if user: + if user := await cls.get_or_none(user_id=user_id): if user.nickname: _tmp = "" - black_word = Config.get_config("nickname", "BLACK_WORD") - if black_word: + if black_word := Config.get_config("nickname", "BLACK_WORD"): for x in user.nickname: _tmp += "*" if x in black_word else x return _tmp return "" @classmethod - async def set_friend_nickname(cls, user_id: int, nickname: str) -> bool: + async def set_user_nickname(cls, user_id: int, nickname: str): """ 说明: 设置用户昵称 @@ -98,18 +57,4 @@ class FriendUser(db.Model): :param user_id: qq号 :param nickname: 昵称 """ - try: - query = cls.query.where(cls.user_id == user_id) - user = await query.with_for_update().gino.first() - if not user: - await cls.create( - user_id=user_id, - nickname=nickname, - ) - else: - await user.update( - nickname=nickname, - ).apply() - return True - except Exception: - return False + await cls.update_or_create(user_id=user_id, defaults={"nickname": nickname}) diff --git a/models/goods_info.py b/models/goods_info.py index 64926be4..431c1da8 100644 --- a/models/goods_info.py +++ b/models/goods_info.py @@ -1,38 +1,50 @@ -from services.db_context import db -from typing import Optional, List, Tuple -from services.log import logger +from typing import Dict, List, Optional, Tuple + +from tortoise import fields + +from services.db_context import Model -class GoodsInfo(db.Model): +class GoodsInfo(Model): __tablename__ = "goods_info" - id = db.Column(db.Integer(), primary_key=True) - goods_name = db.Column(db.TEXT(), nullable=False) # 名称 - goods_price = db.Column(db.Integer(), nullable=False) # 价格 - goods_description = db.Column(db.TEXT(), nullable=False) # 商品描述 - goods_discount = db.Column(db.Numeric(scale=3, asdecimal=False), default=1) # 打折 - goods_limit_time = db.Column(db.BigInteger(), default=0) # 限时 - daily_limit = db.Column(db.Integer(), nullable=False, default=0) # 每日购买限制 - daily_purchase_limit = db.Column( - db.JSON(), nullable=False, default={} - ) # 每日购买限制数据存储 - is_passive = db.Column(db.Boolean(), nullable=False, default=0) # 是否为被动 - icon = db.Column(db.String(), default=0) # 图标 + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + goods_name = fields.CharField(255, unique=True) + """商品名称""" + goods_price = fields.IntField() + """价格""" + goods_description = fields.TextField() + """描述""" + goods_discount = fields.FloatField(default=1) + """折扣""" + goods_limit_time = fields.BigIntField(default=0) + """限时""" + daily_limit = fields.IntField(default=0) + """每日限购""" + daily_purchase_limit: Dict[str, Dict[str, int]] = fields.JSONField(default={}) + """用户限购记录""" + is_passive = fields.BooleanField(default=False) + """是否为被动道具""" + icon = fields.TextField(null=True) + """图标路径""" - _idx1 = db.Index("goods_group_users_idx1", "goods_name", unique=True) + class Meta: + table = "goods_info" + table_description = "商品数据表" @classmethod async def add_goods( - cls, - goods_name: str, - goods_price: int, - goods_description: str, - goods_discount: float = 1, - goods_limit_time: int = 0, - daily_limit: int = 0, - is_passive: bool = False, - icon: Optional[str] = None, - ) -> bool: + cls, + goods_name: str, + goods_price: int, + goods_description: str, + goods_discount: float = 1, + goods_limit_time: int = 0, + daily_limit: int = 0, + is_passive: bool = False, + icon: Optional[str] = None, + ): """ 说明: 添加商品 @@ -46,22 +58,17 @@ class GoodsInfo(db.Model): :param is_passive: 是否为被动道具 :param icon: 图标 """ - try: - if not await cls.get_goods_info(goods_name): - await cls.create( - goods_name=goods_name, - goods_price=goods_price, - goods_description=goods_description, - goods_discount=goods_discount, - goods_limit_time=goods_limit_time, - daily_limit=daily_limit, - is_passive=is_passive, - icon=icon - ) - return True - except Exception as e: - logger.error(f"GoodsInfo add_goods {goods_name} 发生错误 {type(e)}:{e}") - return False + if not await cls.filter(goods_name=goods_name).first(): + await cls.create( + goods_name=goods_name, + goods_price=goods_price, + goods_description=goods_description, + goods_discount=goods_discount, + goods_limit_time=goods_limit_time, + daily_limit=daily_limit, + is_passive=is_passive, + icon=icon, + ) @classmethod async def delete_goods(cls, goods_name: str) -> bool: @@ -71,28 +78,23 @@ class GoodsInfo(db.Model): 参数: :param goods_name: 商品名称 """ - query = ( - await cls.query.where(cls.goods_name == goods_name) - .with_for_update() - .gino.first() - ) - if not query: - return False - await query.delete() - return True + if goods := await cls.get_or_none(goods_name=goods_name): + await goods.delete() + return True + return False @classmethod async def update_goods( - cls, - goods_name: str, - goods_price: Optional[int] = None, - goods_description: Optional[str] = None, - goods_discount: Optional[float] = None, - goods_limit_time: Optional[int] = None, - daily_limit: Optional[int] = None, - is_passive: Optional[bool] = None, - icon: Optional[str] = None, - ) -> bool: + cls, + goods_name: str, + goods_price: Optional[int] = None, + goods_description: Optional[str] = None, + goods_discount: Optional[float] = None, + goods_limit_time: Optional[int] = None, + daily_limit: Optional[int] = None, + is_passive: Optional[bool] = None, + icon: Optional[str] = None, + ): """ 说明: 更新商品信息 @@ -106,37 +108,25 @@ class GoodsInfo(db.Model): :param is_passive: 是否为被动 :param icon: 图标 """ - try: - query = ( - await cls.query.where(cls.goods_name == goods_name) - .with_for_update() - .gino.first() + if goods := await cls.get_or_none(goods_name=goods_name): + await cls.update_or_create( + goods_name=goods_name, + defaults={ + "goods_price": goods_price or goods.goods_price, + "goods_description": goods_description or goods.goods_description, + "goods_discount": goods_discount or goods.goods_discount, + "goods_limit_time": goods_limit_time + if goods_limit_time is not None + else goods.goods_limit_time, + "daily_limit": daily_limit + if daily_limit is not None + else goods.daily_limit, + "is_passive": is_passive + if is_passive is not None + else goods.is_passive, + "icon": icon or goods.icon, + }, ) - if not query: - return False - await query.update( - goods_price=goods_price or query.goods_price, - goods_description=goods_description or query.goods_description, - goods_discount=goods_discount or query.goods_discount, - goods_limit_time=goods_limit_time if goods_limit_time is not None else query.goods_limit_time, - daily_limit=daily_limit if daily_limit is not None else query.daily_limit, - is_passive=is_passive if is_passive is not None else query.is_passive, - icon=icon or query.icon - ).apply() - return True - except Exception as e: - logger.error(f"GoodsInfo update_goods 发生错误 {type(e)}:{e}") - return False - - @classmethod - async def get_goods_info(cls, goods_name: str) -> "GoodsInfo": - """ - 说明: - 获取商品对象 - 参数: - :param goods_name: 商品名称 - """ - return await cls.query.where(cls.goods_name == goods_name).gino.first() @classmethod async def get_all_goods(cls) -> List["GoodsInfo"]: @@ -144,7 +134,7 @@ class GoodsInfo(db.Model): 说明: 获得全部有序商品对象 """ - query = await cls.query.gino.all() + query = await cls.all() id_lst = [x.id for x in query] goods_lst = [] for _ in range(len(query)): @@ -155,7 +145,7 @@ class GoodsInfo(db.Model): @classmethod async def add_user_daily_purchase( - cls, goods: "GoodsInfo", user_id: int, group_id: int, num: int = 1 + cls, goods: "GoodsInfo", user_id_: int, group_id_: int, num: int = 1 ): """ 说明: @@ -166,19 +156,19 @@ class GoodsInfo(db.Model): :param group_id: 群号 :param num: 数量 """ - user_id = str(user_id) - group_id = str(group_id) + user_id = str(user_id_) + group_id = str(group_id_) if goods and goods.daily_limit and goods.daily_limit > 0: if not goods.daily_purchase_limit.get(group_id): goods.daily_purchase_limit[group_id] = {} if not goods.daily_purchase_limit[group_id].get(user_id): goods.daily_purchase_limit[group_id][user_id] = 0 goods.daily_purchase_limit[group_id][user_id] += num - await goods.update(daily_purchase_limit=goods.daily_purchase_limit).apply() + await goods.save(update_fields=["daily_purchase_limit"]) @classmethod async def check_user_daily_purchase( - cls, goods: "GoodsInfo", user_id: int, group_id: int, num: int = 1 + cls, goods: "GoodsInfo", user_id_: int, group_id_: int, num: int = 1 ) -> Tuple[bool, int]: """ 说明: @@ -189,8 +179,8 @@ class GoodsInfo(db.Model): :param group_id: 群号 :param num: 数量 """ - user_id = str(user_id) - group_id = str(group_id) + user_id = str(user_id_) + group_id = str(group_id_) if goods and goods.daily_limit > 0: if ( not goods.daily_limit @@ -204,10 +194,3 @@ class GoodsInfo(db.Model): goods.daily_limit - goods.daily_purchase_limit[group_id][user_id], ) return False, 0 - - @classmethod - async def reset_daily_purchase(cls): - """ - 重置每次次数限制 - """ - await cls.update.values(daily_purchase_limit={}).gino.status() diff --git a/models/group_info.py b/models/group_info.py index 6131b341..88c598da 100755 --- a/models/group_info.py +++ b/models/group_info.py @@ -1,111 +1,24 @@ -from services.db_context import db -from services.log import logger from typing import List, Optional +from tortoise import fields -class GroupInfo(db.Model): - __tablename__ = "group_info" +from services.db_context import Model +from services.log import logger - group_id = db.Column(db.BigInteger(), nullable=False, primary_key=True) - group_name = db.Column(db.Unicode(), nullable=False, default="") - max_member_count = db.Column(db.Integer(), nullable=False, default=0) - member_count = db.Column(db.Integer(), nullable=False, default=0) - group_flag = db.Column(db.Integer(), nullable=False, default=0) - _idx1 = db.Index("group_info_idx1", "group_id", unique=True) +class GroupInfo(Model): - @classmethod - async def get_group_info(cls, group_id: int) -> "GroupInfo": - """ - 说明: - 获取群信息 - 参数: - :param group_id: 群号 - """ - query = cls.query.where(cls.group_id == group_id) - return await query.gino.first() + group_id = fields.BigIntField(pk=True) + """群聊id""" + group_name = fields.TextField(default="") + """群聊名称""" + max_member_count = fields.IntField(default=0) + """最大人数""" + member_count = fields.IntField(default=0) + """当前人数""" + group_flag: int = fields.IntField(default=0) + """群认证标记""" - @classmethod - async def add_group_info( - cls, - group_id: int, - group_name: str, - max_member_count: int, - member_count: int, - group_flag: Optional[int] = None, - ) -> bool: - """ - 说明: - 添加群信息 - 参数: - :param group_id: 群号 - :param group_name: 群名称 - :param max_member_count: 群员最大数量 - :param member_count: 群员数量 - :param group_flag: 群认证,0为未认证,1为认证 - """ - try: - group = ( - await cls.query.where(cls.group_id == group_id) - .with_for_update() - .gino.first() - ) - if group: - await group.update( - group_name=group_name, - max_member_count=max_member_count, - member_count=member_count, - ).apply() - if group_flag is not None: - await group.update(group_flag=group_flag).apply() - else: - await cls.create( - group_id=group_id, - group_name=group_name, - max_member_count=max_member_count, - member_count=member_count, - group_flag=group_flag, - ) - return True - except Exception as e: - logger.info(f"GroupInfo 调用 add_group_info 发生错误 {type(e)}:{e}") - return False - - @classmethod - async def delete_group_info(cls, group_id: int): - """ - 说明: - 删除群信息 - 参数: - :param group_id: 群号 - """ - await cls.delete.where(cls.group_id == group_id).gino.status() - - @classmethod - async def get_all_group(cls) -> List["GroupInfo"]: - """ - 说明: - 获取所有群对象 - """ - query = await cls.query.gino.all() - return query - - @classmethod - async def set_group_flag(cls, group_id: int, group_flag: int) -> bool: - """ - 设置群认证 - :param group_id: 群号 - :param group_flag: 群认证,0为未认证,1为认证 - """ - group = ( - await cls.query.where(cls.group_id == group_id) - .with_for_update() - .gino.first() - ) - if group: - if group.group_flag != group_flag: - await group.update( - group_flag=group_flag, - ).apply() - return True - return False + class Meta: + table = "group_info" + table_description = "群聊信息表" diff --git a/models/group_member_info.py b/models/group_member_info.py index 4d37b10d..d374dc63 100755 --- a/models/group_member_info.py +++ b/models/group_member_info.py @@ -1,116 +1,48 @@ from datetime import datetime +from typing import List, Optional, Set + +from tortoise import fields + from configs.config import Config -from services.db_context import db -from typing import List, Optional +from services.db_context import Model -class GroupInfoUser(db.Model): - __tablename__ = "group_info_users" +class GroupInfoUser(Model): - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - user_name = db.Column(db.Unicode(), nullable=False) - group_id = db.Column(db.BigInteger(), nullable=False) - user_join_time = db.Column(db.DateTime(), nullable=False) - nickname = db.Column(db.Unicode()) - uid = db.Column(db.BigInteger()) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + user_name = fields.CharField(255, default="") + """用户昵称""" + group_id = fields.BigIntField() + """群聊id""" + user_join_time = fields.DatetimeField(null=True) + """用户入群时间""" + nickname = fields.CharField(255, null=True) + """群聊昵称""" + uid = fields.BigIntField(null=True) + """用户uid""" - _idx1 = db.Index("info_group_users_idx1", "user_qq", "group_id", unique=True) + class Meta: + table = "group_info_users" + table_description = "群员信息数据表" + unique_together = ("user_qq", "group_id") @classmethod - async def add_member_info( - cls, - user_qq: int, - group_id: int, - user_name: str, - user_join_time: datetime, - uid: Optional[int] = None, - ) -> bool: - """ - 说明: - 添加群内用户信息 - 参数: - :param user_qq: qq号 - :param group_id: 群号 - :param user_name: 用户名称 - :param user_join_time: 入群时间 - :param uid: 用户唯一 id(自动生成) - """ - query = cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - try: - if not await query.gino.first(): - await cls.create( - user_qq=user_qq, - user_name=user_name, - group_id=group_id, - user_join_time=user_join_time, - uid=uid - ) - return True - except Exception: - return False - - @classmethod - async def get_member_info( - cls, user_qq: int, group_id: int - ) -> "GroupInfoUser": - """ - 说明: - 查询群员信息 - 参数: - :param user_qq: qq号 - :param group_id: 群号 - """ - query = cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - return await query.gino.first() - - @classmethod - async def delete_member_info(cls, user_qq: int, group_id: int) -> bool: - """ - 说明: - 删除群员信息 - 参数: - :param user_qq: qq号 - :param group_id: 群号 - """ - query = cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - query = query.with_for_update() - user = await query.gino.first() - try: - if user is None: - return True - else: - await cls.delete.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ).gino.status() - return True - except Exception: - return False - - @classmethod - async def get_group_member_id_list(cls, group_id: int) -> List[int]: + async def get_group_member_id_list(cls, group_id: int) -> Set[int]: """ 说明: 获取该群所有用户qq 参数: :param group_id: 群号 """ - member_list = [] - query = cls.query.where((cls.group_id == group_id)) - for user in await query.gino.all(): - member_list.append(user.user_qq) - return member_list + return set( + await cls.filter(group_id=group_id).values_list("user_qq", flat=True) + ) @classmethod - async def set_group_member_nickname( - cls, user_qq: int, group_id: int, nickname: str - ) -> bool: + async def set_user_nickname(cls, user_qq: int, group_id: int, nickname: str): """ 说明: 设置群员在该群内的昵称 @@ -119,14 +51,11 @@ class GroupInfoUser(db.Model): :param group_id: 群号 :param nickname: 昵称 """ - query = cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) + await cls.update_or_create( + user_qq=user_qq, + group_id=group_id, + defaults={"nickname": nickname}, ) - user = await query.with_for_update().gino.first() - if user: - await user.update(nickname=nickname).apply() - return True - return False @classmethod async def get_user_all_group(cls, user_qq: int) -> List[int]: @@ -136,13 +65,12 @@ class GroupInfoUser(db.Model): 参数: :param user_qq: 用户qq """ - query = await cls.query.where(cls.user_qq == user_qq).gino.all() - if query: - query = [x.group_id for x in query] - return query + return list( + await cls.filter(user_qq=user_qq).values_list("group_id", flat=True) + ) @classmethod - async def get_group_member_nickname(cls, user_qq: int, group_id: int) -> str: + async def get_user_nickname(cls, user_qq: int, group_id: int) -> str: """ 说明: 获取用户在该群的昵称 @@ -150,46 +78,34 @@ class GroupInfoUser(db.Model): :param user_qq: qq号 :param group_id: 群号 """ - query = cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - user = await query.gino.first() - if user: + if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id): if user.nickname: - _tmp = "" - black_word = Config.get_config("nickname", "BLACK_WORD") - if black_word: + nickname = "" + if black_word := Config.get_config("nickname", "BLACK_WORD"): for x in user.nickname: - _tmp += "*" if x in black_word else x - return _tmp + nickname += "*" if x in black_word else x + return nickname + return user.nickname return "" @classmethod - async def get_group_member_uid(cls, user_qq: int, group_id: int) -> Optional[str]: - query = cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - user = await query.gino.first() - _max_uid = cls.query.where((cls.user_qq == 114514) & (cls.group_id == 114514)).with_for_update() - _max_uid_user = await _max_uid.gino.first() + async def get_group_member_uid(cls, user_qq: int, group_id: int) -> Optional[int]: + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + _max_uid_user, _ = await cls.get_or_create(user_qq=114514, group_id=114514) _max_uid = _max_uid_user.uid - if not user or not user.uid: - all_user = await cls.query.where(cls.user_qq == user_qq).gino.all() + if not user.uid: + all_user = await cls.filter(user_qq=user_qq).all() for x in all_user: if x.uid: return x.uid - else: - if not user: - await GroupInfoUser.add_member_info(user_qq, group_id, '', datetime.min) - user = await cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ).gino.first() - await user.update( - uid=_max_uid + 1, - ).apply() - await _max_uid_user.update( - uid=_max_uid + 1, - ).apply() - - return user.uid if user and user.uid else None + user.uid = _max_uid + 1 + _max_uid_user.uid = _max_uid + 1 + await cls.bulk_update([user, _max_uid_user], ["uid"]) + return user.uid + @classmethod + async def _run_script(cls): + await cls.raw( + "alter table group_info_users alter user_join_time drop not null;" + ) + """允许 user_join_time 为空""" diff --git a/models/level_user.py b/models/level_user.py index bbc02db7..e0766fca 100755 --- a/models/level_user.py +++ b/models/level_user.py @@ -1,18 +1,25 @@ -from asyncpg import UniqueViolationError +from tortoise import fields -from services.db_context import db +from services.db_context import Model -class LevelUser(db.Model): - __tablename__ = "level_users" +class LevelUser(Model): - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger(), nullable=False) - user_level = db.Column(db.BigInteger(), nullable=False) - group_flag = db.Column(db.Integer(), nullable=False, default=0) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField() + """群聊id""" + user_level = fields.BigIntField() + """用户权限等级""" + group_flag = fields.IntField(default=0) + """特殊标记,是否随群管理员变更而设置权限""" - _idx1 = db.Index("level_group_users_idx1", "user_qq", "group_id", unique=True) + class Meta: + table = "level_users" + table_description = "用户权限数据库" + unique_together = ("user_qq", "group_id") @classmethod async def get_user_level(cls, user_qq: int, group_id: int) -> int: @@ -23,17 +30,14 @@ class LevelUser(db.Model): :param user_qq: qq号 :param group_id: 群号 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - user = await query.gino.first() - if user: + if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id): return user.user_level - else: - return -1 + return -1 @classmethod async def set_level( cls, user_qq: int, group_id: int, level: int, group_flag: int = 0 - ) -> bool: + ): """ 说明: 设置用户在群内的权限 @@ -43,23 +47,11 @@ class LevelUser(db.Model): :param level: 权限等级 :param group_flag: 是否被自动更新刷新权限 0:是,1:否 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - query = query.with_for_update() - user = await query.gino.first() - try: - if not user: - await cls.create( - user_qq=user_qq, - group_id=group_id, - user_level=level, - group_flag=group_flag, - ) - return True - else: - await user.update(user_level=level, group_flag=group_flag).apply() - return False - except UniqueViolationError: - return False + await cls.update_or_create( + user_qq=user_qq, + group_id=group_id, + defaults={"user_level": level, "group_flag": group_flag}, + ) @classmethod async def delete_level(cls, user_qq: int, group_id: int) -> bool: @@ -70,14 +62,10 @@ class LevelUser(db.Model): :param user_qq: qq号 :param group_id: 群号 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - query = query.with_for_update() - user = await query.gino.first() - if user is None: - return False - else: + if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id): await user.delete() return True + return False @classmethod async def check_level(cls, user_qq: int, group_id: int, level: int) -> bool: @@ -89,25 +77,14 @@ class LevelUser(db.Model): :param group_id: 群号 :param level: 权限等级 """ - if group_id != 0: - query = cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - user = await query.gino.first() - if user is None: - return False - user_level = user.user_level + if group_id: + if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id): + return user.user_level > level else: - query = cls.query.where(cls.user_qq == user_qq) - highest_level = 0 - for user in await query.gino.all(): - if user.user_level > highest_level: - highest_level = user.user_level - user_level = highest_level - if user_level >= level: - return True - else: - return False + user_list = await cls.filter(user_qq=user_qq).all() + user = max(user_list, key=lambda x: x.user_level) + return user.user_level > level + return False @classmethod async def is_group_flag(cls, user_qq: int, group_id: int) -> bool: @@ -118,12 +95,6 @@ class LevelUser(db.Model): :param user_qq: qq号 :param group_id: 群号 """ - user = await cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ).gino.first() - if not user: - return False - if user.group_flag == 1: - return True - else: - return False + if user := await cls.get_or_none(user_qq=user_qq, group_id=group_id): + return user.group_flag == 1 + return False diff --git a/models/sign_group_user.py b/models/sign_group_user.py index c7919b9f..a0803f05 100755 --- a/models/sign_group_user.py +++ b/models/sign_group_user.py @@ -1,99 +1,78 @@ from datetime import datetime -from typing import List -from services.db_context import db +from typing import Dict, List, Literal, Optional, Tuple, Union + +from tortoise import fields + +from services.db_context import Model -class SignGroupUser(db.Model): - __tablename__ = "sign_group_users" +class SignGroupUser(Model): - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger(), nullable=False) - checkin_count = db.Column(db.Integer(), nullable=False) - checkin_time_last = db.Column(db.DateTime(timezone=True), nullable=False) - impression = db.Column(db.Numeric(scale=3, asdecimal=False), nullable=False) - add_probability = db.Column( - db.Numeric(scale=3, asdecimal=False), nullable=False, default=0 - ) - specify_probability = db.Column( - db.Numeric(scale=3, asdecimal=False), nullable=False, default=0 - ) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField() + """群聊id""" + checkin_count = fields.IntField(default=0) + """签到次数""" + checkin_time_last = fields.DatetimeField(auto_now=True) + """最后签到时间""" + impression = fields.DecimalField(10, 3, default=0) + """好感度""" + add_probability = fields.DecimalField(10, 3, default=0) + """双倍签到增加概率""" + specify_probability = fields.DecimalField(10, 3, default=0) + """使用指定双倍概率""" + # specify_probability = fields.DecimalField(10, 3, default=0) - _idx1 = db.Index("sign_group_users_idx1", "user_qq", "group_id", unique=True) + class Meta: + table = "sign_group_users" + table_description = "群员签到数据表" + unique_together = ("user_qq", "group_id") @classmethod - async def ensure( - cls, user_qq: int, group_id: int, for_update: bool = False - ) -> "SignGroupUser": - """ - 说明: - 获取签到用户 - 参数: - :param user_qq: 用户qq - :param group_id: 所在群聊 - :param for_update: 是否存在修改数据 - """ - query = cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - if for_update: - query = query.with_for_update() - user = await query.gino.first() - return user or await cls.create( - user_qq=user_qq, - group_id=group_id, - checkin_count=0, - checkin_time_last=datetime.min, # 从未签到过 - impression=0, - ) - - @classmethod - async def get_user_all_data(cls, user_qq: int) -> List["SignGroupUser"]: - """ - 说明: - 获取某用户所有数据 - 参数: - :param user_qq: 用户qq - """ - query = cls.query.where(cls.user_qq == user_qq) - query = query.with_for_update() - return await query.gino.all() - - @classmethod - async def sign(cls, user: "SignGroupUser", impression: float, checkin_time_last: datetime): + async def sign(cls, user: "SignGroupUser", impression: float): """ 说明: 签到 说明: :param user: 用户 :param impression: 增加的好感度 - :param checkin_time_last: 签到时间 """ - await user.update( - checkin_count=user.checkin_count + 1, - checkin_time_last=checkin_time_last, - impression=user.impression + impression, - add_probability=0, - specify_probability=0, - ).apply() + user.checkin_count = user.checkin_count + 1 + user.add_probability = 0 + user.specify_probability = 0 + user.impression = float(user.impression) + impression + await user.save( + update_fields=[ + "checkin_count", + "add_probability", + "specify_probability", + "impression", + ] + ) @classmethod - async def get_all_impression(cls, group_id: int) -> "list, list, list": + async def get_all_impression( + cls, group_id: Optional[int] + ) -> Tuple[List[int], List[int], List[float]]: """ 说明: 获取该群所有用户 id 及对应 好感度 参数: :param group_id: 群号 """ - impression_list = [] - user_qq_list = [] - user_group = [] if group_id: - query = cls.query.where(cls.group_id == group_id) + query = cls.filter(group_id=group_id) else: - query = cls.query - for user in await query.gino.all(): - impression_list.append(user.impression) - user_qq_list.append(user.user_qq) - user_group.append(user.group_id) - return user_qq_list, impression_list, user_group + query = cls + value_list = await query.all().values_list("user_qq", "group_id", "impression") # type: ignore + qq_list = [] + group_list = [] + impression_list = [] + for value in value_list: + qq_list.append(value[0]) + group_list.append(value[1]) + impression_list.append(float(value[2])) + return qq_list, impression_list, group_list diff --git a/models/user_shop_gold_log.py b/models/user_shop_gold_log.py index 76868fcb..5e444196 100644 --- a/models/user_shop_gold_log.py +++ b/models/user_shop_gold_log.py @@ -1,59 +1,29 @@ from datetime import datetime -from services.db_context import db +from tortoise import fields + +from services.db_context import Model -class UserShopGoldLog(db.Model): - __tablename__ = "user_shop_gold_log" - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger(), nullable=False) - type = db.Column(db.Integer(), nullable=False) # 0: 购买,1: 使用,2: 插件 - name = db.Column(db.String()) - spend_gold = db.Column(db.Integer(), nullable=False) - num = db.Column(db.Integer(), nullable=False) - create_time = db.Column(db.DateTime(timezone=True), nullable=False) +class UserShopGoldLog(Model): - @classmethod - async def add_shop_log( - cls, - user_qq: int, - group_id: int, - type_: int, - name: str, - num: int, - spend_gold: int = 0, - ): - """ - 说明: - 添加商店购买或使用日志 - 参数: - :param user_qq: qq号 - :param group_id: 所在群号 - :param type_: 类型 - :param name: 商品名称 - :param num: 数量 - :param spend_gold: 花费金币 - """ - await cls.create( - user_qq=user_qq, - group_id=group_id, - type=type_, - name=name, - num=num, - spend_gold=spend_gold, - create_time=datetime.now(), - ) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField() + """群聊id""" + type = fields.IntField() + """金币使用类型 0: 购买, 1: 使用, 2: 插件""" + name = fields.CharField(255) + """商品/插件 名称""" + spend_gold = fields.IntField(default=0) + """花费金币""" + num = fields.IntField() + """数量""" + create_time = fields.DatetimeField(auto_now_add=True) + """创建时间""" - @classmethod - async def get_user_log(cls, user_qq: int, group_id: int) -> "UserShopGoldLog": - """ - 说明: - 获取用户日志 - 参数: - :param user_qq: qq号 - :param group_id: 所在群号 - """ - return await cls.query.where( - (cls.user_qq == user_qq) & (cls.group_qq == group_id) - ).first() + class Meta: + table = "user_shop_gold_log" + table_description = "金币使用日志表" diff --git a/plugins/aconfig/__init__.py b/plugins/aconfig/__init__.py index 27afa36e..3397495b 100755 --- a/plugins/aconfig/__init__.py +++ b/plugins/aconfig/__init__.py @@ -1,20 +1,20 @@ -from utils.message_builder import image -from configs.path_config import IMAGE_PATH -from nonebot import on_command -from nonebot.rule import to_me +import os +import random + +from nonebot import on_command, on_keyword from nonebot.adapters.onebot.v11 import GroupMessageEvent from nonebot.adapters.onebot.v11.permission import GROUP -from utils.utils import FreqLimiter -from configs.config import NICKNAME -import random -from nonebot import on_keyword -import os +from nonebot.rule import to_me +from configs.config import NICKNAME +from configs.path_config import IMAGE_PATH +from utils.message_builder import image +from utils.utils import FreqLimiter __zx_plugin_name__ = "基本设置 [Hidden]" __plugin_usage__ = "用法: 无" __plugin_version__ = 0.1 -__plugin_author__ = 'HibiKier' +__plugin_author__ = "HibiKier" _flmt = FreqLimiter(300) @@ -29,7 +29,7 @@ async def _(event: GroupMessageEvent): return _flmt.start_cd(event.group_id) await config_play_game.finish( - image(random.choice(os.listdir(IMAGE_PATH / "dayouxi")), "dayouxi") + image(IMAGE_PATH / random.choice(os.listdir(IMAGE_PATH / "dayouxi"))) ) @@ -40,14 +40,14 @@ self_introduction = on_command( @self_introduction.handle() async def _(): - if NICKNAME.find('真寻') != -1: + if NICKNAME.find("真寻") != -1: result = ( "我叫绪山真寻\n" "你们可以叫我真寻,小真寻,哪怕你们叫我小寻子我也能接受!\n" "年龄的话我还是个**岁初中生(至少现在是)\n" "身高保密!!!(也就比美波里(姐姐..(妹妹))矮一点)\n" "我生日是在3月6号, 能记住的话我会很高兴的\n现在是自宅警备系的现役JC\n" - "最好的朋友是椛!\n" + image("zhenxun") + "最好的朋友是椛!\n" + image("zhenxun.jpg") ) await self_introduction.finish(result) @@ -57,5 +57,4 @@ my_wife = on_keyword({"老婆"}, rule=to_me(), priority=5, block=True) @my_wife.handle() async def _(): - await my_wife.finish(image("laopo.jpg", "other")) - + await my_wife.finish(image(IMAGE_PATH / "other" / "laopo.jpg")) diff --git a/plugins/ai/__init__.py b/plugins/ai/__init__.py index 96a1ad0c..361f5e34 100755 --- a/plugins/ai/__init__.py +++ b/plugins/ai/__init__.py @@ -1,18 +1,14 @@ - from nonebot import on_message -from nonebot.adapters.onebot.v11 import ( - Bot, - GroupMessageEvent, - Message, - MessageEvent, -) +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent from nonebot.rule import to_me + +from configs.config import NICKNAME, Config from models.friend_user import FriendUser from models.group_member_info import GroupInfoUser from services.log import logger from utils.utils import get_message_img, get_message_text + from .data_source import get_chat_result, hello, no_result -from configs.config import NICKNAME, Config __zx_plugin_name__ = "AI" __plugin_usage__ = f""" @@ -61,11 +57,9 @@ async def _(bot: Bot, event: MessageEvent): await ai.finish(hello()) img = img[0] if img else "" if isinstance(event, GroupMessageEvent): - nickname = await GroupInfoUser.get_group_member_nickname( - event.user_id, event.group_id - ) + nickname = await GroupInfoUser.get_user_nickname(event.user_id, event.group_id) else: - nickname = await FriendUser.get_friend_nickname(event.user_id) + nickname = await FriendUser.get_user_nickname(event.user_id) if not nickname: if isinstance(event, GroupMessageEvent): nickname = event.sender.card or event.sender.nickname diff --git a/plugins/ai/data_source.py b/plugins/ai/data_source.py index 8aadfa7a..295b5b40 100755 --- a/plugins/ai/data_source.py +++ b/plugins/ai/data_source.py @@ -1,11 +1,13 @@ import os import random import re -from utils.http_utils import AsyncHttpx -from configs.path_config import IMAGE_PATH, DATA_PATH + +from configs.config import NICKNAME, Config +from configs.path_config import DATA_PATH, IMAGE_PATH from services.log import logger -from utils.message_builder import image, face -from configs.config import Config, NICKNAME +from utils.http_utils import AsyncHttpx +from utils.message_builder import face, image + from .utils import ai_message_manager try: @@ -124,7 +126,9 @@ async def xie_ai(text: str) -> str: :param text: 问题 :return: 青云可回复 """ - res = await AsyncHttpx.get(f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={text}") + res = await AsyncHttpx.get( + f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={text}" + ) content = "" try: data = json.loads(res.text) @@ -176,9 +180,9 @@ def hello() -> str: ) img = random.choice(os.listdir(IMAGE_PATH / "zai")) if img[-4:] == ".gif": - result += image(img, "zai") + result += image(IMAGE_PATH / "zai" / img) else: - result += image(img, "zai") + result += image(IMAGE_PATH / "zai" / img) return result @@ -187,17 +191,16 @@ def no_result() -> str: """ 没有回答时的回复 """ - return ( - random.choice( - [ - "你在说啥子?", - f"纯洁的{NICKNAME}没听懂", - "下次再告诉你(下次一定)", - "你觉得我听懂了吗?嗯?", - "我!不!知!道!", - ] - ) - + image(random.choice(os.listdir(IMAGE_PATH / "noresult")), "noresult") + return random.choice( + [ + "你在说啥子?", + f"纯洁的{NICKNAME}没听懂", + "下次再告诉你(下次一定)", + "你觉得我听懂了吗?嗯?", + "我!不!知!道!", + ] + ) + image( + IMAGE_PATH / "noresult" / random.choice(os.listdir(IMAGE_PATH / "noresult")) ) diff --git a/plugins/bilibili_sub/__init__.py b/plugins/bilibili_sub/__init__.py index ec567d33..83f30471 100755 --- a/plugins/bilibili_sub/__init__.py +++ b/plugins/bilibili_sub/__init__.py @@ -1,28 +1,29 @@ -from nonebot import on_command, on_regex -from nonebot.typing import T_State -from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, Message +from typing import Any, Optional, Tuple +import nonebot +from nonebot import Driver, on_command, on_regex +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent +from nonebot.params import ArgStr, CommandArg, RegexGroup +from nonebot.typing import T_State + +from configs.config import Config +from models.level_user import LevelUser +from services.log import logger from utils.image_utils import text2image +from utils.manager import group_manager from utils.message_builder import image +from utils.utils import get_bot, is_number, scheduler + from .data_source import ( + BilibiliSub, + SubManager, add_live_sub, - delete_sub, - add_up_sub, add_season_sub, + add_up_sub, + delete_sub, get_media_id, get_sub_status, - SubManager, - BilibiliSub, ) -from models.level_user import LevelUser -from utils.manager import group_manager -from configs.config import Config -from utils.utils import is_number, scheduler, get_bot -from typing import Optional, Tuple, Any -from services.log import logger -from nonebot import Driver -from nonebot.params import CommandArg, ArgStr, RegexGroup -import nonebot __zx_plugin_name__ = "B站订阅" __plugin_usage__ = """ @@ -168,20 +169,20 @@ async def _( sub_user: str = ArgStr("sub_user"), ): if sub_type in ["主播", "直播"]: - result = await BilibiliSub.delete_bilibili_sub(int(id_),sub_user,"live") + result = await BilibiliSub.delete_bilibili_sub(int(id_), sub_user, "live") elif sub_type.lower() in ["up", "用户"]: - result = await BilibiliSub.delete_bilibili_sub(int(id_),sub_user,"up") - else: result = await BilibiliSub.delete_bilibili_sub(int(id_),sub_user) + result = await BilibiliSub.delete_bilibili_sub(int(id_), sub_user, "up") + else: + result = await BilibiliSub.delete_bilibili_sub(int(id_), sub_user) if result: await del_sub.send(f"删除订阅id:{id_} 成功...") logger.info( - f"(USER {event.user_id}, GROUP " - f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" - f" 删除订阅 {id_}" - ) + f"(USER {event.user_id}, GROUP " + f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" + f" 删除订阅 {id_}" + ) else: await del_sub.send(f"删除订阅id:{id_} 失败...") - @show_sub_info.handle() @@ -190,7 +191,7 @@ async def _(event: MessageEvent): id_ = f"{event.group_id}" else: id_ = f"{event.user_id}" - data = await BilibiliSub.get_sub_data(id_) + data = await BilibiliSub.filter(sub_users__contains=id_).all() live_rst = "" up_rst = "" season_rst = "" @@ -217,11 +218,9 @@ async def _(event: MessageEvent): ) await show_sub_info.send( image( - b64=( - await text2image( - live_rst + up_rst + season_rst, padding=10, color="#f9f6f2" - ) - ).pic2bs4() + await text2image( + live_rst + up_rst + season_rst, padding=10, color="#f9f6f2" + ) ) ) @@ -277,7 +276,9 @@ async def send_sub_msg(rst: str, sub: BilibiliSub, bot: Bot): ): rst = "[CQ:at,qq=all]\n" + rst if group_manager.get_plugin_status("bilibili_sub", group_id): - await bot.send_group_msg(group_id=group_id, message=Message(rst)) + await bot.send_group_msg( + group_id=group_id, message=Message(rst) + ) else: await bot.send_private_msg(user_id=int(x), message=Message(rst)) except Exception as e: diff --git a/plugins/bilibili_sub/data_source.py b/plugins/bilibili_sub/data_source.py index dd3cdebb..a8175257 100755 --- a/plugins/bilibili_sub/data_source.py +++ b/plugins/bilibili_sub/data_source.py @@ -1,34 +1,33 @@ -from bilireq.exceptions import ResponseCodeError -from nonebot.adapters.onebot.v11 import MessageSegment - -from utils.manager import resources_manager +import random from asyncio.exceptions import TimeoutError +from datetime import datetime +from typing import Optional, Tuple -from utils.utils import get_bot -from .model import BilibiliSub -from bilireq.live import get_room_info_by_id -from .utils import get_meta, get_user_card -from utils.message_builder import image -from bilireq.user import get_videos # from .utils import get_videos from bilireq import dynamic -from typing import Optional, Tuple -from configs.path_config import IMAGE_PATH -from datetime import datetime -from utils.browser import get_browser -from services.db_context import db +from bilireq.exceptions import ResponseCodeError +from bilireq.live import get_room_info_by_id +from bilireq.user import get_videos +from nonebot.adapters.onebot.v11 import MessageSegment + +from configs.path_config import IMAGE_PATH, TEMP_PATH from services.log import logger -from utils.http_utils import AsyncHttpx -import random +from utils.browser import get_browser +from utils.http_utils import AsyncHttpx, AsyncPlaywright +from utils.manager import resources_manager +from utils.message_builder import image +from utils.utils import get_bot + +from .model import BilibiliSub +from .utils import get_meta, get_user_card + +SEARCH_URL = "https://api.bilibili.com/x/web-interface/search/all/v2" + +DYNAMIC_PATH = IMAGE_PATH / "bilibili_sub" / "dynamic" +DYNAMIC_PATH.mkdir(exist_ok=True, parents=True) -bilibili_search_url = "https://api.bilibili.com/x/web-interface/search/all/v2" - -dynamic_path = IMAGE_PATH / "bilibili_sub" / "dynamic" -dynamic_path.mkdir(exist_ok=True, parents=True) - - -resources_manager.add_temp_dir(dynamic_path) +resources_manager.add_temp_dir(DYNAMIC_PATH) async def add_live_sub(live_id: int, sub_user: str) -> str: @@ -49,7 +48,7 @@ async def add_live_sub(live_id: int, sub_user: str) -> str: short_id = live_info["short_id"] title = live_info["title"] live_status = live_info["live_status"] - if await BilibiliSub.add_bilibili_sub( + if await BilibiliSub.sub_handle( room_id, "live", sub_user, @@ -58,14 +57,14 @@ async def add_live_sub(live_id: int, sub_user: str) -> str: live_status=live_status, ): await _get_up_status(room_id) - uname = (await BilibiliSub.get_sub(room_id)).uname + uname = (await BilibiliSub.get_or_none(sub_id=room_id)).uname return ( "已成功订阅主播:\n" f"\ttitle:{title}\n" f"\tname: {uname}\n" f"\tlive_id:{room_id}\n" f"\tuid:{uid}" - ) + ) else: return "添加订阅失败..." except Exception as e: @@ -80,35 +79,34 @@ async def add_up_sub(uid: int, sub_user: str) -> str: :param sub_user: 订阅用户 """ try: - async with db.transaction(): - try: - """bilibili_api.user库中User类的get_user_info改为bilireq.user库的get_user_info方法""" - user_info = await get_user_card(uid) - except ResponseCodeError: - return f"未找到UpId:{uid} 的信息,请检查Id是否正确" - uname = user_info["name"] - """bilibili_api.user库中User类的get_dynamics改为bilireq.dynamic库的get_user_dynamics方法""" - dynamic_info = await dynamic.get_user_dynamics(uid) - dynamic_upload_time = 0 - if dynamic_info.get("cards"): - dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"] - """bilibili_api.user库中User类的get_videos改为bilireq.user库的get_videos方法""" - video_info = await get_videos(uid) - latest_video_created = 0 - if video_info["list"].get("vlist"): - latest_video_created = video_info["list"]["vlist"][0]["created"] - if await BilibiliSub.add_bilibili_sub( - uid, - "up", - sub_user, - uid=uid, - uname=uname, - dynamic_upload_time=dynamic_upload_time, - latest_video_created=latest_video_created, - ): - return "已成功订阅UP:\n" f"\tname: {uname}\n" f"\tuid:{uid}" - else: - return "添加订阅失败..." + try: + """bilibili_api.user库中User类的get_user_info改为bilireq.user库的get_user_info方法""" + user_info = await get_user_card(uid) + except ResponseCodeError: + return f"未找到UpId:{uid} 的信息,请检查Id是否正确" + uname = user_info["name"] + """bilibili_api.user库中User类的get_dynamics改为bilireq.dynamic库的get_user_dynamics方法""" + dynamic_info = await dynamic.get_user_dynamics(uid) + dynamic_upload_time = 0 + if dynamic_info.get("cards"): + dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"] + """bilibili_api.user库中User类的get_videos改为bilireq.user库的get_videos方法""" + video_info = await get_videos(uid) + latest_video_created = 0 + if video_info["list"].get("vlist"): + latest_video_created = video_info["list"]["vlist"][0]["created"] + if await BilibiliSub.sub_handle( + uid, + "up", + sub_user, + uid=uid, + uname=uname, + dynamic_upload_time=dynamic_upload_time, + latest_video_created=latest_video_created, + ): + return "已成功订阅UP:\n" f"\tname: {uname}\n" f"\tuid:{uid}" + else: + return "添加订阅失败..." except Exception as e: logger.error(f"订阅Up uid:{uid} 发生了错误 {type(e)}:{e}") return "添加订阅失败..." @@ -121,30 +119,29 @@ async def add_season_sub(media_id: int, sub_user: str) -> str: :param sub_user: 订阅用户 """ try: - async with db.transaction(): - try: - """bilibili_api.bangumi库中get_meta改为bilireq.bangumi库的get_meta方法""" - season_info = await get_meta(media_id) - except ResponseCodeError: - return f"未找到media_id:{media_id} 的信息,请检查Id是否正确" - season_id = season_info["media"]["season_id"] - season_current_episode = season_info["media"]["new_ep"]["index"] - season_name = season_info["media"]["title"] - if await BilibiliSub.add_bilibili_sub( - media_id, - "season", - sub_user, - season_name=season_name, - season_id=season_id, - season_current_episode=season_current_episode, - ): - return ( - "已成功订阅番剧:\n" - f"\ttitle: {season_name}\n" - f"\tcurrent_episode: {season_current_episode}" - ) - else: - return "添加订阅失败..." + try: + """bilibili_api.bangumi库中get_meta改为bilireq.bangumi库的get_meta方法""" + season_info = await get_meta(media_id) + except ResponseCodeError: + return f"未找到media_id:{media_id} 的信息,请检查Id是否正确" + season_id = season_info["media"]["season_id"] + season_current_episode = season_info["media"]["new_ep"]["index"] + season_name = season_info["media"]["title"] + if await BilibiliSub.sub_handle( + media_id, + "season", + sub_user, + season_name=season_name, + season_id=season_id, + season_current_episode=season_current_episode, + ): + return ( + "已成功订阅番剧:\n" + f"\ttitle: {season_name}\n" + f"\tcurrent_episode: {season_current_episode}" + ) + else: + return "添加订阅失败..." except Exception as e: logger.error(f"订阅番剧 media_id:{media_id} 发生了错误 {type(e)}:{e}") return "添加订阅失败..." @@ -171,9 +168,7 @@ async def get_media_id(keyword: str) -> dict: for _ in range(3): try: _season_data = {} - response = await AsyncHttpx.get( - bilibili_search_url, params=params, timeout=5 - ) + response = await AsyncHttpx.get(SEARCH_URL, params=params, timeout=5) if response.status_code == 200: data = response.json() if data.get("data"): @@ -227,9 +222,9 @@ async def _get_live_status(id_: int) -> Optional[str]: room_id = live_info["room_id"] live_status = live_info["live_status"] cover = live_info["user_cover"] - sub = await BilibiliSub.get_sub(id_) + sub = await BilibiliSub.get_or_none(sub_id=id_) if sub.live_status != live_status: - await BilibiliSub.update_sub_info(id_, live_status=live_status) + await BilibiliSub.sub_handle(id_, live_status=live_status) if sub.live_status in [0, 2] and live_status == 1: return ( f"" @@ -247,7 +242,7 @@ async def _get_up_status(id_: int) -> Optional[str]: :param id_: 订阅 id :return: """ - _user = await BilibiliSub.get_sub(id_) + _user = await BilibiliSub.get_or_none(sub_id=id_) """bilibili_api.user库中User类的get_user_info改为bilireq.user库的get_user_info方法""" user_info = await get_user_card(_user.uid) uname = user_info["name"] @@ -257,14 +252,14 @@ async def _get_up_status(id_: int) -> Optional[str]: video = None dividing_line = "\n-------------\n" if _user.uname != uname: - await BilibiliSub.update_sub_info(id_, uname=uname) + await BilibiliSub.sub_handle(id_, uname=uname) dynamic_img, dynamic_upload_time, link = await get_user_dynamic(_user.uid, _user) if video_info["list"].get("vlist"): video = video_info["list"]["vlist"][0] latest_video_created = video["created"] rst = "" if dynamic_img: - await BilibiliSub.update_sub_info(id_, dynamic_upload_time=dynamic_upload_time) + await BilibiliSub.sub_handle(id_, dynamic_upload_time=dynamic_upload_time) rst += f"{uname} 发布了动态!\n" f"{dynamic_img}\n{link}" if ( latest_video_created @@ -273,9 +268,7 @@ async def _get_up_status(id_: int) -> Optional[str]: and _user.latest_video_created < latest_video_created ): rst = rst + dividing_line if rst else rst - await BilibiliSub.update_sub_info( - id_, latest_video_created=latest_video_created - ) + await BilibiliSub.sub_handle(id_, latest_video_created=latest_video_created) rst += ( f'{image(video["pic"])}\n' f"{uname} 投稿了新视频啦\n" @@ -295,10 +288,10 @@ async def _get_season_status(id_) -> Optional[str]: """bilibili_api.bangumi库中get_meta改为bilireq.bangumi库的get_meta方法""" season_info = await get_meta(id_) title = season_info["media"]["title"] - _idx = (await BilibiliSub.get_sub(id_)).season_current_episode + _idx = (await BilibiliSub.get_or_none(sub_id=id_)).season_current_episode new_ep = season_info["media"]["new_ep"]["index"] if new_ep != _idx: - await BilibiliSub.update_sub_info( + await BilibiliSub.sub_handle( id_, season_current_episode=new_ep, season_update_time=datetime.now() ) return ( @@ -320,55 +313,22 @@ async def get_user_dynamic( """ """bilibili_api.user库中User类的get_dynamics改为bilireq.dynamic库的get_user_dynamics方法""" dynamic_info = await dynamic.get_user_dynamics(uid) - browser = await get_browser() - if dynamic_info.get("cards") and browser: + if dynamic_info.get("cards"): dynamic_upload_time = dynamic_info["cards"][0]["desc"]["timestamp"] dynamic_id = dynamic_info["cards"][0]["desc"]["dynamic_id"] if local_user.dynamic_upload_time < dynamic_upload_time: - context = await browser.new_context() - page = await context.new_page() - try: - await page.goto( - f"https://t.bilibili.com/{dynamic_id}", - wait_until="networkidle", - timeout=10000, - ) - # await page.set_viewport_size({"width": 2560, "height": 1080, "timeout": 10000*20}) # timeout: 200s - # 删除置顶 - # await page.evaluate( - # """ - # xs = document.getElementsByClassName('bili-dyn-item__tag'); - # for (x of xs) { - # x.parentNode.parentNode.remove(); - # } - # """ - # ) - # async with page.expect_popup() as popup_info: - # await page.locator(".bili-rich-text__content").click() - # details_page = await popup_info.value - await page.set_viewport_size( - {"width": 2560, "height": 1080, "timeout": 10000 * 20} - ) - await page.wait_for_selector(".bili-dyn-item__main") - card = page.locator(".bili-dyn-item__main") - await card.wait_for() - await card.screenshot( - path=dynamic_path / f"{local_user.sub_id}_{dynamic_upload_time}.jpg", - ) - except Exception as e: - logger.error(f"B站订阅:获取用户动态 发送错误 {type(e)}:{e}") - finally: - await context.close() - await page.close() - return ( - image( - f"{local_user.sub_id}_{dynamic_upload_time}.jpg", - "bilibili_sub/dynamic", - ), - dynamic_upload_time, - f"https://t.bilibili.com/{dynamic_id}" + image = await AsyncPlaywright.screenshot( + f"https://t.bilibili.com/{dynamic_id}", + DYNAMIC_PATH / f"sub_{local_user.sub_id}.png", + ".bili-dyn-item__main", + wait_until="networkidle", ) - return None, 0, '' + return ( + image, + dynamic_upload_time, + f"https://t.bilibili.com/{dynamic_id}", + ) + return None, 0, "" class SubManager: diff --git a/plugins/bilibili_sub/model.py b/plugins/bilibili_sub/model.py index 4ef5ae70..1f73d39d 100755 --- a/plugins/bilibili_sub/model.py +++ b/plugins/bilibili_sub/model.py @@ -1,43 +1,58 @@ -from services.log import logger -from services.db_context import db from datetime import datetime -from typing import Optional, List +from typing import List, Optional, Tuple + +from tortoise import fields + +from services.db_context import Model +from services.log import logger -class BilibiliSub(db.Model): - __tablename__ = "bilibili_sub" +class BilibiliSub(Model): - id = db.Column(db.Integer(), primary_key=True) - sub_id = db.Column(db.Integer(), nullable=False) - sub_type = db.Column(db.String(), nullable=False) - # 订阅用户 - sub_users = db.Column(db.String(), nullable=False) - # 直播 - live_short_id = db.Column(db.Integer()) - live_status = db.Column(db.Integer) - # 主播/UP - uid = db.Column(db.BigInteger()) - uname = db.Column(db.String()) - latest_video_created = db.Column(db.BigInteger()) # 视频上传时间 - dynamic_upload_time = db.Column(db.BigInteger(), default=0) # 动态发布时间 - # 番剧 - season_name = db.Column(db.String()) - season_id = db.Column(db.Integer()) - season_current_episode = db.Column(db.String()) - season_update_time = db.Column(db.DateTime()) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + sub_id = fields.IntField() + """订阅id""" + sub_type = fields.CharField(255) + """订阅类型""" + sub_users = fields.TextField() + """订阅用户""" + live_short_id = fields.IntField(null=True) + """直播短id""" + live_status = fields.IntField(null=True) + """直播状态 0: 停播 1: 直播""" + uid = fields.BigIntField(null=True) + """主播/UP UID""" + uname = fields.CharField(255, null=True) + """主播/UP 名称""" + latest_video_created = fields.BigIntField(null=True) + """最后视频上传时间""" + dynamic_upload_time = fields.BigIntField(null=True, default=0) + """动态发布时间""" + season_name = fields.CharField(255, null=True) + """番剧名称""" + season_id = fields.IntField(null=True) + """番剧id""" + season_current_episode = fields.CharField(255, null=True) + """番剧最新集数""" + season_update_time = fields.DateField(null=True) + """番剧更新日期""" - _idx1 = db.Index("bilibili_sub_idx1", "sub_id", "sub_type", unique=True) + class Meta: + table = "bilibili_sub" + table_description = "B站订阅数据表" + unique_together = ("sub_id", "sub_type") @classmethod - async def add_bilibili_sub( + async def sub_handle( cls, sub_id: int, - sub_type: str, - sub_user: str, + sub_type: Optional[str] = None, + sub_user: str = "", *, live_short_id: Optional[int] = None, live_status: Optional[int] = None, - dynamic_upload_time: Optional[int] = None, + dynamic_upload_time: int = 0, uid: Optional[int] = None, uname: Optional[str] = None, latest_video_created: Optional[int] = None, @@ -64,50 +79,60 @@ class BilibiliSub(db.Model): :param season_current_episode: 番剧最新集数 :param season_update_time: 番剧更新时间 """ - try: - query = ( - await cls.query.where( (cls.sub_id == sub_id) & (cls.sub_type == sub_type) ) - .with_for_update() - .gino.first() - ) + # try: + data = { + "sub_type": sub_type, + "sub_user": sub_user, + "live_short_id": live_short_id, + "live_status": live_status, + "dynamic_upload_time": dynamic_upload_time, + "uid": uid, + "uname": uname, + "latest_video_created": latest_video_created, + "season_name": season_name, + "season_id": season_id, + "season_current_episode": season_current_episode, + "season_update_time": season_update_time, + } + if sub_user: sub_user = sub_user if sub_user[-1] == "," else f"{sub_user}," - if query: - if sub_user not in query.sub_users: - sub_users = query.sub_users + sub_user - await query.update(sub_users=sub_users).apply() - else: - sub = await cls.create( - sub_id=sub_id, sub_type=sub_type, sub_users=sub_user - ) - await sub.update( - live_short_id=live_short_id - if live_short_id - else sub.live_short_id, - live_status=live_status if live_status else sub.live_status, - dynamic_upload_time=dynamic_upload_time - if dynamic_upload_time - else sub.dynamic_upload_time, - uid=uid if uid else sub.uid, - uname=uname if uname else sub.uname, - latest_video_created=latest_video_created - if latest_video_created - else sub.latest_video_created, - season_update_time=season_update_time - if season_update_time - else sub.season_update_time, - season_current_episode=season_current_episode - if season_current_episode - else sub.season_current_episode, - season_id=season_id if season_id else sub.season_id, - season_name=season_name if season_name else sub.season_name, - ).apply() - return True - except Exception as e: - logger.info(f"bilibili_sub 添加订阅错误 {type(e)}: {e}") - return False + sub = None + if sub_type: + sub = await cls.get_or_none(sub_id=sub_id, sub_type=sub_type) + else: + sub = await cls.get_or_none(sub_id=sub_id) + if sub: + sub_users = sub.sub_users + sub_user + data["sub_type"] = sub_type or sub.sub_type + data["sub_user"] = sub_users + data["live_short_id"] = live_short_id or sub.live_short_id + data["live_status"] = ( + live_status if live_status is not None else sub.live_status + ) + data["dynamic_upload_time"] = dynamic_upload_time or sub.dynamic_upload_time + data["uid"] = uid or sub.uid + data["uname"] = uname or sub.uname + data["latest_video_created"] = ( + latest_video_created or sub.latest_video_created + ) + data["season_name"] = season_name or sub.season_name + data["season_id"] = season_id or sub.season_id + data["season_current_episode"] = ( + season_current_episode or sub.season_current_episode + ) + data["season_update_time"] = season_update_time or sub.season_update_time + else: + await cls.create(sub_id=sub_id, sub_type=sub_type, sub_users=sub_user) + await cls.update_or_create(sub_id=sub_id, defaults=data) + return True + # except Exception as e: + # logger.info(f"bilibili_sub 添加订阅错误 {type(e)}: {e}") + # return False @classmethod - async def delete_bilibili_sub(cls, sub_id: int, sub_user: str,sub_type: Optional[str] = None) -> bool: + async def delete_bilibili_sub( + cls, sub_id: int, sub_user: str, sub_type: Optional[str] = None + ) -> bool: """ 说明: 删除订阅 @@ -116,129 +141,30 @@ class BilibiliSub(db.Model): :param sub_user: 删除此条目的用户 """ try: - async with db.transaction(): - if sub_type: - query = ( - await cls.query.where( - (cls.sub_id == sub_id) & (cls.sub_users.contains(sub_user) & (cls.sub_type == sub_type)) - ) - .with_for_update() - .gino.first() - ) - else: - query = ( - await cls.query.where( - (cls.sub_id == sub_id) & (cls.sub_users.contains(sub_user)) - ) - .with_for_update() - .gino.first() - ) - if not query: - return False - await query.update( - sub_users=query.sub_users.replace(f"{sub_user},", "") - ).apply() - if not query.sub_users.strip(): - await query.delete() - return True + if sub_type: + sub = await cls.filter( + sub_id=sub_id, sub_type=sub_type, sub_users__contains=sub_user + ).first() + else: + sub = await cls.filter( + sub_id=sub_id, sub_users__contains=sub_user + ).first() + if not sub: + return False + sub.sub_users = sub.sub_users.replace(f"{sub_user},", "") + if sub.sub_users.strip(): + await sub.save(update_fields=["sub_users"]) + else: + await sub.delete() + return True except Exception as e: logger.info(f"bilibili_sub 删除订阅错误 {type(e)}: {e}") return False - @classmethod - async def get_sub(cls, sub_id: int) -> Optional["BilibiliSub"]: - """ - 说明: - 获取订阅对象 - 参数: - :param sub_id: 订阅 id - """ - return await cls.query.where(cls.sub_id == sub_id).gino.first() - - @classmethod - async def get_sub_data(cls, id_: str) -> List["BilibiliSub"]: - """ - 获取 id_ 订阅的所有内容 - :param id_: id - """ - query = cls.query.where(cls.sub_users.contains(id_)) - return await query.gino.all() - - @classmethod - async def update_sub_info( - cls, - sub_id: int, - *, - live_short_id: Optional[int] = None, - live_status: Optional[int] = None, - dynamic_upload_time: Optional[int] = None, - uid: Optional[int] = None, - uname: Optional[str] = None, - latest_video_created: Optional[int] = None, - season_name: Optional[str] = None, - season_id: Optional[int] = None, - season_current_episode: Optional[str] = None, - season_update_time: Optional[datetime] = None, - ) -> bool: - """ - 说明: - 更新订阅信息 - 参数: - :param sub_id: 订阅名称,房间号,番剧号等 - :param live_short_id: 直接短 id - :param live_status: 主播开播状态 - :param dynamic_upload_time: 主播/UP最新动态时间 - :param uid: 主播/UP uid - :param uname: 用户名称 - :param latest_video_created: 最新视频上传时间 - :param season_name: 番剧名称 - :param season_id: 番剧 season_id - :param season_current_episode: 番剧最新集数 - :param season_update_time: 番剧更新时间 - """ - try: - async with db.transaction(): - sub = ( - await cls.query.where(cls.sub_id == sub_id) - .with_for_update() - .gino.first() - ) - if sub: - await sub.update( - live_short_id=live_short_id - if live_short_id is not None - else sub.live_short_id, - live_status=live_status - if live_status is not None - else sub.live_status, - dynamic_upload_time=dynamic_upload_time - if dynamic_upload_time is not None - else sub.dynamic_upload_time, - uid=uid if uid is not None else sub.uid, - uname=uname if uname is not None else sub.uname, - latest_video_created=latest_video_created - if latest_video_created is not None - else sub.latest_video_created, - season_update_time=season_update_time - if season_update_time is not None - else sub.season_update_time, - season_current_episode=season_current_episode - if season_current_episode is not None - else sub.season_current_episode, - season_id=season_id if season_id is not None else sub.season_id, - season_name=season_name - if season_name is not None - else sub.season_name, - ).apply() - return True - except Exception as e: - logger.info(f"bilibili_sub 更新订阅错误 {type(e)}: {e}") - return False - @classmethod async def get_all_sub_data( cls, - ) -> "List[BilibiliSub], List[BilibiliSub], List[BilibiliSub]": + ) -> Tuple[List["BilibiliSub"], List["BilibiliSub"], List["BilibiliSub"]]: """ 说明: 分类获取所有数据 @@ -246,7 +172,7 @@ class BilibiliSub(db.Model): live_data = [] up_data = [] season_data = [] - query = await cls.query.gino.all() + query = await cls.all() for x in query: if x.sub_type == "live": live_data.append(x) diff --git a/plugins/black_word/__init__.py b/plugins/black_word/__init__.py index 1e1b1639..04372d59 100644 --- a/plugins/black_word/__init__.py +++ b/plugins/black_word/__init__.py @@ -1,28 +1,30 @@ +from datetime import datetime +from typing import Any, Tuple + +from nonebot import on_command, on_message, on_regex from nonebot.adapters.onebot.v11 import ( + Bot, Event, - MessageEvent, GroupMessageEvent, Message, - Bot, + MessageEvent, ) from nonebot.matcher import Matcher from nonebot.message import run_preprocessor +from nonebot.params import CommandArg, RegexGroup +from nonebot.permission import SUPERUSER + +from configs.config import NICKNAME, Config +from models.ban_user import BanUser +from services.log import logger from utils.image_utils import BuildImage from utils.manager import group_manager -from utils.utils import get_message_text, is_number -from nonebot.params import RegexGroup, CommandArg -from .utils import black_word_manager -from nonebot import on_command, on_message, on_regex -from configs.config import Config, NICKNAME -from nonebot.permission import SUPERUSER -from .data_source import show_black_text_image, set_user_punish -from services.log import logger -from models.ban_user import BanUser -from datetime import datetime from utils.message_builder import image -from .model import BlackWord -from typing import Tuple, Any +from utils.utils import get_message_text, is_number +from .data_source import set_user_punish, show_black_text_image +from .model import BlackWord +from .utils import black_word_manager __zx_plugin_name__ = "敏感词检测" __plugin_usage__ = """ @@ -38,7 +40,7 @@ usage: 设置惩罚id需要通过 '记录名单u:xxxxxxxx' 获取 指令: 记录名单 - 设置惩罚 [user_id] [id] [punish_level] + 设置惩罚 [user_id] [下标] [惩罚等级] 示例:记录名单 示例:记录名单u:12345678 示例:设置惩罚 12345678 1 4 @@ -150,24 +152,32 @@ async def _( matcher: Matcher, event: Event, ): + msg = get_message_text(event.json()) if ( isinstance(event, MessageEvent) and event.is_tome() - and matcher.plugin_name == "black_word" - and not await BanUser.is_ban(event.user_id) - and str(event.user_id) not in bot.config.superusers - and not get_message_text(event.json()).startswith("原神绑定") + and not msg.startswith("原神绑定") ): - # 屏蔽群权限-1的群 - if isinstance(event, GroupMessageEvent) and group_manager.get_group_level(event.group_id) < 0: - return - user_id = event.user_id - group_id = event.group_id if isinstance(event, GroupMessageEvent) else None - msg = get_message_text(event.json()) - if await black_word_manager.check(user_id, group_id, msg) and Config.get_config( - "black_word", "CONTAIN_BLACK_STOP_PROPAGATION" + # if str(event.user_id) not in bot.config.superusers: + # return logger.debug(f"超级用户跳过黑名单词汇检查 Message: {msg}", target=event.user_id) + if ( + event.is_tome() + and matcher.plugin_name == "black_word" + and not await BanUser.is_ban(event.user_id) ): - matcher.stop_propagation() + # 屏蔽群权限-1的群 + if ( + isinstance(event, GroupMessageEvent) + and group_manager.get_group_level(event.group_id) < 0 + ): + return + user_id = event.user_id + group_id = event.group_id if isinstance(event, GroupMessageEvent) else None + msg = get_message_text(event.json()) + if await black_word_manager.check( + user_id, group_id, msg + ) and Config.get_config("black_word", "CONTAIN_BLACK_STOP_PROPAGATION"): + matcher.stop_propagation() @show_black.handle() diff --git a/plugins/black_word/model.py b/plugins/black_word/model.py index 30d74b4e..ef746f77 100644 --- a/plugins/black_word/model.py +++ b/plugins/black_word/model.py @@ -1,47 +1,34 @@ -from services.db_context import db -from typing import Optional, List from datetime import datetime, timedelta +from typing import List, Optional + +from tortoise import fields + +from services.db_context import Model -class BlackWord(db.Model): - __tablename__ = "black_word" +class BlackWord(Model): + # __tablename__ = "black_word" - id = db.Column(db.Integer(), primary_key=True, autoincrement=True) - user_qq = db.Column(db.BigInteger(), nullable=False, primary_key=True) - group_id = db.Column(db.BigInteger()) - plant_text = db.Column(db.String()) - black_word = db.Column(db.String()) - punish = db.Column(db.String(), default="") - punish_level = db.Column(db.Integer()) - create_time = db.Column(db.DateTime(timezone=True), nullable=False) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField(null=True) + """群聊id""" + plant_text = fields.TextField() + """检测文本""" + black_word = fields.TextField() + """黑名单词语""" + punish = fields.TextField(default="") + """惩罚内容""" + punish_level = fields.IntField() + """惩罚等级""" + create_time = fields.DatetimeField(auto_now_add=True) + """创建时间""" - @classmethod - async def add_user_black_word( - cls, - user_qq: int, - group_id: Optional[int], - black_word: str, - plant_text: str, - punish_level: int, - ): - """ - 说明: - 添加用户发送的敏感词 - 参数: - :param user_qq: 用户id - :param group_id: 群号 - :param black_word: 黑名单词汇 - :param plant_text: 消息文本 - :param punish_level: 惩罚等级 - """ - await cls.create( - user_qq=user_qq, - group_id=group_id, - plant_text=plant_text, - black_word=black_word, - punish_level=punish_level, - create_time=datetime.now(), - ) + class Meta: + table = "black_word" + table_description = "惩罚机制数据表" @classmethod async def set_user_punish( @@ -63,17 +50,21 @@ class BlackWord(db.Model): user = None if (not black_word and not id_) or not punish: return False - query = cls.query.where(cls.user_qq == user_qq).with_for_update() if black_word: - user = await query.where(cls.black_word == black_word).order_by(cls.id.desc()).gino.first() + user = ( + await cls.filter(user_qq=user_qq, black_word=black_word) + .order_by("id") + .first() + ) elif id_: - user_list = await query.gino.all() + user_list = await cls.filter(user_qq=user_qq).order_by("id").all() if len(user_list) == 0 or (id_ < 0 or id_ > len(user_list)): return False user = user_list[id_] if not user: return False - await user.update(punish=cls.punish + punish + " ").apply() + user.punish = f"{user.punish}{punish} " + await user.save(update_fields=["punish"]) return True @classmethod @@ -88,15 +79,14 @@ class BlackWord(db.Model): :param days: 周期天数 :param punish_level: 惩罚等级 """ - setattr(BlackWord, "count", db.func.count(cls.id).label("count")) - query = cls.select("count").where( - (cls.user_qq == user_qq) - & (cls.punish_level != -1) - & (cls.create_time > datetime.now() - timedelta(days=days)) + query = cls.filter( + user_qq=user_qq, + create_time__gte=datetime.now() - timedelta(days=days), + punish_level__not_in=[-1], ) if punish_level is not None: - query = query.where(cls.punish_level == punish_level) - return (await query.gino.first())[0] + query = query.filter(punish_level=punish_level) + return await query.count() @classmethod async def get_user_punish_level(cls, user_qq: int, days: int = 7) -> Optional[int]: @@ -108,12 +98,14 @@ class BlackWord(db.Model): :param days: 周期天数 """ if ( - query := await cls.query.where(cls.user_qq == user_qq) - .where(cls.create_time > datetime.now() - timedelta(days=days)) - .order_by(cls.id.desc()) - .gino.first() + user := await cls.filter( + user_qq=user_qq, + create_time__gte=datetime.now() - timedelta(days=days), + ) + .order_by("id") + .first() ): - return query.punish_level + return user.punish_level return None @classmethod @@ -133,16 +125,18 @@ class BlackWord(db.Model): :param date: 日期 :param date_type: 日期查询类型 """ - query = cls.query + query = cls if user_qq: - query = query.where(cls.user_qq == user_qq) + query = query.filter(user_qq=user_qq) if group_id: - query = query.where(cls.group_id == group_id) + query = query.filter(group_id=group_id) if date: if date_type == "=": - query = query.where(cls.create_time == date) + query = query.filter( + create_time__range=[date, date + timedelta(days=1)] + ) elif date_type == ">": - query = query.where(cls.create_time > date) + query = query.filter(create_time__gte=date) elif date_type == "<": - query = query.where(cls.create_time < date) - return await query.gino.all() + query = query.filter(create_time__lte=date) + return await query.order_by("id").all() diff --git a/plugins/black_word/utils.py b/plugins/black_word/utils.py index 88b45ec3..bcd42254 100644 --- a/plugins/black_word/utils.py +++ b/plugins/black_word/utils.py @@ -1,15 +1,18 @@ -from utils.utils import cn2py, get_bot -from configs.path_config import DATA_PATH -from typing import Optional, Union, Tuple -from .model import BlackWord -from configs.config import Config -from pathlib import Path -from services.log import logger -from models.ban_user import BanUser -from nonebot.adapters.onebot.v11 import ActionFailed -from models.group_member_info import GroupInfoUser -from utils.http_utils import AsyncHttpx import random +from pathlib import Path +from typing import Optional, Tuple, Union + +from nonebot.adapters.onebot.v11 import ActionFailed + +from configs.config import Config +from configs.path_config import DATA_PATH +from models.ban_user import BanUser +from models.group_member_info import GroupInfoUser +from services.log import logger +from utils.http_utils import AsyncHttpx +from utils.utils import cn2py, get_bot + +from .model import BlackWord try: import ujson as json @@ -49,7 +52,7 @@ class BlackWordManager: "hanbi", "hanpi", "laji", - "fw" + "fw", ], "5": [], } @@ -94,9 +97,9 @@ class BlackWordManager: user_id, group_id, data[0], message, int(data[1]) ) return True - if Config.get_config( - "black_word", "ALAPI_CHECK_FLAG" - ) and not await check_text(message): + if Config.get_config("black_word", "ALAPI_CHECK_FLAG") and not await check_text( + message + ): await send_msg( 0, None, f"USER {user_id} GROUP {group_id} ALAPI 疑似检测:{message}" ) @@ -146,8 +149,12 @@ async def _add_user_black_word( "black_word", "AUTO_ADD_PUNISH_LEVEL" ) and user_count > Config.get_config("black_word", "ADD_PUNISH_LEVEL_TO_COUNT"): punish_level -= 1 - await BlackWord.add_user_black_word( - user_id, group_id, black_word, message, punish_level + await BlackWord.create( + user_qq=user_id, + group_id=group_id, + plant_text=message, + black_word=black_word, + punish_level=punish_level, ) logger.info( f"已将 USER {user_id} GROUP {group_id} 添加至黑名单词汇记录 Black_word:{black_word} Plant_text:{message}" @@ -172,7 +179,9 @@ async def _punish_handle( # 用户周期内触发punish_level级惩罚的次数 user_count = await BlackWord.get_user_count(user_id, cycle_days, punish_level) # 获取最近一次的惩罚等级,将在此基础上增加 - punish_level = await BlackWord.get_user_punish_level(user_id, cycle_days) or punish_level + punish_level = ( + await BlackWord.get_user_punish_level(user_id, cycle_days) or punish_level + ) # 容忍次数:List[int] tolerate_count = Config.get_config("black_word", "TOLERATE_COUNT") if not tolerate_count or len(tolerate_count) < 5: @@ -226,15 +235,17 @@ async def _get_punish( ban_4_duration = Config.get_config("black_word", "BAN_4_DURATION") # 口头警告内容 warning_result = Config.get_config("black_word", "WARNING_RESULT") - try: - uname = (await GroupInfoUser.get_member_info(user_id, group_id)).user_name - except AttributeError: + if user := await GroupInfoUser.get_or_none(user_qq=user_id, group_id=group_id): + uname = user.user_name + else: uname = user_id # 永久ban if id_ == 1: if str(user_id) not in bot.config.superusers: await BanUser.ban(user_id, 10, 99999999) - await send_msg(user_id, group_id, f"BlackWordChecker 永久ban USER {uname}({user_id})") + await send_msg( + user_id, group_id, f"BlackWordChecker 永久ban USER {uname}({user_id})" + ) logger.info(f"BlackWord 永久封禁 USER {user_id}...") # 删除好友(有的话 elif id_ == 2: @@ -319,4 +330,7 @@ async def check_text(text: str) -> bool: return True -black_word_manager = BlackWordManager(DATA_PATH / "black_word" / "black_word.json", DATA_PATH / "black_word" / "black_py.json") +black_word_manager = BlackWordManager( + DATA_PATH / "black_word" / "black_word.json", + DATA_PATH / "black_word" / "black_py.json", +) diff --git a/plugins/bt/data_source.py b/plugins/bt/data_source.py index b5035e3d..a35382ae 100755 --- a/plugins/bt/data_source.py +++ b/plugins/bt/data_source.py @@ -1,13 +1,7 @@ -from utils.http_utils import AsyncHttpx -from configs.config import Config from bs4 import BeautifulSoup -import platform - -# if platform.system() == "Windows": -# import asyncio -# -# asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) +from configs.config import Config +from utils.http_utils import AsyncHttpx url = "http://www.eclzz.love" @@ -28,10 +22,7 @@ async def get_bt_info(keyword: str, page: int): for item in item_lst[:bt_max_num]: divs = item.find_all("div") title = ( - str(divs[0].find("a").text) - .replace("", "") - .replace("", "") - .strip() + str(divs[0].find("a").text).replace("", "").replace("", "").strip() ) spans = divs[2].find_all("span") type_ = spans[0].text diff --git a/plugins/genshin/almanac/__init__.py b/plugins/genshin/almanac/__init__.py index a28a33ba..a1f29e7f 100755 --- a/plugins/genshin/almanac/__init__.py +++ b/plugins/genshin/almanac/__init__.py @@ -65,4 +65,4 @@ async def _(): mes = "[[_task|genshin_alc]]" + alc_img for gid in gl: if group_manager.check_group_task_status(gid, "genshin_alc"): - await bot.send_group_msg(group_id=int(gid), message="" + mes) + await bot.send_group_msg(group_id=int(gid), message=mes) diff --git a/plugins/genshin/material_remind/__init__.py b/plugins/genshin/material_remind/__init__.py index df64ee27..116e2df0 100755 --- a/plugins/genshin/material_remind/__init__.py +++ b/plugins/genshin/material_remind/__init__.py @@ -1,18 +1,19 @@ -from nonebot import on_command, Driver -from nonebot.adapters.onebot.v11 import MessageEvent, Message, GroupMessageEvent -from utils.message_builder import image -from utils.image_utils import BuildImage -from utils.browser import get_browser -from configs.path_config import IMAGE_PATH -import nonebot -from services.log import logger -from nonebot.permission import SUPERUSER -from typing import List -from datetime import datetime, timedelta -import os import asyncio +import os import time +from datetime import datetime, timedelta +from typing import List +import nonebot +from nonebot import Driver, on_command +from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageEvent +from nonebot.permission import SUPERUSER + +from configs.path_config import IMAGE_PATH +from services.log import logger +from utils.browser import get_browser +from utils.image_utils import BuildImage +from utils.message_builder import image __zx_plugin_name__ = "原神今日素材" __plugin_usage__ = """ @@ -56,7 +57,7 @@ async def _(event: MessageEvent): await update_image() await material.send( Message( - image(f"{file_name}.png", "genshin/material") + image(IMAGE_PATH / "genshin" / "material" / f"{file_name}.png") + "\n※ 每日素材数据来源于 genshin.pub" ) ) @@ -163,5 +164,3 @@ def get_background_height(weapons_img: List[str]) -> int: last_weapon.save(weapons_img[-1]) return height - - diff --git a/plugins/genshin/query_user/_models/__init__.py b/plugins/genshin/query_user/_models/__init__.py index 5c775c3f..c33b83c5 100644 --- a/plugins/genshin/query_user/_models/__init__.py +++ b/plugins/genshin/query_user/_models/__init__.py @@ -1,219 +1,49 @@ -from services.db_context import db -from typing import Optional, Union, List -from datetime import datetime, timedelta import random +from datetime import datetime, timedelta +from typing import Optional + import pytz +from tortoise import fields +from tortoise.contrib.postgres.functions import Random + +from services.db_context import Model -class Genshin(db.Model): - __tablename__ = "genshin" +class Genshin(Model): - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - uid = db.Column(db.BigInteger()) - mys_id = db.Column(db.BigInteger()) - cookie = db.Column(db.String(), default="") - today_query_uid = db.Column(db.String(), default="") # 该cookie今日查询的uid - auto_sign = db.Column(db.Boolean(), default=False) - auto_sign_time = db.Column(db.DateTime(timezone=True)) - resin_remind = db.Column(db.Boolean(), default=False) # 树脂提醒 - resin_recovery_time = db.Column(db.DateTime(timezone=True)) # 满树脂提醒日期 - bind_group = db.Column(db.BigInteger()) - login_ticket = db.Column(db.String(), default="") - stuid = db.Column(db.String(), default="") - stoken = db.Column(db.String(), default="") + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + uid = fields.BigIntField() + """uid""" + mys_id: int = fields.BigIntField(null=True) + """米游社id""" + cookie: str = fields.TextField(default="") + """米游社cookie""" + auto_sign = fields.BooleanField(default=False) + """是否自动签到""" + today_query_uid = fields.TextField(default="") + """cookie今日查询uid""" + auto_sign_time = fields.DatetimeField(null=True) + """签到日期时间""" + resin_remind = fields.BooleanField(default=False) + """树脂提醒""" + resin_recovery_time = fields.DatetimeField(null=True) + """满树脂提醒日期""" + bind_group: int = fields.BigIntField(null=True) + """发送提示 绑定群聊""" + login_ticket = fields.TextField(default="") + """login_ticket""" + stuid: str = fields.TextField(default="") + """stuid""" + stoken: str = fields.TextField(default="") + """stoken""" - _idx1 = db.Index("genshin_uid_idx1", "user_qq", "uid", unique=True) - - @classmethod - async def add_uid(cls, user_qq: int, uid: int): - """ - 说明: - 添加一个uid - 参数: - :param user_qq: 用户qq - :param uid: 原神uid - """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.uid == uid)) - user = await query.gino.first() - if not user: - await cls.create( - user_qq=user_qq, - uid=uid, - ) - return True - return False - - @classmethod - async def set_mys_id(cls, uid: int, mys_id: int) -> bool: - """ - 说明: - 设置米游社id - 参数: - :param uid: 原神uid - :param mys_id: 米游社id - """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() - if user: - await user.update(mys_id=mys_id).apply() - return True - return False - - @classmethod - async def set_bind_group(cls, uid: int, bind_group) -> bool: - """ - 说明: - 绑定group_id,除私聊外的提醒将在此群发送 - 参数: - :param uid: uid - :param bind_group: 群号 - """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() - if user: - await user.update(bind_group=bind_group).apply() - return True - return False - - @classmethod - async def get_bind_group(cls, uid: int) -> Optional[int]: - """ - 说明: - 获取用户绑定的群聊 - 参数: - :param uid: uid - """ - user = await cls.query.where(cls.uid == uid).gino.first() - if user: - return user.bind_group - return None - - @classmethod - async def set_cookie(cls, uid: int, cookie: str) -> bool: - """ - 说明: - 设置cookie - 参数: - :param uid: 原神uid - :param cookie: 米游社id - """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() - if user: - await user.update(cookie=cookie).apply() - return True - return False - - @classmethod - async def set_resin_remind(cls, uid: int, flag: bool) -> bool: - """ - 说明: - 设置体力提醒 - 参数: - :param uid: 原神uid - :param flag: 开关状态 - """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() - if user: - await user.update(resin_remind=flag).apply() - return True - return False - - @classmethod - async def set_user_resin_recovery_time(cls, uid: int, date: datetime): - """ - 说明: - 设置体力完成时间 - 参数: - :param uid: uid - :param date: 提醒日期 - """ - u = await cls.query.where(cls.uid == uid).gino.first() - if u: - await u.update(resin_recovery_time=date).apply() - - @classmethod - async def get_user_resin_recovery_time(cls, uid: int) -> Optional[datetime]: - """ - 说明: - 获取体力完成时间 - 参数: - :param uid: uid - """ - u = await cls.query.where(cls.uid == uid).gino.first() - if u: - return u.resin_recovery_time.astimezone(pytz.timezone("Asia/Shanghai")) - return None - - @classmethod - async def get_all_resin_remind_user(cls) -> List["Genshin"]: - """ - 说明: - 获取所有开启体力提醒的用户 - """ - return await cls.query.where(cls.resin_remind == True).gino.all() - - @classmethod - async def clear_resin_remind_time(cls, uid: int) -> bool: - """ - 说明: - 清空提醒日期 - 参数: - :param uid: uid - """ - user = await cls.query.where(cls.uid == uid).gino.first() - if user: - await user.update(resin_recovery_time=None).apply() - return True - return False - - @classmethod - async def set_auto_sign(cls, uid: int, flag: bool) -> bool: - """ - 说明: - 设置米游社/原神自动签到 - 参数: - :param uid: 原神uid - :param flag: 开关状态 - """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() - if user: - await user.update(auto_sign=flag).apply() - return True - return False - - @classmethod - async def get_all_auto_sign_user(cls) -> List["Genshin"]: - """ - 说明: - 获取所有开启自动签到的用户 - """ - return await cls.query.where(cls.auto_sign == True).gino.all() - - @classmethod - async def get_all_sign_user(cls) -> List["Genshin"]: - """ - 说明: - 获取 原神 所有今日签到用户 - """ - return await cls.query.where(cls.auto_sign_time != None).gino.all() - - @classmethod - async def clear_sign_time(cls, uid: int) -> bool: - """ - 说明: - 清空签到日期 - 参数: - :param uid: uid - """ - user = await cls.query.where(cls.uid == uid).gino.first() - if user: - await user.update(auto_sign_time=None).apply() - return True - return False + class Meta: + table = "genshin" + table_description = "原神数据表" + unique_together = ("user_qq", "uid") @classmethod async def random_sign_time(cls, uid: int) -> Optional[datetime]: @@ -223,262 +53,43 @@ class Genshin(db.Model): 说明: :param uid: uid """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() + user = await cls.get_or_none(uid=uid) if user and user.cookie: if user.auto_sign_time and user.auto_sign_time.astimezone( - pytz.timezone("Asia/Shanghai") + pytz.timezone("Asia/Shanghai") ) - timedelta(seconds=2) >= datetime.now(pytz.timezone("Asia/Shanghai")): return user.auto_sign_time.astimezone(pytz.timezone("Asia/Shanghai")) hours = int(str(datetime.now()).split()[1].split(":")[0]) minutes = int(str(datetime.now()).split()[1].split(":")[1]) date = ( - datetime.now() - + timedelta(days=1) - - timedelta(hours=hours) - - timedelta(minutes=minutes - 1) + datetime.now() + + timedelta(days=1) + - timedelta(hours=hours) + - timedelta(minutes=minutes - 1) ) random_hours = random.randint(0, 22) random_minutes = random.randint(1, 59) date += timedelta(hours=random_hours) + timedelta(minutes=random_minutes) - await user.update(auto_sign_time=date).apply() + user.auto_sign_time = date + await user.save(update_fields=["auto_sign_time"]) return date return None @classmethod - async def get_query_cookie(cls, uid: int) -> Optional[str]: + async def random_cookie(cls, uid: int) -> Optional[str]: """ 说明: - 获取查询角色信息cookie + 随机获取查询角色信息cookie 参数: :param uid: 原神uid """ # 查找用户今日是否已经查找过,防止重复 - query = cls.query.where(cls.today_query_uid.contains(str(uid))) - x = await query.gino.first() - if x: - await cls._add_query_uid(uid, uid) - return x.cookie - for u in await cls.query.where(cls.cookie != "").order_by(db.func.random()).gino.all(): - if not u.today_query_uid or len(u.today_query_uid[:-1].split()) < 30: - await cls._add_query_uid(uid, u.uid) - return u.cookie - return None - - @classmethod - async def get_user_cookie(cls, uid: int, flag: bool = False) -> Optional[str]: - """ - 说明: - 获取用户cookie - 参数: - :param uid:原神uid - :param flag:必须使用自己的cookie - """ - cookie = await cls._get_user_data(None, uid, "cookie") - if not cookie and not flag: - cookie = await cls.get_query_cookie(uid) - return cookie - - @classmethod - async def get_user_by_qq(cls, user_qq: int) -> Optional["Genshin"]: - """ - 说明: - 通过qq获取用户对象 - 参数: - :param user_qq: qq - """ - return await cls.query.where(cls.user_qq == user_qq).gino.first() - - @classmethod - async def get_user_by_uid(cls, uid: int) -> Optional["Genshin"]: - """ - 说明: - 通过uid获取用户对象 - 参数: - :param uid: qq - """ - return await cls.query.where(cls.uid == uid).gino.first() - - @classmethod - async def get_user_uid(cls, user_qq: int) -> Optional[int]: - """ - 说明: - 获取用户uid - 参数: - :param user_qq:用户qq - """ - return await cls._get_user_data(user_qq, None, "uid") - - @classmethod - async def get_user_mys_id(cls, uid: int) -> Optional[int]: - """ - 说嘛: - 获取用户米游社id - 参数: - :param uid:原神id - """ - return await cls._get_user_data(None, uid, "mys_id") - - @classmethod - async def delete_user_cookie(cls, uid: int): - """ - 说明: - 删除用户cookie - 参数: - :param uid: 原神uid - """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() + user = await cls.get_or_none(today_query_uid__contains=str(uid)) if user: - await user.update(cookie="").apply() - - @classmethod - async def delete_user(cls, user_qq: int): - """ - 说明: - 删除用户数据 - 参数: - :param user_qq: 用户qq - """ - query = cls.query.where(cls.user_qq == user_qq).with_for_update() - user = await query.gino.first() - if not user: - return False - await user.delete() - return True - - @classmethod - async def _add_query_uid(cls, uid: int, cookie_uid: int): - """ - 说明: - 添加每日查询重复uid的cookie - 参数: - :param uid: 原神uid - :param cookie_uid: cookie的uid - """ - query = cls.query.where(cls.uid == cookie_uid).with_for_update() - user = await query.gino.first() - await user.update(today_query_uid=user.today_query_uid + f"{uid} ").apply() - - @classmethod - async def _get_user_data( - cls, user_qq: Optional[int], uid: Optional[int], type_: str - ) -> Optional[Union[int, str]]: - """ - 说明: - 获取用户数据 - 参数: - :param user_qq: 用户qq - :param uid: uid - :param type_: 数据类型 - """ - if type_ == "uid": - user = await cls.query.where(cls.user_qq == user_qq).gino.first() - return user.uid if user else None - user = await cls.query.where(cls.uid == uid).gino.first() - if not user: - return None - if type_ == "mys_id": - return user.mys_id - elif type_ == "cookie": return user.cookie - return None - - @classmethod - async def reset_today_query_uid(cls): - for u in await cls.query.with_for_update().gino.all(): - if u.today_query_uid: - await u.update(today_query_uid="").apply() - - @classmethod - async def set_stuid(cls, uid: int, stuid: str) -> bool: - """ - 说明: - 设置stuid - 参数: - :param uid: 原神uid - :param stuid: stuid - """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() - if user: - await user.update(stuid=stuid).apply() - return True - return False - - @classmethod - async def set_stoken(cls, uid: int, stoken: str) -> bool: - """ - 说明: - 设置stoken - 参数: - :param uid: 原神uid - :param stoken: stoken - """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() - if user: - await user.update(stoken=stoken).apply() - return True - return False - - @classmethod - async def set_login_ticket(cls, uid: int, login_ticket: str) -> bool: - """ - 说明: - 设置login_ticket - 参数: - :param uid: 原神uid - :param login_ticket: login_ticket - """ - query = cls.query.where(cls.uid == uid).with_for_update() - user = await query.gino.first() - if user: - await user.update(login_ticket=login_ticket).apply() - return True - return False - - # 获取login_ticket - @classmethod - async def get_login_ticket(cls, uid: int) -> Optional[str]: - """ - 说明: - 获取login_ticket - 参数: - :param uid: 原神uid - """ - query = cls.query.where(cls.uid == uid) - user = await query.gino.first() - if user: - return user.login_ticket - return None - - # 获取stuid - @classmethod - async def get_stuid(cls, uid: int) -> Optional[str]: - """ - 说明: - 获取stuid - 参数: - :param uid: 原神uid - """ - query = cls.query.where(cls.uid == uid) - user = await query.gino.first() - if user: - return user.stuid - return None - - # 获取stoken - @classmethod - async def get_stoken(cls, uid: int) -> Optional[str]: - """ - 说明: - 获取stoken - 参数: - :param uid: 原神uid - """ - query = cls.query.where(cls.uid == uid) - user = await query.gino.first() - if user: - return user.stoken + for user in await cls.filter(cookie__not="").annotate(rand=Random()).all(): + if not user.today_query_uid or len(user.today_query_uid[:-1].split()) < 30: + user.today_query_uid = user.today_query_uid + f"{uid} " + await user.save(update_fields=["today_query_uid"]) + return user.cookie return None diff --git a/plugins/genshin/query_user/_utils/__init__.py b/plugins/genshin/query_user/_utils/__init__.py index 10fa36f8..8dddb2c9 100644 --- a/plugins/genshin/query_user/_utils/__init__.py +++ b/plugins/genshin/query_user/_utils/__init__.py @@ -1,9 +1,10 @@ -from configs.config import Config -import json -import time -import random import hashlib +import json +import random import string +import time + +from configs.config import Config def _md5(text): @@ -15,7 +16,7 @@ def _md5(text): def get_old_ds() -> str: n = Config.get_config("genshin", "n") i = str(int(time.time())) - r = ''.join(random.sample(string.ascii_lowercase + string.digits, 6)) + r = "".join(random.sample(string.ascii_lowercase + string.digits, 6)) c = _md5("salt=" + n + "&t=" + i + "&r=" + r) return i + "," + r + "," + c @@ -33,7 +34,7 @@ def get_ds(q: str = "", b: dict = None) -> str: def random_hex(length: int) -> str: - result = hex(random.randint(0, 16 ** length)).replace("0x", "").upper() + result = hex(random.randint(0, 16**length)).replace("0x", "").upper() if len(result) < length: result = "0" * (length - len(result)) + result return result diff --git a/plugins/genshin/query_user/bind/__init__.py b/plugins/genshin/query_user/bind/__init__.py index cc77b022..9c9bbaca 100644 --- a/plugins/genshin/query_user/bind/__init__.py +++ b/plugins/genshin/query_user/bind/__init__.py @@ -1,13 +1,16 @@ -from nonebot import on_command -from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent, Message -from utils.utils import is_number -from .._models import Genshin -from services.log import logger -from nonebot.params import CommandArg, Command -from typing import Tuple -from utils.http_utils import AsyncHttpx import json +from typing import Tuple +from nonebot import on_command +from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageEvent +from nonebot.params import Command, CommandArg + +from services.log import logger +from utils.depends import OneCommand +from utils.http_utils import AsyncHttpx +from utils.utils import is_number + +from .._models import Genshin __zx_plugin_name__ = "原神绑定" __plugin_usage__ = """ @@ -43,82 +46,94 @@ unbind = on_command("原神解绑", priority=5, block=True) web_Api = "https://api-takumi.mihoyo.com" bbs_Cookie_url = "https://webapi.account.mihoyo.com/Api/cookie_accountinfo_by_loginticket?login_ticket={}" -bbs_Cookie_url2 = web_Api + "/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}" +bbs_Cookie_url2 = ( + web_Api + + "/auth/api/getMultiTokenByLoginTicket?login_ticket={}&token_types=3&uid={}" +) @bind.handle() -async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()): - cmd = cmd[0] +async def _(event: MessageEvent, cmd: str = OneCommand(), arg: Message = CommandArg()): msg = arg.extract_plain_text().strip() + user = await Genshin.get_or_none(user_qq=event.user_id) if cmd in ["原神绑定uid", "原神绑定米游社id"]: if not is_number(msg): await bind.finish("uid/id必须为纯数字!", at_senders=True) msg = int(msg) if cmd == "原神绑定uid": - uid = await Genshin.get_user_uid(event.user_id) - if uid: - await bind.finish(f"您已绑定过uid:{uid},如果希望更换uid,请先发送原神解绑") - flag = await Genshin.add_uid(event.user_id, msg) - if not flag: + if user: + await bind.finish(f"您已绑定过uid:{user.uid},如果希望更换uid,请先发送原神解绑") + if await Genshin.get_or_none(user_qq=event.user_id, uid=msg): await bind.finish("添加失败,该uid可能已存在...") + user = await Genshin.create(user_qq=event.user_id, uid=msg) _x = f"已成功添加原神uid:{msg}" elif cmd == "原神绑定米游社id": - uid = await Genshin.get_user_uid(event.user_id) - if not uid: + if not user: await bind.finish("请先绑定原神uid..") - await Genshin.set_mys_id(uid, msg) - _x = f"已成功为uid:{uid} 设置米游社id:{msg}" + user.mys_id = int(msg) + _x = f"已成功为uid:{user.uid} 设置米游社id:{msg}" else: if not msg: - await bind.finish("""私聊发送!! + await bind.finish( + """私聊发送!! 1.以无痕模式打开浏览器(Edge请新建InPrivate窗口) - 2.打开http://bbs.mihoyo.com/ys/并登陆 + 2.打开http://bbs.mihoyo.com/ys/ 并登陆 3.登陆后打开http://user.mihoyo.com/进行登陆 4.按下F12,打开控制台,输入以下命令: var cookie=document.cookie;var ask=confirm('Cookie:'+cookie+'\\n\\nDo you want to copy the cookie to the clipboard?');if(ask==true){copy(cookie);msg=cookie}else{msg='Cancel'} - 5.私聊发送:原神绑定cookie 刚刚复制的cookie""") + 5.私聊发送:原神绑定cookie 刚刚复制的cookie""" + ) if isinstance(event, GroupMessageEvent): await bind.finish("请立即撤回你的消息并私聊发送!") - uid = await Genshin.get_user_uid(event.user_id) - if not uid: + if not user: await bind.finish("请先绑定原神uid..") if msg.startswith('"') or msg.startswith("'"): msg = msg[1:] if msg.endswith('"') or msg.endswith("'"): msg = msg[:-1] - await Genshin.set_cookie(uid, msg) cookie = msg # 用: 代替=, ,代替; - cookie = '{"' + cookie.replace('=', '": "').replace("; ", '","') + '"}' - print(cookie) + cookie = '{"' + cookie.replace("=", '": "').replace("; ", '","') + '"}' + # print(cookie) cookie_json = json.loads(cookie) - print(cookie_json) - if 'login_ticket' not in cookie_json: + # print(cookie_json) + if "login_ticket" not in cookie_json: await bind.finish("请发送正确完整的cookie!") - login_ticket = cookie_json['login_ticket'] + user.cookie = str(msg) + login_ticket = cookie_json["login_ticket"] # try: res = await AsyncHttpx.get(url=bbs_Cookie_url.format(login_ticket)) res.encoding = "utf-8" data = json.loads(res.text) - print(data) + # print(data) if "成功" in data["data"]["msg"]: stuid = str(data["data"]["cookie_info"]["account_id"]) - res = await AsyncHttpx.get(url=bbs_Cookie_url2.format( - login_ticket, stuid)) + res = await AsyncHttpx.get(url=bbs_Cookie_url2.format(login_ticket, stuid)) res.encoding = "utf-8" data = json.loads(res.text) stoken = data["data"]["list"][0]["token"] # await Genshin.set_cookie(uid, cookie) - await Genshin.set_stoken(uid, stoken) - await Genshin.set_stuid(uid, stuid) - await Genshin.set_login_ticket(uid, login_ticket) + user.stoken = stoken + user.stuid = stuid + user.login_ticket = login_ticket # except Exception as e: # await bind.finish("获取登陆信息失败,请检查cookie是否正确或更新cookie") elif data["data"]["msg"] == "登录信息已失效,请重新登录": await bind.finish("登录信息失效,请重新获取最新cookie进行绑定") - _x = f"已成功为uid:{uid} 设置cookie" + _x = f"已成功为uid:{user.uid} 设置cookie" if isinstance(event, GroupMessageEvent): - await Genshin.set_bind_group(uid, event.group_id) + user.bind_group = event.group_id + if user: + await user.save( + update_fields=[ + "mys_id", + "cookie", + "stoken", + "stuid", + "login_ticket", + "bind_group", + ] + ) await bind.send(_x) logger.info( f"(USER {event.user_id}, " @@ -129,12 +144,10 @@ async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message @unbind.handle() async def _(event: MessageEvent): - if await Genshin.delete_user(event.user_id): - await unbind.send("用户数据删除成功...") - logger.info( - f"(USER {event.user_id}, GROUP " - f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" - f"原神解绑" - ) - else: - await unbind.send("该用户数据不存在..") + await Genshin.filter(user_qq=event.user_id).delete() + await unbind.send("用户数据删除成功...") + logger.info( + f"(USER {event.user_id}, GROUP " + f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" + f"原神解绑" + ) diff --git a/plugins/genshin/query_user/genshin_sign/__init__.py b/plugins/genshin/query_user/genshin_sign/__init__.py index 99585662..79a76c31 100644 --- a/plugins/genshin/query_user/genshin_sign/__init__.py +++ b/plugins/genshin/query_user/genshin_sign/__init__.py @@ -1,14 +1,17 @@ -from .data_source import get_sign_reward_list, genshin_sign -from ..mihoyobbs_sign import mihoyobbs_sign -from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent -from nonebot import on_command -from services.log import logger -from .init_task import add_job, scheduler, _sign -from apscheduler.jobstores.base import JobLookupError -from .._models import Genshin -from nonebot.params import Command from typing import Tuple +from apscheduler.jobstores.base import JobLookupError +from nonebot import on_command +from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent +from nonebot.params import Command + +from services.log import logger +from utils.depends import OneCommand + +from .._models import Genshin +from ..mihoyobbs_sign import mihoyobbs_sign +from .data_source import genshin_sign, get_sign_reward_list +from .init_task import _sign, add_job, scheduler __zx_plugin_name__ = "原神自动签到" __plugin_usage__ = """ @@ -39,45 +42,46 @@ genshin_matcher = on_command( @genshin_matcher.handle() -async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command()): - cmd = cmd[0] - uid = await Genshin.get_user_uid(event.user_id) +async def _(event: MessageEvent, cmd: str = OneCommand()): + user = await Genshin.get_or_none(user_qq=event.user_id) + if not user: + await genshin_matcher.finish("请先绑定user.uid...") if cmd == "查看我的cookie": - my_cookie = await Genshin.get_user_cookie(uid, True) if isinstance(event, GroupMessageEvent): await genshin_matcher.finish("请私聊查看您的cookie!") - await genshin_matcher.finish("您的cookie为" + my_cookie) - if not uid or not await Genshin.get_user_cookie(uid, True): - await genshin_matcher.finish("请先绑定uid和cookie!") - # if "account_id" not in await Genshin.get_user_cookie(uid, True): + await genshin_matcher.finish("您的cookie为" + user.cookie) + if not user.uid or not user.cookie: + await genshin_matcher.finish("请先绑定user.uid和cookie!") + # if "account_id" not in await Genshin.get_user_cookie(user.uid, True): # await genshin_matcher.finish("请更新cookie!") if cmd == "原神我硬签": try: await genshin_matcher.send("正在进行签到...", at_sender=True) - msg = await genshin_sign(uid) + msg = await genshin_sign(user.uid) return_data = await mihoyobbs_sign(event.user_id) logger.info( f"(USER {event.user_id}, " - f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) UID:{uid} 原神签到" + f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) UID:{user.uid} 原神签到" ) logger.info(msg) # 硬签,移除定时任务 try: for i in range(3): - scheduler.remove_job(f"genshin_auto_sign_{uid}_{event.user_id}_{i}",) + scheduler.remove_job( + f"genshin_auto_sign_{user.uid}_{event.user_id}_{i}", + ) except JobLookupError: pass - u = await Genshin.get_user_by_uid(uid) - if u and u.auto_sign: - await u.clear_sign_time(uid) - next_date = await Genshin.random_sign_time(uid) - add_job(event.user_id, uid, next_date) + if user.auto_sign: + user.auto_sign_time = None + next_date = await Genshin.random_sign_time(user.uid) + add_job(event.user_id, user.uid, next_date) msg += f"\n{return_data}\n因开启自动签到\n下一次签到时间为:{next_date.replace(microsecond=0)}" except Exception as e: msg = "原神签到失败..请尝试检查cookie或报告至管理员!" logger.info( f"(USER {event.user_id}, " - f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) UID:{uid} 原神签到发生错误 " + f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) UID:{user.uid} 原神签到发生错误 " f"{type(e)}:{e}" ) msg = msg or "请检查cookie是否更新!" @@ -85,13 +89,16 @@ async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command()): else: for i in range(3): try: - scheduler.remove_job(f"genshin_auto_sign_{uid}_{event.user_id}_{i}") + scheduler.remove_job( + f"genshin_auto_sign_{user.uid}_{event.user_id}_{i}" + ) except JobLookupError: pass if cmd[0] == "开": - await Genshin.set_auto_sign(uid, True) - next_date = await Genshin.random_sign_time(uid) - add_job(event.user_id, uid, next_date) + next_date = await Genshin.random_sign_time(user.uid) + user.auto_sign = True + user.auto_sign_time = next_date + add_job(event.user_id, user.uid, next_date) await genshin_matcher.send( f"已开启原神自动签到!\n下一次签到时间为:{next_date.replace(microsecond=0)}", at_sender=True, @@ -102,11 +109,13 @@ async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command()): f" 开启原神自动签到" ) else: - await Genshin.set_auto_sign(uid, False) - await Genshin.clear_sign_time(uid) + user.auto_sign = False + user.auto_sign_time = None await genshin_matcher.send(f"已关闭原神自动签到!", at_sender=True) logger.info( f"(USER {event.user_id}, GROUP " f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" f" 关闭原神自动签到" ) + if user: + await user.save(update_fields=["auto_sign_time", "auto_sign"]) diff --git a/plugins/genshin/query_user/genshin_sign/data_source.py b/plugins/genshin/query_user/genshin_sign/data_source.py index ba39e1e0..1dbaac4c 100644 --- a/plugins/genshin/query_user/genshin_sign/data_source.py +++ b/plugins/genshin/query_user/genshin_sign/data_source.py @@ -1,14 +1,16 @@ -from utils.http_utils import AsyncHttpx -from configs.config import Config -from services.log import logger -from ..mihoyobbs_sign.setting import * -from .._models import Genshin -from typing import Optional, Dict import hashlib import random import string -import uuid import time +import uuid +from typing import Dict, Optional + +from configs.config import Config +from services.log import logger +from utils.http_utils import AsyncHttpx + +from .._models import Genshin +from ..mihoyobbs_sign.setting import * async def genshin_sign(uid: int) -> Optional[str]: @@ -28,10 +30,10 @@ async def genshin_sign(uid: int) -> Optional[str]: sign_list = await get_sign_reward_list() get_reward = sign_list["data"]["awards"][ int(sign_info["total_sign_day"]) - 1 - ]["name"] + ]["name"] reward_num = sign_list["data"]["awards"][ int(sign_info["total_sign_day"]) - 1 - ]["cnt"] + ]["cnt"] get_im = f"本次签到获得:{get_reward}x{reward_num}" logger.info("get_im:" + get_im + "\nsign_info:" + str(sign_info)) if status == "OK" and sign_info["is_sign"]: @@ -64,7 +66,7 @@ def timestamp() -> int: def random_text(num: int) -> str: - return ''.join(random.sample(string.ascii_lowercase + string.digits, num)) + return "".join(random.sample(string.ascii_lowercase + string.digits, num)) def md5(text: str) -> str: @@ -75,8 +77,7 @@ def md5(text: str) -> str: # 生成一个device id def get_device_id(cookie) -> str: - return str(uuid.uuid3(uuid.NAMESPACE_URL, cookie)).replace( - '-', '').upper() + return str(uuid.uuid3(uuid.NAMESPACE_URL, cookie)).replace("-", "").upper() async def _sign(uid: int, server_id: str = "cn_gf01") -> Optional[Dict[str, str]]: @@ -88,18 +89,20 @@ async def _sign(uid: int, server_id: str = "cn_gf01") -> Optional[Dict[str, str] if str(uid)[0] == "5": server_id = "cn_qd01" try: - cookie = await Genshin.get_user_cookie(uid, True) - headers['DS'] = get_ds(web=True) - headers['Referer'] = 'https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?bbs_auth_required=true' \ - f'&act_id={genshin_Act_id}&utm_source=bbs&utm_medium=mys&utm_campaign=icon' - headers['Cookie'] = cookie - headers['x-rpc-device_id'] = get_device_id(cookie) - req = await AsyncHttpx.post( - url=genshin_Signurl, - headers=headers, - json={"act_id": genshin_Act_id, "uid": uid, "region": server_id}, - ) - return req.json() + if user := await Genshin.get_or_none(uid=uid): + headers["DS"] = get_ds(web=True) + headers["Referer"] = ( + "https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?bbs_auth_required=true" + f"&act_id={genshin_Act_id}&utm_source=bbs&utm_medium=mys&utm_campaign=icon" + ) + headers["Cookie"] = user.cookie + headers["x-rpc-device_id"] = get_device_id(user.cookie) + req = await AsyncHttpx.post( + url=genshin_Signurl, + headers=headers, + json={"act_id": genshin_Act_id, "uid": uid, "region": server_id}, + ) + return req.json() except Exception as e: logger.error(f"米游社签到发生错误 UID:{uid} {type(e)}:{e}") return None @@ -129,17 +132,22 @@ async def _get_sign_info(uid: int, server_id: str = "cn_gf01"): if str(uid)[0] == "5": server_id = "cn_qd01" try: - req = await AsyncHttpx.get( - url=f"https://api-takumi.mihoyo.com/event/bbs_sign_reward/info?act_id=e202009291139501®ion={server_id}&uid={uid}", - headers={ - "x-rpc-app_version": str(Config.get_config("genshin", "mhyVersion")), - "Cookie": await Genshin.get_user_cookie(int(uid), True), - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1", - "x-rpc-client_type": str(Config.get_config("genshin", "client_type")), - "Referer": "https://webstatic.mihoyo.com/", - }, - ) - return req.json() + if user := await Genshin.get_or_none(uid=uid): + req = await AsyncHttpx.get( + url=f"https://api-takumi.mihoyo.com/event/bbs_sign_reward/info?act_id=e202009291139501®ion={server_id}&uid={uid}", + headers={ + "x-rpc-app_version": str( + Config.get_config("genshin", "mhyVersion") + ), + "Cookie": user.cookie, + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1", + "x-rpc-client_type": str( + Config.get_config("genshin", "client_type") + ), + "Referer": "https://webstatic.mihoyo.com/", + }, + ) + return req.json() except Exception as e: logger.error(f"获取签到信息发生错误 UID:{uid} {type(e)}:{e}") return None diff --git a/plugins/genshin/query_user/genshin_sign/init_task.py b/plugins/genshin/query_user/genshin_sign/init_task.py index b1453400..318a0867 100644 --- a/plugins/genshin/query_user/genshin_sign/init_task.py +++ b/plugins/genshin/query_user/genshin_sign/init_task.py @@ -1,17 +1,19 @@ -from .data_source import genshin_sign -from ..mihoyobbs_sign import mihoyobbs_sign -from models.group_member_info import GroupInfoUser -from utils.message_builder import at -from services.log import logger -from utils.utils import scheduler, get_bot -from apscheduler.jobstores.base import ConflictingIdError -from .._models import Genshin -from datetime import datetime, timedelta -from nonebot import Driver -import nonebot import random -import pytz +from datetime import datetime, timedelta +import nonebot +import pytz +from apscheduler.jobstores.base import ConflictingIdError +from nonebot import Driver + +from models.group_member_info import GroupInfoUser +from services.log import logger +from utils.message_builder import at +from utils.utils import get_bot, scheduler + +from .._models import Genshin +from ..mihoyobbs_sign import mihoyobbs_sign +from .data_source import genshin_sign driver: Driver = nonebot.get_driver() @@ -21,7 +23,7 @@ async def _(): """ 启动时分配定时任务 """ - g_list = await Genshin.get_all_auto_sign_user() + g_list = await Genshin.filter(auto_sign=True).all() for u in g_list: if u.auto_sign_time: if date := await Genshin.random_sign_time(u.uid): @@ -33,7 +35,8 @@ async def _(): args=[u.user_qq, u.uid, 0], ) logger.info( - f"genshin_sign add_job:USER:{u.user_qq} UID:{u.uid} " f"{date} 原神自动签到" + f"genshin_sign add_job:USER:{u.user_qq} UID:{u.uid} " + f"{date} 原神自动签到" ) @@ -110,10 +113,12 @@ async def _sign(user_id: int, uid: int, count: int): await bot.send_private_msg(user_id=user_id, message=return_data) await bot.send_private_msg(user_id=user_id, message=msg) else: - if not (group_id := await Genshin.get_bind_group(uid)): - group_list = await GroupInfoUser.get_user_all_group(user_id) - if group_list: - group_id = group_list[0] - await bot.send_group_msg( - group_id=group_id, message=at(user_id) + msg - ) + if user := await Genshin.get_or_none(uid=uid): + group_id = user.bind_group + if not group_id: + if group_list := await GroupInfoUser.get_user_all_group(user_id): + group_id = group_list[0] + if group_id: + await bot.send_group_msg( + group_id=group_id, message=at(user_id) + msg + ) diff --git a/plugins/genshin/query_user/mihoyobbs_sign/__init__.py b/plugins/genshin/query_user/mihoyobbs_sign/__init__.py index 4298183c..e98d67cd 100644 --- a/plugins/genshin/query_user/mihoyobbs_sign/__init__.py +++ b/plugins/genshin/query_user/mihoyobbs_sign/__init__.py @@ -1,14 +1,16 @@ -from nonebot.adapters.onebot.v11 import MessageEvent +from typing import Tuple + from nonebot import on_command +from nonebot.adapters.onebot.v11 import MessageEvent +from nonebot.params import Command + from services.log import logger + # from .init_task import add_job, scheduler, _sign # from apscheduler.jobstores.base import JobLookupError from .._models import Genshin -from nonebot.params import Command -from typing import Tuple from .mihoyobbs import * - __zx_plugin_name__ = "米游社自动签到" __plugin_usage__ = """ usage: @@ -28,12 +30,10 @@ __plugin_settings__ = { "level": 5, "default_status": True, "limit_superuser": False, - "cmd": ["原神签到"], + "cmd": ["米游社签到"], } -mihoyobbs_matcher = on_command( - "米游社签到", aliases={"米游社我硬签"}, priority=5, block=True -) +mihoyobbs_matcher = on_command("米游社签到", aliases={"米游社我硬签"}, priority=5, block=True) @mihoyobbs_matcher.handle() @@ -47,24 +47,29 @@ async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command()): async def mihoyobbs_sign(user_id): - uid = await Genshin.get_user_uid(user_id) - if not uid or not await Genshin.get_user_cookie(uid, True): + user = await Genshin.get_or_none(user_qq=user_id) + if not user or not user.uid or not user.cookie: await mihoyobbs_matcher.finish("请先绑定uid和cookie!", at_sender=True) - stuid = await Genshin.get_stuid(uid) - stoken = await Genshin.get_stoken(uid) - cookie = await Genshin.get_user_cookie(uid) - bbs = mihoyobbs.Mihoyobbs(stuid=stuid, stoken=stoken, cookie=cookie) + bbs = mihoyobbs.Mihoyobbs(stuid=user.stuid, stoken=user.stoken, cookie=user.cookie) await bbs.init() return_data = "" - if bbs.Task_do["bbs_Sign"] and bbs.Task_do["bbs_Read_posts"] and bbs.Task_do["bbs_Like_posts"] and \ - bbs.Task_do["bbs_Share"]: - return_data += f"今天的米游社签到任务已经全部完成了!\n" \ - f"一共获得{mihoyobbs.today_have_get_coins}个米游币\n目前有{mihoyobbs.Have_coins}个米游币" - logger.info(f"今天已经全部完成了!一共获得{mihoyobbs.today_have_get_coins}个米游币,目前有{mihoyobbs.Have_coins}个米游币") + if ( + bbs.Task_do["bbs_Sign"] + and bbs.Task_do["bbs_Read_posts"] + and bbs.Task_do["bbs_Like_posts"] + and bbs.Task_do["bbs_Share"] + ): + return_data += ( + f"今天的米游社签到任务已经全部完成了!\n" + f"一共获得{mihoyobbs.today_have_get_coins}个米游币\n目前有{mihoyobbs.Have_coins}个米游币" + ) + logger.info( + f"今天已经全部完成了!一共获得{mihoyobbs.today_have_get_coins}个米游币,目前有{mihoyobbs.Have_coins}个米游币" + ) else: i = 0 - print("开始签到") - print(mihoyobbs.today_have_get_coins) + # print("开始签到") + # print(mihoyobbs.today_have_get_coins) while mihoyobbs.today_get_coins != 0 and i < 3: # if i > 0: await bbs.refresh_list() @@ -74,8 +79,12 @@ async def mihoyobbs_sign(user_id): await bbs.share_post() await bbs.get_tasks_list() i += 1 - return_data += "\n" + f"今天已经获得{mihoyobbs.today_have_get_coins}个米游币\n" \ - f"还能获得{mihoyobbs.today_get_coins}个米游币\n目前有{mihoyobbs.Have_coins}个米游币" - logger.info(f"今天已经获得{mihoyobbs.today_have_get_coins}个米游币," - f"还能获得{mihoyobbs.today_get_coins}个米游币,目前有{mihoyobbs.Have_coins}个米游币") + return_data += ( + "\n" + f"今天已经获得{mihoyobbs.today_have_get_coins}个米游币\n" + f"还能获得{mihoyobbs.today_get_coins}个米游币\n目前有{mihoyobbs.Have_coins}个米游币" + ) + logger.info( + f"今天已经获得{mihoyobbs.today_have_get_coins}个米游币," + f"还能获得{mihoyobbs.today_get_coins}个米游币,目前有{mihoyobbs.Have_coins}个米游币" + ) return return_data diff --git a/plugins/genshin/query_user/query_memo/__init__.py b/plugins/genshin/query_user/query_memo/__init__.py index 3d66f3bf..5331c2bf 100644 --- a/plugins/genshin/query_user/query_memo/__init__.py +++ b/plugins/genshin/query_user/query_memo/__init__.py @@ -1,9 +1,10 @@ from nonebot import on_command -from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent -from services.log import logger -from .data_source import get_user_memo, get_memo -from .._models import Genshin +from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent +from services.log import logger + +from .._models import Genshin +from .data_source import get_memo, get_user_memo __zx_plugin_name__ = "原神便笺查询" __plugin_usage__ = """ @@ -27,30 +28,27 @@ __plugin_settings__ = { __plugin_block_limit__ = {} -query_memo_matcher = on_command("原神便签查询", aliases={"原神便笺查询", "yss"}, priority=5, block=True) +query_memo_matcher = on_command( + "原神便签查询", aliases={"原神便笺查询", "yss"}, priority=5, block=True +) @query_memo_matcher.handle() async def _(event: MessageEvent): - uid = await Genshin.get_user_uid(event.user_id) - if not uid or not await Genshin.get_user_cookie(uid, True): + user = await Genshin.get_or_none(user_qq=event.user_id) + if not user or not user.uid or not user.cookie: await query_memo_matcher.finish("请先绑定uid和cookie!") if isinstance(event, GroupMessageEvent): uname = event.sender.card or event.sender.nickname else: uname = event.sender.nickname - data = await get_user_memo(event.user_id, uid, uname) + data = await get_user_memo(event.user_id, user.uid, uname) if data: await query_memo_matcher.send(data) logger.info( f"(USER {event.user_id}, " f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) " - f"使用原神便笺查询 uid:{uid}" + f"使用原神便笺查询 uid:{user.uid}" ) else: await query_memo_matcher.send("未查询到数据...") - - - - - diff --git a/plugins/genshin/query_user/query_memo/data_source.py b/plugins/genshin/query_user/query_memo/data_source.py index e1e1c25a..f7fe592f 100644 --- a/plugins/genshin/query_user/query_memo/data_source.py +++ b/plugins/genshin/query_user/query_memo/data_source.py @@ -1,20 +1,22 @@ -from typing import Optional, Union -from nonebot.adapters.onebot.v11 import MessageSegment -from configs.config import Config -from asyncio.exceptions import TimeoutError -from services.log import logger -from configs.path_config import IMAGE_PATH -from utils.image_utils import BuildImage -from utils.http_utils import AsyncHttpx -from utils.utils import get_user_avatar -from utils.message_builder import image -from .._utils import get_ds -from .._models import Genshin -from io import BytesIO -from nonebot import Driver import asyncio -import nonebot +from asyncio.exceptions import TimeoutError +from io import BytesIO +from typing import Optional, Tuple, Union +import nonebot +from nonebot import Driver +from nonebot.adapters.onebot.v11 import MessageSegment + +from configs.config import Config +from configs.path_config import IMAGE_PATH +from services.log import logger +from utils.http_utils import AsyncHttpx +from utils.image_utils import BuildImage +from utils.message_builder import image +from utils.utils import get_user_avatar + +from .._models import Genshin +from .._utils import get_ds driver: Driver = nonebot.get_driver() @@ -26,10 +28,7 @@ memo_path.mkdir(exist_ok=True, parents=True) @driver.on_startup async def _(): for name, url in zip( - [ - "resin.png", "task.png", "resin_discount.png", "chengehu.png", - "zhibian.png" - ], + ["resin.png", "task.png", "resin_discount.png", "chengehu.png", "zhibian.png"], [ "https://upload-bbs.mihoyo.com/upload/2021/09/29/8819732/54266243c7d15ba31690c8f5d63cc3c6_71491376413333325" "20.png?x-oss-process=image//resize,s_600/quality,q_80/auto-orient,0/interlace,1/format,png", @@ -45,8 +44,9 @@ async def _(): logger.info(f"已下载原神便签资源 -> {file}...") -async def get_user_memo(user_id: int, uid: int, - uname: str) -> Optional[Union[str, MessageSegment]]: +async def get_user_memo( + user_id: int, uid: int, uname: str +) -> Optional[Union[str, MessageSegment]]: uid = str(uid) if uid[0] in ["1", "2"]: server_id = "cn_gf01" @@ -57,21 +57,17 @@ async def get_user_memo(user_id: int, uid: int, return await parse_data_and_draw(user_id, uid, server_id, uname) -async def get_memo(uid: str, server_id: str) -> "Union[str, dict], int": +async def get_memo(uid: str, server_id: str) -> Tuple[Union[str, dict], int]: try: req = await AsyncHttpx.get( - url= - f"https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/dailyNote?server={server_id}&role_id={uid}", + url=f"https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/dailyNote?server={server_id}&role_id={uid}", headers={ "DS": get_ds(f"role_id={uid}&server={server_id}"), - "x-rpc-app_version": Config.get_config("genshin", - "mhyVersion"), - "User-Agent": - "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1", - "x-rpc-client_type": Config.get_config("genshin", - "client_type"), + "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"), + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1", + "x-rpc-client_type": Config.get_config("genshin", "client_type"), "Referer": "https://webstatic.mihoyo.com/", - "Cookie": await Genshin.get_user_cookie(int(uid)) + "Cookie": await Genshin.random_cookie(uid), }, ) data = req.json() @@ -85,18 +81,13 @@ async def get_memo(uid: str, server_id: str) -> "Union[str, dict], int": return "发生了一些错误,请稍后再试", 998 -def create_border(image_name: str, content: str, notice_text: str, - value: str) -> BuildImage: - border = BuildImage(500, - 75, - color="#E0D9D1", - font="HYWenHei-85W.ttf", - font_size=20) - text_bk = BuildImage(350, - 75, - color="#F5F1EB", - font_size=23, - font="HYWenHei-85W.ttf") +def create_border( + image_name: str, content: str, notice_text: str, value: str +) -> BuildImage: + border = BuildImage(500, 75, color="#E0D9D1", font="HYWenHei-85W.ttf", font_size=20) + text_bk = BuildImage( + 350, 75, color="#F5F1EB", font_size=23, font="HYWenHei-85W.ttf" + ) _x = 70 if image_name == "resin.png" else 50 _px = 10 if image_name == "resin.png" else 20 text_bk.paste( @@ -119,15 +110,14 @@ def create_border(image_name: str, content: str, notice_text: str, True, ) font_width, _ = border.getsize(value) - border.text((350 + 76 - int(font_width / 2), 0), - value, - center_type="by_height") + border.text((350 + 76 - int(font_width / 2), 0), value, center_type="by_height") border.paste(text_bk, (2, 0), center_type="by_height") return border -async def parse_data_and_draw(user_id: int, uid: str, server_id: str, - uname: str) -> Union[str, MessageSegment]: +async def parse_data_and_draw( + user_id: int, uid: str, server_id: str, uname: str +) -> Union[str, MessageSegment]: data, code = await get_memo(uid, server_id) if code != 200: return data @@ -138,11 +128,13 @@ async def parse_data_and_draw(user_id: int, uid: str, server_id: str, if not role_avatar.exists(): await AsyncHttpx.download_file(x["avatar_side_icon"], role_avatar) return await asyncio.get_event_loop().run_in_executor( - None, _parse_data_and_draw, data, user_avatar, uid, uname) + None, _parse_data_and_draw, data, user_avatar, uid, uname + ) -def _parse_data_and_draw(data: dict, user_avatar: BytesIO, uid: int, - uname: str) -> Union[str, MessageSegment]: +def _parse_data_and_draw( + data: dict, user_avatar: BytesIO, uid: int, uname: str +) -> Union[str, MessageSegment]: current_resin = data["current_resin"] # 当前树脂 max_resin = data["max_resin"] # 最大树脂 resin_recovery_time = data["resin_recovery_time"] # 树脂全部回复时间 @@ -157,33 +149,24 @@ def _parse_data_and_draw(data: dict, user_avatar: BytesIO, uid: int, max_coin = data["max_home_coin"] # 最大宝钱 coin_recovery_time = data["home_coin_recovery_time"] # 宝钱全部回复时间 transformer_available = data["transformer"]["obtained"] # 参量质变仪可获取 - transformer_state = data["transformer"]["recovery_time"][ - "reached"] # 参量质变仪状态 - transformer_recovery_time = data["transformer"]["recovery_time"][ - "Day"] # 参量质变仪回复时间 + transformer_state = data["transformer"]["recovery_time"]["reached"] # 参量质变仪状态 + transformer_recovery_time = data["transformer"]["recovery_time"]["Day"] # 参量质变仪回复时间 transformer_recovery_hour = data["transformer"]["recovery_time"][ - "Hour"] # 参量质变仪回复时间 + "Hour" + ] # 参量质变仪回复时间 coin_minute, coin_second = divmod(int(coin_recovery_time), 60) coin_hour, coin_minute = divmod(coin_minute, 60) - #print(data) + # print(data) minute, second = divmod(int(resin_recovery_time), 60) hour, minute = divmod(minute, 60) - A = BuildImage(1030, - 570, - color="#f1e9e1", - font_size=15, - font="HYWenHei-85W.ttf") + A = BuildImage(1030, 570, color="#f1e9e1", font_size=15, font="HYWenHei-85W.ttf") A.text((10, 15), "原神便笺 | Create By ZhenXun", (198, 186, 177)) ava = BuildImage(100, 100, background=user_avatar) ava.circle() A.paste(ava, (40, 40), True) A.paste( - BuildImage(0, - 0, - plain_text=uname, - font_size=20, - font="HYWenHei-85W.ttf"), + BuildImage(0, 0, plain_text=uname, font_size=20, font="HYWenHei-85W.ttf"), (160, 62), True, ) @@ -225,40 +208,39 @@ def _parse_data_and_draw(data: dict, user_avatar: BytesIO, uid: int, "chengehu.png", "洞天财翁-洞天宝钱", "洞天财翁已达到存储上限" - if current_coin == max_coin else f"{coin_hour}小时{coin_minute}分钟后存满", + if current_coin == max_coin + else f"{coin_hour}小时{coin_minute}分钟后存满", f"{current_coin}/{max_coin}", ) A.paste(border, (10, 395)) border = create_border( "zhibian.png", "参量质变仪", - "不存在" if not transformer_available else - "已准备完成 " if transformer_state else f"{transformer_recovery_hour}小时后可使用" if not transformer_recovery_time else f"{transformer_recovery_time}天后可使用", - "不存在" if not transformer_available else - "可使用" if transformer_state else "冷却中", + "不存在" + if not transformer_available + else "已准备完成 " + if transformer_state + else f"{transformer_recovery_hour}小时后可使用" + if not transformer_recovery_time + else f"{transformer_recovery_time}天后可使用", + "不存在" if not transformer_available else "可使用" if transformer_state else "冷却中", ) A.paste(border, (10, 475)) - expeditions_border = BuildImage(470, - 510, - color="#E0D9D1", - font="HYWenHei-85W.ttf", - font_size=20) - expeditions_text = BuildImage(466, - 506, - color="#F5F1EB", - font_size=23, - font="HYWenHei-85W.ttf") + expeditions_border = BuildImage( + 470, 510, color="#E0D9D1", font="HYWenHei-85W.ttf", font_size=20 + ) + expeditions_text = BuildImage( + 466, 506, color="#F5F1EB", font_size=23, font="HYWenHei-85W.ttf" + ) expeditions_text.text( - (5, 5), f"探索派遣限制{current_expedition_num}/{max_expedition_num}", - (100, 100, 98)) + (5, 5), f"探索派遣限制{current_expedition_num}/{max_expedition_num}", (100, 100, 98) + ) h = 45 for x in expeditions: - _bk = BuildImage(400, - 82, - color="#ECE3D8", - font="HYWenHei-85W.ttf", - font_size=21) + _bk = BuildImage( + 400, 82, color="#ECE3D8", font="HYWenHei-85W.ttf", font_size=21 + ) file_name = x["avatar_side_icon"].split("_")[-1] role_avatar = memo_path / "role_avatar" / file_name _ava_img = BuildImage(75, 75, background=role_avatar) diff --git a/plugins/genshin/query_user/query_role/__init__.py b/plugins/genshin/query_user/query_role/__init__.py index e4623e79..ce39e809 100644 --- a/plugins/genshin/query_user/query_role/__init__.py +++ b/plugins/genshin/query_user/query_role/__init__.py @@ -1,11 +1,13 @@ +from httpx import ConnectError from nonebot import on_command -from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent, Message -from services.log import logger -from .data_source import query_role_data -from .._models import Genshin -from utils.utils import is_number +from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageEvent from nonebot.params import CommandArg +from services.log import logger +from utils.utils import is_number + +from .._models import Genshin +from .data_source import query_role_data __zx_plugin_name__ = "原神玩家查询" __plugin_usage__ = """ @@ -29,7 +31,9 @@ __plugin_settings__ = { __plugin_block_limit__ = {} -query_role_info_matcher = on_command("原神玩家查询", aliases={"原神玩家查找", "ys"}, priority=5, block=True) +query_role_info_matcher = on_command( + "原神玩家查询", aliases={"原神玩家查找", "ys"}, priority=5, block=True +) @query_role_info_matcher.handle() @@ -39,24 +43,25 @@ async def _(event: MessageEvent, arg: Message = CommandArg()): if not is_number(msg): await query_role_info_matcher.finish("查询uid必须为数字!") msg = int(msg) - if not msg: - uid = await Genshin.get_user_uid(event.user_id) + uid = None + user = await Genshin.get_or_none(user_qq=event.user_id) + if not msg and user: + uid = user.uid else: uid = msg if not uid: # or not await Genshin.get_user_cookie(uid): await query_role_info_matcher.finish("请先绑定uid和cookie!") nickname = event.sender.card or event.sender.nickname - mys_id = await Genshin.get_user_mys_id(uid) - data = await query_role_data(event.user_id, uid, mys_id, nickname) - if data: - await query_role_info_matcher.send(data) - logger.info( - f"(USER {event.user_id}, " - f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 使用原神玩家查询 uid:{uid}" - ) - else: - await query_role_info_matcher.send("查询失败..") - - - - + mys_id = user.mys_id if user else None + try: + data = await query_role_data(event.user_id, uid, mys_id, nickname) + if data: + await query_role_info_matcher.send(data) + logger.info( + f"(USER {event.user_id}, " + f"GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) 使用原神玩家查询 uid:{uid}" + ) + else: + await query_role_info_matcher.send("查询失败..") + except ConnectError: + await query_role_info_matcher.send("网络出小差啦~") diff --git a/plugins/genshin/query_user/query_role/data_source.py b/plugins/genshin/query_user/query_role/data_source.py index 01b6fb70..58f0ffe9 100644 --- a/plugins/genshin/query_user/query_role/data_source.py +++ b/plugins/genshin/query_user/query_role/data_source.py @@ -1,11 +1,14 @@ -from typing import Optional, List, Dict, Union -from .draw_image import init_image, get_genshin_image +from typing import Dict, List, Optional, Tuple, Union + from nonebot.adapters.onebot.v11 import MessageSegment -from .._utils import get_ds, element_mastery + +from configs.config import Config from services.log import logger from utils.http_utils import AsyncHttpx -from configs.config import Config + from .._models import Genshin +from .._utils import element_mastery, get_ds +from .draw_image import get_genshin_image, init_image try: import ujson as json @@ -96,66 +99,66 @@ b=body q=query """ -async def get_info(uid_: str, server_id: str) -> "Optional[Union[dict, str]], int": - try: - req = await AsyncHttpx.get( - url=f"https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/index?server={server_id}&role_id={uid_}", - headers={ - "Accept": "application/json, text/plain, */*", - "DS": get_ds(f"role_id={uid_}&server={server_id}"), - "Origin": "https://webstatic.mihoyo.com", - "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"), - "User-Agent": "Mozilla/5.0 (Linux; Android 9; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 miHoYoBBS/2.2.0", - "x-rpc-client_type": Config.get_config("genshin", "client_type"), - "Referer": "https://webstatic.mihoyo.com/app/community-game-records/index.html?v=6", - "Accept-Encoding": "gzip, deflate", - "Accept-Language": "zh-CN,en-US;q=0.8", - "X-Requested-With": "com.mihoyo.hyperion", - "Cookie": await Genshin.get_user_cookie(int(uid_)) - }, - ) - data = req.json() - if data["message"] == "OK": - return data["data"], 200 - return data["message"], 999 - except Exception as e: - logger.error(f"访问失败,请重试! {type(e)}: {e}") +async def get_info(uid_: str, server_id: str) -> Tuple[Optional[Union[dict, str]], int]: + # try: + req = await AsyncHttpx.get( + url=f"https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/index?server={server_id}&role_id={uid_}", + headers={ + "Accept": "application/json, text/plain, */*", + "DS": get_ds(f"role_id={uid_}&server={server_id}"), + "Origin": "https://webstatic.mihoyo.com", + "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"), + "User-Agent": "Mozilla/5.0 (Linux; Android 9; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 miHoYoBBS/2.2.0", + "x-rpc-client_type": Config.get_config("genshin", "client_type"), + "Referer": "https://webstatic.mihoyo.com/app/community-game-records/index.html?v=6", + "Accept-Encoding": "gzip, deflate", + "Accept-Language": "zh-CN,en-US;q=0.8", + "X-Requested-With": "com.mihoyo.hyperion", + "Cookie": await Genshin.random_cookie(uid_), + }, + ) + data = req.json() + if data["message"] == "OK": + return data["data"], 200 + return data["message"], 999 + # except Exception as e: + # logger.error(f"访问失败,请重试! {type(e)}: {e}") return None, -1 async def get_character( uid: str, character_ids: List[str], server_id="cn_gf01" ) -> Optional[dict]: - try: - req = await AsyncHttpx.post( - url="https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/character", - headers={ - "Accept": "application/json, text/plain, */*", - "DS": get_ds( - "", - { - "character_ids": character_ids, - "role_id": uid, - "server": server_id, - }, - ), - "Origin": "https://webstatic.mihoyo.com", - "Cookie": await Genshin.get_user_cookie(int(uid)), - "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"), - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1", - "x-rpc-client_type": "5", - "Referer": "https://webstatic.mihoyo.com/", - "Accept-Encoding": "gzip, deflate", - "Accept-Language": "zh-CN,en-US;q=0.8", - "X-Requested-With": "com.mihoyo.hyperion", - }, - json={"character_ids": character_ids, "role_id": uid, "server": server_id}, - ) - data = req.json() - if data["message"] == "OK": - return data["data"] - except Exception as e: - logger.error(f"访问失败,请重试! {type(e)}: {e}") + # try: + req = await AsyncHttpx.post( + url="https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/character", + headers={ + "Accept": "application/json, text/plain, */*", + "DS": get_ds( + "", + { + "character_ids": character_ids, + "role_id": uid, + "server": server_id, + }, + ), + "Origin": "https://webstatic.mihoyo.com", + "Cookie": await Genshin.random_cookie(uid), + "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"), + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1", + "x-rpc-client_type": "5", + "Referer": "https://webstatic.mihoyo.com/", + "Accept-Encoding": "gzip, deflate", + "Accept-Language": "zh-CN,en-US;q=0.8", + "X-Requested-With": "com.mihoyo.hyperion", + }, + json={"character_ids": character_ids, "role_id": uid, "server": server_id}, + ) + data = req.json() + if data["message"] == "OK": + return data["data"] + # except Exception as e: + # logger.error(f"访问失败,请重试! {type(e)}: {e}") return None @@ -205,7 +208,7 @@ def parsed_data( "image": world["icon"], "name": world["name"], "offerings": world["offerings"], - "icon": world["icon"] + "icon": world["icon"], } world_data_dict[world["name"]] = _x home_data_list = [] @@ -231,21 +234,21 @@ async def get_mys_data(uid: str, mys_id: Optional[str]) -> Optional[List[Dict]]: :param mys_id: 米游社id """ if mys_id: - try: - req = await AsyncHttpx.get( - url=f"https://api-takumi-record.mihoyo.com/game_record/card/wapi/getGameRecordCard?uid={mys_id}", - headers={ - "DS": get_ds(f"uid={mys_id}"), - "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"), - "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1", - "x-rpc-client_type": "5", - "Referer": "https://webstatic.mihoyo.com/", - "Cookie": await Genshin.get_user_cookie(int(uid)) - }, - ) - data = req.json() - if data["message"] == "OK": - return data["data"]["list"] - except Exception as e: - logger.error(f"访问失败,请重试! {type(e)}: {e}") + # try: + req = await AsyncHttpx.get( + url=f"https://api-takumi-record.mihoyo.com/game_record/card/wapi/getGameRecordCard?uid={mys_id}", + headers={ + "DS": get_ds(f"uid={mys_id}"), + "x-rpc-app_version": Config.get_config("genshin", "mhyVersion"), + "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1", + "x-rpc-client_type": "5", + "Referer": "https://webstatic.mihoyo.com/", + "Cookie": await Genshin.random_cookie(uid), + }, + ) + data = req.json() + if data["message"] == "OK": + return data["data"]["list"] + # except Exception as e: + # logger.error(f"访问失败,请重试! {type(e)}: {e}") return None diff --git a/plugins/genshin/query_user/reset_today_query_user_data/__init__.py b/plugins/genshin/query_user/reset_today_query_user_data/__init__.py index 867e1982..667ba90a 100644 --- a/plugins/genshin/query_user/reset_today_query_user_data/__init__.py +++ b/plugins/genshin/query_user/reset_today_query_user_data/__init__.py @@ -1,6 +1,7 @@ -from utils.utils import scheduler -from .._models import Genshin from services.log import logger +from utils.utils import scheduler + +from .._models import Genshin @scheduler.scheduled_job( @@ -10,7 +11,7 @@ from services.log import logger ) async def _(): try: - await Genshin.reset_today_query_uid() + await Genshin.all().update(today_query_uid="") logger.warning(f"重置原神查询记录成功..") except Exception as e: logger.error(f"重置原神查询记录失败. {type(e)}:{e}") diff --git a/plugins/genshin/query_user/resin_remind/__init__.py b/plugins/genshin/query_user/resin_remind/__init__.py index 4de7b2a9..60c1984d 100644 --- a/plugins/genshin/query_user/resin_remind/__init__.py +++ b/plugins/genshin/query_user/resin_remind/__init__.py @@ -1,12 +1,15 @@ -from nonebot import on_command -from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent -from apscheduler.jobstores.base import JobLookupError -from services.log import logger -from .init_task import scheduler, add_job -from .._models import Genshin -from nonebot.params import Command from typing import Tuple +from apscheduler.jobstores.base import JobLookupError +from nonebot import on_command +from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent +from nonebot.params import Command + +from services.log import logger +from utils.depends import OneCommand + +from .._models import Genshin +from .init_task import add_job, scheduler __zx_plugin_name__ = "原神树脂提醒" __plugin_usage__ = """ @@ -33,31 +36,32 @@ __plugin_configs__ = { "AUTO_CLOSE_QUERY_FAIL_RESIN_REMIND": { "value": True, "help": "当请求连续三次失败时,关闭用户的树脂提醒", - "default_value": True + "default_value": True, }, "CUSTOM_RESIN_OVERFLOW_REMIND": { "value": 20, "help": "自定义树脂溢出指定数量时的提醒,空值是为关闭", - "default_value": None - } + "default_value": None, + }, } resin_remind = on_command("开原神树脂提醒", aliases={"关原神树脂提醒"}, priority=5, block=True) @resin_remind.handle() -async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command()): - cmd = cmd[0] - uid = await Genshin.get_user_uid(event.user_id) - if not uid or not await Genshin.get_user_cookie(uid, True): +async def _(event: MessageEvent, cmd: str = OneCommand()): + user = await Genshin.get_or_none(user_qq=event.user_id) + if not user or not user.uid or not user.cookie: await resin_remind.finish("请先绑定uid和cookie!") try: - scheduler.remove_job(f"genshin_resin_remind_{uid}_{event.user_id}") + scheduler.remove_job(f"genshin_resin_remind_{user.uid}_{event.user_id}") except JobLookupError: pass if cmd[0] == "开": - await Genshin.set_resin_remind(uid, True) - add_job(event.user_id, uid) + if user.resin_remind: + await resin_remind.finish("原神树脂提醒已经是开启状态,请勿重复开启!", at_sender=True) + user.resin_remind = True + add_job(event.user_id, user.uid) logger.info( f"(USER {event.user_id}, GROUP " f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" @@ -65,12 +69,15 @@ async def _(event: MessageEvent, cmd: Tuple[str, ...] = Command()): ) await resin_remind.send("开启原神树脂提醒成功!", at_sender=True) else: - await Genshin.set_resin_remind(uid, False) - await Genshin.clear_resin_remind_time(uid) + if not user.resin_remind: + await resin_remind.finish("原神树脂提醒已经是开启状态,请勿重复开启!", at_sender=True) + user.resin_remind = False + user.resin_recovery_time = None logger.info( f"(USER {event.user_id}, GROUP " f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" f" 关闭原神体力提醒" ) await resin_remind.send("已关闭原神树脂提醒..", at_sender=True) - + if user: + await user.save(update_fields=["resin_remind", "resin_recovery_time"]) diff --git a/plugins/genshin/query_user/resin_remind/init_task.py b/plugins/genshin/query_user/resin_remind/init_task.py index f8cf62a8..63d0b288 100644 --- a/plugins/genshin/query_user/resin_remind/init_task.py +++ b/plugins/genshin/query_user/resin_remind/init_task.py @@ -1,20 +1,20 @@ -from nonebot.adapters.onebot.v11 import ActionFailed - -from utils.utils import get_bot, scheduler -from utils.message_builder import at -from models.group_member_info import GroupInfoUser -from apscheduler.jobstores.base import ConflictingIdError -from nonebot import Driver -from .._models import Genshin -from datetime import datetime, timedelta -from apscheduler.jobstores.base import JobLookupError -from services.log import logger -from nonebot.plugin import require -from configs.config import Config import random +from datetime import datetime, timedelta + import nonebot import pytz +from apscheduler.jobstores.base import ConflictingIdError, JobLookupError +from nonebot import Driver +from nonebot.adapters.onebot.v11 import ActionFailed +from nonebot.plugin import require +from configs.config import Config +from models.group_member_info import GroupInfoUser +from services.log import logger +from utils.message_builder import at +from utils.utils import get_bot, scheduler + +from .._models import Genshin driver: Driver = nonebot.get_driver() @@ -23,7 +23,6 @@ require("query_memo") from ..query_memo import get_memo - global_map = {} @@ -80,12 +79,13 @@ async def _(): """ 启动时分配定时任务 """ - g_list = await Genshin.get_all_resin_remind_user() + g_list = await Genshin.filter(resin_remind=True).all() + update_list = [] date = datetime.now(pytz.timezone("Asia/Shanghai")) + timedelta(seconds=30) for u in g_list: if u.resin_remind: if u.resin_recovery_time: - if await Genshin.get_user_resin_recovery_time(u.uid) > datetime.now( + if u.resin_recovery_time and u.resin_recovery_time > datetime.now( pytz.timezone("Asia/Shanghai") ): # date = await Genshin.get_user_resin_recovery_time(u.uid) # 不能要,因为可能在这期间用户使用了树脂 @@ -101,7 +101,8 @@ async def _(): f"genshin_resin_remind add_job:USER:{u.user_qq} UID:{u.uid}启动原神树脂提醒 " ) else: - await Genshin.clear_resin_remind_time(u.uid) + u.resin_recovery_time = None + update_list.append(u) add_job(u.user_qq, u.uid) logger.info( f"genshin_resin_remind add_job CHECK:USER:{u.user_qq} UID:{u.uid}启动原神树脂提醒 " @@ -111,6 +112,8 @@ async def _(): logger.info( f"genshin_resin_remind add_job CHECK:USER:{u.user_qq} UID:{u.uid}启动原神树脂提醒 " ) + if update_list: + await Genshin.bulk_update(update_list, ["resin_recovery_time"]) def add_job(user_id: int, uid: int): @@ -133,6 +136,7 @@ def add_job(user_id: int, uid: int): async def _remind(user_id: int, uid: str): + user = await Genshin.get_or_none(user_qq=user_id, uid=int(uid)) uid = str(uid) if uid[0] in ["1", "2"]: server_id = "cn_gf01" @@ -158,7 +162,9 @@ async def _remind(user_id: int, uid: str): elif max_resin - 20 <= current_resin < max_resin: next_time = now + timedelta(minutes=(max_resin - current_resin) * 8) elif current_resin == max_resin: - custom_overflow_resin = Config.get_config("resin_remind", "CUSTOM_RESIN_OVERFLOW_REMIND") + custom_overflow_resin = Config.get_config( + "resin_remind", "CUSTOM_RESIN_OVERFLOW_REMIND" + ) if user_manager.is_overflow(uid) and custom_overflow_resin: next_time = now + timedelta(minutes=custom_overflow_resin * 8) user_manager.add_overflow(uid) @@ -178,28 +184,35 @@ async def _remind(user_id: int, uid: str): message=msg, ) else: - group_id = await Genshin.get_bind_group(int(uid)) - if not group_id: - group_list = await GroupInfoUser.get_user_all_group(user_id) - if group_list: - group_id = group_list[0] - try: - await bot.send_group_msg( - group_id=group_id, - message=at(user_id) + msg - ) - except ActionFailed as e: - logger.error(f"树脂提醒推送发生错误 {type(e)}:{e}") + if user: + group_id = user.bind_group + if not group_id: + if group_list := await GroupInfoUser.get_user_all_group( + user_id + ): + group_id = group_list[0] + try: + await bot.send_group_msg( + group_id=group_id, message=at(user_id) + msg + ) + except ActionFailed as e: + logger.error(f"树脂提醒推送发生错误 {type(e)}:{e}") if not next_time: - if user_manager.check(uid) and Config.get_config("resin_remind", "AUTO_CLOSE_QUERY_FAIL_RESIN_REMIND"): - await Genshin.set_resin_remind(int(uid), False) - await Genshin.clear_resin_remind_time(int(uid)) + if user_manager.check(uid) and Config.get_config( + "resin_remind", "AUTO_CLOSE_QUERY_FAIL_RESIN_REMIND" + ): + if user: + user.resin_remind = False + user.resin_recovery_time = None + await user.save(update_fields=["resin_recovery_time", "resin_remind"]) next_time = now + timedelta(minutes=(20 + random.randint(5, 20)) * 8) user_manager.add_error_count(uid) else: user_manager.remove_error_count(uid) - await Genshin.set_user_resin_recovery_time(int(uid), next_time) + if user: + user.resin_recovery_time = next_time + await user.save(update_fields=["resin_recovery_time", "resin_remind"]) scheduler.add_job( _remind, "date", @@ -208,6 +221,5 @@ async def _remind(user_id: int, uid: str): args=[user_id, uid], ) logger.info( - f"genshin_resin_remind add_job:USER:{user_id} UID:{uid} " - f"{next_time} 原神树脂提醒" - ) + f"genshin_resin_remind add_job:USER:{user_id} UID:{uid} " f"{next_time} 原神树脂提醒" + ) diff --git a/plugins/gold_redbag/model.py b/plugins/gold_redbag/model.py index a797463b..9516a4bd 100755 --- a/plugins/gold_redbag/model.py +++ b/plugins/gold_redbag/model.py @@ -1,73 +1,58 @@ -from services.db_context import db from typing import List +from tortoise import fields -class RedbagUser(db.Model): - __tablename__ = "redbag_users" +from services.db_context import Model - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger(), nullable=False) - send_redbag_count = db.Column(db.Integer(), default=0) - get_redbag_count = db.Column(db.Integer(), default=0) - spend_gold = db.Column(db.Integer(), default=0) - get_gold = db.Column(db.Integer(), default=0) - _idx1 = db.Index("redbag_group_users_idx1", "user_qq", "group_id", unique=True) +class RedbagUser(Model): + + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField() + """群聊id""" + send_redbag_count = fields.IntField(default=0) + """发送红包次数""" + get_redbag_count = fields.IntField(default=0) + """开启红包次数""" + spend_gold = fields.IntField(default=0) + """发送红包花费金额""" + get_gold = fields.IntField(default=0) + """开启红包获取金额""" + + class Meta: + table = "redbag_users" + table_description = "红包统计数据表" + unique_together = ("user_qq", "group_id") @classmethod - async def add_redbag_data(cls, user_qq: int, group_id: int, itype: str, money: int): + async def add_redbag_data( + cls, user_qq: int, group_id: int, i_type: str, money: int + ): """ 说明: 添加收发红包数据 参数: :param user_qq: qq号 :param group_id: 群号 - :param itype: 收或发 + :param i_type: 收或发 :param money: 金钱数量 """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - user = await query.with_for_update().gino.first() or await cls.create( - user_qq=user_qq, - group_id=group_id, - ) - if itype == "get": - await user.update( - get_redbag_count=user.get_redbag_count + 1, - get_gold=user.get_gold + money, - ).apply() - else: - await user.update( - send_redbag_count=user.send_redbag_count + 1, - spend_gold=user.spend_gold + money, - ).apply() - @classmethod - async def ensure(cls, user_qq: int, group_id: int) -> bool: - """ - 说明: - 获取用户对象 - 参数: - :param user_qq: qq号 - :param group_id: 群号 - """ - query = cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - user = await query.gino.first() or await cls.create( - user_qq=user_qq, - group_id=group_id, - ) - return user - - @classmethod - async def get_user_all(cls, group_id: int = None) -> List["RedbagUser"]: - """ - 说明: - 获取所有用户对象 - 参数: - :param group_id: 群号 - """ - if not group_id: - query = await cls.query.gino.all() + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + if i_type == "get": + user.get_redbag_count = user.get_redbag_count + 1 + user.get_gold = user.get_gold + money else: - query = await cls.query.where((cls.group_id == group_id)).gino.all() - return query + user.send_redbag_count = user.send_redbag_count + 1 + user.spend_gold = user.spend_gold + money + await user.save( + update_fields=[ + "get_redbag_count", + "get_gold", + "send_redbag_count", + "spend_gold", + ] + ) diff --git a/plugins/image_management/send_image/__init__.py b/plugins/image_management/send_image/__init__.py index 64e1b2e9..827201c4 100755 --- a/plugins/image_management/send_image/__init__.py +++ b/plugins/image_management/send_image/__init__.py @@ -1,15 +1,17 @@ -from nonebot import on_message, on_regex -from configs.path_config import IMAGE_PATH -from utils.message_builder import image -from utils.utils import is_number, get_message_text -from services.log import logger -from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent -from utils.utils import FreqLimiter, cn2py -from configs.config import Config -from utils.manager import withdraw_message_manager -from .rule import rule -import random import os +import random + +from nonebot import on_message, on_regex +from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent + +from configs.config import Config +from configs.path_config import IMAGE_PATH +from services.log import logger +from utils.manager import withdraw_message_manager +from utils.message_builder import image +from utils.utils import FreqLimiter, cn2py, get_message_text, is_number + +from .rule import rule try: import ujson as json @@ -66,16 +68,14 @@ async def _(event: MessageEvent): if len(msg) > 1: img_id = msg[1] path = _path / cn2py(gallery) - if gallery in Config.get_config( - "image_management", "IMAGE_DIR_LIST" - ): + if gallery in Config.get_config("image_management", "IMAGE_DIR_LIST"): if not path.exists() and (path.parent.parent / cn2py(gallery)).exists(): path = IMAGE_PATH / cn2py(gallery) else: path.mkdir(parents=True, exist_ok=True) length = len(os.listdir(path)) if length == 0: - logger.warning(f'图库 {cn2py(gallery)} 为空,调用取消!') + logger.warning(f"图库 {cn2py(gallery)} 为空,调用取消!") await send_img.finish("该图库中没有图片噢") index = img_id if img_id else str(random.randint(0, length - 1)) if not is_number(index): @@ -87,8 +87,7 @@ async def _(event: MessageEvent): logger.info( f"(USER {event.user_id}, GROUP " f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'}) " - f"发送{cn2py(gallery)}:" - + result + f"发送{cn2py(gallery)}:" + result ) msg_id = await send_img.send( f"id:{index}" + result @@ -113,4 +112,6 @@ async def _(event: MessageEvent): async def _(event: MessageEvent): if _flmt.check(event.user_id): _flmt.start_cd(event.user_id) - await pa_reg.finish(image(random.choice(os.listdir(IMAGE_PATH / "pa")), "pa")) + await pa_reg.finish( + image(IMAGE_PATH / "pa" / random.choice(os.listdir(IMAGE_PATH / "pa"))) + ) diff --git a/plugins/my_info/__init__.py b/plugins/my_info/__init__.py index 4b5946e3..e7ba778c 100644 --- a/plugins/my_info/__init__.py +++ b/plugins/my_info/__init__.py @@ -1,10 +1,11 @@ -from nonebot import on_command -from nonebot.adapters.onebot.v11.permission import GROUP -from nonebot.adapters.onebot.v11 import GroupMessageEvent -from models.group_member_info import GroupInfoUser from datetime import timedelta -from models.level_user import LevelUser +from nonebot import on_command +from nonebot.adapters.onebot.v11 import GroupMessageEvent +from nonebot.adapters.onebot.v11.permission import GROUP + +from models.group_member_info import GroupInfoUser +from models.level_user import LevelUser __zx_plugin_name__ = "个人信息权限查看" __plugin_usage__ = """ @@ -31,13 +32,13 @@ async def _(event: GroupMessageEvent): async def get_member_info(user_qq: int, group_id: int) -> str: - user = await GroupInfoUser.get_member_info(user_qq, group_id) - if user is None: + if user := await GroupInfoUser.get_or_none(user_qq=user_qq, group_id=group_id): + result = "" + result += "昵称:" + user.user_name + "\n" + result += "加群时间:" + str(user.user_join_time.date()) + return result + else: return "该群员不在列表中,请更新群成员信息" - result = "" - result += "昵称:" + user.user_name + "\n" - result += "加群时间:" + str(user.user_join_time.date() + timedelta(hours=8)) - return result @my_level.handle() diff --git a/plugins/open_cases/models/buff_prices.py b/plugins/open_cases/models/buff_prices.py index 1c988ee1..5402d5e0 100755 --- a/plugins/open_cases/models/buff_prices.py +++ b/plugins/open_cases/models/buff_prices.py @@ -1,35 +1,23 @@ -from datetime import datetime +from tortoise import fields -from services.db_context import db +from services.db_context import Model # 1.狂牙武器箱 -class BuffPrice(db.Model): - __tablename__ = 'buff_prices' - __table_args__ = {'extend_existing': True} +class BuffPrice(Model): - id = db.Column(db.Integer(), primary_key=True) - case_id = db.Column(db.Integer(), nullable=False) - skin_name = db.Column(db.Unicode(), nullable=False) - skin_price = db.Column(db.Float(), nullable=False) - update_date = db.Column(db.DateTime(), nullable=False) - - _idx1 = db.Index('buff_price_idx1', 'skin_name', unique=True) - - @classmethod - async def ensure(cls, skin_name: str, for_update: bool = False) -> 'BuffPrice': - query = cls.query.where( - (cls.skin_name == skin_name) - ) - if for_update: - query = query.with_for_update() - user = await query.gino.first() - return user or await cls.create( - case_id=1, - skin_name=skin_name, - skin_price=0, - update_date=datetime.min, - ) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + case_id = fields.IntField() + """箱子id""" + skin_name = fields.CharField(255, unique=True) + """皮肤名称""" + skin_price = fields.FloatField() + """皮肤价格""" + update_date = fields.DatetimeField() + class Meta: + table = "buff_prices" + table_description = "Buff价格数据表" diff --git a/plugins/open_cases/models/open_cases_user.py b/plugins/open_cases/models/open_cases_user.py index c74cd9f6..0cef31bc 100755 --- a/plugins/open_cases/models/open_cases_user.py +++ b/plugins/open_cases/models/open_cases_user.py @@ -1,60 +1,51 @@ -from datetime import datetime +from tortoise import fields -from services.db_context import db - - -class OpenCasesUser(db.Model): - __tablename__ = 'open_cases_users' - __table_args__ = {'extend_existing': True} - - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger(), nullable=False) - total_count = db.Column(db.Integer(), nullable=False, default=0) - blue_count = db.Column(db.Integer(), nullable=False, default=0) - blue_st_count = db.Column(db.Integer(), nullable=False, default=0) - purple_count = db.Column(db.Integer(), nullable=False, default=0) - purple_st_count = db.Column(db.Integer(), nullable=False, default=0) - pink_count = db.Column(db.Integer(), nullable=False, default=0) - pink_st_count = db.Column(db.Integer(), nullable=False, default=0) - red_count = db.Column(db.Integer(), nullable=False, default=0) - red_st_count = db.Column(db.Integer(), nullable=False, default=0) - knife_count = db.Column(db.Integer(), nullable=False, default=0) - knife_st_count = db.Column(db.Integer(), nullable=False, default=0) - spend_money = db.Column(db.Integer(), nullable=False, default=0) - make_money = db.Column(db.Float(), nullable=False, default=0) - today_open_total = db.Column(db.Integer(), nullable=False, default=0) - open_cases_time_last = db.Column(db.DateTime(timezone=True), nullable=False, default=datetime.now()) - knifes_name = db.Column(db.Unicode(), nullable=False, default="") - - _idx1 = db.Index('open_cases_group_users_idx1', 'user_qq', 'group_id', unique=True) - - @classmethod - async def ensure(cls, user_qq: int, group_id: int, for_update: bool = False) -> 'OpenCasesUser': - query = cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - if for_update: - query = query.with_for_update() - user = await query.gino.first() - return user or await cls.create( - user_qq=user_qq, - group_id=group_id, - ) - - @classmethod - async def get_user_all(cls, group_id: int = None) -> 'list': - user_list = [] - if not group_id: - query = await cls.query.gino.all() - else: - query = await cls.query.where( - (cls.group_id == group_id) - ).gino.all() - for user in query: - user_list.append(user) - return user_list +from services.db_context import Model +class OpenCasesUser(Model): + __tablename__ = "open_cases_users" + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField() + """群聊id""" + total_count = fields.IntField(default=0) + """总开启次数""" + blue_count = fields.IntField(default=0) + """蓝色""" + blue_st_count = fields.IntField(default=0) + """蓝色暗金""" + purple_count = fields.IntField(default=0) + """紫色""" + purple_st_count = fields.IntField(default=0) + """紫色暗金""" + pink_count = fields.IntField(default=0) + """粉色""" + pink_st_count = fields.IntField(default=0) + """粉色暗金""" + red_count = fields.IntField(default=0) + """紫色""" + red_st_count = fields.IntField(default=0) + """紫色暗金""" + knife_count = fields.IntField(default=0) + """金色""" + knife_st_count = fields.IntField(default=0) + """金色暗金""" + spend_money = fields.IntField(default=0) + """花费金币""" + make_money = fields.IntField(default=0) + """赚取金币""" + today_open_total = fields.IntField(default=0) + """今日开箱数量""" + open_cases_time_last = fields.DatetimeField() + """最后开箱日期""" + knifes_name = fields.TextField(default="") + """已获取金色""" + class Meta: + table = "open_cases_users" + table_description = "开箱统计数据表" + unique_together = ("user_qq", "group_id") diff --git a/plugins/open_cases/open_cases_c.py b/plugins/open_cases/open_cases_c.py index 792ce4d5..ed1c83b3 100755 --- a/plugins/open_cases/open_cases_c.py +++ b/plugins/open_cases/open_cases_c.py @@ -1,20 +1,22 @@ -from datetime import datetime, timedelta -from .config import * -from services.log import logger -from services.db_context import db -from .models.open_cases_user import OpenCasesUser -from models.sign_group_user import SignGroupUser -from utils.message_builder import image -import pypinyin -import random -from .utils import get_price -from .models.buff_prices import BuffPrice -from PIL import Image -from utils.image_utils import alpha2white_pil, BuildImage -from configs.path_config import IMAGE_PATH import asyncio -from utils.utils import cn2py +import random +from datetime import datetime, timedelta + +import pypinyin +from PIL import Image + from configs.config import Config +from configs.path_config import IMAGE_PATH +from models.sign_group_user import SignGroupUser +from services.log import logger +from utils.image_utils import BuildImage, alpha2white_pil +from utils.message_builder import image +from utils.utils import cn2py + +from .config import * +from .models.buff_prices import BuffPrice +from .models.open_cases_user import OpenCasesUser +from .utils import get_price async def open_case(user_qq: int, group: int, case_name: str = "狂牙大行动") -> str: @@ -26,71 +28,76 @@ async def open_case(user_qq: int, group: int, case_name: str = "狂牙大行动" case = "" for i in pypinyin.pinyin(case_name, style=pypinyin.NORMAL): case += "".join(i) - impression = (await SignGroupUser.ensure(user_qq, group)).impression + user, _ = await SignGroupUser.get_or_create(user_qq=user_qq, group_id=group) + impression = user.impression rand = random.random() - async with db.transaction(): - user = await OpenCasesUser.ensure(user_qq, group, for_update=True) - # 一天次数上限 - if user.today_open_total >= int( - Config.get_config("open_cases", "INITIAL_OPEN_CASE_COUNT") - + int(impression) - / Config.get_config("open_cases", "EACH_IMPRESSION_ADD_COUNT") - ): - return _handle_is_MAX_COUNT() - skin, mosun = get_color_quality(rand, case_name) - # 调侃 - if skin[:2] == "军规": - if skin.find("StatTrak") == -1: - uplist[0] = 1 - else: - uplist[1] = 1 - ridicule_result = random.choice(["这样看着才舒服", "是自己人,大伙把刀收好", "非常舒适~"]) - if skin[:2] == "受限": - if skin.find("StatTrak") == -1: - uplist[2] = 1 - else: - uplist[3] = 1 - ridicule_result = random.choice( - ["还行吧,勉强接受一下下", "居然不是蓝色,太假了", "运气-1-1-1-1-1..."] - ) - if skin[:2] == "保密": - if skin.find("StatTrak") == -1: - uplist[4] = 1 - else: - uplist[5] = 1 - ridicule_result = random.choice( - ["开始不适....", "你妈妈买菜必涨价!涨三倍!", "你最近不适合出门,真的"] - ) - if skin[:2] == "隐秘": - if skin.find("StatTrak") == -1: - uplist[6] = 1 - else: - uplist[7] = 1 - ridicule_result = random.choice( - ["已经非常不适", "好兄弟你开的什么箱子啊,一般箱子不是只有蓝色的吗", "开始拿阳寿开箱子了?"] - ) - if skin[:2] == "罕见": - knifes_flag = True - if skin.find("StatTrak") == -1: - uplist[8] = 1 - else: - uplist[9] = 1 - ridicule_result = random.choice( - ["你的好运我收到了,你可以去喂鲨鱼了", "最近该吃啥就迟点啥吧,哎,好好的一个人怎么就....哎", "众所周知,欧皇寿命极短."] - ) - if skin.find("(") != -1: - cskin = skin.split("(") - skin = cskin[0].strip() + "(" + cskin[1].strip() - skin = skin.split("|")[0].strip() + " | " + skin.split("|")[1].strip() - # 价格 - if skin.find("无涂装") == -1: - dbprice = await BuffPrice.ensure(skin[9:]) + await OpenCasesUser.get_or_none(user_qq=user_qq, group_id=group) + # user, _ = await OpenCasesUser.get_or_create(user_qq=user_qq, group_id=group) + user = await OpenCasesUser.get_or_none(user_qq=user_qq, group_id=group) + if not user: + user = await OpenCasesUser.create( + user_qq=user_qq, group_id=group, open_cases_time_last=datetime.now() + ) + # 一天次数上限 + if user.today_open_total >= int( + Config.get_config("open_cases", "INITIAL_OPEN_CASE_COUNT") + + int(impression) / Config.get_config("open_cases", "EACH_IMPRESSION_ADD_COUNT") + ): + return _handle_is_MAX_COUNT() + skin, mosun = get_color_quality(rand, case_name) + # 调侃 + if skin[:2] == "军规": + if skin.find("StatTrak") == -1: + uplist[0] = 1 else: - dbprice = await BuffPrice.ensure(skin[9 : skin.rfind("(")].strip()) - if dbprice.skin_price != 0: - price_result = dbprice.skin_price - logger.info("数据库查询到价格: ", dbprice.skin_price) - uplist[10] = dbprice.skin_price + uplist[1] = 1 + ridicule_result = random.choice(["这样看着才舒服", "是自己人,大伙把刀收好", "非常舒适~"]) + if skin[:2] == "受限": + if skin.find("StatTrak") == -1: + uplist[2] = 1 + else: + uplist[3] = 1 + ridicule_result = random.choice( + ["还行吧,勉强接受一下下", "居然不是蓝色,太假了", "运气-1-1-1-1-1..."] + ) + if skin[:2] == "保密": + if skin.find("StatTrak") == -1: + uplist[4] = 1 + else: + uplist[5] = 1 + ridicule_result = random.choice(["开始不适....", "你妈妈买菜必涨价!涨三倍!", "你最近不适合出门,真的"]) + if skin[:2] == "隐秘": + if skin.find("StatTrak") == -1: + uplist[6] = 1 + else: + uplist[7] = 1 + ridicule_result = random.choice( + ["已经非常不适", "好兄弟你开的什么箱子啊,一般箱子不是只有蓝色的吗", "开始拿阳寿开箱子了?"] + ) + if skin[:2] == "罕见": + knifes_flag = True + if skin.find("StatTrak") == -1: + uplist[8] = 1 + else: + uplist[9] = 1 + ridicule_result = random.choice( + ["你的好运我收到了,你可以去喂鲨鱼了", "最近该吃啥就迟点啥吧,哎,好好的一个人怎么就....哎", "众所周知,欧皇寿命极短."] + ) + if skin.find("(") != -1: + cskin = skin.split("(") + skin = cskin[0].strip() + "(" + cskin[1].strip() + skin = skin.split("|")[0].strip() + " | " + skin.split("|")[1].strip() + # 价格 + if skin.find("无涂装") == -1: + search_name = skin[9:] + else: + search_name = skin[9 : skin.rfind("(")].strip() + price_result = 0 + if data := await BuffPrice.get_or_none(skin_name=search_name): + if data.skin_price != 0: + price_result = data.skin_price + logger.info("数据库查询到价格: ", data.skin_price) + uplist[10] = data.skin_price else: price = -1 price_result = "未查询到" @@ -102,55 +109,56 @@ async def open_case(user_qq: int, group: int, case_name: str = "狂牙大行动" price = float(pcp[1].strip()) break if price != -1: - logger.info("存储入数据库---->", price) + logger.info("存储入数据库---->{price}") uplist[10] = price price_result = str(price) - await dbprice.update( - skin_price=price, - update_date=datetime.now(), - ).apply() - # sp = skin.split("|") - # cskin_word = sp[1][:sp[1].find("(") - 1].strip() - if knifes_flag: - await user.update( - knifes_name=user.knifes_name - + f"{case}||{skin.split(':')[1].strip()} 磨损:{str(mosun)[:11]}, 价格:{uplist[10]}," - ).apply() - cskin_word = skin.split(":")[1].replace("|", "-").replace("(StatTrak™)", "") - cskin_word = cskin_word[: cskin_word.rfind("(")].strip() - skin_name = cn2py( - cskin_word.replace("|", "-").replace("(StatTrak™)", "").strip() + data.skin_price = price + data.update_date = datetime.now() + await data.save(update_fields=["skin_price", "update_date"]) + # sp = skin.split("|") + # cskin_word = sp[1][:sp[1].find("(") - 1].strip() + if knifes_flag: + await user.update( + knifes_name=user.knifes_name + + f"{case}||{skin.split(':')[1].strip()} 磨损:{str(mosun)[:11]}, 价格:{uplist[10]}," + ).apply() + cskin_word = skin.split(":")[1].replace("|", "-").replace("(StatTrak™)", "") + cskin_word = cskin_word[: cskin_word.rfind("(")].strip() + skin_name = cn2py(cskin_word.replace("|", "-").replace("(StatTrak™)", "").strip()) + img = image(IMAGE_PATH / "cases" / case / f"{skin_name}.png") + # if knifes_flag: + # await user.update( + # knifes_name=user.knifes_name + f"{skin} 磨损:{mosun}, 价格:{uplist[10]}" + # ).apply() + if await update_user_total(user, uplist): + logger.info( + f"qq:{user_qq} 群:{group} 开启{case_name}武器箱 获得 {skin} 磨损:{mosun}, 价格:{uplist[10]}, 数据更新成功" ) - img = image(f"{skin_name}.png", "cases/" + case) - # if knifes_flag: - # await user.update( - # knifes_name=user.knifes_name + f"{skin} 磨损:{mosun}, 价格:{uplist[10]}" - # ).apply() - if await update_user_total(user, uplist): - logger.info( - f"qq:{user_qq} 群:{group} 开启{case_name}武器箱 获得 {skin} 磨损:{mosun}, 价格:{uplist[10]}, 数据更新成功" - ) - else: - logger.warning( - f"qq:{user_qq} 群:{group} 开启{case_name}武器箱 获得 {skin} 磨损:{mosun}, 价格:{uplist[10]}, 数据更新失败" - ) - user = await OpenCasesUser.ensure(user_qq, group, for_update=True) - over_count = int( + else: + logger.warning( + f"qq:{user_qq} 群:{group} 开启{case_name}武器箱 获得 {skin} 磨损:{mosun}, 价格:{uplist[10]}, 数据更新失败" + ) + user, _ = await OpenCasesUser.get_or_create(user_qq=user_qq, group_id=group) + over_count = ( + int( Config.get_config("open_cases", "INITIAL_OPEN_CASE_COUNT") + int(impression) / Config.get_config("open_cases", "EACH_IMPRESSION_ADD_COUNT") - ) - user.today_open_total - return ( - f"开启{case_name}武器箱.\n剩余开箱次数:{over_count}.\n" + img + "\n" + f"皮肤:{skin}\n" - f"磨损:{mosun:.9f}\n" - f"价格:{price_result}\n" - f"{ridicule_result}" ) + - user.today_open_total + ) + return ( + f"开启{case_name}武器箱.\n剩余开箱次数:{over_count}.\n" + img + "\n" + f"皮肤:{skin}\n" + f"磨损:{mosun:.9f}\n" + f"价格:{price_result}\n" + f"{ridicule_result}" + ) async def open_shilian_case(user_qq: int, group: int, case_name: str, num: int = 10): - user = await OpenCasesUser.ensure(user_qq, group, for_update=True) - impression = (await SignGroupUser.ensure(user_qq, group)).impression + user, _ = await OpenCasesUser.get_or_create(user_qq=user_qq, group_id=group) + sign_user, _ = await SignGroupUser.get_or_create(user_qq=user_qq, group_id=group) + impression = sign_user.impression max_count = int( Config.get_config("open_cases", "INITIAL_OPEN_CASE_COUNT") + int(impression) / Config.get_config("open_cases", "EACH_IMPRESSION_ADD_COUNT") @@ -162,11 +170,10 @@ async def open_shilian_case(user_qq: int, group: int, case_name: str, num: int = f"今天开箱次数不足{num}次噢,请单抽试试看(也许单抽运气更好?)" f"\n剩余开箱次数:{max_count - user.today_open_total}" ) - await user.update( - total_count=user.total_count + num, - spend_money=user.spend_money + 17 * num, - today_open_total=user.today_open_total + num, - ).apply() + user.total_count = user.total_count + num + user.spend_money = user.spend_money + 17 * num + user.today_open_total = user.today_open_total + num + await user.save(update_fields=["total_count", "spend_money", "today_open_total"]) if num < 5: h = 270 elif num % 5 == 0: @@ -180,46 +187,46 @@ async def open_shilian_case(user_qq: int, group: int, case_name: str, num: int = uplist = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0] img_list = [] name_list = ["蓝", "蓝(暗金)", "紫", "紫(暗金)", "粉", "粉(暗金)", "红", "红(暗金)", "金", "金(暗金)"] - async with db.transaction(): - for _ in range(num): - knifes_flag = False - rand = random.random() - skin, mosun = get_color_quality(rand, case_name) - if skin[:2] == "军规": - if skin.find("StatTrak") == -1: - uplist[0] += 1 - else: - uplist[1] += 1 - if skin[:2] == "受限": - if skin.find("StatTrak") == -1: - uplist[2] += 1 - else: - uplist[3] += 1 - if skin[:2] == "保密": - if skin.find("StatTrak") == -1: - uplist[4] += 1 - else: - uplist[5] += 1 - if skin[:2] == "隐秘": - if skin.find("StatTrak") == -1: - uplist[6] += 1 - else: - uplist[7] += 1 - if skin[:2] == "罕见": - knifes_flag = True - if skin.find("StatTrak") == -1: - uplist[8] += 1 - else: - uplist[9] += 1 - if skin.find("(") != -1: - cskin = skin.split("(") - skin = cskin[0].strip() + "(" + cskin[1].strip() - skin = skin.split("|")[0].strip() + " | " + skin.split("|")[1].strip() - # 价格 - if skin.find("无涂装") == -1: - dbprice = await BuffPrice.ensure(skin[9:]) + for _ in range(num): + knifes_flag = False + rand = random.random() + skin, mosun = get_color_quality(rand, case_name) + if skin[:2] == "军规": + if skin.find("StatTrak") == -1: + uplist[0] += 1 else: - dbprice = await BuffPrice.ensure(skin[9 : skin.rfind("(")].strip()) + uplist[1] += 1 + if skin[:2] == "受限": + if skin.find("StatTrak") == -1: + uplist[2] += 1 + else: + uplist[3] += 1 + if skin[:2] == "保密": + if skin.find("StatTrak") == -1: + uplist[4] += 1 + else: + uplist[5] += 1 + if skin[:2] == "隐秘": + if skin.find("StatTrak") == -1: + uplist[6] += 1 + else: + uplist[7] += 1 + if skin[:2] == "罕见": + knifes_flag = True + if skin.find("StatTrak") == -1: + uplist[8] += 1 + else: + uplist[9] += 1 + if skin.find("(") != -1: + cskin = skin.split("(") + skin = cskin[0].strip() + "(" + cskin[1].strip() + skin = skin.split("|")[0].strip() + " | " + skin.split("|")[1].strip() + # 价格 + if skin.find("无涂装") == -1: + search_name = skin[9:] + else: + search_name = skin[9 : skin.rfind("(")].strip() + if dbprice := await BuffPrice.get_or_none(skin_name=search_name): if dbprice.skin_price != 0: price_result = dbprice.skin_price uplist[10] += price_result @@ -238,7 +245,6 @@ async def open_shilian_case(user_qq: int, group: int, case_name: str, num: int = style=pypinyin.NORMAL, ): skin_name += "".join(i) - # img = image(skin_name, "cases/" + case, "png") wImg = BuildImage(200, 270, 200, 200) wImg.paste( alpha2white_pil( @@ -296,7 +302,7 @@ def _handle_is_MAX_COUNT() -> str: async def update_user_total(user: OpenCasesUser, up_list: list, num: int = 1) -> bool: try: - await user.update( + await user.update_or_create( total_count=user.total_count + num, blue_count=user.blue_count + up_list[0], blue_st_count=user.blue_st_count + up_list[1], @@ -312,36 +318,35 @@ async def update_user_total(user: OpenCasesUser, up_list: list, num: int = 1) -> make_money=user.make_money + up_list[10], today_open_total=user.today_open_total + num, open_cases_time_last=datetime.now(), - ).apply() + ) return True except: return False async def total_open_statistics(user_qq: int, group: int) -> str: - async with db.transaction(): - user = await OpenCasesUser.ensure(user_qq, group, for_update=True) - return ( - f"开箱总数:{user.total_count}\n" - f"今日开箱:{user.today_open_total}\n" - f"蓝色军规:{user.blue_count}\n" - f"蓝色暗金:{user.blue_st_count}\n" - f"紫色受限:{user.purple_count}\n" - f"紫色暗金:{user.purple_st_count}\n" - f"粉色保密:{user.pink_count}\n" - f"粉色暗金:{user.pink_st_count}\n" - f"红色隐秘:{user.red_count}\n" - f"红色暗金:{user.red_st_count}\n" - f"金色罕见:{user.knife_count}\n" - f"金色暗金:{user.knife_st_count}\n" - f"花费金额:{user.spend_money}\n" - f"获取金额:{user.make_money:.2f}\n" - f"最后开箱日期:{(user.open_cases_time_last + timedelta(hours=8)).date()}" - ) + user, _ = await OpenCasesUser.get_or_create(user_qq=user_qq, group_id=group) + return ( + f"开箱总数:{user.total_count}\n" + f"今日开箱:{user.today_open_total}\n" + f"蓝色军规:{user.blue_count}\n" + f"蓝色暗金:{user.blue_st_count}\n" + f"紫色受限:{user.purple_count}\n" + f"紫色暗金:{user.purple_st_count}\n" + f"粉色保密:{user.pink_count}\n" + f"粉色暗金:{user.pink_st_count}\n" + f"红色隐秘:{user.red_count}\n" + f"红色暗金:{user.red_st_count}\n" + f"金色罕见:{user.knife_count}\n" + f"金色暗金:{user.knife_st_count}\n" + f"花费金额:{user.spend_money}\n" + f"获取金额:{user.make_money:.2f}\n" + f"最后开箱日期:{user.open_cases_time_last.date()}" + ) async def group_statistics(group: int): - user_list = await OpenCasesUser.get_user_all(group) + user_list = await OpenCasesUser.filter(group_id=group).all() # lan zi fen hong jin pricei uplist = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0.0, 0, 0] for user in user_list: @@ -377,7 +382,8 @@ async def group_statistics(group: int): async def my_knifes_name(user_id: int, group: int): - knifes_name = (await OpenCasesUser.ensure(user_id, group)).knifes_name + user, _ = await OpenCasesUser.get_or_create(user_qq=user_id, group_id=group) + knifes_name = user.knifes_name if knifes_name: knifes_list = knifes_name[:-1].split(",") length = len(knifes_list) diff --git a/plugins/open_cases/utils.py b/plugins/open_cases/utils.py index 83f3787e..d588e55d 100755 --- a/plugins/open_cases/utils.py +++ b/plugins/open_cases/utils.py @@ -1,17 +1,19 @@ -from .models.buff_prices import BuffPrice -from services.db_context import db -from datetime import datetime, timedelta -from configs.path_config import IMAGE_PATH -from utils.http_utils import AsyncHttpx -from .models.open_cases_user import OpenCasesUser -from services.log import logger -from utils.utils import get_bot, cn2py -from asyncio.exceptions import TimeoutError -from nonebot.adapters.onebot.v11 import ActionFailed -from configs.config import Config -from utils.manager import group_manager -from .config import * import os +from asyncio.exceptions import TimeoutError +from datetime import datetime, timedelta + +from nonebot.adapters.onebot.v11 import ActionFailed + +from configs.config import Config +from configs.path_config import IMAGE_PATH +from services.log import logger +from utils.http_utils import AsyncHttpx +from utils.manager import group_manager +from utils.utils import cn2py, get_bot + +from .config import * +from .models.buff_prices import BuffPrice +from .models.open_cases_user import OpenCasesUser url = "https://buff.163.com/api/market/goods" # proxies = 'http://49.75.59.242:3128' @@ -49,108 +51,102 @@ async def util_get_buff_price(case_name: str = "狂牙大行动") -> str: "骷髅匕首 | 无涂装", ]: skin = skin.split("|")[0].strip() - async with db.transaction(): - name_list = [] - price_list = [] - parameter = {"game": "csgo", "page_num": "1", "search": skin} - try: - response = await AsyncHttpx.get(url, proxy=Config.get_config("open_cases", "BUFF_PROXY"), + name_list = [] + price_list = [] + parameter = {"game": "csgo", "page_num": "1", "search": skin} + try: + response = await AsyncHttpx.get( + url, + proxy=Config.get_config("open_cases", "BUFF_PROXY"), params=parameter, - cookies=cookie,) - if response.status_code == 200: - data = response.json()["data"] - total_page = data["total_page"] - data = data["items"] - flag = False - if ( - skin.find("|") == -1 - ): # in ['蝴蝶刀', '求生匕首', '流浪者匕首', '系绳匕首', '骷髅匕首']: - for i in range(1, total_page + 1): - name_list = [] - price_list = [] - parameter = { - "game": "csgo", - "page_num": f"{i}", - "search": skin, - } - res = await AsyncHttpx.get(url, params=parameter, cookies=cookie) - data = res.json()["data"][ - "items" - ] - for j in range(len(data)): - if data[j]["name"] in [f"{skin}(★)"]: - name = data[j]["name"] - price = data[j][ - "sell_reference_price" - ] - name_list.append( - name.split("(")[0].strip() - + " | 无涂装" - ) - price_list.append(price) - flag = True - break - if flag: + cookies=cookie, + ) + if response.status_code == 200: + data = response.json()["data"] + total_page = data["total_page"] + data = data["items"] + flag = False + if ( + skin.find("|") == -1 + ): # in ['蝴蝶刀', '求生匕首', '流浪者匕首', '系绳匕首', '骷髅匕首']: + for i in range(1, total_page + 1): + name_list = [] + price_list = [] + parameter = { + "game": "csgo", + "page_num": f"{i}", + "search": skin, + } + res = await AsyncHttpx.get( + url, params=parameter, cookies=cookie + ) + data = res.json()["data"]["items"] + for j in range(len(data)): + if data[j]["name"] in [f"{skin}(★)"]: + name = data[j]["name"] + price = data[j]["sell_reference_price"] + name_list.append( + name.split("(")[0].strip() + " | 无涂装" + ) + price_list.append(price) + flag = True break - else: - try: - for _ in range(total_page): - for i in range(len(data)): - name = data[i]["name"] - price = data[i]["sell_reference_price"] - name_list.append(name) - price_list.append(price) - except Exception as e: - failed_list.append(skin) - logger.warning(f"{skin}更新失败") + if flag: + break else: - failed_list.append(skin) - logger.warning(f"{skin}更新失败") - except Exception: + try: + for _ in range(total_page): + for i in range(len(data)): + name = data[i]["name"] + price = data[i]["sell_reference_price"] + name_list.append(name) + price_list.append(price) + except Exception as e: + failed_list.append(skin) + logger.warning(f"{skin}更新失败") + else: failed_list.append(skin) logger.warning(f"{skin}更新失败") - continue - for i in range(len(name_list)): - name = name_list[i].strip() - price = float(price_list[i]) - if name.find("(★)") != -1: - name = name[: name.find("(")] + name[name.find(")") + 1 :] - if name.find("消音") != -1 and name.find("(S") != -1: - name = name.split("(")[0][:-4] + "(" + name.split("(")[1] - name = ( - name.split("|")[0].strip() - + " | " - + name.split("|")[1].strip() - ) - elif name.find("消音") != -1: - name = ( - name.split("|")[0][:-5].strip() - + " | " - + name.split("|")[1].strip() - ) - if name.find(" 18 ") != -1 and name.find("(S") != -1: - name = name.split("(")[0][:-5] + "(" + name.split("(")[1] - name = ( - name.split("|")[0].strip() - + " | " - + name.split("|")[1].strip() - ) - elif name.find(" 18 ") != -1: - name = ( - name.split("|")[0][:-6].strip() - + " | " - + name.split("|")[1].strip() - ) - dbskin = await BuffPrice.ensure(name, True) - if ( - dbskin.update_date + timedelta(8) - ).date() == datetime.now().date(): + except Exception: + failed_list.append(skin) + logger.warning(f"{skin}更新失败") + continue + for i in range(len(name_list)): + name = name_list[i].strip() + price = float(price_list[i]) + if name.find("(★)") != -1: + name = name[: name.find("(")] + name[name.find(")") + 1 :] + if name.find("消音") != -1 and name.find("(S") != -1: + name = name.split("(")[0][:-4] + "(" + name.split("(")[1] + name = ( + name.split("|")[0].strip() + " | " + name.split("|")[1].strip() + ) + elif name.find("消音") != -1: + name = ( + name.split("|")[0][:-5].strip() + + " | " + + name.split("|")[1].strip() + ) + if name.find(" 18 ") != -1 and name.find("(S") != -1: + name = name.split("(")[0][:-5] + "(" + name.split("(")[1] + name = ( + name.split("|")[0].strip() + " | " + name.split("|")[1].strip() + ) + elif name.find(" 18 ") != -1: + name = ( + name.split("|")[0][:-6].strip() + + " | " + + name.split("|")[1].strip() + ) + if dbskin := await BuffPrice.get_or_none(skin_name=name): + if dbskin.update_date.date() == datetime.now().date(): continue - await dbskin.update( - case_id=case_id, - skin_price=price, - update_date=datetime.now(), - ).apply() + dbskin.case_id = case_id + dbskin.skin_price = price + dbskin.update_date = datetime.now() + await dbskin.save( + update_fields=["case_id", "skin_price", "update_date"] + ) logger.info(f"{name_list[i]}---------->成功更新") result = None if failed_list: @@ -186,14 +182,16 @@ async def util_get_buff_img(case_name: str = "狂牙大行动") -> str: logger.info(f"开始更新----->{skin}") skin_name = "" # try: - response = await AsyncHttpx.get(url, proxy=Config.get_config("open_cases", "BUFF_PROXY"), params=parameter) + response = await AsyncHttpx.get( + url, + proxy=Config.get_config("open_cases", "BUFF_PROXY"), + params=parameter, + ) if response.status_code == 200: data = response.json()["data"] total_page = data["total_page"] flag = False - if ( - skin.find("|") == -1 - ): # in ['蝴蝶刀', '求生匕首', '流浪者匕首', '系绳匕首', '骷髅匕首']: + if skin.find("|") == -1: # in ['蝴蝶刀', '求生匕首', '流浪者匕首', '系绳匕首', '骷髅匕首']: for i in range(1, total_page + 1): res = await AsyncHttpx.get(url, params=parameter) data = res.json()["data"]["items"] @@ -201,17 +199,21 @@ async def util_get_buff_img(case_name: str = "狂牙大行动") -> str: if data[j]["name"] in [f"{skin}(★)"]: img_url = data[j]["goods_info"]["icon_url"] skin_name = cn2py(skin + "无涂装") - await AsyncHttpx.download_file(img_url, path / f"{skin_name}.png") + await AsyncHttpx.download_file( + img_url, path / f"{skin_name}.png" + ) flag = True break if flag: break else: - img_url = (await response.json())["data"]["items"][0][ - "goods_info" - ]["icon_url"] + img_url = (await response.json())["data"]["items"][0]["goods_info"][ + "icon_url" + ] skin_name += cn2py(skin.replace("|", "-").strip()) - if await AsyncHttpx.download_file(img_url, path / f"{skin_name}.png"): + if await AsyncHttpx.download_file( + img_url, path / f"{skin_name}.png" + ): logger.info(f"------->写入 {skin} 成功") else: logger.info(f"------->写入 {skin} 失败") @@ -255,18 +257,15 @@ async def get_price(d_name): async def update_count_daily(): try: - users = await OpenCasesUser.get_user_all() - if users: - for user in users: - await user.update( - today_open_total=0, - ).apply() + await OpenCasesUser.all().update(today_open_total=0) bot = get_bot() gl = await bot.get_group_list() gl = [g["group_id"] for g in gl] for g in gl: try: - await bot.send_group_msg(group_id=g, message="[[_task|open_case_reset_remind]]今日开箱次数重置成功") + await bot.send_group_msg( + group_id=g, message="[[_task|open_case_reset_remind]]今日开箱次数重置成功" + ) except ActionFailed: logger.warning(f"{g} 群被禁言,无法发送 开箱重置提醒") logger.info("今日开箱次数重置成功") diff --git a/plugins/parse_bilibili_json.py b/plugins/parse_bilibili_json.py index 2dfe8817..13aa7e57 100755 --- a/plugins/parse_bilibili_json.py +++ b/plugins/parse_bilibili_json.py @@ -1,22 +1,24 @@ -from nonebot import on_message -from services.log import logger -from nonebot.adapters.onebot.v11 import GroupMessageEvent, ActionFailed - -from utils.manager import group_manager -from utils.utils import get_message_json, get_local_proxy, is_number, get_message_text -from nonebot.adapters.onebot.v11.permission import GROUP -from utils.message_builder import image -from utils.image_utils import BuildImage -from utils.browser import get_browser -from configs.path_config import IMAGE_PATH -from utils.http_utils import AsyncHttpx -from configs.config import Config -from utils.user_agent import get_user_agent -import aiohttp import asyncio import time + +import aiohttp import ujson as json from bilireq import video +from nonebot import on_message +from nonebot.adapters.onebot.v11 import ActionFailed, GroupMessageEvent +from nonebot.adapters.onebot.v11.permission import GROUP + +from configs.config import Config +from configs.path_config import IMAGE_PATH, TEMP_PATH +from services.log import logger +from utils.browser import get_browser +from utils.http_utils import AsyncHttpx +from utils.image_utils import BuildImage +from utils.manager import group_manager +from utils.message_builder import image +from utils.user_agent import get_user_agent +from utils.utils import get_local_proxy, get_message_json, get_message_text, is_number + __zx_plugin_name__ = "B站转发解析" __plugin_usage__ = """ usage: @@ -39,7 +41,10 @@ Config.add_plugin_config( async def plugin_on_checker(event: GroupMessageEvent) -> bool: return group_manager.get_plugin_status("parse_bilibili_json", event.group_id) -parse_bilibili_json = on_message(priority=1, permission=GROUP, block=False, rule=plugin_on_checker) + +parse_bilibili_json = on_message( + priority=1, permission=GROUP, block=False, rule=plugin_on_checker +) _tmp = {} @@ -55,16 +60,14 @@ async def _(event: GroupMessageEvent): data = None if data: # 转发视频 - if data.get("desc") == "哔哩哔哩" or data.get('prompt').find('哔哩哔哩') != -1: - async with aiohttp.ClientSession( - headers=get_user_agent() - ) as session: + if data.get("desc") == "哔哩哔哩" or "哔哩哔哩" in data.get("prompt"): + async with aiohttp.ClientSession(headers=get_user_agent()) as session: async with session.get( - data["meta"]["detail_1"]["qqdocurl"], - timeout=7, + data["meta"]["detail_1"]["qqdocurl"], + timeout=7, ) as response: url = str(response.url).split("?")[0] - if url[-1] == '/': + if url[-1] == "/": url = url[:-1] bvid = url.split("/")[-1] vd_info = await video.get_video_base_info(bvid) @@ -93,10 +96,11 @@ async def _(event: GroupMessageEvent): timeout=100000, ) await asyncio.get_event_loop().run_in_executor( - None, resize, f"{IMAGE_PATH}/temp/cv_{event.user_id}.png" + None, resize, TEMP_PATH / f"cv_{event.user_id}.png" ) await parse_bilibili_json.send( - "[[_task|bilibili_parse]]" + image(f"cv_{event.user_id}.png", "temp") + "[[_task|bilibili_parse]]" + + image(TEMP_PATH / f"cv_{event.user_id}.png") ) await page.close() logger.info( @@ -121,15 +125,13 @@ async def _(event: GroupMessageEvent): msg = msg[index + 2 : index + 11] if is_number(msg): url = f"https://www.bilibili.com/video/av{msg}" - vd_info = await video.get_video_base_info('av' + msg) + vd_info = await video.get_video_base_info("av" + msg) elif "https://b23.tv" in msg: - url = "https://" + msg[msg.find("b23.tv"): msg.find("b23.tv") + 14] - async with aiohttp.ClientSession( - headers=get_user_agent() - ) as session: + url = "https://" + msg[msg.find("b23.tv") : msg.find("b23.tv") + 14] + async with aiohttp.ClientSession(headers=get_user_agent()) as session: async with session.get( - url, - timeout=7, + url, + timeout=7, ) as response: url = (str(response.url).split("?")[0]).strip("/") bvid = url.split("/")[-1] @@ -150,8 +152,9 @@ async def _(event: GroupMessageEvent): date = time.strftime("%Y-%m-%d", time.localtime(vd_info["ctime"])) try: await parse_bilibili_json.send( - "[[_task|bilibili_parse]]" + - image(vd_info["pic"]) + f"\nav{aid}\n标题:{title}\n" + "[[_task|bilibili_parse]]" + + image(vd_info["pic"]) + + f"\nav{aid}\n标题:{title}\n" f"UP:{author}\n" f"上传日期:{date}\n" f"回复:{reply},收藏:{favorite},投币:{coin}\n" diff --git a/plugins/pid_search.py b/plugins/pid_search.py index 6e1483af..ff8da011 100755 --- a/plugins/pid_search.py +++ b/plugins/pid_search.py @@ -1,17 +1,17 @@ +from asyncio.exceptions import TimeoutError + from nonebot import on_command -from nonebot.adapters.onebot.v11 import Bot, MessageEvent, Message, GroupMessageEvent +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent +from nonebot.params import Arg, CommandArg from nonebot.typing import T_State from configs.config import Config -from utils.utils import is_number, change_pixiv_image_links -from utils.message_builder import image +from configs.path_config import IMAGE_PATH, TEMP_PATH from services.log import logger -from asyncio.exceptions import TimeoutError -from configs.path_config import IMAGE_PATH -from utils.manager import withdraw_message_manager from utils.http_utils import AsyncHttpx -from nonebot.params import CommandArg, Arg - +from utils.manager import withdraw_message_manager +from utils.message_builder import image +from utils.utils import change_pixiv_image_links, is_number __zx_plugin_name__ = "pid搜索" __plugin_usage__ = """ @@ -88,7 +88,7 @@ async def _g(event: MessageEvent, state: T_State, pid: str = Arg("pid")): img_url = change_pixiv_image_links(img_url) if not await AsyncHttpx.download_file( img_url, - IMAGE_PATH / "temp" / f"pid_search_{event.user_id}_{i}.png", + TEMP_PATH / f"pid_search_{event.user_id}_{i}.png", headers=headers, ): await pid_search.send("图片下载失败了....", at_sender=True) @@ -101,7 +101,7 @@ async def _g(event: MessageEvent, state: T_State, pid: str = Arg("pid")): f"pid:{pid}\n" f"author:{author}\n" f"author_id:{author_id}\n" - f'{image(f"pid_search_{event.user_id}_{i}.png", "temp")}' + f'{image(TEMP_PATH / f"pid_search_{event.user_id}_{i}.png")}' f"{tmp}" ) ) diff --git a/plugins/pix_gallery/__init__.py b/plugins/pix_gallery/__init__.py index d1952402..b33c447a 100755 --- a/plugins/pix_gallery/__init__.py +++ b/plugins/pix_gallery/__init__.py @@ -1,7 +1,7 @@ -from configs.config import Config -from utils.utils import GDict import nonebot +from configs.config import Config +from utils.utils import GDict Config.add_plugin_config( "hibiapi", @@ -10,59 +10,42 @@ Config.add_plugin_config( help_="如果没有自建或其他hibiapi请不要修改", default_value="https://api.obfs.dev", ) -Config.add_plugin_config( - "pixiv", - "PIXIV_NGINX_URL", - "i.pximg.cf", - help_="Pixiv反向代理" -) +Config.add_plugin_config("pixiv", "PIXIV_NGINX_URL", "i.pximg.cf", help_="Pixiv反向代理") Config.add_plugin_config( "pix", "PIX_IMAGE_SIZE", "master", name="PIX图库", help_="PIX图库下载的画质 可能的值:original:原图,master:缩略图(加快发送速度)", - default_value="master" + default_value="master", ) Config.add_plugin_config( "pix", "SEARCH_HIBIAPI_BOOKMARKS", 5000, help_="最低收藏,PIX使用HIBIAPI搜索图片时达到最低收藏才会添加至图库", - default_value=5000 + default_value=5000, ) Config.add_plugin_config( "pix", "WITHDRAW_PIX_MESSAGE", (0, 1), help_="自动撤回,参1:延迟撤回色图时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)", - default_value=(0, 1) + default_value=(0, 1), ) Config.add_plugin_config( "pix", "PIX_OMEGA_PIXIV_RATIO", (10, 0), help_="PIX图库 与 额外图库OmegaPixivIllusts 混合搜索的比例 参1:PIX图库 参2:OmegaPixivIllusts扩展图库(没有此图库请设置为0)", - default_value=(10, 0) -) -Config.add_plugin_config( - "pix", - "TIMEOUT", - 10, - help_="下载图片超时限制(秒)", - default_value=10 + default_value=(10, 0), ) +Config.add_plugin_config("pix", "TIMEOUT", 10, help_="下载图片超时限制(秒)", default_value=10) Config.add_plugin_config( - "pix", - "SHOW_INFO", - True, - help_="是否显示图片的基本信息,如PID等", - default_value=True + "pix", "SHOW_INFO", True, help_="是否显示图片的基本信息,如PID等", default_value=True ) -GDict['run_sql'].append("ALTER TABLE omega_pixiv_illusts ADD classified Integer;") +GDict["run_sql"].append("ALTER TABLE omega_pixiv_illusts ADD classified Integer;") nonebot.load_plugins("plugins/pix_gallery") - - diff --git a/plugins/pix_gallery/_data_source.py b/plugins/pix_gallery/_data_source.py index 28fd52e8..0b9f92d8 100644 --- a/plugins/pix_gallery/_data_source.py +++ b/plugins/pix_gallery/_data_source.py @@ -1,19 +1,23 @@ -from asyncpg.exceptions import UniqueViolationError -from ._model.omega_pixiv_illusts import OmegaPixivIllusts -from asyncio.locks import Semaphore -from asyncio.exceptions import TimeoutError -from ._model.pixiv import Pixiv -from typing import List, Optional -from utils.utils import change_pixiv_image_links, change_img_md5 -from utils.image_utils import BuildImage -from utils.http_utils import AsyncHttpx -from services.log import logger -from configs.config import Config -from configs.path_config import TEMP_PATH -import aiofiles -import platform import asyncio import math +import platform +from asyncio.exceptions import TimeoutError +from asyncio.locks import Semaphore +from copy import deepcopy +from typing import List, Optional, Tuple + +import aiofiles +from asyncpg.exceptions import UniqueViolationError + +from configs.config import Config +from configs.path_config import TEMP_PATH +from services.log import logger +from utils.http_utils import AsyncHttpx +from utils.image_utils import BuildImage +from utils.utils import change_img_md5, change_pixiv_image_links + +from ._model.omega_pixiv_illusts import OmegaPixivIllusts +from ._model.pixiv import Pixiv try: import ujson as json @@ -65,9 +69,7 @@ async def start_update_image_url( params = {"word": keyword, "page": page} tasks.append( asyncio.ensure_future( - search_image( - url, keyword, params, semaphore, page, black_pid - ) + search_image(url, keyword, params, semaphore, page, black_pid) ) ) if keyword.startswith("pid:"): @@ -101,96 +103,91 @@ async def search_image( pic_count = 0 pid_count = 0 async with semaphore: - try: - data = (await AsyncHttpx.get(url, params=params)).json() - if ( - not data - or data.get("error") - or (not data.get("illusts") and not data.get("illust")) - ): - return 0, 0 - if url != f"{HIBIAPI}/api/pixiv/illust": - logger.info(f'{keyword}: 获取数据成功...数据总量:{len(data["illusts"])}') - data = data["illusts"] + # try: + data = (await AsyncHttpx.get(url, params=params)).json() + if ( + not data + or data.get("error") + or (not data.get("illusts") and not data.get("illust")) + ): + return 0, 0 + if url != f"{HIBIAPI}/api/pixiv/illust": + logger.info(f'{keyword}: 获取数据成功...数据总量:{len(data["illusts"])}') + data = data["illusts"] + else: + logger.info(f'获取数据成功...PID:{params.get("id")}') + data = [data["illust"]] + img_data = {} + for x in data: + pid = x["id"] + title = x["title"] + width = x["width"] + height = x["height"] + view = x["total_view"] + bookmarks = x["total_bookmarks"] + uid = x["user"]["id"] + author = x["user"]["name"] + tags = [] + for tag in x["tags"]: + for i in tag: + if tag[i]: + tags.append(tag[i]) + img_urls = [] + if x["page_count"] == 1: + img_urls.append(x["meta_single_page"]["original_image_url"]) else: - logger.info(f'获取数据成功...PID:{params.get("id")}') - data = [data["illust"]] - img_data = {} - for x in data: - pid = x["id"] - title = x["title"] - width = x["width"] - height = x["height"] - view = x["total_view"] - bookmarks = x["total_bookmarks"] - uid = x["user"]["id"] - author = x["user"]["name"] - tags = [] - for tag in x["tags"]: - for i in tag: - if tag[i]: - tags.append(tag[i]) - img_urls = [] - if x["page_count"] == 1: - img_urls.append(x["meta_single_page"]["original_image_url"]) - else: - for urls in x["meta_pages"]: - img_urls.append(urls["image_urls"]["original"]) - if ( - ( - bookmarks - >= Config.get_config("pix", "SEARCH_HIBIAPI_BOOKMARKS") - or ( - url == f"{HIBIAPI}/api/pixiv/member_illust" - and bookmarks >= 1500 - ) - or (url == f"{HIBIAPI}/api/pixiv/illust") + for urls in x["meta_pages"]: + img_urls.append(urls["image_urls"]["original"]) + if ( + ( + bookmarks >= Config.get_config("pix", "SEARCH_HIBIAPI_BOOKMARKS") + or ( + url == f"{HIBIAPI}/api/pixiv/member_illust" + and bookmarks >= 1500 ) - and len(img_urls) < 10 - and _check_black(img_urls, black) + or (url == f"{HIBIAPI}/api/pixiv/illust") + ) + and len(img_urls) < 10 + and _check_black(img_urls, black) + ): + img_data[pid] = { + "pid": pid, + "title": title, + "width": width, + "height": height, + "view": view, + "bookmarks": bookmarks, + "img_urls": img_urls, + "uid": uid, + "author": author, + "tags": tags, + } + else: + continue + for x in img_data.keys(): + data = img_data[x] + data_copy = deepcopy(data) + del data_copy["img_urls"] + for img_url in data["img_urls"]: + img_p = img_url[img_url.rfind("_") + 1 : img_url.rfind(".")] + data_copy["img_url"] = img_url + data_copy["img_p"] = img_p + data_copy["is_r18"] = "R-18" in data["tags"] + if not await Pixiv.exists( + pid=data["pid"], img_url=img_url, img_p=img_p ): - img_data[pid] = { - "pid": pid, - "title": title, - "width": width, - "height": height, - "view": view, - "bookmarks": bookmarks, - "img_urls": img_urls, - "uid": uid, - "author": author, - "tags": tags, - } + data_copy["img_url"] = img_url + await Pixiv.create(**data_copy) + if data["pid"] not in tmp_pid: + pid_count += 1 + tmp_pid.append(data["pid"]) + pic_count += 1 + logger.info(f'存储图片PID:{data["pid"]} IMG_P:{img_p}') else: - continue - for x in img_data.keys(): - data = img_data[x] - for img_url in data["img_urls"]: - img_p = img_url[img_url.rfind("_") + 1 : img_url.rfind(".")] - try: - if await Pixiv.add_image_data( - data["pid"], - data["title"], - data["width"], - data["height"], - data["view"], - data["bookmarks"], - img_url, - img_p, - data["uid"], - data["author"], - ",".join(data["tags"]), - ): - if data["pid"] not in tmp_pid: - pid_count += 1 - tmp_pid.append(data["pid"]) - pic_count += 1 - logger.info(f'存储图片PID:{data["pid"]} IMG_P:{img_p}') - except UniqueViolationError: - logger.warning(f'{data["pid"]} | {img_url} 已存在...') - except Exception as e: - logger.warning(f"PIX在线搜索图片错误,已再次调用 {type(e)}:{e}") - await search_image(url, keyword, params, semaphore, page, black) + logger.warning(f'{data["pid"]} | {img_url} 已存在...') + # except Exception as e: + # logger.warning(f"PIX在线搜索图片错误,已再次调用 {type(e)}:{e}") + # await search_image(url, keyword, params, semaphore, page, black) return pid_count, pic_count @@ -206,7 +203,9 @@ async def get_image(img_url: str, user_id: int) -> Optional[str]: params = {"id": pid} for _ in range(3): try: - response = await AsyncHttpx.get(f"{HIBIAPI}/api/pixiv/illust", params=params) + response = await AsyncHttpx.get( + f"{HIBIAPI}/api/pixiv/illust", params=params + ) if response.status_code == 200: data = response.json() if data.get("illust"): @@ -215,22 +214,28 @@ async def get_image(img_url: str, user_id: int) -> Optional[str]: "original_image_url" ] else: - img_url = data["illust"]["meta_pages"][0][ - "image_urls" - ]["original"] + img_url = data["illust"]["meta_pages"][0]["image_urls"][ + "original" + ] break except TimeoutError: pass old_img_url = img_url img_url = change_pixiv_image_links( - img_url, Config.get_config("pix", "PIX_IMAGE_SIZE"), Config.get_config("pixiv", "PIXIV_NGINX_URL") + img_url, + Config.get_config("pix", "PIX_IMAGE_SIZE"), + Config.get_config("pixiv", "PIXIV_NGINX_URL"), ) old_img_url = change_pixiv_image_links( old_img_url, None, Config.get_config("pixiv", "PIXIV_NGINX_URL") ) for _ in range(3): try: - response = await AsyncHttpx.get(img_url, headers=headers, timeout=Config.get_config("pix", "TIMEOUT"),) + response = await AsyncHttpx.get( + img_url, + headers=headers, + timeout=Config.get_config("pix", "TIMEOUT"), + ) if response.status_code == 404: img_url = old_img_url continue @@ -238,7 +243,9 @@ async def get_image(img_url: str, user_id: int) -> Optional[str]: TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg", "wb" ) as f: await f.write(response.content) - change_img_md5(TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg") + change_img_md5( + TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg" + ) return TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg" except TimeoutError: logger.warning(f"PIX:{img_url} 图片下载超时...") @@ -264,7 +271,7 @@ async def uid_pid_exists(id_: str) -> bool: return True -async def get_keyword_num(keyword: str) -> "int, int, int, int, int": +async def get_keyword_num(keyword: str) -> Tuple[int, int, int, int, int]: """ 查看图片相关 tag 数量 :param keyword: 关键词tag @@ -276,7 +283,7 @@ async def get_keyword_num(keyword: str) -> "int, int, int, int, int": return count, r18_count, count_, setu_count, r18_count_ -async def remove_image(pid: int, img_p: str) -> bool: +async def remove_image(pid: int, img_p: Optional[str]): """ 删除置顶图片 :param pid: pid @@ -285,7 +292,10 @@ async def remove_image(pid: int, img_p: str) -> bool: if img_p: if "p" not in img_p: img_p = f"p{img_p}" - return await Pixiv.remove_image_data(pid, img_p) + if img_p: + await Pixiv.filter(pid=pid, img_p=img_p).delete() + else: + await Pixiv.filter(pid=pid).delete() def gen_keyword_pic( @@ -392,6 +402,3 @@ def _check_black(img_urls: List[str], black: List[str]) -> bool: if b in img_url: return False return True - - - diff --git a/plugins/pix_gallery/_model/omega_pixiv_illusts.py b/plugins/pix_gallery/_model/omega_pixiv_illusts.py index 27721fc3..c80c5f45 100644 --- a/plugins/pix_gallery/_model/omega_pixiv_illusts.py +++ b/plugins/pix_gallery/_model/omega_pixiv_illusts.py @@ -1,79 +1,50 @@ -from typing import Optional, List, Tuple -from services.db_context import db +from typing import List, Optional, Tuple + +from tortoise import fields +from tortoise.contrib.postgres.functions import Random + +from services.db_context import Model -class OmegaPixivIllusts(db.Model): - __tablename__ = "omega_pixiv_illusts" - __table_args__ = {'extend_existing': True} +class OmegaPixivIllusts(Model): - id = db.Column(db.Integer(), primary_key=True) - pid = db.Column(db.BigInteger(), nullable=False) - uid = db.Column(db.BigInteger(), nullable=False) - title = db.Column(db.String(), nullable=False) - uname = db.Column(db.String(), nullable=False) - classified = db.Column(db.Integer(), nullable=False) - nsfw_tag = db.Column(db.Integer(), nullable=False) - width = db.Column(db.Integer(), nullable=False) - height = db.Column(db.Integer(), nullable=False) - tags = db.Column(db.String(), nullable=False) - url = db.Column(db.String(), nullable=False) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + pid = fields.BigIntField() + """pid""" + uid = fields.BigIntField() + """uid""" + title = fields.CharField(255) + """标题""" + uname = fields.CharField(255) + """画师名称""" + classified = fields.IntField() + """标记标签, 0=未标记, 1=已人工标记或从可信已标记来源获取""" + nsfw_tag = fields.IntField() + """nsfw标签,-1=未标记, 0=safe, 1=setu. 2=r18""" + width = fields.IntField() + """宽度""" + height = fields.IntField() + """高度""" + tags = fields.TextField() + """tags""" + url = fields.CharField(255) + """pixiv url链接""" - _idx1 = db.Index("omega_pixiv_illusts_idx1", "pid", "url", unique=True) - - @classmethod - async def add_image_data( - cls, - pid: int, - title: str, - width: int, - height: int, - url: str, - uid: int, - uname: str, - classified: int, - nsfw_tag: int, - tags: str, - ): - """ - 说明: - 添加图片信息 - 参数: - :param pid: pid - :param title: 标题 - :param width: 宽度 - :param height: 长度 - :param url: url链接 - :param uid: 作者uid - :param uname: 作者名称 - :param classified: 标记标签, 0=未标记, 1=已人工标记或从可信已标记来源获取 - :param nsfw_tag: nsfw标签,-1=未标记, 0=safe, 1=setu. 2=r18 - :param tags: 相关tag - """ - if not await cls.check_exists(pid): - await cls.create( - pid=pid, - title=title, - width=width, - height=height, - url=url, - uid=uid, - uname=uname, - classified=classified, - nsfw_tag=nsfw_tag, - tags=tags, - ) - return True - return False + class Meta: + table = "omega_pixiv_illusts" + table_description = "omega图库数据表" + unique_together = ("pid", "url") @classmethod async def query_images( - cls, - keywords: Optional[List[str]] = None, - uid: Optional[int] = None, - pid: Optional[int] = None, - nsfw_tag: Optional[int] = 0, - num: int = 100 - ) -> List[Optional["OmegaPixivIllusts"]]: + cls, + keywords: Optional[List[str]] = None, + uid: Optional[int] = None, + pid: Optional[int] = None, + nsfw_tag: Optional[int] = 0, + num: int = 100, + ) -> List["OmegaPixivIllusts"]: """ 说明: 查找符合条件的图片 @@ -84,66 +55,38 @@ class OmegaPixivIllusts(db.Model): :param nsfw_tag: nsfw标签, 0=safe, 1=setu. 2=r18 :param num: 获取图片数量 """ + if not num: + return [] + query = cls if nsfw_tag is not None: - query = cls.query.where(cls.nsfw_tag == nsfw_tag) - else: - query = cls.query + query = cls.filter(nsfw_tag=nsfw_tag) if keywords: for keyword in keywords: - query = query.where(cls.tags.contains(keyword)) + query = query.filter(tags__contains=keyword) elif uid: - query = query.where(cls.uid == uid) + query = query.filter(uid=uid) elif pid: - query = query.where(cls.uid == pid) - query = query.order_by(db.func.random()).limit(num) - return await query.gino.all() + query = query.filter(pid=pid) + query = query.annotate(rand=Random()).limit(num) + return await query.all() # type: ignore @classmethod - async def check_exists(cls, pid: int) -> bool: - """ - 说明: - 检测pid是否已存在 - 参数: - :param pid: 图片PID - """ - query = await cls.query.where(cls.pid == pid).gino.all() - return bool(query) - - @classmethod - async def get_keyword_num(cls, tags: List[str] = None) -> Tuple[int, int, int]: + async def get_keyword_num( + cls, tags: Optional[List[str]] = None + ) -> Tuple[int, int, int]: """ 说明: 获取相关关键词(keyword, tag)在图库中的数量 参数: :param tags: 关键词/Tag """ - setattr(OmegaPixivIllusts, 'count', db.func.count(cls.pid).label('count')) - query = cls.select('count') + query = cls if tags: for tag in tags: - query = query.where(cls.tags.contains(tag)) - count = await query.where(cls.nsfw_tag == 0).gino.first() - setu_count = await query.where(cls.nsfw_tag == 1).gino.first() - r18_count = await query.where(cls.nsfw_tag == 2).gino.first() - return count[0], setu_count[0], r18_count[0] - - @classmethod - async def get_all_pid(cls) -> List[int]: - """ - 说明: - 获取所有图片PID - """ - data = await cls.select('pid').gino.all() - return [x[0] for x in data] - - # async def test(cls, nsfw_tag: int = 1): - # if nsfw_tag is not None: - # query = cls.query.where(cls.nsfw_tag == nsfw_tag) - # else: - # query = cls.query - # query = query.where((cls.width - cls.height) < 50) - # for x in await query.gino.all(): - # print(x.pid) - - - + query = query.filter(tags__contains=tag) + else: + query = query.all() + count = await query.filter(nsfw_tag=0).count() + setu_count = await query.filter(nsfw_tag=1).count() + r18_count = await query.filter(nsfw_tag=2).count() + return count, setu_count, r18_count diff --git a/plugins/pix_gallery/_model/pixiv.py b/plugins/pix_gallery/_model/pixiv.py index 485d5934..4af995a5 100644 --- a/plugins/pix_gallery/_model/pixiv.py +++ b/plugins/pix_gallery/_model/pixiv.py @@ -1,105 +1,43 @@ -from typing import Optional, List -from services.db_context import db +from typing import List, Optional, Tuple + +from tortoise import fields +from tortoise.contrib.postgres.functions import Random + +from services.db_context import Model -class Pixiv(db.Model): - __tablename__ = "pixiv" - __table_args__ = {'extend_existing': True} +class Pixiv(Model): - id = db.Column(db.Integer(), primary_key=True) - pid = db.Column(db.BigInteger(), nullable=False) - title = db.Column(db.String(), nullable=False) - width = db.Column(db.Integer(), nullable=False) - height = db.Column(db.Integer(), nullable=False) - view = db.Column(db.Integer(), nullable=False) - bookmarks = db.Column(db.Integer(), nullable=False) - img_url = db.Column(db.String(), nullable=False) - img_p = db.Column(db.String(), nullable=False) - uid = db.Column(db.BigInteger(), nullable=False) - author = db.Column(db.String(), nullable=False) - is_r18 = db.Column(db.Boolean(), nullable=False) - tags = db.Column(db.String(), nullable=False) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + pid = fields.BigIntField() + """pid""" + uid = fields.BigIntField() + """uid""" + author = fields.CharField(255) + """作者""" + title = fields.CharField(255) + """标题""" + width = fields.IntField() + """宽度""" + height = fields.IntField() + """高度""" + view = fields.IntField() + """pixiv查看数""" + bookmarks = fields.IntField() + """收藏数""" + tags = fields.TextField() + """tags""" + img_url = fields.CharField(255) + """pixiv url链接""" + img_p = fields.CharField(255) + """图片pN""" + is_r18 = fields.BooleanField() - _idx1 = db.Index("pixiv_idx1", "pid", "img_url", unique=True) - - @classmethod - async def add_image_data( - cls, - pid: int, - title: str, - width: int, - height: int, - view: int, - bookmarks: int, - img_url: str, - img_p: str, - uid: int, - author: str, - tags: str, - ): - """ - 说明: - 添加图片信息 - 参数: - :param pid: pid - :param title: 标题 - :param width: 宽度 - :param height: 长度 - :param view: 被查看次数 - :param bookmarks: 收藏数 - :param img_url: url链接 - :param img_p: 张数 - :param uid: 作者uid - :param author: 作者名称 - :param tags: 相关tag - """ - if not await cls.check_exists(pid, img_p): - await cls.create( - pid=pid, - title=title, - width=width, - height=height, - view=view, - bookmarks=bookmarks, - img_url=img_url, - img_p=img_p, - uid=uid, - author=author, - is_r18=True if "R-18" in tags else False, - tags=tags, - ) - return True - return False - - @classmethod - async def remove_image_data(cls, pid: int, img_p: str) -> bool: - """ - 说明: - 删除图片数据 - 参数: - :param pid: 图片pid - :param img_p: 图片pid的张数,如:p0,p1 - """ - try: - if img_p: - await cls.delete.where( - (cls.pid == pid) & (cls.img_p == img_p) - ).gino.status() - else: - await cls.delete.where(cls.pid == pid).gino.status() - return True - except Exception: - return False - - @classmethod - async def get_all_pid(cls) -> List[int]: - """ - 说明: - 获取所有PID - """ - query = await cls.query.select("pid").gino.first() - pid = [x[0] for x in query] - return list(set(pid)) + class Meta: + table = "pixiv" + table_description = "pix图库数据表" + unique_together = ("pid", "img_url", "img_p") # 0:非r18 1:r18 2:混合 @classmethod @@ -109,7 +47,7 @@ class Pixiv(db.Model): uid: Optional[int] = None, pid: Optional[int] = None, r18: Optional[int] = 0, - num: int = 100 + num: int = 100, ) -> List[Optional["Pixiv"]]: """ 说明: @@ -121,50 +59,37 @@ class Pixiv(db.Model): :param r18: 是否r18,0:非r18 1:r18 2:混合 :param num: 查找图片的数量 """ + if not num: + return [] + query = cls if r18 == 0: - query = cls.query.where(cls.is_r18 == False) + query = query.filter(is_r18=False) elif r18 == 1: - query = cls.query.where(cls.is_r18 == True) - else: - query = cls.query + query = query.filter(is_r18=True) if keywords: for keyword in keywords: - query = query.where(cls.tags.contains(keyword)) + query = query.filter(tags__contains=keyword) elif uid: - query = query.where(cls.uid == uid) + query = query.filter(uid=uid) elif pid: - query = query.where(cls.pid == pid) - query = query.order_by(db.func.random()).limit(num) - return await query.gino.all() + query = query.filter(pid=pid) + query = query.annotate(rand=Random()).limit(num) + return await query.all() # type: ignore @classmethod - async def check_exists(cls, pid: int, img_p: str) -> bool: - """ - 说明: - 检测pid是否已存在 - 参数: - :param pid: 图片PID - :param img_p: 张数 - """ - query = await cls.query.where( - (cls.pid == pid) & (cls.img_p == img_p) - ).gino.all() - return bool(query) - - @classmethod - async def get_keyword_num(cls, tags: List[str] = None) -> "int, int": + async def get_keyword_num(cls, tags: Optional[List[str]] = None) -> Tuple[int, int]: """ 说明: 获取相关关键词(keyword, tag)在图库中的数量 参数: :param tags: 关键词/Tag """ - setattr(Pixiv, 'count', db.func.count(cls.pid).label('count')) - query = cls.select('count') + query = cls if tags: for tag in tags: - query = query.where(cls.tags.contains(tag)) - count = await query.where(cls.is_r18 == False).gino.first() - r18_count = await query.where(cls.is_r18 == True).gino.first() - return count[0], r18_count[0] - + query = query.filter(tags__contains=tag) + else: + query = query.all() + count = await query.filter(is_r18=False).count() + r18_count = await query.filter(is_r18=True).count() + return count, r18_count diff --git a/plugins/pix_gallery/_model/pixiv_keyword_user.py b/plugins/pix_gallery/_model/pixiv_keyword_user.py index 8271c179..4e7797c0 100644 --- a/plugins/pix_gallery/_model/pixiv_keyword_user.py +++ b/plugins/pix_gallery/_model/pixiv_keyword_user.py @@ -1,101 +1,42 @@ -from services.db_context import db -from typing import Set, List +from typing import List, Set, Tuple + +from tortoise import fields + +from services.db_context import Model -class PixivKeywordUser(db.Model): +class PixivKeywordUser(Model): __tablename__ = "pixiv_keyword_users" - __table_args__ = {'extend_existing': True} + __table_args__ = {"extend_existing": True} - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger(), nullable=False) - keyword = db.Column(db.String(), nullable=False) - is_pass = db.Column(db.Boolean(), default=False) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField() + """群聊id""" + keyword = fields.CharField(255, unique=True) + """关键词""" + is_pass = fields.BooleanField() + """是否通过""" - _idx1 = db.Index("pixiv_keyword_users_idx1", "keyword", unique=True) + class Meta: + table = "pixiv_keyword_users" + table_description = "pixiv关键词数据表" @classmethod - async def add_keyword( - cls, user_qq: int, group_id: int, keyword: str, superusers: Set[str] - ) -> bool: - """ - 说明: - 添加搜图的关键词 - 参数: - :param user_qq: qq号 - :param group_id: 群号 - :param keyword: 关键词 - :param superusers: 是否为超级用户 - """ - is_pass = True if str(user_qq) in superusers else False - if not await cls._check_keyword_exists(keyword): - await cls.create( - user_qq=user_qq, group_id=group_id, keyword=keyword, is_pass=is_pass - ) - return True - return False - - @classmethod - async def delete_keyword(cls, keyword: str) -> bool: - """ - 说明: - 删除关键词 - 参数: - :param keyword: 关键词 - """ - if await cls._check_keyword_exists(keyword): - query = cls.query.where(cls.keyword == keyword).with_for_update() - query = await query.gino.first() - await query.delete() - return True - return False - - @classmethod - async def set_keyword_pass(cls, keyword: str, is_pass: bool) -> "int, int": - """ - 说明: - 通过或禁用关键词 - 参数: - :param keyword: 关键词 - :param is_pass: 通过状态 - """ - if await cls._check_keyword_exists(keyword): - query = cls.query.where(cls.keyword == keyword).with_for_update() - query = await query.gino.first() - await query.update( - is_pass=is_pass, - ).apply() - return query.user_qq, query.group_id - return 0, 0 - - @classmethod - async def get_all_user_dict(cls) -> dict: - """ - 说明: - 获取关键词数据库各个用户贡献的关键词字典 - """ - tmp = {} - query = await cls.query.gino.all() - for user in query: - if not tmp.get(user.user_qq): - tmp[user.user_qq] = {"keyword": []} - tmp[user.user_qq]["keyword"].append(user.keyword) - return tmp - - @classmethod - async def get_current_keyword(cls) -> "List[str], List[str]": + async def get_current_keyword(cls) -> Tuple[List[str], List[str]]: """ 说明: 获取当前通过与未通过的关键词 """ pass_keyword = [] not_pass_keyword = [] - query = await cls.query.gino.all() - for user in query: - if user.is_pass: - pass_keyword.append(user.keyword) + for data in await cls.all().values_list("keyword", "is_pass"): + if data[1]: + pass_keyword.append(data[0]) else: - not_pass_keyword.append(user.keyword) + not_pass_keyword.append(data[0]) return pass_keyword, not_pass_keyword @classmethod @@ -105,23 +46,9 @@ class PixivKeywordUser(db.Model): 获取黑名单PID """ black_pid = [] - query = await cls.query.where(cls.user_qq == 114514).gino.all() - for image in query: - black_pid.append(image.keyword[6:]) + keyword_list = await cls.filter(user_qq=114514).values_list( + "keyword", flat=True + ) + for image in keyword_list: + black_pid.append(image[6:]) return black_pid - - @classmethod - async def _check_keyword_exists(cls, keyword: str) -> bool: - """ - 说明: - 检测关键词是否已存在 - 参数: - :param keyword: 关键词 - """ - current_keyword = [] - query = await cls.query.gino.all() - for user in query: - current_keyword.append(user.keyword) - if keyword in current_keyword: - return True - return False diff --git a/plugins/pix_gallery/pix_add_keyword.py b/plugins/pix_gallery/pix_add_keyword.py index 2f3b8c0e..9ac1bdf1 100755 --- a/plugins/pix_gallery/pix_add_keyword.py +++ b/plugins/pix_gallery/pix_add_keyword.py @@ -1,14 +1,17 @@ -from nonebot import on_command -from utils.utils import is_number -from services.log import logger -from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, Message -from nonebot.params import CommandArg, Command from typing import Tuple -from ._data_source import uid_pid_exists -from ._model.pixiv_keyword_user import PixivKeywordUser -from ._model.pixiv import Pixiv + +from nonebot import on_command +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent +from nonebot.params import Command, CommandArg from nonebot.permission import SUPERUSER +from services.log import logger +from utils.utils import is_number + +from ._data_source import uid_pid_exists +from ._model.pixiv import Pixiv +from ._model.pixiv_keyword_user import PixivKeywordUser + __zx_plugin_name__ = "PIX关键词/UID/PID添加管理 [Superuser]" __plugin_usage__ = """ usage: @@ -46,9 +49,16 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): if isinstance(event, GroupMessageEvent): group_id = event.group_id if msg: - if await PixivKeywordUser.add_keyword( - event.user_id, group_id, msg, bot.config.superusers - ): + # if await PixivKeywordUser.add_keyword( + # event.user_id, group_id, msg, bot.config.superusers + # ): + if not await PixivKeywordUser.exists(keyword=msg): + await PixivKeywordUser.create( + user_qq=event.user_id, + group_id=group_id, + keyword=msg, + is_pass=str(event.user_id) in bot.config.superusers, + ) await add_keyword.send( f"已成功添加pixiv搜图关键词:{msg},请等待管理员通过该关键词!", at_sender=True ) @@ -63,7 +73,12 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): @add_uid_pid.handle() -async def _(bot: Bot, event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()): +async def _( + bot: Bot, + event: MessageEvent, + cmd: Tuple[str, ...] = Command(), + arg: Message = CommandArg(), +): msg = arg.extract_plain_text().strip() exists_flag = True if msg.find("-f") != -1 and str(event.user_id) in bot.config.superusers: @@ -77,16 +92,23 @@ async def _(bot: Bot, event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg msg = f"uid:{msg}" else: msg = f"pid:{msg}" - if await Pixiv.check_exists(int(msg[4:]), "p0"): + if await Pixiv.get_or_none(pid=int(msg[4:]), img_p="p0"): await add_uid_pid.finish(f"该PID:{msg[4:]}已存在...", at_sender=True) if not await uid_pid_exists(msg) and exists_flag: await add_uid_pid.finish("画师或作品不存在或搜索正在CD,请稍等...", at_sender=True) group_id = -1 if isinstance(event, GroupMessageEvent): group_id = event.group_id - if await PixivKeywordUser.add_keyword( - event.user_id, group_id, msg, bot.config.superusers - ): + # if await PixivKeywordUser.add_keyword( + # event.user_id, group_id, msg, bot.config.superusers + # ): + if not await PixivKeywordUser.exists(keyword=msg): + await PixivKeywordUser.create( + user_qq=event.user_id, + group_id=group_id, + keyword=msg, + is_pass=str(event.user_id) in bot.config.superusers, + ) await add_uid_pid.send( f"已成功添加pixiv搜图UID/PID:{msg[4:]},请等待管理员通过!", at_sender=True ) @@ -106,12 +128,21 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): pid = pid[: pid.find("p")] if not is_number(pid): await add_black_pid.finish("PID必须全部是数字!", at_sender=True) - if await PixivKeywordUser.add_keyword( - 114514, - 114514, - f"black:{pid}{f'_p{img_p}' if img_p else ''}", - bot.config.superusers, + # if await PixivKeywordUser.add_keyword( + # 114514, + # 114514, + # f"black:{pid}{f'_p{img_p}' if img_p else ''}", + # bot.config.superusers, + # ): + if not await PixivKeywordUser.exists( + keyword=f"black:{pid}{f'_p{img_p}' if img_p else ''}" ): + await PixivKeywordUser.create( + user_qq=114514, + group_id=114514, + keyword=f"black:{pid}{f'_p{img_p}' if img_p else ''}", + is_pass=str(event.user_id) in bot.config.superusers, + ) await add_black_pid.send(f"已添加PID:{pid} 至黑名单中...") logger.info( f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" diff --git a/plugins/pix_gallery/pix_pass_del_keyword.py b/plugins/pix_gallery/pix_pass_del_keyword.py index 3ce7e83b..293bb80b 100755 --- a/plugins/pix_gallery/pix_pass_del_keyword.py +++ b/plugins/pix_gallery/pix_pass_del_keyword.py @@ -1,15 +1,17 @@ -from nonebot import on_command -from utils.utils import is_number -from utils.message_builder import at -from services.log import logger -from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent, Message -from nonebot.params import CommandArg, Command -from nonebot.permission import SUPERUSER -from ._data_source import remove_image -from ._model.pixiv_keyword_user import PixivKeywordUser -from ._model.pixiv import Pixiv from typing import Tuple +from nonebot import on_command +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent +from nonebot.params import Command, CommandArg +from nonebot.permission import SUPERUSER + +from services.log import logger +from utils.message_builder import at +from utils.utils import is_number + +from ._data_source import remove_image +from ._model.pixiv import Pixiv +from ._model.pixiv_keyword_user import PixivKeywordUser __zx_plugin_name__ = "PIX关键词/UID/PID删除管理 [Superuser]" __plugin_usage__ = """ @@ -60,7 +62,8 @@ async def _(event: MessageEvent, arg: Message = CommandArg()): msg = f"uid:{msg}" if msg.lower().startswith("pid"): msg = "pid:" + msg.replace("pid", "").replace(":", "") - if await PixivKeywordUser.delete_keyword(msg): + if data := await PixivKeywordUser.get_or_none(keyword=msg): + await data.delete() await del_keyword.send(f"删除搜图关键词/UID:{msg} 成功...") logger.info( f"(USER {event.user_id}, GROUP {event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" @@ -97,22 +100,31 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): if await remove_image(int(pid), img_p): msg += f'{pid}{f"_p{img_p}" if img_p else ""},' if flag: - if await PixivKeywordUser.add_keyword( - 114514, - 114514, - f"black:{pid}{f'_p{img_p}' if img_p else ''}", - bot.config.superusers, + # if await PixivKeywordUser.add_keyword( + # 114514, + # 114514, + # f"black:{pid}{f'_p{img_p}' if img_p else ''}", + # bot.config.superusers, + # ): + if await PixivKeywordUser.exists( + keyword=f"black:{pid}{f'_p{img_p}' if img_p else ''}" ): + await PixivKeywordUser.create( + user_qq=114514, + group_id=114514, + keyword=f"black:{pid}{f'_p{img_p}' if img_p else ''}", + is_pass=False, + ) black_pid += f'{pid}{f"_p{img_p}" if img_p else ""},' logger.info( f"(USER {event.user_id}, GROUP " f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" f" 删除了PIX图片 PID:{pid}{f'_p{img_p}' if img_p else ''}" ) - else: - await del_pic.send( - f"PIX:删除pid:{pid}{f'_p{img_p}' if img_p else ''} 失败.." - ) + # else: + # await del_pic.send( + # f"PIX:删除pid:{pid}{f'_p{img_p}' if img_p else ''} 失败.." + # ) else: await del_pic.send( f"PIX:图片pix:{pid}{f'_p{img_p}' if img_p else ''} 不存在...无法删除.." @@ -127,7 +139,12 @@ async def _(bot: Bot, event: MessageEvent, arg: Message = CommandArg()): @pass_keyword.handle() -async def _(bot: Bot, event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg: Message = CommandArg()): +async def _( + bot: Bot, + event: MessageEvent, + cmd: Tuple[str, ...] = Command(), + arg: Message = CommandArg(), +): tmp = {"group": {}, "private": {}} msg = arg.extract_plain_text().strip() if not msg: @@ -145,7 +162,13 @@ async def _(bot: Bot, event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg if not is_number(x[4:]): await pass_keyword.send(f"UID/PID:{x} 非全数字,跳过该关键词...") continue - user_id, group_id = await PixivKeywordUser.set_keyword_pass(x, flag) + data = await PixivKeywordUser.get_or_none(keyword=x) + user_id = 0 + group_id = 0 + if data: + data.is_pass = flag + await data.save(update_fields=["is_pass"]) + user_id, group_id = data.user_qq, data.group_id if not user_id: await pass_keyword.send(f"未找到关键词/UID:{x},请检查关键词/UID是否存在...") continue @@ -163,7 +186,7 @@ async def _(bot: Bot, event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg else: tmp["group"][group_id][user_id]["keyword"].append(x) msg = " ".join(msg) - await pass_keyword.send(f'已成功{cmd[0][:2]}搜图关键词:{msg}....') + await pass_keyword.send(f"已成功{cmd[0][:2]}搜图关键词:{msg}....") for user in tmp["private"]: x = ",".join(tmp["private"][user]["keyword"]) await bot.send_private_msg( diff --git a/plugins/pix_gallery/pix_show_info.py b/plugins/pix_gallery/pix_show_info.py index 6ea55d03..dcfbef4b 100755 --- a/plugins/pix_gallery/pix_show_info.py +++ b/plugins/pix_gallery/pix_show_info.py @@ -1,11 +1,13 @@ -from nonebot import on_command -from utils.message_builder import image -from nonebot.adapters.onebot.v11 import Bot, MessageEvent, Message -from ._data_source import gen_keyword_pic, get_keyword_num -from ._model.pixiv_keyword_user import PixivKeywordUser -from nonebot.params import CommandArg import asyncio +from nonebot import on_command +from nonebot.adapters.onebot.v11 import Bot, Message, MessageEvent +from nonebot.params import CommandArg + +from utils.message_builder import image + +from ._data_source import gen_keyword_pic, get_keyword_num +from ._model.pixiv_keyword_user import PixivKeywordUser __zx_plugin_name__ = "查看pix图库" __plugin_usage__ = """ @@ -35,12 +37,12 @@ show_pix = on_command("查看pix图库", priority=1, block=True) @my_keyword.handle() async def _(event: MessageEvent): - data = await PixivKeywordUser.get_all_user_dict() - if data.get(event.user_id) is None or not data[event.user_id]["keyword"]: - await my_keyword.finish("您目前没有提供任何Pixiv搜图关键字...", at_sender=True) - await my_keyword.send( - f"您目前提供的如下关键字:\n\t" + ",".join(data[event.user_id]["keyword"]) + data = await PixivKeywordUser.filter(user_qq=event.user_id).values_list( + "keyword", flat=True ) + if not data: + await my_keyword.finish("您目前没有提供任何Pixiv搜图关键字...", at_sender=True) + await my_keyword.send(f"您目前提供的如下关键字:\n\t" + ",".join(data)) @show_keyword.handle() diff --git a/plugins/pix_gallery/pix_update.py b/plugins/pix_gallery/pix_update.py index 4db3d5fa..ce19604f 100755 --- a/plugins/pix_gallery/pix_update.py +++ b/plugins/pix_gallery/pix_update.py @@ -1,21 +1,22 @@ -import re - -from nonebot import on_command -from utils.utils import is_number -from nonebot.permission import SUPERUSER -from ._data_source import start_update_image_url -from ._model.pixiv_keyword_user import PixivKeywordUser -from ._model.omega_pixiv_illusts import OmegaPixivIllusts -from ._model.pixiv import Pixiv -from nonebot.adapters.onebot.v11 import Message -from nonebot.params import CommandArg -import time -from services.log import logger -from pathlib import Path -from typing import List import asyncio import os +import re +import time +from pathlib import Path +from typing import List +from nonebot import on_command +from nonebot.adapters.onebot.v11 import Message +from nonebot.params import CommandArg +from nonebot.permission import SUPERUSER + +from services.log import logger +from utils.utils import is_number + +from ._data_source import start_update_image_url +from ._model.omega_pixiv_illusts import OmegaPixivIllusts +from ._model.pixiv import Pixiv +from ._model.pixiv_keyword_user import PixivKeywordUser __zx_plugin_name__ = "pix检查更新 [Superuser]" __plugin_usage__ = """ @@ -146,11 +147,11 @@ async def _(arg: Message = CommandArg()): async def _(): async def _tasks(line: str, all_pid: List[int], length: int, index: int): data = line.split("VALUES", maxsplit=1)[-1].strip()[1:-2] - num_list = re.findall(r'(\d+)', data) + num_list = re.findall(r"(\d+)", data) pid = int(num_list[1]) uid = int(num_list[2]) id_ = 3 - while num_list[id_] not in ['0', '1']: + while num_list[id_] not in ["0", "1"]: id_ += 1 classified = int(num_list[id_]) nsfw_tag = int(num_list[id_ + 1]) @@ -164,23 +165,25 @@ async def _(): if pid in all_pid: logger.info(f"添加OmegaPixivIllusts图库数据已存在 ---> pid:{pid}") return - if await OmegaPixivIllusts.add_image_data( - pid=pid, - title=title, - width=width, - height=height, - url=url, - uid=uid, - nsfw_tag=nsfw_tag, - tags=tags, - uname=uname, - classified=classified - ): + _, is_create = await OmegaPixivIllusts.get_or_create( + pid=pid, + title=title, + width=width, + height=height, + url=url, + uid=uid, + nsfw_tag=nsfw_tag, + tags=tags, + uname=uname, + classified=classified, + ) + if is_create: logger.info( f"成功添加OmegaPixivIllusts图库数据 pid:{pid} 本次预计存储 {length} 张,已更新第 {index} 张" ) else: logger.info(f"添加OmegaPixivIllusts图库数据已存在 ---> pid:{pid}") + omega_pixiv_illusts = None for file in os.listdir("."): if "omega_pixiv_artwork" in file and ".sql" in file: @@ -190,13 +193,13 @@ async def _(): lines = f.readlines() tasks = [] length = len([x for x in lines if "INSERT INTO" in x.upper()]) - all_pid = await OmegaPixivIllusts.get_all_pid() + all_pid = await OmegaPixivIllusts.all().values_list("pid", flat=True) index = 0 logger.info("检测到OmegaPixivIllusts数据库,准备开始更新....") for line in lines: if "INSERT INTO" in line.upper(): index += 1 - logger.info(f'line: {line} 加入更新计划') + logger.info(f"line: {line} 加入更新计划") tasks.append( asyncio.ensure_future(_tasks(line, all_pid, length, index)) ) diff --git a/plugins/pixiv_rank_search/data_source.py b/plugins/pixiv_rank_search/data_source.py index b4eb3f2e..d9b007a0 100755 --- a/plugins/pixiv_rank_search/data_source.py +++ b/plugins/pixiv_rank_search/data_source.py @@ -1,13 +1,14 @@ -from configs.path_config import IMAGE_PATH +import platform +from asyncio.exceptions import TimeoutError +from pathlib import Path +from typing import Optional + +from configs.config import Config +from configs.path_config import IMAGE_PATH, TEMP_PATH +from services.log import logger +from utils.http_utils import AsyncHttpx from utils.message_builder import image from utils.utils import change_img_md5 -from asyncio.exceptions import TimeoutError -from configs.config import Config -from utils.http_utils import AsyncHttpx -from typing import Optional -from services.log import logger -from pathlib import Path -import platform # if platform.system() == "Windows": # import asyncio @@ -133,9 +134,9 @@ async def download_pixiv_imgs( ) try: file = ( - f"{IMAGE_PATH}/temp/{user_id}_{forward_msg_index}_{index}_pixiv.jpg" + TEMP_PATH / f"{user_id}_{forward_msg_index}_{index}_pixiv.jpg" if forward_msg_index is not None - else f"{IMAGE_PATH}/temp/{user_id}_{index}_pixiv.jpg" + else TEMP_PATH / f"{user_id}_{index}_pixiv.jpg" ) file = Path(file) try: @@ -147,11 +148,11 @@ async def download_pixiv_imgs( change_img_md5(file) if forward_msg_index is not None: result += image( - f"{user_id}_{forward_msg_index}_{index}_pixiv.jpg", - "temp", + TEMP_PATH + / f"{user_id}_{forward_msg_index}_{index}_pixiv.jpg", ) else: - result += image(f"{user_id}_{index}_pixiv.jpg", "temp") + result += image(TEMP_PATH / f"{user_id}_{index}_pixiv.jpg") index += 1 except OSError: if file.exists(): diff --git a/plugins/poke/__init__.py b/plugins/poke/__init__.py index 43e87ebb..180935c2 100755 --- a/plugins/poke/__init__.py +++ b/plugins/poke/__init__.py @@ -1,12 +1,14 @@ +import os +import random + from nonebot import on_notice from nonebot.adapters.onebot.v11 import PokeNotifyEvent -from configs.path_config import RECORD_PATH, IMAGE_PATH -from utils.message_builder import record, image, poke -from services.log import logger -import random -from utils.utils import CountLimiter + +from configs.path_config import IMAGE_PATH, RECORD_PATH from models.ban_user import BanUser -import os +from services.log import logger +from utils.message_builder import image, poke, record +from utils.utils import CountLimiter __zx_plugin_name__ = "戳一戳" @@ -64,13 +66,17 @@ async def _poke_event(event: PokeNotifyEvent): rand = random.random() path = random.choice(["luoli", "meitu"]) if rand <= 0.3 and len(os.listdir(IMAGE_PATH / "image_management" / path)) > 0: - index = random.randint(0, len(os.listdir(IMAGE_PATH / "image_management" / path)) - 1) - result = f"id:{index}" + image(f"{index}.jpg", "image_management/" + path) + index = random.randint( + 0, len(os.listdir(IMAGE_PATH / "image_management" / path)) - 1 + ) + result = f"id:{index}" + image( + IMAGE_PATH / "image_management" / path / f"{index}.jpg" + ) await poke_.send(result) logger.info(f"USER {event.user_id} 戳了戳我 回复: {result} {result}") elif 0.3 < rand < 0.6: voice = random.choice(os.listdir(RECORD_PATH / "dinggong")) - result = record(voice, "dinggong") + result = record(RECORD_PATH / "dinggong" / voice) await poke_.send(result) await poke_.send(voice.split("_")[1]) logger.info( diff --git a/plugins/russian/__init__.py b/plugins/russian/__init__.py index c10ea1e6..99b8792d 100755 --- a/plugins/russian/__init__.py +++ b/plugins/russian/__init__.py @@ -1,20 +1,22 @@ +import asyncio +import random +import time +from typing import Tuple + from nonebot import on_command from nonebot.adapters.onebot.v11 import GROUP, Bot, GroupMessageEvent, Message +from nonebot.params import ArgStr, Command, CommandArg from nonebot.typing import T_State -from utils.utils import is_number, get_message_at -from nonebot.params import CommandArg, Command, ArgStr -from models.group_member_info import GroupInfoUser -from utils.message_builder import at, image -from .model import RussianUser -from models.bag_user import BagUser -from services.log import logger -from .data_source import rank -from configs.config import NICKNAME, Config -from typing import Tuple -import random -import asyncio -import time +from configs.config import NICKNAME, Config +from models.bag_user import BagUser +from models.group_member_info import GroupInfoUser +from services.log import logger +from utils.message_builder import at, image +from utils.utils import get_message_at, is_number + +from .data_source import rank +from .model import RussianUser __zx_plugin_name__ = "俄罗斯轮盘" __plugin_usage__ = """ @@ -156,7 +158,9 @@ async def _(event: GroupMessageEvent): await accept.finish("又不是找你决斗,你拒绝什么啊!气!", at_sender=True) if rs_player[event.group_id]["at"] == event.user_id: at_player_name = ( - await GroupInfoUser.get_member_info(event.user_id, event.group_id) + await GroupInfoUser.get_or_none( + user_qq=event.user_id, group_id=event.group_id + ) ).user_name await accept.send( Message(f"{at(rs_player[event.group_id][1])}\n" f"{at_player_name}拒绝了你的对决!") @@ -291,7 +295,7 @@ async def _( at_ = at_[0] try: at_player_name = ( - await GroupInfoUser.get_member_info(at_, event.group_id) + await GroupInfoUser.get_or_none(user_qq=at_, group_id=event.group_id) ).user_name except AttributeError: at_player_name = at(at_) @@ -359,14 +363,14 @@ async def _(bot: Bot, event: GroupMessageEvent): [ f"不要打扰 {player1_name} 和 {player2_name} 的决斗啊!", f"给我好好做好一个观众!不然{NICKNAME}就要生气了", - f"不要捣乱啊baka{(await GroupInfoUser.get_member_info(event.user_id, event.group_id)).user_name}!", + f"不要捣乱啊baka{(await GroupInfoUser.get_or_none(user_qq=event.user_id, group_id=event.group_id)).user_name}!", ] ), at_sender=True, ) await shot.finish( f"你的左轮不是连发的!该 " - f'{(await GroupInfoUser.get_member_info(int(rs_player[event.group_id]["next"]), event.group_id)).user_name} 开枪了' + f'{(await GroupInfoUser.get_or_none(user_qq=int(rs_player[event.group_id]["next"]), group_id=event.group_id)).user_name} 开枪了' ) if rs_player[event.group_id]["bullet"][rs_player[event.group_id]["index"]] != 1: await shot.send( @@ -440,8 +444,12 @@ async def end_game(bot: Bot, event: GroupMessageEvent): await RussianUser.money(lose_user_id, event.group_id, "lose", money) await BagUser.add_gold(win_user_id, event.group_id, money - fee) await BagUser.spend_gold(lose_user_id, event.group_id, money) - win_user = await RussianUser.ensure(win_user_id, event.group_id) - lose_user = await RussianUser.ensure(lose_user_id, event.group_id) + win_user, _ = await RussianUser.get_or_create( + user_qq=win_user_id, group_id=event.group_id + ) + lose_user, _ = await RussianUser.get_or_create( + user_qq=lose_user_id, group_id=event.group_id + ) bullet_str = "" for x in rs_player[event.group_id]["bullet"]: bullet_str += "__ " if x == 0 else "| " @@ -467,7 +475,9 @@ async def end_game(bot: Bot, event: GroupMessageEvent): @record.handle() async def _(event: GroupMessageEvent): - user = await RussianUser.ensure(event.user_id, event.group_id) + user, _ = await RussianUser.get_or_create( + user_qq=event.user_id, group_id=event.group_id + ) await record.send( f"俄罗斯轮盘\n" f"总胜利场次:{user.win_count}\n" diff --git a/plugins/russian/data_source.py b/plugins/russian/data_source.py index 765cbaca..e5ae6e7d 100755 --- a/plugins/russian/data_source.py +++ b/plugins/russian/data_source.py @@ -1,42 +1,33 @@ -from .model import RussianUser from typing import Optional + from utils.data_utils import init_rank from utils.image_utils import BuildMat +from .model import RussianUser + async def rank(group_id: int, itype: str, num: int) -> Optional[BuildMat]: - all_users = await RussianUser.get_all_user(group_id) + all_users = await RussianUser.filter(group_id=group_id).all() all_user_id = [user.user_qq for user in all_users] - if itype == 'win_rank': - rank_name = '胜场排行榜' + if itype == "win_rank": + rank_name = "胜场排行榜" all_user_data = [user.win_count for user in all_users] - elif itype == 'lose_rank': - rank_name = '败场排行榜' + elif itype == "lose_rank": + rank_name = "败场排行榜" all_user_data = [user.fail_count for user in all_users] - elif itype == 'make_money': - rank_name = '赢取金币排行榜' + elif itype == "make_money": + rank_name = "赢取金币排行榜" all_user_data = [user.make_money for user in all_users] - elif itype == 'spend_money': - rank_name = '输掉金币排行榜' + elif itype == "spend_money": + rank_name = "输掉金币排行榜" all_user_data = [user.lose_money for user in all_users] - elif itype == 'max_winning_streak': - rank_name = '最高连胜排行榜' + elif itype == "max_winning_streak": + rank_name = "最高连胜排行榜" all_user_data = [user.max_winning_streak for user in all_users] else: - rank_name = '最高连败排行榜' + rank_name = "最高连败排行榜" all_user_data = [user.max_losing_streak for user in all_users] rst = None if all_users: rst = await init_rank(rank_name, all_user_id, all_user_data, group_id, num) return rst - - - - - - - - - - - diff --git a/plugins/russian/model.py b/plugins/russian/model.py index 577b96c8..62a4a08c 100755 --- a/plugins/russian/model.py +++ b/plugins/russian/model.py @@ -1,43 +1,41 @@ -from services.db_context import db -from typing import List + +from tortoise import fields + +from services.db_context import Model -class RussianUser(db.Model): - __tablename__ = "russian_users" - __table_args__ = {'extend_existing': True} +class RussianUser(Model): - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger(), nullable=False) - win_count = db.Column(db.Integer(), default=0) - fail_count = db.Column(db.Integer(), default=0) - make_money = db.Column(db.Integer(), default=0) - lose_money = db.Column(db.Integer(), default=0) - winning_streak = db.Column(db.Integer(), default=0) - losing_streak = db.Column(db.Integer(), default=0) - max_winning_streak = db.Column(db.Integer(), default=0) - max_losing_streak = db.Column(db.Integer(), default=0) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField() + """群聊id""" + win_count = fields.IntField(default=0) + """胜利次数""" + fail_count = fields.IntField(default=0) + """失败次数""" + make_money = fields.IntField(default=0) + """赢得金币""" + lose_money = fields.IntField(default=0) + """输得金币""" + winning_streak = fields.IntField(default=0) + """当前连胜""" + losing_streak = fields.IntField(default=0) + """当前连败""" + max_winning_streak = fields.IntField(default=0) + """最大连胜""" + max_losing_streak = fields.IntField(default=0) + """最大连败""" - _idx1 = db.Index("russian_group_users_idx1", "user_qq", "group_id", unique=True) + class Meta: + table = "russian_users" + table_description = "俄罗斯轮盘数据表" + unique_together = ("user_qq", "group_id") @classmethod - async def ensure(cls, user_qq: int, group_id: int) -> "RussianUser": - """ - 说明: - 获取用户对象 - 参数: - :param user_qq: qq号 - :param group_id: 群号 - """ - user = ( - await cls.query.where((cls.user_qq == user_qq) & (cls.group_id == group_id)) - .with_for_update() - .gino.first() - ) - return user or await cls.create(user_qq=user_qq, group_id=group_id) - - @classmethod - async def add_count(cls, user_qq: int, group_id: int, itype: str) -> bool: + async def add_count(cls, user_qq: int, group_id: int, itype: str): """ 说明: 添加用户输赢次数 @@ -46,43 +44,43 @@ class RussianUser(db.Model): :param group_id: 群号 :param itype: 输或赢 'win' or 'lose' """ - try: - user = ( - await cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - .with_for_update() - .gino.first() + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + if itype == "win": + _max = ( + user.max_winning_streak + if user.max_winning_streak > user.winning_streak + 1 + else user.winning_streak + 1 + ) + user.win_count = user.win_count + 1 + user.winning_streak = user.winning_streak + 1 + user.losing_streak = 0 + user.max_winning_streak = _max + await user.save( + update_fields=[ + "win_count", + "winning_streak", + "losing_streak", + "max_winning_streak", + ] + ) + elif itype == "lose": + _max = ( + user.max_losing_streak + if user.max_losing_streak > user.losing_streak + 1 + else user.losing_streak + 1 + ) + user.fail_count = user.fail_count + 1 + user.losing_streak = user.losing_streak + 1 + user.winning_streak = 0 + user.max_losing_streak = _max + await user.save( + update_fields=[ + "fail_count", + "winning_streak", + "losing_streak", + "max_losing_streak", + ] ) - if not user: - user = await cls.create(user_qq=user_qq, group_id=group_id) - if itype == "win": - _max = ( - user.max_winning_streak - if user.max_winning_streak > user.winning_streak + 1 - else user.winning_streak + 1 - ) - await user.update( - win_count=user.win_count + 1, - winning_streak=user.winning_streak + 1, - losing_streak=0, - max_winning_streak=_max - ).apply() - elif itype == "lose": - _max = ( - user.max_losing_streak - if user.max_losing_streak > user.losing_streak + 1 - else user.losing_streak + 1 - ) - await user.update( - fail_count=user.fail_count + 1, - losing_streak=user.losing_streak + 1, - winning_streak=0, - max_losing_streak=_max, - ).apply() - return True - except Exception: - return False @classmethod async def money(cls, user_qq: int, group_id: int, itype: str, count: int) -> bool: @@ -95,35 +93,9 @@ class RussianUser(db.Model): :param itype: 输或赢 'win' or 'lose' :param count: 金钱数量 """ - try: - user = ( - await cls.query.where( - (cls.user_qq == user_qq) & (cls.group_id == group_id) - ) - .with_for_update() - .gino.first() - ) - if not user: - user = await cls.create(user_qq=user_qq, group_id=group_id) - if itype == "win": - await user.update( - make_money=user.make_money + count, - ).apply() - elif itype == "lose": - await user.update( - lose_money=user.lose_money + count, - ).apply() - return True - except Exception: - return False - - @classmethod - async def get_all_user(cls, group_id: int) -> List["RussianUser"]: - """ - 说明: - 获取该群所有用户对象 - 参数: - :param group_id: 群号 - """ - users = await cls.query.where((cls.group_id == group_id)).gino.all() - return users + user, _ = await cls.get_or_create(user_qq=user_qq, group_id=group_id) + if itype == "win": + user.make_money = user.make_money + count + elif itype == "lose": + user.lose_money = user.lose_money + count + await user.save(update_fields=["make_money", "lose_money"]) diff --git a/plugins/send_dinggong_voice/__init__.py b/plugins/send_dinggong_voice/__init__.py index 438634f7..5fe37c44 100755 --- a/plugins/send_dinggong_voice/__init__.py +++ b/plugins/send_dinggong_voice/__init__.py @@ -1,12 +1,14 @@ +import os +import random + from nonebot import on_keyword -from utils.message_builder import record +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent +from nonebot.rule import to_me +from nonebot.typing import T_State + from configs.path_config import RECORD_PATH from services.log import logger -from nonebot.typing import T_State -from nonebot.adapters.onebot.v11 import Bot, MessageEvent, GroupMessageEvent -from nonebot.rule import to_me -import random -import os +from utils.message_builder import record __zx_plugin_name__ = "骂我" __plugin_usage__ = """ @@ -25,10 +27,7 @@ __plugin_settings__ = { "limit_superuser": False, "cmd": ["骂老子", "骂我"], } -__plugin_cd_limit__ = { - "cd": 3, - "rst": "就...就算求我骂你也得慢慢来..." -} +__plugin_cd_limit__ = {"cd": 3, "rst": "就...就算求我骂你也得慢慢来..."} dg_voice = on_keyword({"骂"}, rule=to_me(), priority=5, block=True) @@ -38,7 +37,9 @@ dg_voice = on_keyword({"骂"}, rule=to_me(), priority=5, block=True) async def _(bot: Bot, event: MessageEvent, state: T_State): if len(str((event.get_message()))) > 1: voice = random.choice(os.listdir(RECORD_PATH / "dinggong")) - result = record(voice, "dinggong") + result = record( + RECORD_PATH / "dinggong" / voice, + ) await dg_voice.send(result) await dg_voice.send(voice.split("_")[1]) logger.info( diff --git a/plugins/send_setu_/_model.py b/plugins/send_setu_/_model.py index 403333a9..82f52c3b 100644 --- a/plugins/send_setu_/_model.py +++ b/plugins/send_setu_/_model.py @@ -1,64 +1,44 @@ -from services.db_context import db from typing import List, Optional +from tortoise import fields +from tortoise.contrib.postgres.functions import Random +from tortoise.expressions import Q -class Setu(db.Model): - __tablename__ = "setu" - __table_args__ = {'extend_existing': True} +from services.db_context import Model - id = db.Column(db.Integer(), primary_key=True) - local_id = db.Column(db.Integer(), nullable=False) - title = db.Column(db.String(), nullable=False) - author = db.Column(db.String(), nullable=False) - pid = db.Column(db.BigInteger(), nullable=False) - img_hash = db.Column(db.String(), nullable=False) - img_url = db.Column(db.String(), nullable=False) - is_r18 = db.Column(db.Boolean(), nullable=False) - tags = db.Column(db.String()) - _idx1 = db.Index("setu_pid_img_url_idx1", "pid", "img_url", unique=True) +class Setu(Model): - @classmethod - async def add_setu_data( - cls, - local_id: int, - title: str, - author: str, - pid: int, - img_hash: str, - img_url: str, - tags: str, - ): - """ - 说明: - 添加一份色图数据 - 参数: - :param local_id: 本地存储id - :param title: 标题 - :param author: 作者 - :param pid: 图片pid - :param img_hash: 图片hash值 - :param img_url: 图片链接 - :param tags: 图片标签 - """ - if not await cls._check_exists(pid, img_url): - await cls.create( - local_id=local_id, - title=title, - author=author, - pid=pid, - img_hash=img_hash, - img_url=img_url, - is_r18=True if "R-18" in tags else False, - tags=tags, - ) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + local_id = fields.IntField() + """本地存储下标""" + title = fields.CharField(255) + """标题""" + author = fields.CharField(255) + """作者""" + pid = fields.BigIntField() + """pid""" + img_hash = fields.TextField() + """图片hash""" + img_url = fields.CharField(255) + """pixiv url链接""" + is_r18 = fields.BooleanField() + """是否r18""" + tags = fields.TextField() + """tags""" + + class Meta: + table = "setu" + table_description = "色图数据表" + unique_together = ("pid", "img_url") @classmethod async def query_image( cls, local_id: Optional[int] = None, tags: Optional[List[str]] = None, - r18: int = 0, + r18: bool = False, limit: int = 50, ): """ @@ -71,58 +51,17 @@ class Setu(db.Model): :param limit: 获取数量 """ if local_id: - flag = True if r18 == 1 else False - return await cls.query.where( - (cls.local_id == local_id) & (cls.is_r18 == flag) - ).gino.first() - if r18 == 0: - query = cls.query.where(cls.is_r18 == False) - elif r18 == 1: - query = cls.query.where(cls.is_r18 == True) - else: - query = cls.query + return await cls.filter(is_r18=r18, local_id=local_id).first() + query = cls.filter(is_r18=r18) if tags: for tag in tags: - query = query.where(cls.tags.contains(tag) | cls.title.contains(tag) | cls.author.contains(tag)) - query = query.order_by(db.func.random()).limit(limit) - return await query.gino.all() - - @classmethod - async def get_image_count(cls, r18: int = 0) -> int: - """ - 说明: - 查询图片数量 - """ - flag = False if r18 == 0 else True - setattr(Setu, 'count', db.func.count(cls.local_id).label('count')) - count = await cls.select('count').where(cls.is_r18 == flag).gino.first() - return count[0] - - @classmethod - async def get_image_in_hash(cls, img_hash: str) -> "Setu": - """ - 说明: - 通过图像hash获取图像信息 - 参数: - :param img_hash: = 图像hash值 - """ - query = await cls.query.where(cls.img_hash == img_hash).gino.first() - return query - - @classmethod - async def _check_exists(cls, pid: int, img_url: str) -> bool: - """ - 说明: - 检测图片是否存在 - 参数: - :param pid: 图片pid - :param img_url: 图片链接 - """ - return bool( - await cls.query.where( - (cls.pid == pid) & (cls.img_url == img_url) - ).gino.first() - ) + query = query.filter( + Q(tags__contains=tag) + | Q(title__contains=tag) + | Q(author__contains=tag) + ) + query = query.annotate(rand=Random()).limit(limit) + return await query.all() @classmethod async def delete_image(cls, pid: int) -> int: @@ -132,66 +71,14 @@ class Setu(db.Model): 参数: :param pid: 图片pid """ - query = await cls.query.where(cls.pid == pid).gino.first() + return_id = -1 + query = await cls.get_or_none(pid=pid) if query: - is_r18 = query.is_r18 - num = await cls.get_image_count(is_r18) - x = await cls.query.where((cls.is_r18 == is_r18) & (cls.local_id == num - 1)).gino.first() - _tmp_local_id = x.local_id - if x: - x.update(local_id=query.local_id).apply() - await cls.delete.where(cls.pid == pid).gino.status() - return _tmp_local_id - return -1 - - @classmethod - async def update_setu_data( - cls, - pid: int, - *, - local_id: Optional[int] = None, - title: Optional[str] = None, - author: Optional[str] = None, - img_hash: Optional[str] = None, - img_url: Optional[str] = None, - tags: Optional[str] = None, - ) -> bool: - """ - 说明: - 根据PID修改图片数据 - 参数: - :param local_id: 本地id - :param pid: 图片pid - :param title: 标题 - :param author: 作者 - :param img_hash: 图片hash值 - :param img_url: 图片链接 - :param tags: 图片标签 - """ - query = cls.query.where(cls.pid == pid).with_for_update() - image_list = await query.gino.all() - if image_list: - for image in image_list: - if local_id: - await image.update(local_id=local_id).apply() - if title: - await image.update(title=title).apply() - if author: - await image.update(author=author).apply() - if img_hash: - await image.update(img_hash=img_hash).apply() - if img_url: - await image.update(img_url=img_url).apply() - if tags: - await image.update(tags=tags).apply() - return True - return False - - @classmethod - async def get_all_setu(cls) -> List["Setu"]: - """ - 说明: - 获取所有图片对象 - """ - return await cls.query.gino.all() - + num = await cls.filter(is_r18=query.is_r18).count() + last_image = await cls.get_or_none(is_r18=query.is_r18, local_id=num - 1) + if last_image: + return_id = last_image.local_id + last_image.local_id = query.local_id + await last_image.save(update_fields=["local_id"]) + await query.delete() + return return_id diff --git a/plugins/send_setu_/send_setu/__init__.py b/plugins/send_setu_/send_setu/__init__.py index 1a9cf25d..b6c4fb6c 100755 --- a/plugins/send_setu_/send_setu/__init__.py +++ b/plugins/send_setu_/send_setu/__init__.py @@ -1,42 +1,40 @@ import random -from nonebot import on_command, on_regex -from services.log import logger -from models.sign_group_user import SignGroupUser -from nonebot.message import run_postprocessor -from nonebot.matcher import Matcher -from typing import Optional, Type, Any -from gino.exceptions import UninitializedError +import re +from typing import Any, Optional, Tuple, Type -from utils.message_builder import custom_forward_msg -from utils.utils import ( - is_number, -) -from nonebot.typing import T_State +from nonebot import on_command, on_regex from nonebot.adapters.onebot.v11 import ( - Bot, ActionFailed, - MessageEvent, - GroupMessageEvent, - PrivateMessageEvent, - Message, + Bot, Event, + GroupMessageEvent, + Message, + MessageEvent, + PrivateMessageEvent, ) +from nonebot.matcher import Matcher +from nonebot.message import run_postprocessor +from nonebot.params import Command, CommandArg, RegexGroup +from nonebot.typing import T_State + +from configs.config import NICKNAME, Config +from models.sign_group_user import SignGroupUser +from services.log import logger +from utils.manager import withdraw_message_manager +from utils.message_builder import custom_forward_msg +from utils.utils import is_number + +from .._model import Setu from .data_source import ( - get_setu_list, - get_luoxiang, - search_online_setu, - get_setu_urls, + add_data_to_database, + check_local_exists_or_download, find_img_index, gen_message, - check_local_exists_or_download, - add_data_to_database, - get_setu_count, + get_luoxiang, + get_setu_list, + get_setu_urls, + search_online_setu, ) -from configs.config import Config, NICKNAME -from utils.manager import withdraw_message_manager -from nonebot.params import CommandArg, Command, RegexGroup -from typing import Tuple -import re try: import ujson as json @@ -126,7 +124,7 @@ async def do_something( await add_data_to_database(setu_data_list) logger.info("色图数据自动存储数据库成功...") setu_data_list = [] - except UninitializedError: + except Exception: pass @@ -146,17 +144,18 @@ async def _( ): msg = arg.extract_plain_text().strip() if isinstance(event, GroupMessageEvent): - impression = ( - await SignGroupUser.ensure(event.user_id, event.group_id) - ).impression + user, _ = await SignGroupUser.get_or_create( + user_qq=event.user_id, group_id=event.group_id + ) + impression = user.impression luox = get_luoxiang(impression) if luox: await setu.finish(luox) - r18 = 0 + r18 = False num = 1 # 是否看r18 if cmd[0] == "色图r" and isinstance(event, PrivateMessageEvent): - r18 = 1 + r18 = True num = 10 elif cmd[0] == "色图r" and isinstance(event, GroupMessageEvent): if not Config.get_config("send_setu", "ALLOW_GROUP_R18"): @@ -164,18 +163,20 @@ async def _( random.choice(["这种不好意思的东西怎么可能给这么多人看啦", "羞羞脸!给我滚出克私聊!", "变态变态变态变态大变态!"]) ) else: - r18 = 1 + r18 = False # 有 数字 的话先尝试本地色图id if msg and is_number(msg): setu_list, code = await get_setu_list(int(msg), r18=r18) if code != 200: await setu.finish(setu_list[0], at_sender=True) setu_img, code = await check_local_exists_or_download(setu_list[0]) - msg_id = await setu.send(gen_message(setu_list[0]) + setu_img, at_sender=True) + msg_id = await setu.send( + Message(gen_message(setu_list[0])) + setu_img, at_sender=True + ) logger.info( f"(USER {event.user_id}, GROUP " f"{event.group_id if isinstance(event, GroupMessageEvent) else 'private'})" - f" 发送色图 {setu_list[0].local_id}.png" + f" 发送色图 {setu_list[0].local_id}.jpd" ) if msg_id: withdraw_message_manager.withdraw_message( @@ -205,9 +206,10 @@ num_key = { @setu_reg.handle() async def _(bot: Bot, event: MessageEvent, reg_group: Tuple[Any, ...] = RegexGroup()): if isinstance(event, GroupMessageEvent): - impression = ( - await SignGroupUser.ensure(event.user_id, event.group_id) - ).impression + user, _ = await SignGroupUser.get_or_create( + user_qq=event.user_id, group_id=event.group_id + ) + impression = user.impression luox = get_luoxiang(impression) if luox: await setu.finish(luox, at_sender=True) @@ -220,7 +222,7 @@ async def _(bot: Bot, event: MessageEvent, reg_group: Tuple[Any, ...] = RegexGro num = int(num) except ValueError: num = 1 - await send_setu_handle(bot, setu_reg, event, "色图", tags, num, 0) + await send_setu_handle(bot, setu_reg, event, "色图", tags, num, False) async def send_setu_handle( @@ -230,7 +232,7 @@ async def send_setu_handle( command: str, msg: str, num: int, - r18: int, + r18: bool, ): global setu_data_list # 非 id,在线搜索 @@ -240,7 +242,7 @@ async def send_setu_handle( await matcher.finish("咳咳咳,虽然我很可爱,但是我木有自己的色图~~~有的话记得发我一份呀") # 本地先拿图,下载失败补上去 setu_list, code = None, 200 - setu_count = await get_setu_count(r18) + setu_count = await Setu.filter(is_r18=r18).count() if ( not Config.get_config("send_setu", "ONLY_USE_LOCAL_SETU") and tags ) or setu_count <= 0: diff --git a/plugins/send_setu_/send_setu/data_source.py b/plugins/send_setu_/send_setu/data_source.py index 66f3c046..4bca9e12 100755 --- a/plugins/send_setu_/send_setu/data_source.py +++ b/plugins/send_setu_/send_setu/data_source.py @@ -1,18 +1,22 @@ -from configs.path_config import IMAGE_PATH, TEMP_PATH -from utils.message_builder import image -from services.log import logger -from utils.image_utils import get_img_hash, compressed_image -from utils.utils import change_img_md5 -from asyncpg.exceptions import UniqueViolationError -from asyncio.exceptions import TimeoutError -from typing import List, Optional -from configs.config import NICKNAME, Config -from utils.http_utils import AsyncHttpx -from .._model import Setu import asyncio import os import random import re +from asyncio.exceptions import TimeoutError +from typing import List, Optional, Tuple, Union + +from asyncpg.exceptions import UniqueViolationError +from nonebot.adapters.onebot.v11 import Message, MessageSegment + +from configs.config import NICKNAME, Config +from configs.path_config import IMAGE_PATH, TEMP_PATH +from services.log import logger +from utils.http_utils import AsyncHttpx +from utils.image_utils import compressed_image, get_img_hash +from utils.message_builder import image +from utils.utils import change_img_md5 + +from .._model import Setu try: import ujson as json @@ -28,11 +32,11 @@ host_pattern = re.compile(r"https?://([^/]+)") # 获取url async def get_setu_urls( - tags: List[str], num: int = 1, r18: int = 0, command: str = "" -) -> "List[str], List[str], List[tuple], int": + tags: List[str], num: int = 1, r18: bool = False, command: str = "" +) -> Tuple[List[str], List[str], List[tuple], int]: tags = tags[:3] if len(tags) > 3 else tags params = { - "r18": r18, # 添加r18参数 0为否,1为是,2为混合 + "r18": 1 if r18 else 0, # 添加r18参数 0为否,1为是,2为混合 "tag": tags, # 若指定tag "num": 20, # 一次返回的结果数量 "size": ["original"], @@ -82,7 +86,7 @@ headers = { async def search_online_setu( url_: str, id_: Optional[int] = None, path_: Optional[str] = None -) -> "MessageSegment, int": +) -> Tuple[Union[MessageSegment, str], int]: """ 下载色图 :param url_: 色图url @@ -108,10 +112,7 @@ async def search_online_setu( ): continue if id_ is not None: - if ( - os.path.getsize(path_ / f"{index}.jpg") - > 1024 * 1024 * 1.5 - ): + if os.path.getsize(path_ / f"{index}.jpg") > 1024 * 1024 * 1.5: compressed_image( path_ / f"{index}.jpg", ) @@ -126,7 +127,9 @@ async def search_online_setu( # 检测本地是否有id涩图,无的话则下载 -async def check_local_exists_or_download(setu_image: Setu) -> "MessageSegment, int": +async def check_local_exists_or_download( + setu_image: Setu, +) -> Tuple[MessageSegment, int]: path_ = None id_ = None if Config.get_config("send_setu", "DOWNLOAD_SETU"): @@ -148,27 +151,28 @@ async def add_data_to_database(lst: List[tuple]): if tmp: for x in tmp: try: - r18 = 1 if "R-18" in x[5] else 0 - idx = await Setu.get_image_count(r18) - await Setu.add_setu_data( - idx, - x[0], - x[1], - x[2], - x[3], - x[4], - x[5], - ) + idx = await Setu.filter(is_r18="R-18" in x[5]).count() + if not await Setu.exists(pid=x[2], img_url=x[4]): + await Setu.create( + local_id=idx, + title=x[0], + author=x[1], + pid=x[2], + img_hash=x[3], + img_url=x[4], + tags=x[5], + is_r18="R-18" in x[5], + ) except UniqueViolationError: pass # 拿到本地色图列表 async def get_setu_list( - index: Optional[int] = None, tags: Optional[List[str]] = None, r18: int = 0 -) -> "list, int": + index: Optional[int] = None, tags: Optional[List[str]] = None, r18: bool = False +) -> Tuple[list, int]: if index: - image_count = await Setu.get_image_count(r18) - 1 + image_count = await Setu.filter(is_r18=r18).count() - 1 if index < 0 or index > image_count: return [f"超过当前上下限!({image_count})"], 999 image_list = [await Setu.query_image(index, r18=r18)] @@ -182,44 +186,44 @@ async def get_setu_list( # 初始化消息 -def gen_message(setu_image: Setu, img_msg: bool = False) -> str: +def gen_message( + setu_image: Setu, img_msg: bool = False +) -> Union[Message, MessageSegment]: local_id = setu_image.local_id title = setu_image.title author = setu_image.author pid = setu_image.pid + path_ = r18_path if setu_image.is_r18 else path + image_path = IMAGE_PATH / path_ / f"{local_id}.jpg" if Config.get_config("send_setu", "SHOW_INFO"): - return ( + return Message( f"id:{local_id}\n" f"title:{title}\n" f"author:{author}\n" - f"PID:{pid}\n" - f"{image(f'{local_id}', f'{r18_path if setu_image.is_r18 else path}') if img_msg else ''}" + f"PID:{pid}\n" + image(image_path) ) - return f"{image(f'{local_id}', f'{r18_path if setu_image.is_r18 else path}') if img_msg else ''}" + return image(image_path) # 罗翔老师! def get_luoxiang(impression): probability = ( - impression + Config.get_config("send_setu", "INITIAL_SETU_PROBABILITY") * 100 + float(impression) + + Config.get_config("send_setu", "INITIAL_SETU_PROBABILITY") * 100 ) if probability < random.randint(1, 101): return ( "我为什么要给你发这个?" - + image(random.choice(os.listdir(IMAGE_PATH / "luoxiang")), "luoxiang") + + image( + IMAGE_PATH + / "luoxiang" + / random.choice(os.listdir(IMAGE_PATH / "luoxiang")) + ) + f"\n(快向{NICKNAME}签到提升好感度吧!)" ) return None -async def get_setu_count(r18: int) -> int: - """ - 获取色图数量 - :param r18: r18类型 - """ - return await Setu.get_image_count(r18) - - async def find_img_index(img_url, user_id): if not await AsyncHttpx.download_file( img_url, @@ -228,8 +232,7 @@ async def find_img_index(img_url, user_id): ): return "检索图片下载上失败..." img_hash = str(get_img_hash(TEMP_PATH / f"{user_id}_find_setu_index.jpg")) - setu_img = await Setu.get_image_in_hash(img_hash) - if setu_img: + if setu_img := await Setu.get_or_none(img_hash=img_hash): return ( f"id:{setu_img.local_id}\n" f"title:{setu_img.title}\n" diff --git a/plugins/send_setu_/update_setu/data_source.py b/plugins/send_setu_/update_setu/data_source.py index 771b0d83..039e3118 100755 --- a/plugins/send_setu_/update_setu/data_source.py +++ b/plugins/send_setu_/update_setu/data_source.py @@ -1,18 +1,21 @@ -from configs.path_config import IMAGE_PATH, TEXT_PATH, TEMP_PATH -from services.log import logger -from datetime import datetime -from utils.image_utils import compressed_image, get_img_hash -from utils.utils import get_bot -from PIL import UnidentifiedImageError -from .._model import Setu -from asyncpg.exceptions import UniqueViolationError -from configs.config import Config -from utils.http_utils import AsyncHttpx -from nonebot import Driver -import nonebot import os -import ujson as json import shutil +from datetime import datetime + +import nonebot +import ujson as json +from asyncpg.exceptions import UniqueViolationError +from nonebot import Driver +from PIL import UnidentifiedImageError + +from configs.config import Config +from configs.path_config import IMAGE_PATH, TEMP_PATH, TEXT_PATH +from services.log import logger +from utils.http_utils import AsyncHttpx +from utils.image_utils import compressed_image, get_img_hash +from utils.utils import change_pixiv_image_links, get_bot + +from .._model import Setu driver: Driver = nonebot.get_driver() @@ -47,15 +50,17 @@ async def update_old_setu_data(): ) # idx = r18_index if 'R-18' in data[x]["tags"] else index try: - await Setu.add_setu_data( - idx, - data[x]["title"], - data[x]["author"], - data[x]["pid"], - data[x]["img_hash"], - img_url, - ",".join(data[x]["tags"]), - ) + if not await Setu.exists(pid=data[x]["pid"], url=img_url): + await Setu.create( + local_id=idx, + title=data[x]["title"], + author=data[x]["author"], + pid=data[x]["pid"], + img_hash=data[x]["img_hash"], + img_url=img_url, + is_r18="R-18" in data[x]["tags"], + tags=",".join(data[x]["tags"]), + ) count += 1 if "R-18" in data[x]["tags"]: r18_index += 1 @@ -94,7 +99,7 @@ async def update_setu_img(flag: bool = False): 更新色图 :param flag: 是否手动更新 """ - image_list = await Setu.get_all_setu() + image_list = await Setu.all().order_by("local_id") image_list.reverse() _success = 0 error_info = [] @@ -110,12 +115,7 @@ async def update_setu_img(flag: bool = False): temp_file = TEMP_PATH / f"{image.local_id}.jpg" if temp_file.exists(): temp_file.unlink() - url_ = image.img_url - ws_url = Config.get_config("pixiv", "PIXIV_NGINX_URL") - if ws_url: - url_ = url_.replace("i.pximg.net", ws_url).replace( - "i.pixiv.cat", ws_url - ) + url_ = change_pixiv_image_links(image.img_url) try: if not await AsyncHttpx.download_file( url_, TEMP_PATH / f"{image.local_id}.jpg" @@ -146,15 +146,20 @@ async def update_setu_img(flag: bool = False): logger.warning(f"文件 {image.local_id}.jpg 不存在,跳过...") continue img_hash = str(get_img_hash(f"{path}/{image.local_id}.jpg")) - await Setu.update_setu_data(image.pid, img_hash=img_hash) + image.img_hash = img_hash + await image.save(update_fields=["img_hash"]) + # await Setu.update_setu_data(image.pid, img_hash=img_hash) except UnidentifiedImageError: # 图片已删除 - with open(local_image, 'r') as f: - if '404 Not Found' in f.read(): - max_num = await Setu.delete_image(image.pid) - local_image.unlink() - os.rename(path / f"{max_num}.jpg", local_image) - logger.warning(f"更新色图 PID:{image.pid} 404,已删除并替换") + unlink = False + with open(local_image, "r") as f: + if "404 Not Found" in f.read(): + unlink = True + if unlink: + local_image.unlink() + max_num = await Setu.delete_image(image.pid) + os.rename(path / f"{max_num}.jpg", local_image) + logger.warning(f"更新色图 PID:{image.pid} 404,已删除并替换") except Exception as e: _success -= 1 logger.error(f"更新色图 {image.local_id}.jpg 错误 {type(e)}: {e}") diff --git a/plugins/sign_in/goods_register.py b/plugins/sign_in/goods_register.py index 545b5e0d..748400c3 100644 --- a/plugins/sign_in/goods_register.py +++ b/plugins/sign_in/goods_register.py @@ -1,9 +1,9 @@ - -from models.sign_group_user import SignGroupUser -from configs.config import Config -from nonebot import Driver -from utils.decorator.shop import shop_register, NotMeetUseConditionsException import nonebot +from nonebot import Driver + +from configs.config import Config +from models.sign_group_user import SignGroupUser +from utils.decorator.shop import NotMeetUseConditionsException, shop_register driver: Driver = nonebot.get_driver() @@ -22,13 +22,18 @@ async def _(): "下次签到双倍好感度概率 + 20%(平平庸庸)(同类商品将覆盖)", "下次签到双倍好感度概率 + 30%(金币才是真命天子!)(同类商品将覆盖)", ), - load_status=Config.get_config("shop", "IMPORT_DEFAULT_SHOP_GOODS"), - icon=("favorability_card_1.png", "favorability_card_2.png", "favorability_card_3.png"), - ** {"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3}, + load_status=bool(Config.get_config("shop", "IMPORT_DEFAULT_SHOP_GOODS")), + icon=( + "favorability_card_1.png", + "favorability_card_2.png", + "favorability_card_3.png", + ), + **{"好感度双倍加持卡Ⅰ_prob": 0.1, "好感度双倍加持卡Ⅱ_prob": 0.2, "好感度双倍加持卡Ⅲ_prob": 0.3}, ) async def sign_card(user_id: int, group_id: int, prob: float): - user = await SignGroupUser.ensure(user_id, group_id) - await user.update(add_probability=prob).apply() + user, _ = await SignGroupUser.get_or_create(user_qq=user_id, group_id=group_id) + user.add_probability = prob + await user.save(update_fields=["add_probability"]) @shop_register( name="测试道具A", @@ -38,20 +43,17 @@ async def _(): icon="sword.png", ) async def _(user_id: int, group_id: int): - print(user_id, group_id, '使用测试道具') + print(user_id, group_id, "使用测试道具") @shop_register.before_handle(name="测试道具A", load_status=False) async def _(user_id: int, group_id: int): - print(user_id, group_id, '第一个使用前函数(before handle)') + print(user_id, group_id, "第一个使用前函数(before handle)") @shop_register.before_handle(name="测试道具A", load_status=False) async def _(user_id: int, group_id: int): - print(user_id, group_id, '第二个使用前函数(before handle)222') - raise NotMeetUseConditionsException("太笨了!") # 抛出异常,阻断使用,并返回信息 + print(user_id, group_id, "第二个使用前函数(before handle)222") + raise NotMeetUseConditionsException("太笨了!") # 抛出异常,阻断使用,并返回信息 @shop_register.after_handle(name="测试道具A", load_status=False) async def _(user_id: int, group_id: int): - print(user_id, group_id, '第一个使用后函数(after handle)') - - - + print(user_id, group_id, "第一个使用后函数(after handle)") diff --git a/plugins/sign_in/group_user_checkin.py b/plugins/sign_in/group_user_checkin.py index 87174d34..596ff1ec 100755 --- a/plugins/sign_in/group_user_checkin.py +++ b/plugins/sign_in/group_user_checkin.py @@ -1,23 +1,25 @@ -from datetime import datetime, timedelta -from models.sign_group_user import SignGroupUser -from models.group_member_info import GroupInfoUser -from models.bag_user import BagUser -from configs.config import NICKNAME -from nonebot.adapters.onebot.v11 import MessageSegment -from utils.image_utils import BuildImage, BuildMat -from services.db_context import db -from .utils import get_card, SIGN_TODAY_CARD_PATH -from typing import Optional -from services.log import logger -from .random_event import random_event -from utils.data_utils import init_rank -from utils.utils import get_user_avatar -from io import BytesIO -import random -import math import asyncio -import secrets +import math import os +import random +import secrets +from datetime import datetime, timedelta +from io import BytesIO +from typing import Optional + +from nonebot.adapters.onebot.v11 import MessageSegment + +from configs.config import NICKNAME +from models.bag_user import BagUser +from models.group_member_info import GroupInfoUser +from models.sign_group_user import SignGroupUser +from services.log import logger +from utils.data_utils import init_rank +from utils.image_utils import BuildImage, BuildMat +from utils.utils import get_user_avatar + +from .random_event import random_event +from .utils import SIGN_TODAY_CARD_PATH, get_card async def group_user_check_in( @@ -25,18 +27,17 @@ async def group_user_check_in( ) -> MessageSegment: "Returns string describing the result of checking in" present = datetime.now() - async with db.transaction(): - # 取得相应用户 - user = await SignGroupUser.ensure(user_qq, group, for_update=True) - # 如果同一天签到过,特殊处理 - if ( - user.checkin_time_last + timedelta(hours=8) - ).date() >= present.date() or f"{user}_{group}_sign_{datetime.now().date()}" in os.listdir( - SIGN_TODAY_CARD_PATH - ): - gold = await BagUser.get_gold(user_qq, group) - return await get_card(user, nickname, -1, gold, "") - return await _handle_check_in(nickname, user_qq, group, present) # ok + # 取得相应用户 + user, is_create = await SignGroupUser.get_or_create(user_qq=user_qq, group_id=group) + # 如果同一天签到过,特殊处理 + if not is_create and ( + user.checkin_time_last.date() >= present.date() + or f"{user}_{group}_sign_{datetime.now().date()}" + in os.listdir(SIGN_TODAY_CARD_PATH) + ): + gold = await BagUser.get_gold(user_qq, group) + return await get_card(user, nickname, -1, gold, "") + return await _handle_check_in(nickname, user_qq, group, present) # ok async def check_in_all(nickname: str, user_qq: int): @@ -47,40 +48,39 @@ async def check_in_all(nickname: str, user_qq: int): :param nickname: 昵称 :param user_qq: 用户qq """ - async with db.transaction(): - present = datetime.now() - for u in await SignGroupUser.get_user_all_data(user_qq): - group = u.group_id - if not (( - u.checkin_time_last + timedelta(hours=8) - ).date() >= present.date() or f"{u}_{group}_sign_{datetime.now().date()}" in os.listdir( - SIGN_TODAY_CARD_PATH - )): - await _handle_check_in(nickname, user_qq, group, present) + present = datetime.now() + for u in await SignGroupUser.filter(user_qq=user_qq).all(): + group = u.group_id + if not ( + u.checkin_time_last.date() >= present.date() + or f"{u}_{group}_sign_{datetime.now().date()}" + in os.listdir(SIGN_TODAY_CARD_PATH) + ): + await _handle_check_in(nickname, user_qq, group, present) async def _handle_check_in( nickname: str, user_qq: int, group: int, present: datetime ) -> MessageSegment: - user = await SignGroupUser.ensure(user_qq, group, for_update=True) - impression_added = (secrets.randbelow(99)+1)/100 + user, _ = await SignGroupUser.get_or_create(user_qq=user_qq, group_id=group) + impression_added = (secrets.randbelow(99) + 1) / 100 critx2 = random.random() - add_probability = user.add_probability + add_probability = float(user.add_probability) specify_probability = user.specify_probability if critx2 + add_probability > 0.97: impression_added *= 2 elif critx2 < specify_probability: impression_added *= 2 - await SignGroupUser.sign(user, impression_added, present) + await SignGroupUser.sign(user, impression_added) gold = random.randint(1, 100) - gift, gift_type = random_event(user.impression) + gift, gift_type = random_event(float(user.impression)) if gift_type == "gold": await BagUser.add_gold(user_qq, group, gold + gift) gift = f"额外金币 + {gift}" else: await BagUser.add_gold(user_qq, group, gold) await BagUser.add_property(user_qq, group, gift) - gift += ' + 1' + gift += " + 1" logger.info( f"(USER {user.user_qq}, GROUP {user.group_id})" @@ -95,7 +95,7 @@ async def _handle_check_in( async def group_user_check(nickname: str, user_qq: int, group: int) -> MessageSegment: # heuristic: if users find they have never checked in they are probable to check in - user = await SignGroupUser.ensure(user_qq, group) + user, _ = await SignGroupUser.get_or_create(user_qq=user_qq, group_id=group) gold = await BagUser.get_gold(user_qq, group) return await get_card(user, nickname, None, gold, "", is_card_view=True) @@ -171,18 +171,14 @@ async def _pst(users: list, impressions: list, groups: list): impressions.pop(index) users.pop(index) groups.pop(index) - try: - user_name = ( - await GroupInfoUser.get_member_info(user, group) - ).user_name - except AttributeError: + if user_ := await GroupInfoUser.get_or_none(user_qq=user, group_id=group): + user_name = user_.user_name + else: user_name = f"我名字呢?" user_name = user_name if len(user_name) < 11 else user_name[:10] + "..." ava = await get_user_avatar(user) if ava: - ava = BuildImage( - 50, 50, background=BytesIO(ava) - ) + ava = BuildImage(50, 50, background=BytesIO(ava)) else: ava = BuildImage(50, 50, color="white") ava.circle() diff --git a/plugins/sign_in/random_event.py b/plugins/sign_in/random_event.py index 22d799c0..b451d848 100755 --- a/plugins/sign_in/random_event.py +++ b/plugins/sign_in/random_event.py @@ -1,11 +1,12 @@ -from configs.config import Config import random +from typing import Tuple, Union +from configs.config import Config PROB_DATA = None -def random_event(impression: float) -> 'Union[str, int], str': +def random_event(impression: float) -> Tuple[Union[str, int], str]: """ 签到随机事件 :param impression: 好感度 @@ -14,20 +15,17 @@ def random_event(impression: float) -> 'Union[str, int], str': global PROB_DATA if not PROB_DATA: PROB_DATA = { - Config.get_config("sign_in", "SIGN_CARD3_PROB"): '好感度双倍加持卡Ⅲ', - Config.get_config("sign_in", "SIGN_CARD2_PROB"): '好感度双倍加持卡Ⅱ', - Config.get_config("sign_in", "SIGN_CARD1_PROB"): '好感度双倍加持卡Ⅰ' + Config.get_config("sign_in", "SIGN_CARD3_PROB"): "好感度双倍加持卡Ⅲ", + Config.get_config("sign_in", "SIGN_CARD2_PROB"): "好感度双倍加持卡Ⅱ", + Config.get_config("sign_in", "SIGN_CARD1_PROB"): "好感度双倍加持卡Ⅰ", } rand = random.random() - impression / 1000 for prob in PROB_DATA.keys(): if rand <= prob: - return PROB_DATA[prob], 'props' - gold = random.randint(1, random.randint(1, int(1 if impression < 1 else impression))) + return PROB_DATA[prob], "props" + gold = random.randint( + 1, random.randint(1, int(1 if impression < 1 else impression)) + ) max_sign_gold = Config.get_config("sign_in", "MAX_SIGN_GOLD") gold = max_sign_gold if gold > max_sign_gold else gold - return gold, 'gold' - - - - - + return gold, "gold" diff --git a/plugins/sign_in/utils.py b/plugins/sign_in/utils.py index f1a62bc6..741a9392 100755 --- a/plugins/sign_in/utils.py +++ b/plugins/sign_in/utils.py @@ -1,31 +1,33 @@ -from .config import ( - SIGN_RESOURCE_PATH, - SIGN_TODAY_CARD_PATH, - SIGN_BORDER_PATH, - SIGN_BACKGROUND_PATH, - lik2level, - lik2relation, - level2attitude, - weekdays, -) -from models.sign_group_user import SignGroupUser -from models.group_member_info import GroupInfoUser +import asyncio +import os +import random +from datetime import datetime +from io import BytesIO +from pathlib import Path +from typing import List, Optional + +import nonebot +from nonebot import Driver from nonebot.adapters.onebot.v11 import MessageSegment -from configs.config import Config -from utils.utils import get_user_avatar + +from configs.config import NICKNAME, Config +from configs.path_config import IMAGE_PATH +from models.group_member_info import GroupInfoUser +from models.sign_group_user import SignGroupUser from utils.image_utils import BuildImage from utils.message_builder import image -from configs.config import NICKNAME -from pathlib import Path -from datetime import datetime -from typing import Optional, List -from nonebot import Driver -from io import BytesIO -import asyncio -import random -import nonebot -import os +from utils.utils import get_user_avatar +from .config import ( + SIGN_BACKGROUND_PATH, + SIGN_BORDER_PATH, + SIGN_RESOURCE_PATH, + SIGN_TODAY_CARD_PATH, + level2attitude, + lik2level, + lik2relation, + weekdays, +) driver: Driver = nonebot.get_driver() @@ -34,10 +36,13 @@ driver: Driver = nonebot.get_driver() async def init_image(): SIGN_RESOURCE_PATH.mkdir(parents=True, exist_ok=True) SIGN_TODAY_CARD_PATH.mkdir(exist_ok=True, parents=True) - await GroupInfoUser.add_member_info(114514, 114514, "", datetime.min, 0) - _u = await GroupInfoUser.get_member_info(114514, 114514) - if _u.uid is None: - await _u.update(uid=0).apply() + if not await GroupInfoUser.get_or_none(user_qq=114514): + await GroupInfoUser.create( + user_qq=114514, + group_id=114514, + user_name="", + uid=0, + ) generate_progress_bar_pic() clear_sign_data_pic() @@ -58,7 +63,12 @@ async def get_card( Path(SIGN_TODAY_CARD_PATH) / f"{user_id}_{user.group_id}_{_type}_{date}.png" ) if card_file.exists(): - return image(f"{user_id}_{user.group_id}_{_type}_{date}.png", "sign/today_card") + return image( + IMAGE_PATH + / "sign" + / "today_card" + / f"{user_id}_{user.group_id}_{_type}_{date}.png" + ) else: if add_impression == -1: card_file = ( @@ -67,8 +77,10 @@ async def get_card( ) if card_file.exists(): return image( - f"{user_id}_{user.group_id}_view_{date}.png", - "sign/today_card", + IMAGE_PATH + / "sign" + / "today_card" + / f"{user_id}_{user.group_id}_view_{date}.png" ) is_card_view = True ava = BytesIO(await get_user_avatar(user_id)) @@ -244,7 +256,7 @@ def _generate_card( ) today_data.text( (0, 50), - f"色图概率:{(default_setu_prob + user.impression if user.impression < 100 else 100):.2f}%", + f"色图概率:{(default_setu_prob + float(user.impression) if user.impression < 100 else 100):.2f}%", ) today_data.text((0, 75), f"开箱次数:{(20 + int(user.impression / 3))}") _type = "view" @@ -285,7 +297,12 @@ def _generate_card( bk.paste(today_data, (580, 220), True) bk.paste(watermark, (15, 400), True) bk.save(SIGN_TODAY_CARD_PATH / f"{user_id}_{user.group_id}_{_type}_{data}.png") - return image(f"{user_id}_{user.group_id}_{_type}_{data}.png", "sign/today_card") + return image( + IMAGE_PATH + / "sign" + / "today_card" + / f"{user_id}_{user.group_id}_{_type}_{data}.png" + ) def generate_progress_bar_pic(): diff --git a/plugins/statistics/_model.py b/plugins/statistics/_model.py index cc81fe1b..2de65d60 100644 --- a/plugins/statistics/_model.py +++ b/plugins/statistics/_model.py @@ -1,30 +1,23 @@ -from datetime import datetime -from typing import Optional - -from services.db_context import db -class Statistics(db.Model): - __tablename__ = "statistics" - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.BigInteger()) - plugin_name = db.Column(db.String(), nullable=False) - create_time = db.Column(db.DateTime(timezone=True), nullable=False) +from tortoise import fields - @classmethod - async def add_statistic(cls, user_qq: int, group_id: Optional[int], plugin_name: str): - """ - 说明: - 添加记录 - 参数: - :param user_qq: qq - :param group_id: 群号 - :param plugin_name: 插件model - """ - await cls.create( - user_qq=user_qq, - group_id=group_id, - plugin_name=plugin_name, - create_time=datetime.now(), - ) +from services.db_context import Model + + +class Statistics(Model): + + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField(null=True) + """群聊id""" + plugin_name = fields.CharField(255) + """插件名称""" + create_time = fields.DatetimeField(auto_now=True) + """添加日期""" + + class Meta: + table = "statistics" + table_description = "用户权限数据库" diff --git a/plugins/statistics/statistics_handle.py b/plugins/statistics/statistics_handle.py index f0b172fc..df151536 100755 --- a/plugins/statistics/statistics_handle.py +++ b/plugins/statistics/statistics_handle.py @@ -1,14 +1,16 @@ -from nonebot import on_command -from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent, Message -from models.group_info import GroupInfo -from configs.path_config import DATA_PATH, IMAGE_PATH -from nonebot.params import CommandArg, Command -from utils.image_utils import BuildMat -from utils.message_builder import image -from utils.manager import plugins2settings_manager -from typing import Tuple import asyncio import os +from typing import Tuple + +from nonebot import on_command +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent +from nonebot.params import Command, CommandArg + +from configs.path_config import DATA_PATH, IMAGE_PATH +from models.group_info import GroupInfo +from utils.image_utils import BuildMat +from utils.manager import plugins2settings_manager +from utils.message_builder import image try: import ujson as json @@ -160,8 +162,8 @@ async def _(bot: Bot, event: MessageEvent, cmd: Tuple[str, ...] = Command(), arg day_index = data["day_index"] data = data[arg][key] if _type == "group": - name = await GroupInfo.get_group_info(event.group_id) - name = name.group_name if name else str(event.group_id) + group = await GroupInfo.filter(group_id=event.group_id).first() + name = name.group_name if group else str(event.group_id) else: name = event.sender.card or event.sender.nickname img = await generate_statistics_img(data, arg, name, plugin, day_index) diff --git a/plugins/statistics/statistics_hook.py b/plugins/statistics/statistics_hook.py index a0ca29ab..31250b2f 100755 --- a/plugins/statistics/statistics_hook.py +++ b/plugins/statistics/statistics_hook.py @@ -1,12 +1,14 @@ -from configs.path_config import DATA_PATH +from datetime import datetime + +from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent from nonebot.matcher import Matcher from nonebot.message import run_postprocessor -from nonebot.typing import T_State -from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, MessageEvent -from datetime import datetime +from nonebot.typing import Optional, T_State + +from configs.path_config import DATA_PATH from utils.manager import plugins2settings_manager from utils.utils import scheduler -from nonebot.typing import Optional + from ._model import Statistics try: @@ -99,10 +101,11 @@ async def _( and matcher.priority not in [1, 999] and matcher.plugin_name not in ["update_info", "statistics_handle"] ): - await Statistics.add_statistic( - event.user_id, - event.group_id if isinstance(event, GroupMessageEvent) else None, - matcher.plugin_name, + await Statistics.create( + user_qq=event.user_id, + group_id=getattr(event, "group_id", None), + plugin_name=matcher.plugin_name, + create_time=datetime.now(), ) module = matcher.plugin_name day_index = _prefix_count_dict["day_index"] diff --git a/plugins/update_picture.py b/plugins/update_picture.py index f758e546..bdf7f552 100755 --- a/plugins/update_picture.py +++ b/plugins/update_picture.py @@ -1,20 +1,21 @@ -from nonebot import on_command -from PIL import Image, ImageFilter -from utils.message_builder import image -from configs.path_config import TEMP_PATH, IMAGE_PATH -from services.log import logger -from nonebot.rule import to_me -from nonebot.adapters.onebot.v11 import MessageEvent, GroupMessageEvent, Message -from nonebot.typing import T_State -from utils.utils import get_message_img, is_number -from nonebot.params import CommandArg, Arg, ArgStr, Depends -from utils.image_utils import BuildImage, pic2b64 -from configs.config import NICKNAME -from utils.http_utils import AsyncHttpx from typing import Union + import cv2 import numpy as np +from nonebot import on_command +from nonebot.adapters.onebot.v11 import GroupMessageEvent, Message, MessageEvent +from nonebot.params import Arg, ArgStr, CommandArg, Depends +from nonebot.rule import to_me +from nonebot.typing import T_State +from PIL import Image, ImageFilter +from configs.config import NICKNAME +from configs.path_config import IMAGE_PATH, TEMP_PATH +from services.log import logger +from utils.http_utils import AsyncHttpx +from utils.image_utils import BuildImage, pic2b64 +from utils.message_builder import image +from utils.utils import get_message_img, is_number __zx_plugin_name__ = "各种图片简易操作" __plugin_usage__ = """ @@ -88,9 +89,7 @@ update_img_help.save(IMAGE_PATH / "update_img_help.png") def parse_key(key: str): - async def _key_parser( - state: T_State, inp: Union[Message, str] = Arg(key) - ): + async def _key_parser(state: T_State, inp: Union[Message, str] = Arg(key)): if key != "img_list" and isinstance(inp, Message): inp = inp.extract_plain_text().strip() if inp in ["取消", "算了"]: @@ -132,6 +131,7 @@ def parse_key(key: str): if not get_message_img(inp): await update_img.reject_arg("img_list", "没图?没图?没图?来图速来!") state[key] = inp + return _key_parser @@ -167,10 +167,18 @@ async def _(event: MessageEvent, state: T_State, arg: Message = CommandArg()): state["img_list"] = event.message -@update_img.got("method", prompt=f"要使用图片的什么操作呢?{method_str}", parameterless=[Depends(parse_key("method"))]) -@update_img.got("x", prompt="[宽度? 比率? 旋转角度? 底色?]", parameterless=[Depends(parse_key("x"))]) +@update_img.got( + "method", + prompt=f"要使用图片的什么操作呢?{method_str}", + parameterless=[Depends(parse_key("method"))], +) +@update_img.got( + "x", prompt="[宽度? 比率? 旋转角度? 底色?]", parameterless=[Depends(parse_key("x"))] +) @update_img.got("y", prompt="[长度? 0 0 底色?]", parameterless=[Depends(parse_key("y"))]) -@update_img.got("img_list", prompt="图呢图呢图呢图呢?GKD!", parameterless=[Depends(parse_key("img_list"))]) +@update_img.got( + "img_list", prompt="图呢图呢图呢图呢?GKD!", parameterless=[Depends(parse_key("img_list"))] +) async def _( event: MessageEvent, state: T_State, @@ -285,7 +293,7 @@ async def _( img[i, j] = color cv2.imwrite(TEMP_PATH / f"{event.user_id}_{k}_ok_update.png", img) for i in range(index): - result += image(f"{event.user_id}_{i}_ok_update.png", "temp") + result += image(TEMP_PATH / f"{event.user_id}_{i}_ok_update.png") if is_number(method): method = method_list[int(method) - 1] logger.info( diff --git a/plugins/web_ui/api/request.py b/plugins/web_ui/api/request.py index 54d93baf..02615304 100644 --- a/plugins/web_ui/api/request.py +++ b/plugins/web_ui/api/request.py @@ -1,7 +1,8 @@ -from utils.manager import requests_manager -from ..auth import token_to_user, Depends, User -from utils.utils import get_bot from models.group_info import GroupInfo +from utils.manager import requests_manager +from utils.utils import get_bot + +from ..auth import Depends, User, token_to_user from ..config import * @@ -39,18 +40,21 @@ async def _(parma: RequestParma, user: User = Depends(token_to_user)) -> Result: if bot := get_bot(): if parma.handle == "approve": if parma.type == "group": - rid = requests_manager.get_group_id(parma.id) - if await GroupInfo.get_group_info(rid): - await GroupInfo.set_group_flag(rid, 1) - else: - group_info = await bot.get_group_info(group_id=rid) - await GroupInfo.add_group_info( - rid, - group_info["group_name"], - group_info["max_member_count"], - group_info["member_count"], - 1, - ) + if rid := requests_manager.get_group_id(parma.id): + # await GroupInfo.update_or_create(defaults={"group_flag": 1}, ) + if group := await GroupInfo.filter(group_id=rid).first(): + await group.update_or_create(group_flag=1) + else: + group_info = await bot.get_group_info(group_id=rid) + await GroupInfo.update_or_create( + group_id=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, + }, + ) flag = await requests_manager.approve(bot, parma.id, parma.type) elif parma.handle == "refuse": flag = await requests_manager.refused(bot, parma.id, parma.type) diff --git a/plugins/word_bank/_model.py b/plugins/word_bank/_model.py index 12475f45..508ae67c 100644 --- a/plugins/word_bank/_model.py +++ b/plugins/word_bank/_model.py @@ -1,47 +1,63 @@ +import random +import re import time -from nonebot.internal.adapter.template import MessageTemplate +from datetime import datetime +from typing import Any, List, Optional, Tuple, Union + from nonebot.adapters.onebot.v11 import ( + GroupMessageEvent, Message, MessageEvent, - GroupMessageEvent, MessageSegment, ) -from services.db_context import db -from typing import Optional, List, Union, Tuple, Any -from datetime import datetime -from configs.path_config import DATA_PATH -import random -from ._config import int2type -from utils.image_utils import get_img_hash -from utils.http_utils import AsyncHttpx -import re +from nonebot.internal.adapter.template import MessageTemplate +from tortoise import Tortoise, fields +from tortoise.expressions import Q, RawSQL -from utils.message_builder import image, face, at +from configs.path_config import DATA_PATH +from services.db_context import Model +from utils.http_utils import AsyncHttpx +from utils.image_utils import get_img_hash +from utils.message_builder import at, face, image from utils.utils import get_message_img +from ._config import int2type + path = DATA_PATH / "word_bank" -class WordBank(db.Model): - __tablename__ = "word_bank2" +class WordBank(Model): - id = db.Column(db.Integer(), primary_key=True) - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.Integer()) - word_scope = db.Column( - db.Integer(), nullable=False, default=0 - ) # 生效范围 0: 全局 1: 群聊 2: 私聊 - word_type = db.Column( - db.Integer(), nullable=False, default=0 - ) # 词条类型 0: 完全匹配 1: 模糊 2: 正则 3: 图片 - status = db.Column(db.Boolean(), nullable=False, default=True) # 词条状态 - problem = db.Column(db.String(), nullable=False) # 问题,为图片时使用图片hash - answer = db.Column(db.String(), nullable=False) # 回答 - placeholder = db.Column(db.String()) # 占位符 - image_path = db.Column(db.String()) # 使用图片作为问题时图片存储的路径 - to_me = db.Column(db.String()) # 使用图片作为问题时图片存储的路径 - create_time = db.Column(db.DateTime(), nullable=False) - update_time = db.Column(db.DateTime(), nullable=False) + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + user_qq = fields.BigIntField() + """用户id""" + group_id = fields.BigIntField(null=True) + """群聊id""" + word_scope = fields.IntField(default=0) + """生效范围 0: 全局 1: 群聊 2: 私聊""" + word_type = fields.IntField(default=0) + """词条类型 0: 完全匹配 1: 模糊 2: 正则 3: 图片""" + status = fields.BooleanField() + """词条状态""" + problem = fields.TextField() + """问题,为图片时使用图片hash""" + answer = fields.TextField() + """回答""" + placeholder = fields.TextField(null=True) + """占位符""" + image_path = fields.TextField(null=True) + """使用图片作为问题时图片存储的路径""" + to_me = fields.CharField(255, null=True) + """昵称开头时存储的昵称""" + create_time = fields.DatetimeField(auto_now=True) + """创建时间""" + update_time = fields.DatetimeField(auto_now_add=True) + """更新时间""" + + class Meta: + table = "word_bank2" + table_description = "词条数据库" @classmethod async def exists( @@ -64,18 +80,18 @@ class WordBank(db.Model): :param word_scope: 词条范围 :param word_type: 词条类型 """ - query = cls.query.where(cls.problem == problem) + query = cls.filter(problem=problem) if user_id: - query = query.where(cls.user_qq == user_id) + query = query.filter(user_qq=user_id) if group_id: - query = query.where(cls.group_id == group_id) + query = query.filter(group_id=group_id) if answer: - query = query.where(cls.answer == answer) - if word_type: - query = query.where(cls.word_type == word_type) - if word_scope: - query = query.where(cls.word_scope == word_scope) - return bool(await query.gino.first()) + query = query.filter(answer=answer) + if word_type is not None: + query = query.filter(word_type=word_type) + if word_scope is not None: + query = query.filter(word_scope=word_scope) + return bool(await query.first()) @classmethod async def add_problem_answer( @@ -86,7 +102,7 @@ class WordBank(db.Model): word_type: int, problem: Union[str, Message], answer: Union[str, Message], - to_me_nickname: str = None + to_me_nickname: Optional[str] = None, ): """ 说明: @@ -112,7 +128,9 @@ class WordBank(db.Model): problem = str(get_img_hash(_file)) image_path = f"problem/{group_id}/{user_id}_{int(time.time())}.jpg" answer, _list = await cls._answer2format(answer, user_id, group_id) - if not await cls.exists(user_id, group_id, problem, answer, word_scope, word_type): + if not await cls.exists( + user_id, group_id, problem, answer, word_scope, word_type + ): await cls.create( user_qq=user_id, group_id=group_id, @@ -125,7 +143,7 @@ class WordBank(db.Model): placeholder=",".join(_list), create_time=datetime.now().replace(microsecond=0), update_time=datetime.now().replace(microsecond=0), - to_me=to_me_nickname + to_me=to_me_nickname, ) @classmethod @@ -152,7 +170,7 @@ class WordBank(db.Model): text += seg.data["text"] elif seg.type == "face": text += f"[face:placeholder_{index}]" - _list.append(seg.data['id']) + _list.append(seg.data["id"]) elif seg.type == "at": text += f"[at:placeholder_{index}]" _list.append(seg.data["qq"]) @@ -187,12 +205,12 @@ class WordBank(db.Model): if query: answer = query.answer else: - query = await cls.query.where( - (cls.problem == problem) - & (cls.user_qq == user_id) - & (cls.group_id == group_id) - & (cls.answer == answer) - ).gino.first() + query = await cls.get_or_none( + problem=problem, + user_qq=user_id, + group_id=group_id, + answer=answer, + ) if query and query.placeholder: type_list = re.findall(rf"\[(.*?):placeholder_.*?]", answer) temp_answer = re.sub(rf"\[(.*?):placeholder_.*?]", "{}", answer) @@ -208,7 +226,7 @@ class WordBank(db.Model): return answer @classmethod - async def check( + async def check_problem( cls, event: MessageEvent, problem: str, @@ -224,60 +242,35 @@ class WordBank(db.Model): :param word_scope: 词条范围 :param word_type: 词条类型 """ - query = cls.query - sql_text = "SELECT * FROM public.word_bank2 where 1 = 1" - # 救命!!没找到gino的正则表达式方法,暂时使用sql语句 + query = cls if isinstance(event, GroupMessageEvent): if word_scope: - query = query.where(cls.word_scope == word_scope) - sql_text += f" and word_scope = {word_scope}" + query = query.filter(word_scope=word_scope) else: - query = query.where( - (cls.group_id == event.group_id) | (cls.word_scope == 0) - ) - sql_text += f" and (group_id = {event.group_id} or word_scope = 0)" + query = query.filter(Q(group_id=event.group_id) | Q(word_scope=0)) else: - query = query.where((cls.word_scope == 2) | (cls.word_scope == 0)) - sql_text += f" and (word_scope = 2 or word_scope = 0)" + query = query.filter(Q(cword_scope=2) | Q(word_scope=0)) if word_type: - query = query.where(cls.word_scope == word_type) - sql_text += f" and word_scope = {word_scope}" + query = query.filter(word_scope=word_type) # 完全匹配 - if await query.where( - ((cls.word_type == 0) | (cls.word_type == 3)) & (cls.problem == problem) - ).gino.first(): - return query.where( - ((cls.word_type == 0) | (cls.word_type == 3)) & (cls.problem == problem) - ) + if data_list := await query.filter( + Q(Q(word_type=0) | Q(word_type=3)), Q(problem=problem) + ).all(): + return data_list + db = Tortoise.get_connection("default") # 模糊匹配 - if await db.first( - db.text( - sql_text - + f" and word_type = 1 and :problem like '%' || problem || '%';" - ), - problem=problem, - ): - return ( - sql_text - + f" and word_type = 1 and :problem like '%' || problem || '%';" - ) - # 正则匹配 - if await db.first( - db.text( - sql_text - + f" and word_type = 2 and word_scope != 999 and :problem ~ problem;" - ), - problem=problem, - ): - return ( - sql_text - + f" and word_type = 2 and word_scope != 999 and :problem ~ problem;" - ) - # if await db.first( - # db.text(sql_text + f" and word_type = 1 and word_scope != 999 and '{problem}' ~ problem;") - # ): - # return sql_text + f" and word_type = 1 and word_scope != 999 and '{problem}' ~ problem;" - # return None + sql = query.filter(word_type=1).sql() + " and POSITION(problem in $1) > 0" + data_list = await db.execute_query_dict(sql, [problem]) + if data_list: + return [cls(**data) for data in data_list] + # 正则 + sql = ( + query.filter(word_type=2, word_scope__not=999).sql() + " and $1 ~ problem;" + ) + data_list = await db.execute_query_dict(sql, [problem]) + if data_list: + return [cls(**data) for data in data_list] + return None @classmethod async def get_answer( @@ -296,26 +289,16 @@ class WordBank(db.Model): :param word_scope: 词条范围 :param word_type: 词条类型 """ - query = await cls.check(event, problem, word_scope, word_type) - if query is not None: - if isinstance(query, str): - answer_list = await db.all(db.text(query), problem=problem) - answer = random.choice(answer_list) - return ( - await cls._format2answer(answer[6], answer[7], answer[1], answer[2]) - if answer.placeholder - else answer.answer - ) - else: - answer_list = await query.gino.all() - answer = random.choice(answer_list) - return ( - await cls._format2answer( - problem, answer.answer, answer.user_qq, answer.group_id - ) - if answer.placeholder - else answer.answer + data_list = await cls.check_problem(event, problem, word_scope, word_type) + if data_list: + answer = random.choice(data_list) + return ( + await cls._format2answer( + problem, answer.answer, answer.user_qq, answer.group_id ) + if answer.placeholder + else answer.answer + ) @classmethod async def get_problem_all_answer( @@ -336,22 +319,14 @@ class WordBank(db.Model): """ if index is not None: if group_id: - problem = (await cls.query.where(cls.group_id == group_id).gino.all())[ - index - ] + problem_ = (await cls.filter(group_id=group_id).all())[index] else: - problem = ( - await cls.query.where( - cls.word_scope == (word_scope or 0) - ).gino.all() - )[index] - problem = problem.problem - answer = cls.query.where(cls.problem == problem) + problem_ = (await cls.filter(word_scope=(word_scope or 0)).all())[index] + problem = problem_.problem + answer = cls.filter(problem=problem) if group_id: - answer = answer.where(cls.group_id == group_id) - return [ - await cls._format2answer("", "", 0, 0, x) for x in (await answer.gino.all()) - ] + answer = answer.filter(group_id=group_id) + return [await cls._format2answer("", "", 0, 0, x) for x in (await answer.all())] @classmethod async def delete_group_problem( @@ -373,23 +348,17 @@ class WordBank(db.Model): if await cls.exists(None, group_id, problem, None, word_scope): if index is not None: if group_id: - query = await cls.query.where( - (cls.group_id == group_id) & (cls.problem == problem) - ).gino.all() + query = await cls.filter(group_id=group_id, problem=problem).all() else: - query = await cls.query.where( - (cls.word_scope == 0) & (cls.problem == problem) - ).gino.all() + query = await cls.filter(word_scope=0, problem=problem).all() await query[index].delete() else: if group_id: - await WordBank.delete.where( - (cls.group_id == group_id) & (cls.problem == problem) - ).gino.status() + await WordBank.filter(group_id=group_id, problem=problem).delete() else: - await WordBank.delete.where( - (cls.word_scope == word_scope) & (cls.problem == problem) - ).gino.status() + await WordBank.filter( + word_scope=word_scope, problem=problem + ).delete() return True return False @@ -414,23 +383,20 @@ class WordBank(db.Model): """ if index is not None: if group_id: - query = await cls.query.where( - (cls.group_id == group_id) & (cls.problem == problem) - ).gino.all() + query = await cls.filter(group_id=group_id, problem=problem).all() else: - query = await cls.query.where( - (cls.word_scope == word_scope) & (cls.problem == problem) - ).gino.all() - await query[index].update(problem=replace_str).apply() + query = await cls.filter(word_scope=word_scope, problem=problem).all() + query[index].problem = replace_str + await query[index].save(update_fields=["problem"]) else: if group_id: - await WordBank.update.values(problem=replace_str).where( - (cls.group_id == group_id) & (cls.problem == problem) - ).gino.status() + await cls.filter(group_id=group_id, problem=problem).update( + problem=replace_str + ) else: - await WordBank.update.values(problem=replace_str).where( - (cls.word_scope == word_scope) & (cls.problem == problem) - ).gino.status() + await cls.filter(word_scope=word_scope, problem=problem).update( + problem=replace_str + ) @classmethod async def get_group_all_problem( @@ -443,7 +409,7 @@ class WordBank(db.Model): :param group_id: 群号 """ return cls._handle_problem( - await cls.query.where(cls.group_id == group_id).gino.all() + await cls.filter(group_id=group_id).all() # type: ignore ) @classmethod @@ -455,7 +421,7 @@ class WordBank(db.Model): :param word_scope: 词条范围 """ return cls._handle_problem( - await cls.query.where(cls.word_scope == word_scope).gino.all() + await cls.filter(word_scope=word_scope).all() # type: ignore ) @classmethod @@ -467,13 +433,13 @@ class WordBank(db.Model): :param word_type: 词条类型 """ return cls._handle_problem( - await cls.query.where(cls.word_type == word_type).gino.all() + await cls.filter(word_type=word_type).all() # type: ignore ) @classmethod - def _handle_problem(cls, msg_list: List[Union[str, MessageSegment]]): + def _handle_problem(cls, msg_list: List["WordBank"]): """ - 说明: + 说明: 格式化处理问题 参数: :param msg_list: 消息列表 @@ -514,7 +480,9 @@ class WordBank(db.Model): word_scope = 0 word_type = 0 # 对图片做额外处理 - if not await cls.exists(user_id, group_id, problem, answer, word_scope, word_type): + if not await cls.exists( + user_id, group_id, problem, answer, word_scope, word_type + ): await cls.create( user_qq=user_id, group_id=group_id, diff --git a/plugins/word_bank/_old_model.py b/plugins/word_bank/_old_model.py deleted file mode 100644 index 84678def..00000000 --- a/plugins/word_bank/_old_model.py +++ /dev/null @@ -1,20 +0,0 @@ -from services.db_context import db -from typing import List - - -class WordBank(db.Model): - __tablename__ = "word_bank" - - user_qq = db.Column(db.BigInteger(), nullable=False) - group_id = db.Column(db.Integer()) - search_type = db.Column(db.Integer(), nullable=False, default=0) - problem = db.Column(db.String(), nullable=False) - answer = db.Column(db.String(), nullable=False) - format = db.Column(db.String()) - create_time = db.Column(db.DateTime(), nullable=False) - update_time = db.Column(db.DateTime(), nullable=False) - - @classmethod - async def get_all(cls) -> List['WordBank']: - return await cls.query.gino.all() - diff --git a/plugins/word_bank/_rule.py b/plugins/word_bank/_rule.py index e82dd046..443184e7 100644 --- a/plugins/word_bank/_rule.py +++ b/plugins/word_bank/_rule.py @@ -1,14 +1,15 @@ -import imagehash -from PIL import Image from io import BytesIO -from services.log import logger +import imagehash +from nonebot.adapters.onebot.v11 import Bot, MessageEvent from nonebot.typing import T_State -from nonebot.adapters.onebot.v11 import MessageEvent, Bot +from PIL import Image -from utils.utils import get_message_text, get_message_img, get_message_at -from ._model import WordBank +from services.log import logger from utils.http_utils import AsyncHttpx +from utils.utils import get_message_at, get_message_img, get_message_text + +from ._model import WordBank async def check(bot: Bot, event: MessageEvent, state: T_State) -> bool: @@ -23,11 +24,11 @@ async def check(bot: Bot, event: MessageEvent, state: T_State) -> bool: except Exception as e: logger.warning(f"word_bank rule 获取图片失败 {type(e)}:{e}") if at: - temp = '' + temp = "" for seg in event.message: - if seg.type == 'at': + if seg.type == "at": temp += f"[at:{seg.data['qq']}]" - elif seg.type == 'text': + elif seg.type == "text": temp += seg.data["text"] problem = temp if event.to_me and bot.config.nickname: @@ -35,9 +36,13 @@ async def check(bot: Bot, event: MessageEvent, state: T_State) -> bool: problem = f"[at:{bot.self_id}]" + problem else: if problem and bot.config.nickname: - nickname = [nk for nk in bot.config.nickname if str(event.original_message).startswith(nk)] + nickname = [ + nk + for nk in bot.config.nickname + if str(event.original_message).startswith(nk) + ] problem = nickname[0] + problem if nickname else problem - if problem and (await WordBank.check(event, problem) is not None): + if problem and (await WordBank.check_problem(event, problem) is not None): state["problem"] = problem return True return False diff --git a/plugins/word_bank/word_handle.py b/plugins/word_bank/word_handle.py index a56d4d91..04d48594 100644 --- a/plugins/word_bank/word_handle.py +++ b/plugins/word_bank/word_handle.py @@ -1,21 +1,29 @@ import re -from typing import Tuple, Any, Optional +from typing import Any, Optional, Tuple +from nonebot import on_command, on_regex +from nonebot.adapters.onebot.v11 import ( + Bot, + GroupMessageEvent, + Message, + MessageEvent, + PrivateMessageEvent, + unescape, +) +from nonebot.exception import FinishedException from nonebot.internal.params import Arg, ArgStr +from nonebot.params import Command, CommandArg, RegexGroup from nonebot.typing import T_State -from utils.utils import get_message_at, is_number, get_message_img -from nonebot.params import CommandArg, RegexGroup, Command -from nonebot.exception import FinishedException -from services.log import logger -from configs.path_config import DATA_PATH -from utils.message_builder import custom_forward_msg -from ._model import WordBank -from nonebot.adapters.onebot.v11 import Bot, GroupMessageEvent, Message, MessageEvent, PrivateMessageEvent, unescape -from nonebot import on_command, on_regex from configs.config import Config -from ._data_source import delete_word, update_word, show_word +from configs.path_config import DATA_PATH +from services.log import logger +from utils.message_builder import custom_forward_msg +from utils.utils import get_message_at, get_message_img, is_number + from ._config import scope2int, type2int +from ._data_source import delete_word, show_word, update_word +from ._model import WordBank __zx_plugin_name__ = "词库问答 [Admin]" __plugin_usage__ = r""" diff --git a/plugins/word_clouds/data_source.py b/plugins/word_clouds/data_source.py index 5e05e45d..4f941fbf 100644 --- a/plugins/word_clouds/data_source.py +++ b/plugins/word_clouds/data_source.py @@ -1,21 +1,23 @@ import asyncio import os import random -import jieba.analyse import re -from typing import List -from PIL import Image as IMG -import jieba -from emoji import replace_emoji # type: ignore -from wordcloud import WordCloud, ImageColorGenerator -import numpy as np -import matplotlib.pyplot as plt from io import BytesIO -from configs.path_config import IMAGE_PATH, FONT_PATH +from typing import List + +import jieba +import jieba.analyse +import matplotlib.pyplot as plt +import numpy as np +from emoji import replace_emoji # type: ignore +from PIL import Image as IMG +from wordcloud import ImageColorGenerator, WordCloud + +from configs.config import Config +from configs.path_config import FONT_PATH, IMAGE_PATH +from models.chat_history import ChatHistory from services import logger from utils.http_utils import AsyncHttpx -from models.chat_history import ChatHistory -from configs.config import Config async def pre_precess(msg: List[str], config) -> str: @@ -117,10 +119,8 @@ async def draw_word_cloud(messages, config): async def get_list_msg(user_id, group_id, days): - messages_list = ( - await ChatHistory() - ._get_msg(uid=user_id, gid=group_id, type_="group", days=days) - .gino.all() + messages_list = await ChatHistory().get_message( + uid=user_id, gid=group_id, type_="group", days=days ) if messages_list: messages = [i.text for i in messages_list] diff --git a/resources/image/sign/sign_res/bar.png b/resources/image/sign/sign_res/bar.png index 105955c461cc7c8ea40b27dba2b909f5cc1a55c1..4fc99ec35a3c52cb1afac18e8baefa5efd3adcc6 100644 GIT binary patch literal 2535 zcmX|DcU)6v7Y?Fjw6bIs%F-~j76_suG8M#tQ2|qt5mXRGHYh7JVOf?XkgyU$pvaO6 z1P6hDs0>*OX;1@PfwBw;1Vh3|2=I~6et*38cgK03^W5j0_xYW)%NHH?ORGtPK%o85 z^L8#EkofG*y@}*^JJ$gg+ZY6rs)gFwUWq0yjv^m|?KNJrL*Q%uwUs|5-igyG)s$6A zGq`J9S)~I}lPoz`3Q_5lxNDK7wjz7n*{76^ms4t+Qgl|(jfPlVR`@#=5t+fERQho1J!x;CZNM!K(-}D-{6th!CkFJfFVRP z#_-cq*zX-@F~4~?XdFTahT11*0*3XFY5HS&k0#;%IQHaAG^O45)jTfnz&TCmKVNcW0C$Eu`f}wnG>$X3Hr;ZA_c0p`#e)5=e zU|aDC(=c;FkMxGoc5T9()HnJ>OYxH_G**=lJ^M#dN#z?W>FW;t?A~e=U*#*`3ve}$ z+KLr(*|?kkk#;TiqYw%@a!HZfZD*3b{mq~u!Voxh(wj8l{JA%##X3-$!SSWq;Hp%R z+uaY|evJ#+7NbcRR#)RLu~_`!(;J(G4VCwTRM@$3j$07#FL$H~9;00s z=qY+1@J-W#r`6_LT(4$U%i(fH4sCzdNLk!I8JLh#nfO&b7s#=~s_op$FP!D#Wng)j z>yPD{em(m;c$TA|)LgyguDy$VuW`vx#8fg3OOSD0m`?G`@-Cn}#FZ^&mrw>yz7nFB z!|%^1E<9&=iU{d`eU^zeq>vB4G7gzQ5fE%0A9|D%6_}25f`>Hjpb`>`esiOY+|Ls# zI#+T9(yn;A=>aSz0`-1jft67JqX7^mrQDR0hgfT-turXGWv5q zIaC+~uF0~P1-&XxnTcY)&#=_8^dlVIWD%z}_#Zz>=JqrRL!dtg(lYvw$30vM?`UJs z)|bZk@pIvu&(X}dz~o*JKvx*!uA7i!ojpzs4LcgINiMG)Q`au{I~XPWgndq@YcpFD zD4`OritjS*G4exsba6C(8Lv0H%c1l_SxMKn8MekkslbUQR$DXA{2}q~8F-FJ7MgK^ zv7vY2xXsPTte7Y9>R-7gAmqSq)?14~EZ|f|v%!c%Z6`83Gio}{eEoBTDDAIadJ2z+ zdYlEsLlS+d6lTlT7!f0rb%D0KRXohNLvY(d^G+;nJCDQPd#gYrPgG#+r!!kfl<$Vs z7=i8z`Y-%YQrhIu+c}V3sxDXGO$33J@zQ;{TI16%dK!`KK={SdbF)OGvp!6TEdtS= zKSU*B+;GBI{_lhS`LzVvTP!y@4y+xh5&axAC7pKRgQliB3q&Xr45_k^atqyZMLoL} zJWlJML|LFg+BR|bBsaS{LOI$eZ1Y86FLWKv1^KvPF;mR7c~E8NB*E4b_7o-3Q-ga@ z3Ey4+xx1d*Z7OB{`jovu*(Nc(GnajOQ@5A>&222*hzWnxuQS`vHog!0w-!(B zpk>lVQsd&|Xl1i^`SYZze{dA~<`2zdm-7l0axN;FK6Xg;LKttyznd&*m> zHMl$jY8Go6jjIwc_bVE#etz4>C)Xyk{(r(?witqp!?%rZouOjcUrwx&nbpm@vp2}v zd&Ub`En6Q{twSzRg{6S@U8Wbo`s=>eXPfbTl@~2<*LN;Pa}S;E9w)I{_&-8|=h~@z zVy#fSCIxx!(~p^^Oi)7=D#N$Q9)L%N@{4CwE%`I8s93&$kvCJJX5JJN92){Zvg?cq zhA#4tcX$tb{)AGzNQCugI2W?YwZ-PedG!7dx%rV57tJh^tr@hm%fpX$Ey|{>w8Wtl z>>RGcOjd7GAGSdgr(1Zb@(=KSlZ`tm>Ox3(R9lRF{VChw)vA2j@oS>4(s0?6cHSPE zZ*$PuWl*S7t*oq6v82agf0G#qHbOPKu<1XVWDhoiJ(V+MttFY9JzMtsk%e-> zkeT)S`1ym@k5bo&&iAbDA$zqpW4($6AE{R89jh9aawyl0s}LzIkxb|h4ztBfHIh^! z!pu&`6%RIsj8T2o2m_QdUn)!xKp?FgxH(%S*XJz=uz|B)$*&m<=E`ttb@D0WrSX6#Nir~4m71q4IM8`y-h$Yc0a{N#AihyA~zKx zD1%1J4_BIs76jhGkqx2DsV*^sIO3dz2gM?Gw7x+@VW239tc`GMz1;%TFZDGzJ#-*t z>+$wE=boq?A=zg(7rIZILn#O zy3>EH^whV`fZZMttI}{K`74Z>t-nl6YT1_UqCZ-(a?W+%8Q?%r`-^t9XRasy4{6#K Ah5!Hn literal 755 zcmeAS@N?(olHy`uVBq!ia0y~yV15B)8*#7!$@A|%8Za<0?ecVS45^s&_RiU$R}KOV z7v)Z!{rY!)X!)5~UQwpWKL3_KnKM;)k%LZfe5uOQIgc}5J~wxFS#q&I%4m5<$JAo2 z`DZ@I-!8d->dz00OBr(a|5@4Fe%JjTYjJ*`Si0QgQ!%ygqTk&%*kAwd<=?kJfzF?w zl8wyei+pZ9dwKtD$=mn8rXE*Gi#x9U|6kAZ(C@zb+vPv)npf$QmiGMCuQxH#^XF#Y zetx?Dw$Pe}>)9doTKaHgoJ0W(I~2C)+;m zQ@s|>zRQ4}fuSK?_1g88UymHbp8?ex+QcGy6y{c4uarP0QYKG$#(l54wlUTg`CD{7ngtkrmw4002=pUIihVM&K#m(Ij iKpP)i&guNL@V{<)mHO$N>Kj1I7(8A5T-G@yGywoew=(De diff --git a/resources/image/sign/sign_res/bar_white.png b/resources/image/sign/sign_res/bar_white.png index e04bca9849eda05101a13727adca9dc7dbae59b3..c09c4635dc82360d7a0b27691d7c4ade713fe46f 100644 GIT binary patch literal 1431 zcmW+$dr(qo9KEcowoaJS1~q~?ZV$~i%eB-rwA3CJWhD=_L|ZFYL)0+~Z%eot(o1Wm zEH_cfU7sjun3joJOq%0cyJGktV|^@&u0!}DWWU=#ckY~XzVn^$ckcaepFD9GYh`N% zK@c|ZNWf_bGMfbNzpSzV&#sxdT@Yl>2n;wtVGFdwjdw57?ONs*$o;Klo5fo#aHnsv z@UB~VH>l?c1oLEE)vAK2qt$nK!YrOC75~$ro1gCdw#l~F>2Di{j6Z}K1s`!(=M4wQ z?`^kA<@esn6e(8(f96JXo%hheZ{)ItA>B}ta*gU*XLv|N#H`H@T6_shG(xJWpf-B8 z9{_ni-QtwMHLw_d7Dk%J;H@2mDjMk|r`+pIuFs?y1_&(X7c>e)jWz$5OS80A7_gIf z5kr}TX0N&&A2;q1s#Hv)KFyWA4|v6@@?w#M9z?>C4p75vL zh54n|LuD@N=po}7n`MzC*U0RApu9oqJ zzb?VJFF|zQBM1?Y^P0l+vCr!X{YA4Inyy++u5o8tc#v!WGmtZuUbg&BBlS)mauMc> z^8!6BAt*KN>2p0#)1&o@!*vJIx@g79#mlxpUdavKV$9R@((G!iR4U1ga?}S!J&s`s zEu>J{MUi!m*&+uiRt~H~LwUa3##K>C^;G^x`p|k&8#1hZrP&JzqBtx4#$rwxw`(LD zJ33o2GV-6yeH4v49#yg2JKp@!YFUl&TQVIpSqy+CFv~MqCPcLqdNCn*KbMnS_zZRt zm_bm}SF(3qV-4dFq%ZS=fdoLN-a5~kEM-{ z76VD@QYe$1X6#HK9x^G|*W|@W|JrV}cqj6)c9rWMGR7D{@NNYNbXXnN*ZOO+itQpq zx9rItiS+dzOh|4M1G1^Ug6!D;EekfXs!pGk_dP&~!IIn)cu||%1S;uCfxSY@S6q|O zDDA51Q-;FQb5(E^%-UD`(EA%k*=%ZK$`MWJT-biJfhF%#;W19xbi!?;qdHMv5{3ehyrRKHzbh7mvgpk}0MI6V;%yX?WJqU4umkV7^R zk_LB%1odc_AN8MKNpu^ncXyto*3!B>>c<tjUTk^$HKqlerS!Mu>g-Y`Wv{ zAPmI6^uj@4Dmgw{FRlufSK;7-Zqpk zazhZU*GzS=!5?`?=>=AizK;i2P0Ysd6Byp{-_YO|1 z$y8bUaQb(|uO^O)krD0dq$e3{aLFIsOf2@Jy;0^$a)g+>hL`G?8@qo4k^ zrZa8dY9L~*-HvT*vM;?(m9e`mH|`d8)fIXnmC#BDVisix(~rq*(Z(1URrz&#xY&hi zDr5MxW31hTv;Q2i0CP#*8=AcUU5P|L7(x>lPx# literal 584 zcmeAS@N?(olHy`uVBq!ia0y~yV15B)8*#7!$@A|%8Za<0@q4;BhE&XXd&e=a*+9fK zaK)zox|bbYS&m3Z=X?r17s-%T^_AzbiSJ?FJWCd!HY>?$`43rtY!vwz7!K4=d3{&y z<2|Jdu9iS4|7~v`UEWxydZ87>TXS}8jO?p)rq~(G3=9r&r7Ql=u>lG*ScKiVxRiUB z0XqXjLvh!&FOyhf=Kxh4D80GQj;o{)Xe`6qH)Umd7mk8V*)sjj+gtOGFanvfZ&-JI zl>st-{e1B*WI=Z^kohA2>iu;)Ko&6EcGx}-WKQip**r&(;SAFn^K3zk-@b-*^n-+i)`Yw#&@%K diff --git a/services/db_context.py b/services/db_context.py index e67c8a74..8af0cc4f 100755 --- a/services/db_context.py +++ b/services/db_context.py @@ -1,28 +1,61 @@ +from typing import List -from gino import Gino -from .log import logger +from tortoise import Tortoise, fields +from tortoise.connection import connections +from tortoise.models import Model as Model_ +from tortoise.queryset import RawSQLQuery + +from configs.config import address, bind, database, password, port, sql_name, user from utils.text_utils import prompt2cn -from configs.config import bind, sql_name, user, password, address, port, database + +from .log import logger + +MODELS: List[str] = [] + +SCRIPT_METHOD = [] -# 全局数据库连接对象 -db = Gino() +class Model(Model_): + """ + 自动添加模块 + + Args: + Model_ (_type_): _description_ + """ + + def __init_subclass__(cls, **kwargs): + MODELS.append(cls.__module__) + + if func := getattr(cls, "_run_script", None): + SCRIPT_METHOD.append(func) + + +class TestSQL(Model): + + id = fields.IntField(pk=True, generated=True, auto_increment=True) + """自增id""" + + class Meta: + table = "test_sql" + table_description = "执行SQL命令,不记录任何数据" async def init(): - if not bind and (not user and not password and not address and not port and not database): + if not bind and not any([user, password, address, port, database]): raise ValueError("\n" + prompt2cn("数据库配置未填写", 28)) i_bind = bind if not i_bind: i_bind = f"{sql_name}://{user}:{password}@{address}:{port}/{database}" try: - await db.set_bind(i_bind) - await db.gino.create_all() - logger.info(f'Database loaded successfully!') + await Tortoise.init(db_url=i_bind, modules={"models": MODELS}) + await Tortoise.generate_schemas() + logger.info(f"Database loaded successfully!") except Exception as e: - raise Exception(f'数据库连接错误.... {type(e)}: {e}') + raise Exception(f"数据库连接错误.... {type(e)}: {e}") + if SCRIPT_METHOD: + for func in SCRIPT_METHOD: + await func() async def disconnect(): - await db.pop_bind().close() - + await connections.close_all() diff --git a/services/log.py b/services/log.py index ee757aec..7d52bbd5 100755 --- a/services/log.py +++ b/services/log.py @@ -1,24 +1,141 @@ from datetime import datetime, timedelta -from configs.path_config import LOG_PATH +from typing import Any, Dict, Optional + from loguru import logger as logger_ -from nonebot.log import default_format, default_filter +from nonebot.log import default_filter, default_format +from configs.path_config import LOG_PATH -logger = logger_ - - -logger.add( - LOG_PATH / f'{datetime.now().date()}.log', - level='INFO', - rotation='00:00', +logger_.add( + LOG_PATH / f"{datetime.now().date()}.log", + level="INFO", + rotation="00:00", format=default_format, filter=default_filter, - retention=timedelta(days=30)) + retention=timedelta(days=30), +) -logger.add( - LOG_PATH / f'error_{datetime.now().date()}.log', - level='ERROR', - rotation='00:00', +logger_.add( + LOG_PATH / f"error_{datetime.now().date()}.log", + level="ERROR", + rotation="00:00", format=default_format, filter=default_filter, - retention=timedelta(days=30)) \ No newline at end of file + retention=timedelta(days=30), +) + + +class logger: + + TEMPLATE_A = "{}" + TEMPLATE_B = "[{}]: {}" + TEMPLATE_C = "用户[{}] 触发 [{}]: {}" + TEMPLATE_D = "群聊[{}] 用户[{}] 触发 [{}]: {}" + TEMPLATE_E = "群聊[{}] 用户[{}] 触发 [{}] [Target]({}): {}" + + TEMPLATE_USER = "用户[{}] " + TEMPLATE_GROUP = "群聊[{}] " + TEMPLATE_COMMAND = "CMD[{}] " + TEMPLATE_TARGET = "[Target]([{}]) " + + SUCCESS_TEMPLATE = "[{}]: {} | 参数[{}] 返回: [{}]" + + WARNING_TEMPLATE = "[{}]: {}" + + ERROR_TEMPLATE = "[{}]: {}" + + @classmethod + def info( + cls, + info: str, + command: Optional[str] = None, + user_id: Optional[int] = None, + group_id: Optional[int] = None, + target: Optional[Any] = None, + ): + template = cls.__parser_template(info, command, user_id, group_id, target) + logger_.opt(colors=True).info(template) + + @classmethod + def success( + cls, + info: str, + command: str, + param: Optional[Dict[str, Any]] = None, + result: Optional[str] = "", + ): + param_str = "" + if param: + param_str = ",".join([f"{k}:{v}" for k, v in param.items()]) + logger_.opt(colors=True).success( + cls.SUCCESS_TEMPLATE.format(command, info, param_str, result) + ) + + @classmethod + def warning( + cls, + info: str, + command: Optional[str] = None, + user_id: Optional[int] = None, + group_id: Optional[int] = None, + target: Optional[Any] = None, + e: Optional[Exception] = None, + ): + template = cls.__parser_template(info, command, user_id, group_id, target) + if e: + template += f" || 错误{type(e)}: {e}" + logger_.opt(colors=True).warning(template) + + @classmethod + def error( + cls, + info: str, + command: Optional[str] = None, + user_id: Optional[int] = None, + group_id: Optional[int] = None, + target: Optional[Any] = None, + e: Optional[Exception] = None, + ): + template = cls.__parser_template(info, command, user_id, group_id, target) + if e: + template += f" || 错误 {type(e)}: {e}" + logger_.opt(colors=True).error(template) + + @classmethod + def debug( + cls, + info: str, + command: Optional[str] = None, + user_id: Optional[int] = None, + group_id: Optional[int] = None, + target: Optional[Any] = None, + ): + template = cls.__parser_template(info, command, user_id, group_id, target) + logger_.opt(colors=True).debug(template) + + @classmethod + def __parser_template( + cls, + info: str, + command: Optional[str] = None, + user_id: Optional[int] = None, + group_id: Optional[int] = None, + target: Optional[Any] = None, + ) -> str: + arg_list = [] + template = "" + if group_id is not None: + template += cls.TEMPLATE_GROUP + arg_list.append(group_id) + if user_id is not None: + template += cls.TEMPLATE_USER + arg_list.append(user_id) + if command is not None: + template += cls.TEMPLATE_COMMAND + arg_list.append(command) + if target is not None: + template += cls.TEMPLATE_TARGET + arg_list.append(target) + arg_list.append(info) + template += "{}" + return template.format(*arg_list) diff --git a/utils/browser.py b/utils/browser.py index d602968d..c7a15587 100755 --- a/utils/browser.py +++ b/utils/browser.py @@ -1,34 +1,45 @@ import asyncio from typing import Optional + +from nonebot import get_driver from nonebot.log import logger -from playwright.async_api import Browser, async_playwright +from playwright.async_api import Browser, Playwright, async_playwright + from services.log import logger +driver = get_driver() +_playwright: Optional[Playwright] = None _browser: Optional[Browser] = None -async def init(**kwargs) -> Optional[Browser]: +@driver.on_startup +async def start_browser(): + global _playwright global _browser - browser = await async_playwright().start() - try: - _browser = await browser.chromium.launch(**kwargs) - return _browser - except Exception as e: - # logger.warning(f"启动chromium发生错误 {type(e)}:{e}") - await asyncio.get_event_loop().run_in_executor(None, install) - _browser = await browser.chromium.launch(**kwargs) - return None + _playwright = await async_playwright().start() + _browser = await _playwright.chromium.launch() -async def get_browser(**kwargs) -> Browser: - return _browser or await init(**kwargs) +@driver.on_shutdown +async def shutdown_browser(): + if _browser: + await _browser.close() + if _playwright: + _playwright.stop() + + +def get_browser() -> Browser: + if not _browser: + raise RuntimeError("playwright is not initalized") + return _browser def install(): """自动安装、更新 Chromium""" logger.info("正在检查 Chromium 更新") import sys + from playwright.__main__ import main sys.argv = ["", "install", "chromium"] diff --git a/utils/data_utils.py b/utils/data_utils.py index 643415e8..1fcc0a9d 100755 --- a/utils/data_utils.py +++ b/utils/data_utils.py @@ -1,13 +1,18 @@ -from models.group_member_info import GroupInfoUser -from utils.image_utils import BuildMat -from configs.path_config import IMAGE_PATH -from typing import List, Union import asyncio import os +from typing import List, Union + +from configs.path_config import IMAGE_PATH +from models.group_member_info import GroupInfoUser +from utils.image_utils import BuildMat async def init_rank( - title: str, all_user_id: List[int], all_user_data: List[int], group_id: int, total_count: int = 10 + title: str, + all_user_id: List[int], + all_user_data: List[int], + group_id: int, + total_count: int = 10, ) -> BuildMat: """ 说明: @@ -26,11 +31,11 @@ async def init_rank( max_user_id = all_user_id[all_user_data.index(_max)] all_user_id.remove(max_user_id) all_user_data.remove(_max) - try: - user_name = ( - await GroupInfoUser.get_member_info(max_user_id, group_id) - ).user_name - except AttributeError: + if user := await GroupInfoUser.get_or_none( + user_qq=max_user_id, group_id=group_id + ): + user_name = user.user_name + else: user_name = f"{max_user_id}" _uname_lst.append(user_name) _num_lst.append(_max) diff --git a/utils/depends/__init__.py b/utils/depends/__init__.py index a1927a9b..d1ccb88e 100644 --- a/utils/depends/__init__.py +++ b/utils/depends/__init__.py @@ -1,13 +1,18 @@ -from typing import Callable, List, Optional, Union +from typing import Callable, List, Optional, Tuple, Union -from configs.config import Config -from models.bag_user import BagUser -from models.level_user import LevelUser -from models.user_shop_gold_log import UserShopGoldLog from nonebot.adapters.onebot.v11 import GroupMessageEvent, MessageEvent from nonebot.internal.matcher import Matcher from nonebot.internal.params import Depends +from nonebot.params import Command +from configs.config import Config +from models.bag_user import BagUser + +# from models.bag_user import BagUser +from models.level_user import LevelUser +from models.user_shop_gold_log import UserShopGoldLog + +# from models.user_shop_gold_log import UserShopGoldLog from utils.manager import admin_manager from utils.message_builder import at from utils.utils import ( @@ -18,6 +23,19 @@ from utils.utils import ( ) +def OneCommand(): + """ + 获取单个命令Command + """ + + async def dependency( + cmd: Tuple[str, ...] = Command(), + ): + return cmd[0] if cmd else None + + return Depends(dependency) + + def AdminCheck(level: Optional[int] = None): """ 说明: @@ -27,16 +45,19 @@ def AdminCheck(level: Optional[int] = None): """ async def dependency(matcher: Matcher, event: GroupMessageEvent): - plugin_level = admin_manager.get_plugin_module(matcher.plugin_name) - user_level = await LevelUser.get_user_level(event.user_id, event.group_id) - if level is None: - if user_level < plugin_level: - await matcher.finish( - at(event.user_id) + f"你的权限不足喔,该功能需要的权限等级:{plugin_level}" - ) - else: - if user_level < level: - await matcher.finish(at(event.user_id) + f"你的权限不足喔,该功能需要的权限等级:{level}") + if name := matcher.plugin_name: + plugin_level = admin_manager.get_plugin_level(name) + user_level = await LevelUser.get_user_level(event.user_id, event.group_id) + if level is None: + if user_level < plugin_level: + await matcher.finish( + at(event.user_id) + f"你的权限不足喔,该功能需要的权限等级:{plugin_level}" + ) + else: + if user_level < level: + await matcher.finish( + at(event.user_id) + f"你的权限不足喔,该功能需要的权限等级:{level}" + ) return Depends(dependency) @@ -53,8 +74,13 @@ def CostGold(gold: int): if (await BagUser.get_gold(event.user_id, event.group_id)) < gold: await matcher.finish(at(event.user_id) + f"金币不足..该功能需要{gold}金币..") await BagUser.spend_gold(event.user_id, event.group_id, gold) - await UserShopGoldLog.add_shop_log( - event.user_id, event.group_id, 2, matcher.plugin_name, gold, 1 + await UserShopGoldLog.create( + user_qq=event.user_id, + group_id=event.group_id, + type=2, + name=matcher.plugin_name, + num=1, + spend_gold=gold, ) return Depends(dependency) @@ -78,10 +104,12 @@ def GetConfig( async def dependency(matcher: Matcher): module_ = module or matcher.plugin_name - value = Config.get_config(module_, config, default_value) - if value is None: - await matcher.finish(prompt or f"配置项 {config} 未填写!") - return value + if module_: + value = Config.get_config(module_, config, default_value) + if value is None and prompt: + # await matcher.finish(prompt or f"配置项 {config} 未填写!") + await matcher.finish(prompt) + return value return Depends(dependency) @@ -102,10 +130,11 @@ def CheckConfig( async def dependency(matcher: Matcher): module_ = module or matcher.plugin_name - config_list = [config] if isinstance(config, str) else config - for c in config_list: - if Config.get_config(module_, c) is None: - await matcher.finish(prompt or f"配置项 {c} 未填写!") + if module_: + config_list = [config] if isinstance(config, str) else config + for c in config_list: + if Config.get_config(module_, c) is None: + await matcher.finish(prompt or f"配置项 {c} 未填写!") return Depends(dependency) diff --git a/utils/game_utils.py b/utils/game_utils.py new file mode 100644 index 00000000..2ce2eb86 --- /dev/null +++ b/utils/game_utils.py @@ -0,0 +1,192 @@ +from typing import Optional, Dict, Union + +from nonebot.adapters.onebot.v11 import Message, MessageSegment +from pydantic import BaseModel +import time + + +class GameEntry(BaseModel): + game_name: str + module: str + default_msg: str + msg_data: Dict[int, Union[str, Message, MessageSegment]] + timeout: int # 超时时限 + anti_concurrency: bool # 是否阻断 + + +class GroupGameStatus(BaseModel): + game: GameEntry + status: int + time: time.time() # 创建时间 + + +class GameManager: + def __init__(self): + self._data = {} + self._status = {} + + def add_game( + self, + game_name: str, + module: str, + timeout: int, + default_msg: Optional[str] = "游戏还未结束!", + msg_data: Dict[int, Union[str, Message, MessageSegment]] = None, + anti_concurrency: bool = True, + **kwargs, + ): + """ + 参数: + 将游戏添加到游戏管理器 + 说明: + :param game_name: 游戏名称 + :param module: 模块名 + :param timeout: 超时时长 + :param default_msg: 默认回复消息 + :param msg_data: 不同状态回复的消息 + :param anti_concurrency: 是否阻断反并发 + """ + self._data[module] = GameEntry( + game_name=game_name, + module=module, + timeout=timeout, + default_msg=default_msg, + msg_data=msg_data or {}, + anti_concurrency=anti_concurrency, + **kwargs, + ) + + def start(self, group_id: int, module: str): + """ + 说明: + 游戏开始标记 + 参数: + :param group_id: 群号 + :param module: 模块名 + """ + if not self._status.get(group_id): + self._status[group_id] = [] + if module not in [x.game.module for x in self._status[module]]: + self._status[group_id].append( + GroupGameStatus(game=self._data[module], status=0) + ) + + def end(self, group_id: int, module: str): + """ + 说明: + 游戏结束标记 + 参数: + :param group_id: 群号 + :param module: 模块名 + """ + if self._status.get(group_id) and module in [ + x.game.module for x in self._status[group_id] + ]: + for x in self._status[group_id]: + if self._status[group_id][x].game.module == module: + self._status[group_id].remove(x) + break + + def set_status(self, group_id: int, module: str, status: int): + """ + 说明: + 设置游戏状态,根据状态发送不同的提示消息 msg_data + 参数: + :param group_id: 群号 + :param module: 模块名 + :param status: 状态码 + """ + if self._status.get(group_id) and module in [ + x.game.module for x in self._status[group_id] + ]: + [x.game.module for x in self._status[group_id] if x.game.module == module][ + 0 + ].status = status + + def check(self, group_id, module: str) -> Optional[str]: + """ + 说明: + 检查群游戏当前状态并返回提示语句 + 参数: + :param group_id: 群号 + :param module: 模块名 + """ + if module in self._data and self._status.get(group_id): + for x in self._status[group_id]: + if x.game.anti_concurrency: + return f"{x.game.game_name} 还未结束,请等待 {x.game.game_name} 游戏结束!" + if self._status.get(group_id) and module in [ + x.game.module for x in self._status[group_id] + ]: + group_game_status = [ + x.game.module for x in self._status[group_id] if x.game.module == module + ][0] + if time.time() - group_game_status.time > group_game_status.game.timeout: + # 超时结束 + self.end(group_id, module) + else: + return ( + group_game_status.game.msg_data.get(group_game_status.status) + or group_game_status.game.default_msg + ) + + +game_manager = GameManager() + + +class Game: + """ + 反并发,游戏重复开始 + """ + def __init__( + self, + game_name: str, + module: str, + timeout: int = 60, + default_msg: Optional[str] = None, + msg_data: Dict[int, Union[str, Message, MessageSegment]] = None, + anti_concurrency: bool = True, + ): + """ + 参数: + 将游戏添加到游戏管理器 + 说明: + :param game_name: 游戏名称 + :param module: 模块名 + :param timeout: 超时时长 + :param default_msg: 默认回复消息 + :param msg_data: 不同状态回复的消息 + :param anti_concurrency: 是否阻断反并发 + """ + self.module = module + game_manager.add_game( + game_name, module, timeout, default_msg, msg_data, anti_concurrency + ) + + def start(self, group_id: int): + """ + 说明: + 游戏开始标记 + 参数: + :param group_id: 群号 + """ + game_manager.start(group_id, self.module) + + def end(self, group_id: int): + """ + 说明: + 游戏结束标记 + 参数: + :param group_id: 群号 + """ + game_manager.end(group_id, self.module) + + def set_status(self, group_id: int, status: int): + """ + 说明: + 设置游戏状态,根据状态发送不同的提示消息 msg_data + 参数: + :param group_id: 群号 + :param status: 状态码 + """ + game_manager.set_status(group_id, self.module, status) diff --git a/utils/http_utils.py b/utils/http_utils.py index 20b3a959..06362ac1 100644 --- a/utils/http_utils.py +++ b/utils/http_utils.py @@ -1,20 +1,23 @@ -from typing import Dict, Union, Optional, List, Any, Literal -from utils.user_agent import get_user_agent -from .utils import get_local_proxy -from services.log import logger -from pathlib import Path -from httpx import Response -from asyncio.exceptions import TimeoutError -from nonebot.adapters.onebot.v11 import MessageSegment -from playwright.async_api import Page, BrowserContext -from .message_builder import image -from httpx import ConnectTimeout -from .browser import get_browser -from retrying import retry import asyncio +from asyncio.exceptions import TimeoutError +from contextlib import asynccontextmanager +from pathlib import Path +from typing import Any, AsyncGenerator, Dict, List, Literal, Optional, Union + import aiofiles import httpx import rich +from httpx import ConnectTimeout, Response +from nonebot.adapters.onebot.v11 import MessageSegment +from playwright.async_api import BrowserContext, Page +from retrying import retry + +from services.log import logger +from utils.user_agent import get_user_agent + +from .browser import get_browser +from .message_builder import image +from .utils import get_local_proxy class AsyncHttpx: @@ -32,7 +35,7 @@ class AsyncHttpx: cookies: Optional[Dict[str, str]] = None, verify: bool = True, use_proxy: bool = True, - proxy: Dict[str, str] = None, + proxy: Optional[Dict[str, str]] = None, timeout: Optional[int] = 30, **kwargs, ) -> Response: @@ -59,7 +62,7 @@ class AsyncHttpx: headers=headers, cookies=cookies, timeout=timeout, - **kwargs + **kwargs, ) @classmethod @@ -174,7 +177,9 @@ class AsyncHttpx: headers = get_user_agent() proxy = proxy if proxy else cls.proxy if use_proxy else None try: - async with httpx.AsyncClient(proxies=proxy, verify=verify) as client: + async with httpx.AsyncClient( + proxies=proxy, verify=verify + ) as client: async with client.stream( "GET", url, @@ -182,9 +187,11 @@ class AsyncHttpx: headers=headers, cookies=cookies, timeout=timeout, - **kwargs + **kwargs, ) as response: - logger.info(f"开始下载 {path.name}.. Path: {path.absolute()}") + logger.info( + f"开始下载 {path.name}.. Path: {path.absolute()}" + ) async with aiofiles.open(path, "wb") as wf: total = int(response.headers["Content-Length"]) with rich.progress.Progress( @@ -192,13 +199,18 @@ class AsyncHttpx: "[progress.percentage]{task.percentage:>3.0f}%", rich.progress.BarColumn(bar_width=None), rich.progress.DownloadColumn(), - rich.progress.TransferSpeedColumn() + rich.progress.TransferSpeedColumn(), ) as progress: - download_task = progress.add_task("Download", total=total) + download_task = progress.add_task( + "Download", total=total + ) async for chunk in response.aiter_bytes(): await wf.write(chunk) await wf.flush() - progress.update(download_task, completed=response.num_bytes_downloaded) + progress.update( + download_task, + completed=response.num_bytes_downloaded, + ) logger.info(f"下载 {url} 成功.. Path:{path.absolute()}") return True except (TimeoutError, ConnectTimeout): @@ -274,7 +286,7 @@ class AsyncHttpx: use_proxy=use_proxy, timeout=timeout, proxy=proxy, - ** kwargs, + **kwargs, ) ) ) @@ -285,64 +297,23 @@ class AsyncHttpx: class AsyncPlaywright: - @classmethod - async def _new_page(cls, user_agent: Optional[str] = None, **kwargs) -> Page: + @asynccontextmanager + async def new_page(cls, **kwargs) -> AsyncGenerator[Page, None]: """ 说明: 获取一个新页面 参数: :param user_agent: 请求头 """ - browser = await get_browser() - if browser: - return await browser.new_page(user_agent=user_agent, **kwargs) - raise BrowserIsNone("获取Browser失败...") - - @classmethod - async def new_context(cls, user_agent: Optional[str] = None, **kwargs) -> BrowserContext: - """ - 说明: - 获取一个新上下文 - 参数: - :param user_agent: 请求头 - """ - browser = await get_browser() - if browser: - return await browser.new_context(user_agent=user_agent, **kwargs) - raise BrowserIsNone("获取Browser失败...") - - @classmethod - async def goto( - cls, - url: str, - *, - timeout: Optional[float] = 100000, - wait_until: Optional[ - Literal["domcontentloaded", "load", "networkidle"] - ] = "networkidle", - referer: str = None, - **kwargs - ) -> Optional[Page]: - """ - 说明: - goto - 参数: - :param url: 网址 - :param timeout: 超时限制 - :param wait_until: 等待类型 - :param referer: - """ - page = None + browser = get_browser() + ctx = await browser.new_context(**kwargs) + page = await ctx.new_page() try: - page = await cls._new_page(**kwargs) - await page.goto(url, timeout=timeout, wait_until=wait_until, referer=referer) - return page - except Exception as e: - logger.warning(f"Playwright 访问 url:{url} 发生错误 {type(e)}:{e}") - if page: - await page.close() - return None + yield page + finally: + await page.close() + await ctx.close() @classmethod async def screenshot( @@ -352,13 +323,13 @@ class AsyncPlaywright: element: Union[str, List[str]], *, wait_time: Optional[int] = None, - viewport_size: Dict[str, int] = None, + viewport_size: Optional[Dict[str, int]] = None, wait_until: Optional[ Literal["domcontentloaded", "load", "networkidle"] ] = "networkidle", - timeout: float = None, - type_: Literal["jpeg", "png"] = None, - **kwargs + timeout: Optional[float] = None, + type_: Optional[Literal["jpeg", "png"]] = None, + **kwargs, ) -> Optional[MessageSegment]: """ 说明: @@ -373,33 +344,25 @@ class AsyncPlaywright: :param timeout: 超时限制 :param type_: 保存类型 """ - page = None if viewport_size is None: viewport_size = dict(width=2560, height=1080) if isinstance(path, str): path = Path(path) - try: - page = await cls.goto(url, wait_until=wait_until, **kwargs) - await page.set_viewport_size(viewport_size) - if isinstance(element, str): - if wait_time: - card = await page.wait_for_selector(element, timeout=wait_time * 1000) - else: - card = await page.query_selector(element) - else: - card = page - for e in element: - if wait_time: - card = await card.wait_for_selector(e, timeout=wait_time * 1000) - else: - card = await card.query_selector(e) - await card.screenshot(path=path, timeout=timeout, type=type_) - return image(path) - except Exception as e: - logger.warning(f"Playwright 截图 url:{url} element:{element} 发生错误 {type(e)}:{e}") - finally: - if page: - await page.close() + wait_time = wait_time * 1000 if wait_time else None + if isinstance(element, str): + element_list = [element] + else: + element_list = element + async with cls.new_page(viewport=viewport_size) as page: + await page.goto(url, timeout=timeout, wait_until=wait_until) + card = page + for e in element_list: + if not card: + return None + card = await card.wait_for_selector(e, timeout=wait_time) + if card: + await card.screenshot(path=path, timeout=timeout, type=type_) + return image(path) return None diff --git a/utils/image_utils.py b/utils/image_utils.py index a3087b44..f835aa2b 100755 --- a/utils/image_utils.py +++ b/utils/image_utils.py @@ -7,23 +7,28 @@ import uuid from io import BytesIO from math import ceil from pathlib import Path -from typing import List, Literal, Optional, Tuple, Union, Callable, Awaitable - -from PIL.ImageFont import FreeTypeFont -from nonebot.utils import is_coroutine_callable +from typing import Awaitable, Callable, List, Literal, Optional, Tuple, Union import cv2 import imagehash -from configs.path_config import FONT_PATH, IMAGE_PATH from imagehash import ImageHash from matplotlib import pyplot as plt +from nonebot.utils import is_coroutine_callable from PIL import Image, ImageDraw, ImageFile, ImageFilter, ImageFont +from PIL.ImageFont import FreeTypeFont + +from configs.path_config import FONT_PATH, IMAGE_PATH from services import logger ImageFile.LOAD_TRUNCATED_IMAGES = True Image.MAX_IMAGE_PIXELS = None +ModeType = Literal[ + "1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr" +] + + def compare_image_with_hash( image_file1: str, image_file2: str, max_dif: int = 1.5 ) -> bool: @@ -154,7 +159,7 @@ class BuildImage: paste_image_width: int = 0, paste_image_height: int = 0, color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]] = None, - image_mode: str = "RGBA", + image_mode: ModeType = "RGBA", font_size: int = 10, background: Union[Optional[str], BytesIO, Path] = None, font: str = "yz.ttf", @@ -162,7 +167,7 @@ class BuildImage: is_alpha: bool = False, plain_text: Optional[str] = None, font_color: Optional[Union[str, Tuple[int, int, int]]] = None, - **kwargs + **kwargs, ): """ 参数: @@ -320,6 +325,19 @@ class BuildImage: self.markImg.paste(img, pos) self._current_w += self.paste_image_width + @classmethod + def get_text_size(cls, msg: str, font: str, font_size: int) -> Tuple[int, int]: + """ + 说明: + 获取文字在该图片 font_size 下所需要的空间 + 参数: + :param msg: 文字内容 + :param font: 字体 + :param font_size: 字体大小 + """ + font = cls.load_font(font, font_size) + return font.getsize(msg) + def getsize(self, msg: str) -> Tuple[int, int]: """ 说明: @@ -395,7 +413,7 @@ class BuildImage: center_type: Optional[Literal["center", "by_height", "by_width"]] = None, font: Union[FreeTypeFont, str] = None, font_size: Optional[int] = None, - **kwargs + **kwargs, ): """ 说明: @@ -408,7 +426,9 @@ class BuildImage: :param font: 字体 :param font_size: 字体大小 """ - await self.loop.run_in_executor(None, self.text, pos, text, fill, center_type, font, font_size, **kwargs) + await self.loop.run_in_executor( + None, self.text, pos, text, fill, center_type, font, font_size, **kwargs + ) def text( self, @@ -418,7 +438,7 @@ class BuildImage: center_type: Optional[Literal["center", "by_height", "by_width"]] = None, font: Union[FreeTypeFont, str] = None, font_size: Optional[int] = None, - **kwargs + **kwargs, ): """ 说明: @@ -577,9 +597,9 @@ class BuildImage: buf = BytesIO() self.markImg.save(buf, format="PNG") base64_str = base64.b64encode(buf.getvalue()).decode() - return base64_str + return "base64://" + base64_str - def convert(self, type_: str): + def convert(self, type_: ModeType): """ 说明: 修改图片类型 @@ -792,7 +812,7 @@ class BuildImage: """ self.markImg = self.markImg.rotate(angle, expand=expand) - async def atranspose(self, angle: int): + async def atranspose(self, angle: Literal[0, 1, 2, 3, 4, 5, 6]): """ 说明: 异步 旋转图片(包括边框) @@ -801,7 +821,7 @@ class BuildImage: """ await self.loop.run_in_executor(None, self.transpose, angle) - def transpose(self, angle: int): + def transpose(self, angle: Literal[0, 1, 2, 3, 4, 5, 6]): """ 说明: 旋转图片(包括边框) @@ -1663,7 +1683,11 @@ async def build_sort_image( image_group: List[List[BuildImage]], h: Optional[int] = None, padding_top: int = 200, - color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]] = (255, 255, 255), + color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]] = ( + 255, + 255, + 255, + ), background_path: Optional[Path] = None, background_handle: Callable[[BuildImage], Optional[Awaitable]] = None, ) -> BuildImage: diff --git a/utils/manager/admin_manager.py b/utils/manager/admin_manager.py index e2a7c77b..9576582d 100644 --- a/utils/manager/admin_manager.py +++ b/utils/manager/admin_manager.py @@ -1,6 +1,8 @@ -from .models import AdminSetting +from typing import Dict, List, Optional + from utils.manager.data_class import StaticData -from typing import List, Optional, Dict + +from .models import AdminSetting class AdminManager(StaticData): @@ -59,7 +61,7 @@ class AdminManager(StaticData): def get_plugin_level(self, plugin: str) -> int: """ 说明: - 获取插件等级 + 获取插件权限 参数: :param plugin: 模块名 """ @@ -75,6 +77,7 @@ class AdminManager(StaticData): :param cmd: 命令 """ for key in self._data.keys(): - if self._data[key].cmd and cmd in self._data[key].cmd: - return key + if data := self._data.get(key): + if data.cmd and cmd in data.cmd: + return key return None diff --git a/utils/message_builder.py b/utils/message_builder.py index f2331b7c..6c26d62d 100755 --- a/utils/message_builder.py +++ b/utils/message_builder.py @@ -1,46 +1,43 @@ import io from pathlib import Path -from typing import List, Union +from typing import List, Optional, Union + +from nonebot.adapters.onebot.v11.message import Message, MessageSegment from configs.config import NICKNAME from configs.path_config import IMAGE_PATH, RECORD_PATH -from nonebot.adapters.onebot.v11.message import MessageSegment, Message from services.log import logger +from utils.image_utils import BuildImage def image( - file: Union[str, Path, bytes] = None, - path: str = None, - b64: str = None, -) -> Union[MessageSegment, str]: + file: Optional[Union[str, Path, bytes, BuildImage, io.BytesIO]] = None, + b64: Optional[str] = None, +) -> MessageSegment: """ 说明: 生成一个 MessageSegment.image 消息 生成顺序:绝对路径(abspath) > base64(b64) > img_name 参数: - :param file: 图片文件名称,默认在 resource/img 目录下 - :param path: 图片所在路径,默认在 resource/img 目录下 - :param b64: 图片base64 + :param file: 图片文件 + :param b64: 图片base64(兼容旧方法) """ + if b64: + file = b64 if b64.startswith("base64://") else ("base64://" + b64) + if isinstance(file, str): + if file.startswith(("http", "base64://")): + return MessageSegment.image(file) + else: + return MessageSegment.image(IMAGE_PATH / file) if isinstance(file, Path): if file.exists(): return MessageSegment.image(file) logger.warning(f"图片 {file.absolute()}缺失...") - return "" - elif isinstance(file, (bytes, io.BytesIO)): + if isinstance(file, (bytes, io.BytesIO)): return MessageSegment.image(file) - elif b64: - return MessageSegment.image(b64 if "base64://" in b64 else "base64://" + b64) - else: - if file.startswith("http"): - return MessageSegment.image(file) - if len(file.split(".")) == 1: - file += ".jpg" - if (file := IMAGE_PATH / path / file if path else IMAGE_PATH / file).exists(): - return MessageSegment.image(file) - else: - logger.warning(f"图片 {file} 缺失...") - return "" + if isinstance(file, BuildImage): + return MessageSegment.image(file.pic2bs4()) + return MessageSegment.image("") def at(qq: Union[int, str]) -> MessageSegment: @@ -53,29 +50,25 @@ def at(qq: Union[int, str]) -> MessageSegment: return MessageSegment.at(qq) -def record(voice_name: str, path: str = None) -> MessageSegment or str: +def record(file: Union[Path, str, bytes, io.BytesIO]) -> Union[MessageSegment, str]: """ 说明: 生成一个 MessageSegment.record 消息 参数: - :param voice_name: 音频文件名称,默认在 resource/voice 目录下 - :param path: 音频文件路径,默认在 resource/voice 目录下 + :param file: 音频文件名称,默认在 resource/voice 目录下 """ - if len(voice_name.split(".")) == 1: - voice_name += ".mp3" - file = ( - Path(RECORD_PATH) / path / voice_name - if path - else Path(RECORD_PATH) / voice_name - ) - if "http" in voice_name: - return MessageSegment.record(voice_name) - if file.exists(): - result = MessageSegment.record(f"file:///{file.absolute()}") - return result - else: - logger.warning(f"语音{file.absolute()}缺失...") - return "" + if isinstance(file, Path): + if file.exists(): + return MessageSegment.record(file) + logger.warning(f"音频 {file.absolute()}缺失...") + if isinstance(file, (bytes, io.BytesIO)): + return MessageSegment.record(file) + if isinstance(file, str): + if "http" in file: + return MessageSegment.record(file) + else: + return MessageSegment.record(RECORD_PATH / file) + return "" def text(msg: str) -> MessageSegment: @@ -99,7 +92,7 @@ def contact_user(qq: int) -> MessageSegment: def share( - url: str, title: str, content: str = None, image_url: str = None + url: str, title: str, content: Optional[str] = None, image_url: Optional[str] = None ) -> MessageSegment: """ 说明: @@ -158,7 +151,9 @@ def music(type_: str, id_: int) -> MessageSegment: def custom_forward_msg( - msg_list: List[Union[str, Message]], uin: Union[int, str], name: str = f"这里是{NICKNAME}" + msg_list: List[Union[str, Message]], + uin: Union[int, str], + name: str = f"这里是{NICKNAME}", ) -> List[dict]: """ 说明: @@ -202,11 +197,10 @@ class MessageBuilder: def image( self, - file: Union[str, Path, bytes] = None, - path: str = None, - b64: str = None, + file: Optional[Union[str, Path, bytes]] = None, + b64: Optional[str] = None, ): - return MessageBuilder(self._msg + image(file, path, b64)) + return MessageBuilder(self._msg + image(file, b64)) def at(self, qq: int): return MessageBuilder(self._msg + at(qq)) diff --git a/utils/utils.py b/utils/utils.py index f3e286c3..3e87e95e 100755 --- a/utils/utils.py +++ b/utils/utils.py @@ -1,17 +1,20 @@ -from datetime import datetime +import time from collections import defaultdict -from nonebot import require -from configs.config import SYSTEM_PROXY, Config -from typing import List, Union, Optional, Type, Any -from nonebot.adapters.onebot.v11 import Bot, Message -from nonebot.matcher import matchers, Matcher -from services.log import logger +from datetime import datetime from pathlib import Path +from typing import Any, Callable, List, Optional, Set, Type, Union + import httpx import nonebot -import pytz import pypinyin -import time +import pytz +from nonebot import require +from nonebot.adapters import Bot +from nonebot.adapters.onebot.v11 import Message, MessageSegment +from nonebot.matcher import Matcher, matchers + +from configs.config import SYSTEM_PROXY, Config +from services.log import logger try: import ujson as json @@ -25,9 +28,9 @@ scheduler = scheduler # 全局字典 GDict = { - "run_sql": [], # 需要启动前运行的sql语句 - "_shop_before_handle": {}, # 商品使用前函数 - "_shop_after_handle": {}, # 商品使用后函数 + "run_sql": [], # 需要启动前运行的sql语句 + "_shop_before_handle": {}, # 商品使用前函数 + "_shop_after_handle": {}, # 商品使用后函数 } @@ -119,8 +122,8 @@ class BanCheckLimiter: self.mint[key] = 0 return False if ( - self.mint[key] >= self.default_count - and time.time() - self.mtime[key] < self.default_check_time + self.mint[key] >= self.default_count + and time.time() - self.mtime[key] < self.default_check_time ): self.mtime[key] = time.time() self.mint[key] = 0 @@ -157,13 +160,15 @@ class DailyNumberLimiter: self.count[key] = 0 -def is_number(s: str) -> bool: +def is_number(s: Union[int, str]) -> bool: """ 说明: 检测 s 是否为数字 参数: :param s: 文本 """ + if isinstance(s, int): + return True try: float(s) return True @@ -179,14 +184,14 @@ def is_number(s: str) -> bool: return False -def get_bot() -> Optional[Bot]: +def get_bot(id_: Optional[str] = None) -> Optional[Bot]: """ 说明: 获取 bot 对象 """ try: - return list(nonebot.get_bots().values())[0] - except IndexError: + return nonebot.get_bot(id_) + except ValueError: return None @@ -213,14 +218,15 @@ def get_message_at(data: Union[str, Message]) -> List[int]: 说明: 获取消息中所有的 at 对象的 qq 参数: - :param data: event.json() + :param data: event.json(), event.message """ qq_list = [] if isinstance(data, str): - data = json.loads(data) - for msg in data["message"]: - if msg["type"] == "at": - qq_list.append(int(msg["data"]["qq"])) + event = json.loads(data) + if data and (message := event.get("message")): + for msg in message: + if msg and msg.get("type") == "at": + qq_list.append(int(msg["data"]["qq"])) else: for seg in data: if seg.type == "at": @@ -237,10 +243,11 @@ def get_message_img(data: Union[str, Message]) -> List[str]: """ img_list = [] if isinstance(data, str): - data = json.loads(data) - for msg in data["message"]: - if msg["type"] == "image": - img_list.append(msg["data"]["url"]) + event = json.loads(data) + if data and (message := event.get("message")): + for msg in message: + if msg["type"] == "image": + img_list.append(msg["data"]["url"]) else: for seg in data["image"]: img_list.append(seg.data["url"]) @@ -256,10 +263,11 @@ def get_message_face(data: Union[str, Message]) -> List[str]: """ face_list = [] if isinstance(data, str): - data = json.loads(data) - for msg in data["message"]: - if msg["type"] == "face": - face_list.append(msg["data"]["id"]) + event = json.loads(data) + if data and (message := event.get("message")): + for msg in message: + if msg["type"] == "face": + face_list.append(msg["data"]["id"]) else: for seg in data["face"]: face_list.append(seg.data["id"]) @@ -275,10 +283,11 @@ def get_message_img_file(data: Union[str, Message]) -> List[str]: """ file_list = [] if isinstance(data, str): - data = json.loads(data) - for msg in data["message"]: - if msg["type"] == "image": - file_list.append(msg["data"]["file"]) + event = json.loads(data) + if data and (message := event.get("message")): + for msg in message: + if msg["type"] == "image": + file_list.append(msg["data"]["file"]) else: for seg in data["image"]: file_list.append(seg.data["file"]) @@ -294,10 +303,13 @@ def get_message_text(data: Union[str, Message]) -> str: """ result = "" if isinstance(data, str): - data = json.loads(data) - for msg in data["message"]: - if msg["type"] == "text": - result += msg["data"]["text"].strip() + " " + event = json.loads(data) + if data and (message := event.get("message")): + if isinstance(message, str): + return message.strip() + for msg in message: + if msg["type"] == "text": + result += msg["data"]["text"].strip() + " " return result.strip() else: for seg in data["text"]: @@ -314,10 +326,11 @@ def get_message_record(data: Union[str, Message]) -> List[str]: """ record_list = [] if isinstance(data, str): - data = json.loads(data) - for msg in data["message"]: - if msg["type"] == "record": - record_list.append(msg["data"]["url"]) + event = json.loads(data) + if data and (message := event.get("message")): + for msg in message: + if msg["type"] == "record": + record_list.append(msg["data"]["url"]) else: for seg in data["record"]: record_list.append(seg.data["url"]) @@ -333,21 +346,22 @@ def get_message_json(data: str) -> List[dict]: """ try: json_list = [] - data = json.loads(data) - for msg in data["message"]: - if msg["type"] == "json": - json_list.append(msg["data"]) + event = json.loads(data) + if data and (message := event.get("message")): + for msg in message: + if msg["type"] == "json": + json_list.append(msg["data"]) return json_list except KeyError: return [] -def get_local_proxy(): +def get_local_proxy() -> Optional[str]: """ 说明: 获取 config.py 中设置的代理 """ - return SYSTEM_PROXY if SYSTEM_PROXY else None + return SYSTEM_PROXY or None def is_chinese(word: str) -> bool: @@ -411,7 +425,7 @@ def cn2py(word: str) -> str: def change_pixiv_image_links( - url: str, size: Optional[str] = None, nginx_url: Optional[str] = None + url: str, size: Optional[str] = None, nginx_url: Optional[str] = None ): """ 说明: @@ -431,8 +445,8 @@ def change_pixiv_image_links( if nginx_url: url = ( url.replace("i.pximg.net", nginx_url) - .replace("i.pixiv.cat", nginx_url) - .replace("_webp", "") + .replace("i.pixiv.cat", nginx_url) + .replace("_webp", "") ) return url @@ -449,5 +463,69 @@ def change_img_md5(path_file: Union[str, Path]) -> bool: f.write(str(int(time.time() * 1000))) return True except Exception as e: - logger.warning(f"改变图片MD5发生错误 {type(e)}:{e} Path:{path_file}") + logger.warning(f"改变图片MD5错误 Path:{path_file}", e=e) return False + + +async def broadcast_group( + message: Union[str, Message, MessageSegment], + bot: Optional[Union[Bot, List[Bot]]] = None, + bot_id: Optional[Union[str, Set[str]]] = None, + ignore_group: Optional[Set[int]] = None, + check_func: Optional[Callable[[int], bool]] = None, + log_cmd: Optional[str] = None, +): + """获取所有Bot或指定Bot对象广播群聊 + + Args: + message (Any): 广播消息内容 + bot (Optional[Bot], optional): 指定bot对象. Defaults to None. + bot_id (Optional[str], optional): 指定bot id. Defaults to None. + ignore_group (Optional[List[int]], optional): 忽略群聊列表. Defaults to None. + check_func (Optional[Callable[[int], bool]], optional): 发送前对群聊检测方法,判断是否发送. Defaults to None. + log_cmd (Optional[str], optional): 日志标记. Defaults to None. + """ + if not message: + raise ValueError("群聊广播消息不能为空") + bot_dict = nonebot.get_bots() + bot_list: List[Bot] = [] + if bot: + if isinstance(bot, list): + bot_list = bot + else: + bot_list.append(bot) + elif bot_id: + _bot_id_list = bot_id + if isinstance(bot_id, str): + _bot_id_list = [bot_id] + for id_ in _bot_id_list: + if bot_id in bot_dict: + bot_list.append(bot_dict[bot_id]) + else: + logger.warning(f"Bot:{id_} 对象未连接或不存在") + else: + bot_list = list(bot_dict.values()) + _used_group = [] + for _bot in bot_list: + try: + if _group_list := await _bot.get_group_list(): + group_id_list = [g["group_id"] for g in _group_list] + for group_id in set(group_id_list): + try: + if ( + ignore_group and group_id in ignore_group + ) or group_id in _used_group: + continue + if check_func and not check_func(group_id): + continue + _used_group.append(group_id) + await _bot.send_group_msg(group_id=group_id, message=message) + except Exception as e: + logger.error( + f"广播群发消息失败: {message}", + command=log_cmd, + group_id=group_id, + e=e, + ) + except Exception as e: + logger.error(f"Bot: {_bot.self_id} 获取群聊列表失败", command=log_cmd, e=e) From 86ac7709713efed96835c50f6a2356fb3bdbbafb Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Sat, 18 Feb 2023 18:48:36 +0800 Subject: [PATCH 5/5] modified: README.md --- README.md | 330 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 189 insertions(+), 141 deletions(-) diff --git a/README.md b/README.md index 7cca7e8b..c1e7788b 100644 --- a/README.md +++ b/README.md @@ -5,22 +5,27 @@ ![maven](https://img.shields.io/badge/go--cqhttp-1.0.0-red) # 绪山真寻Bot + **** 此项目基于 Nonebot2 和 go-cqhttp 开发,以 postgresql 作为数据库的QQ群娱乐机器人 + ## 关于 + 用爱发电,某些功能学习借鉴了大佬们的代码,因为绪山真寻实在太可爱了因此开发了 绪山真寻bot,实现了一些对群友的娱乐功能和实用功能(大概)。 如果该项目的图片等等侵犯猫豆腐老师权益请联系我删除! -是新手!希望有个地方讨论绪山真寻Bot,或者有问题或建议,可以发送issues或加入[ [是真寻酱哒](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ) ] +是新手!希望有个地方讨论绪山真寻Bot,或者有问题或建议,可以发送issues或加入[ [是真寻酱哒(萌新版)](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ) ] -更喜欢讨论可爱的真寻,讨论插件开发,nonebot2开发,可以加入[ [真寻酱的技术群](https://jq.qq.com/?_wv=1027&k=E6sUkMzo) ]) +[//]: # (是老手!讨论插件开发,nonebot2开发,可以加入[ [真寻酱的技术群](https://jq.qq.com/?_wv=1027&k=u8PgBkMZ) ]) ## 声明 + 此项目仅用于学习交流,请勿用于非法用途 # Nonebot2 + 非常 [ **[NICE](https://github.com/nonebot/nonebot2)** ] 的OneBot框架 @@ -30,18 +35,23 @@ # [传送门](https://hibikier.github.io/zhenxun_bot/) ## 真寻的帮助 + 请对真寻说: '真寻帮助' or '管理员帮助' or '超级用户帮助' or '真寻帮助 指令' ## 普通帮助图片 + ![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/help.png) ## HTML版帮助图片 + ![x](https://raw.githubusercontent.com/HibiKier/zhenxun_bot/main/docs_image/html_help.png) ## Web UI + [zhenxun_bot_webui](https://github.com/HibiKier/zhenxun_bot_webui) ## 一键安装脚本 + [zhenxun_bot-deploy](https://github.com/AkashiCoin/zhenxun_bot-deploy) ## 提供符合真寻标准的插件仓库 @@ -50,152 +60,164 @@ ## 来点优点? - * 实现了许多功能,且提供了大量功能管理命令 - * 通过Config配置项将所有插件配置统计保存至config.yaml,利于统一用户修改 - * 方便增删插件,原生nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息` - * 提供了cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`__plugin_cd_limit__` - * __..... 更多详细请通过`传送门`查看文档!__ - +* 实现了许多功能,且提供了大量功能管理命令 +* 通过Config配置项将所有插件配置统计保存至config.yaml,利于统一用户修改 +* 方便增删插件,原生nonebot2 matcher,不需要额外修改,仅仅通过简单的配置属性就可以生成`帮助图片`和`帮助信息` +* 提供了cd,阻塞,每日次数等限制,仅仅通过简单的属性就可以生成一个限制,例如:`__plugin_cd_limit__` +* __..... 更多详细请通过`传送门`查看文档!__ ## 功能列表 +
已实现的功能 ### 已实现的常用功能 -- [x] 昵称系统(群与群与私聊分开.) -- [x] 图灵AI(会把'你'等关键字替换为你的昵称),且带有 [AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus),够味 -- [x] 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置) -- [x] 发送某文件夹下的随机图片(支持自定义,默认:美图,萝莉,壁纸) -- [x] 色图(这不是基础功能嘛喂) -- [x] coser -- [x] 黑白草图生成器 -- [x] 鸡汤/语录 -- [x] 骂我(钉宫语音) -- [x] 戳一戳(概率发送美图,钉宫语音或者戳回去) -- [x] 模拟开箱/我的开箱/群开箱统计/我的金色/设置cookie(csgo,内置爬虫脚本,需要提前抓取数据和图片,需要session,可能需要代理,阿里云服务器等ip也许已经被ban了(我无代理访问失败),如果访问太多账号API调用可能被禁止访问api!) -- [x] 鲁迅说过 -- [x] 构造假消息(自定义的分享链接) -- [x] 商店/我的金币/购买道具/使用道具 -- [x] 8种手游抽卡 (查看 [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw)) -- [x] 我有一个朋友想问问..(借鉴pcrbot插件) -- [x] 原神黄历 -- [x] 原神今日素材 -- [x] 原神资源查询 (借鉴[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件) -- [x] 原神便笺查询 -- [x] 原神玩家查询 -- [x] 原神树脂提醒 -- [x] 原神签到/自动签到 -- [x] 金币红包 -- [x] 微博热搜 -- [x] B站主播/UP/番剧订阅 -- [x] pil对图片的一些操作 -- [x] BUFF饰品底价查询(需要session) -- [x] 天气查询 -- [x] 疫情查询 -- [x] bt磁力搜索(咳咳,这功能我想dddd) -- [x] reimu搜索(上车) (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件) -- [x] 靠图识番 (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件) -- [x] 以图搜图 (使用[nonebot_plugin_picsearcher](https://github.com/synodriver/nonebot_plugin_picsearcher)插件) -- [x] 搜番 -- [x] 点歌 [nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2)插件(删除了选歌和评论) -- [x] epic免费游戏 -- [x] p站排行榜 -- [x] p站搜图 -- [x] 翻译(日英韩) -- [x] pix图库(一个自己的图库,含有增删查改,黑名单等命令) +* [x] 昵称系统(群与群与私聊分开.) -- [x] 查看当前群欢迎消息 -- [x] 查看该群自己的权限 -- [x] 我的信息(只是为了看看什么时候入群) -- [x] 更新信息(如果继续更新的话) -- [x] go-cqhttp最新版下载和上传(不需要请删除) -- [x] 撤回 -- [x] 滴滴滴-(用户对超级用户发送消息) -- [x] 金币红包/金币排行 -- [x] 俄罗斯轮盘/胜场排行/败场排行/欧洲人排行/慈善家排行 -- [x] 网易云热评 -- [x] 念首古诗 -- [x] 获取b站视频封面 -- [x] 通过PID获取图片 -- [x] 功能统计可视化 -- [x] 词云 -- [x] 关于 +* [x] 图灵AI(会把'你'等关键字替换为你的昵称),且带有 [AnimeThesaurus](https://github.com/Kyomotoi/AnimeThesaurus),够味 +* [x] 签到/我的签到/好感度排行/好感度总排行(影响色图概率和开箱次数,支持配置) +* [x] 发送某文件夹下的随机图片(支持自定义,默认:美图,萝莉,壁纸) +* [x] 色图(这不是基础功能嘛喂) +* [x] coser +* [x] 黑白草图生成器 +* [x] 鸡汤/语录 +* [x] 骂我(钉宫语音) +* [x] 戳一戳(概率发送美图,钉宫语音或者戳回去) +* [x] 模拟开箱/我的开箱/群开箱统计/我的金色/设置cookie(csgo,内置爬虫脚本,需要提前抓取数据和图片,需要session,可能需要代理,阿里云服务器等ip也许已经被ban了(我无代理访问失败),如果访问太多账号API调用可能被禁止访问api!) +* [x] 鲁迅说过 +* [x] 构造假消息(自定义的分享链接) +* [x] 商店/我的金币/购买道具/使用道具 +* [x] 8种手游抽卡 (查看 [nonebot_plugin_gamedraw](https://github.com/HibiKier/nonebot_plugin_gamedraw)) +* [x] 我有一个朋友想问问..(借鉴pcrbot插件) +* [x] 原神黄历 +* [x] 原神今日素材 +* [x] 原神资源查询 (借鉴[Genshin_Impact_bot](https://github.com/H-K-Y/Genshin_Impact_bot)插件) +* [x] 原神便笺查询 +* [x] 原神玩家查询 +* [x] 原神树脂提醒 +* [x] 原神签到/自动签到 +* [x] 金币红包 +* [x] 微博热搜 +* [x] B站主播/UP/番剧订阅 + +* [x] pil对图片的一些操作 +* [x] BUFF饰品底价查询(需要session) +* [x] 天气查询 +* [x] 疫情查询 +* [x] bt磁力搜索(咳咳,这功能我想dddd) +* [x] reimu搜索(上车) (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件) +* [x] 靠图识番 (使用[XUN_Langskip](https://github.com/Angel-Hair/XUN_Bot)的插件) +* [x] 以图搜图 (使用[nonebot_plugin_picsearcher](https://github.com/synodriver/nonebot_plugin_picsearcher)插件) +* [x] 搜番 +* [x] 点歌 [nonebot_plugin_songpicker2](https://github.com/maxesisn/nonebot_plugin_songpicker2)插件(删除了选歌和评论) +* [x] epic免费游戏 +* [x] p站排行榜 +* [x] p站搜图 +* [x] 翻译(日英韩) +* [x] pix图库(一个自己的图库,含有增删查改,黑名单等命令) + +* [x] 查看当前群欢迎消息 +* [x] 查看该群自己的权限 +* [x] 我的信息(只是为了看看什么时候入群) +* [x] 更新信息(如果继续更新的话) +* [x] go-cqhttp最新版下载和上传(不需要请删除) +* [x] 撤回 +* [x] 滴滴滴-(用户对超级用户发送消息) +* [x] 金币红包/金币排行 +* [x] 俄罗斯轮盘/胜场排行/败场排行/欧洲人排行/慈善家排行 +* [x] 网易云热评 +* [x] 念首古诗 +* [x] 获取b站视频封面 +* [x] 通过PID获取图片 +* [x] 功能统计可视化 +* [x] 词云 +* [x] 关于 ### 已实现的管理员功能 -- [x] 更新群组成员信息 -- [x] 95%的群功能开关 -- [x] 查看群内被动技能状态 -- [x] 自定义群欢迎消息(是真寻的不是管家的!) -- [x] .ban/.unban(支持设置ban时长)= 黑白名单 -- [x] 刷屏禁言相关:刷屏检测设置/设置禁言时长/设置检测次数 -- [x] 上传图片/连续上传图片 (上传图片至指定图库) -- [x] 移动图片 (同上) -- [x] 删除图片 (同上) -- [x] 群内B站订阅 -- [x] 词条设置 -- [x] 休息吧/醒来 + +* [x] 更新群组成员信息 + +* [x] 95%的群功能开关 +* [x] 查看群内被动技能状态 +* [x] 自定义群欢迎消息(是真寻的不是管家的!) +* [x] .ban/.unban(支持设置ban时长)= 黑白名单 +* [x] 刷屏禁言相关:刷屏检测设置/设置禁言时长/设置检测次数 +* [x] 上传图片/连续上传图片 (上传图片至指定图库) +* [x] 移动图片 (同上) +* [x] 删除图片 (同上) +* [x] 群内B站订阅 +* [x] 词条设置 +* [x] 休息吧/醒来 ### 已实现的超级用户功能 -- [x] 添加/删除权限(是真寻的管理员权限,不是群管理员) -- [x] 开启/关闭指定群的广播通知 -- [x] 广播 -- [x] 自检(检查系统状态) -- [x] 所有群组/所有好友 -- [x] 退出指定群 -- [x] 更新好友信息/更新群信息 -- [x] /t(对用户进行回复或发送消息) -- [x] 上传/删除/修改商品(需要编写对应的商品功能) -- [x] 节日红包发送 -- [x] 修改群权限 -- [x] ban -- [x] 更新色图 -- [x] 更新价格/更加图片(csgo开箱) -- [x] 重载原神/方舟/赛马娘/坎公骑冠剑卡池 -- [x] 更新原神今日素材/更新原神资源信息 -- [x] PIX相关操作 -- [x] 检查更新真寻 -- [x] 重启 -- [x] 添加/删除/查看群白名单 -- [x] 功能开关(更多设置) -- [x] 功能状态 -- [x] b了 -- [x] 执行sql -- [x] 重载配置 -- [x] 清理临时数据 -- [x] 增删群认证 -- [x] 同意/拒绝好友/群聊请求 -- [x] 配置重载 + +* [x] 添加/删除权限(是真寻的管理员权限,不是群管理员) + +* [x] 开启/关闭指定群的广播通知 +* [x] 广播 +* [x] 自检(检查系统状态) +* [x] 所有群组/所有好友 +* [x] 退出指定群 +* [x] 更新好友信息/更新群信息 +* [x] /t(对用户进行回复或发送消息) +* [x] 上传/删除/修改商品(需要编写对应的商品功能) +* [x] 节日红包发送 +* [x] 修改群权限 +* [x] ban +* [x] 更新色图 +* [x] 更新价格/更加图片(csgo开箱) +* [x] 重载原神/方舟/赛马娘/坎公骑冠剑卡池 +* [x] 更新原神今日素材/更新原神资源信息 +* [x] PIX相关操作 +* [x] 检查更新真寻 +* [x] 重启 +* [x] 添加/删除/查看群白名单 +* [x] 功能开关(更多设置) +* [x] 功能状态 +* [x] b了 +* [x] 执行sql +* [x] 重载配置 +* [x] 清理临时数据 +* [x] 增删群认证 +* [x] 同意/拒绝好友/群聊请求 +* [x] 配置重载 #### 超级用户的被动技能 -- [x] 邀请入群提醒(别人邀请真寻入群) -- [x] 添加好友提醒(别人添加真寻好友) + +* [x] 邀请入群提醒(别人邀请真寻入群) + +* [x] 添加好友提醒(别人添加真寻好友) ### 已实现的被动技能 -- [x] 进群欢迎消息 -- [x] 群早晚安 -- [x] 每日开箱重置提醒 -- [x] b站转发解析(解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv),且5分钟内不解析相同url -- [x] 丢人爬(爬表情包) -- [x] epic通知(每日发送epic免费游戏链接) -- [x] 原神黄历提醒 -- [x] 复读 -### 已实现的看不见的技能! -- [x] 刷屏禁言检测 -- [x] 功能调用统计 -- [x] 检测恶意触发命令(将被最高权限ban掉30分钟,只有最高权限(9级)可以进行unban) -- [x] 自动同意好友请求,加群请求将会提醒管理员,退群提示,加群欢迎等等 -- [x] 群聊时间检测(当群聊最后一人发言时间大于当前36小时后将关闭该群所有通知(即被动技能)) -- [x] 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限 -- [x] 群权限系统 -- [x] 定时更新权限 -- [x] 自动配置重载 +* [x] 进群欢迎消息 + +* [x] 群早晚安 +* [x] 每日开箱重置提醒 +* [x] b站转发解析(解析b站分享信息,支持bv,bilibili链接,b站手机端转发卡片,cv,b23.tv),且5分钟内不解析相同url +* [x] 丢人爬(爬表情包) +* [x] epic通知(每日发送epic免费游戏链接) +* [x] 原神黄历提醒 +* [x] 复读 + +### 已实现的看不见的技能 + +* [x] 刷屏禁言检测 + +* [x] 功能调用统计 +* [x] 检测恶意触发命令(将被最高权限ban掉30分钟,只有最高权限(9级)可以进行unban) +* [x] 自动同意好友请求,加群请求将会提醒管理员,退群提示,加群欢迎等等 +* [x] 群聊时间检测(当群聊最后一人发言时间大于当前36小时后将关闭该群所有通知(即被动技能)) +* [x] 群管理员监控,自动为新晋管理员增加权限,为失去群管理员的用户删除权限 +* [x] 群权限系统 +* [x] 定时更新权限 +* [x] 自动配置重载 +
## 详细配置请前往文档,以下为最简部署和配置,如果你有基础并学习过nonebot2的话 - ## 简单部署 ``` @@ -238,26 +260,38 @@ python bot.py ``` - ## 使用Docker -__Docker 单机版(仅真寻Bot)__ + +**Docker 单机版(仅真寻Bot)** **点击下方的 GitHub 徽标查看教程** [![Github](https://shields.io/badge/GITHUB-Sakuracio-4476AF?logo=github&style=for-the-badge)](https://github.com/Sakuracio/zhenxun_bot_docker) [![DOCKER](https://shields.io/badge/docker-hibikier/zhenxun_bot-4476AF?logo=docker&style=for-the-badge)](https://hub.docker.com/r/hibikier/zhenxun_bot) -__Docker 全量版(包含 真寻Bot PostgreSQL数据库 go-cqhttp webui等)__ +**Docker 全量版(包含 真寻Bot PostgreSQL数据库 go-cqhttp webui等)** [![Github](https://shields.io/badge/GITHUB-SinKy--Yan-4476AF?logo=github&style=for-the-badge)](https://github.com/SinKy-Yan/zhenxunbot-docker) [![DOCKER](https://shields.io/badge/docker-jyishit/zhenxun_bot-4476AF?logo=docker&style=for-the-badge)](https://hub.docker.com/r/jyishit/zhenxun_bot) **点击上方的 GitHub 徽标查看教程** PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能无法正常启动全量版容器** ## [爱发电](https://afdian.net/@HibiKier) +
爱发电 以及 感谢投喂 ### 感谢名单 -(可以告诉我你的 __github__ 地址,我偷偷换掉0v|) +(可以告诉我你的 **github** 地址,我偷偷换掉0v|) + +[shenqi](https://afdian.net/u/fa923a8cfe3d11eba61752540025c377) +[A_Kyuu](https://afdian.net/u/b83954fc2c1211eba9eb52540025c377) +[疯狂混沌](https://afdian.net/u/789a2f9200cd11edb38352540025c377) +[投冥](https://afdian.net/a/144514mm) +[茶喵](https://afdian.net/u/fd22382eac4d11ecbfc652540025c377) +[AemokpaTNR](https://afdian.net/u/1169bb8c8a9611edb0c152540025c377) +[爱发电用户_wrxn](https://afdian.net/u/4aa03d20db4311ecb1e752540025c377) +[qqw](https://afdian.net/u/b71db4e2cc3e11ebb76652540025c377) +[溫一壺月光下酒](https://afdian.net/u/ad667a5c650c11ed89bf52540025c377) +[伝木](https://afdian.net/u/246b80683f9511edba7552540025c377) [阿奎](https://afdian.net/u/da41f72845d511ed930d52540025c377) [醉梦尘逸](https://afdian.net/u/bc11d2683cd011ed99b552540025c377) [Abc](https://afdian.net/u/870dc10a3cd311ed828852540025c377) @@ -280,25 +314,37 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能 [ln](https://afdian.net/u/b51914ba1c6611ed8a4e52540025c377) [爱发电用户_b9S4](https://afdian.net/u/3d8f30581a2911edba6d52540025c377) [爱发电用户_c58s](https://afdian.net/u/a6ad8dda195e11ed9a4152540025c377) -[爱发电用户_eNr9](https://afdian.net/u/05fdb41c0c9a11ed814952540025c377) -[MangataAkihi](https://github.com/Sakuracio) -[炀](https://afdian.net/u/69b76e9ec77b11ec874f52540025c377) +[爱发电用户_eNr9](https://afdian.net/u/05fdb41c0c9a11ed814952540025c377) +[MangataAkihi](https://github.com/Sakuracio) +[炀](https://afdian.net/u/69b76e9ec77b11ec874f52540025c377) [爱发电用户_Bc6j](https://afdian.net/u/8546be24f44111eca64052540025c377) -[大魔王](https://github.com/xipesoy) +[大魔王](https://github.com/xipesoy) [CopilotLaLaLa](https://github.com/CopilotLaLaLa) -[嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377) +[嘿小欧](https://afdian.net/u/daa4bec4f24911ec82e552540025c377) [回忆的秋千](https://afdian.net/u/e315d9c6f14f11ecbeef52540025c377) [十年くん](https://github.com/shinianj) [哇](https://afdian.net/u/9b266244f23911eca19052540025c377) [yajiwa](https://github.com/yajiwa) [爆金币](https://afdian.net/u/0d78879ef23711ecb22452540025c377) -
- ## 更新 +### 2022/2/18 + +* 数据库舍弃`gino`使用`tortoise` +* 昵称提供命令`全局昵称设置` +* `manager_group`群管理操作中`退群`,`修改群权限`,`添加/删除群白名单`,`添加/删除群认证`在群聊中使用命令时且未指定群聊时,默认指定当前群聊 + +### 2022/1/31 + +* 修复B站转发卡片BUG [@pull/1249](https://github.com/HibiKier/zhenxun_bot/pull/1249) + +### 2022/1/27 + +* 替换pixiv反向代理地址 [@pull/1244](https://github.com/HibiKier/zhenxun_bot/pull/1244) + ### 2022/12/31 * 修复epic报错,优化简介 [@pull/1226](https://github.com/HibiKier/zhenxun_bot/pull/1226) @@ -457,7 +503,7 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能 ### 2022/9/27 -* 更新b站转发解析 [@pull/1117](https://github.com/HibiKier/zhenxun_bot/pull/1117) +* 更新b站转发解析 [@pull/1117](https://github.com/HibiKier/zhenxun_bot/pull/1117) ### 2022/9/24 @@ -556,12 +602,14 @@ PS: **ARM平台** 请使用全量版 同时 **如果你的机器 RAM < 1G 可能
-__..... 更多更新信息请查看文档__ +**..... 更多更新信息请查看文档** ## Todo -- [x] web管理 + +* [x] web管理 ## 感谢 + [botuniverse / onebot](https://github.com/botuniverse/onebot) :超棒的机器人协议 [Mrs4s / go-cqhttp](https://github.com/Mrs4s/go-cqhttp) :cqhttp的golang实现,轻量、原生跨平台. [nonebot / nonebot2](https://github.com/nonebot/nonebot2) :跨平台Python异步机器人框架