From 625f17a708d72030f3ba53e198f1fcf227688996 Mon Sep 17 00:00:00 2001 From: HibiKier <775757368@qq.com> Date: Thu, 29 Aug 2024 22:01:34 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20=E4=BF=AE=E5=A4=8Dbug=EF=BC=8C?= =?UTF-8?q?=E7=A7=BB=E9=99=A4plugins=E6=96=87=E4=BB=B6=E5=A4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 2 +- .../admin/group_member_update/_data_source.py | 53 +- .../builtin_plugins/auto_update/__init__.py | 12 +- zhenxun/builtin_plugins/init/__init__.py | 40 +- zhenxun/builtin_plugins/init/init_plugin.py | 121 ++-- .../platform/qq/group_handle.py | 6 +- .../plugin_store/data_source.py | 87 +-- zhenxun/builtin_plugins/record_request.py | 153 ++-- .../builtin_plugins/scheduler/auto_backup.py | 54 +- zhenxun/builtin_plugins/scheduler/morning.py | 11 +- .../superuser/broadcast/_data_source.py | 8 +- zhenxun/builtin_plugins/web_ui/utils.py | 32 +- zhenxun/configs/utils/__init__.py | 126 ++-- zhenxun/models/fg_request.py | 2 +- zhenxun/models/group_console.py | 65 +- zhenxun/models/task_info.py | 40 +- zhenxun/plugins/ai/__init__.py | 87 --- zhenxun/plugins/ai/data_source.py | 241 ------- zhenxun/plugins/ai/utils.py | 153 ---- zhenxun/plugins/alapi/__init__.py | 14 - zhenxun/plugins/alapi/_data_source.py | 29 - zhenxun/plugins/alapi/comments_163.py | 57 -- zhenxun/plugins/alapi/cover.py | 46 -- zhenxun/plugins/alapi/jitang.py | 48 -- zhenxun/plugins/alapi/poetry.py | 57 -- zhenxun/plugins/black_word/__init__.py | 5 - zhenxun/plugins/black_word/black_watch.py | 62 -- zhenxun/plugins/black_word/black_word.py | 235 ------- zhenxun/plugins/black_word/data_source.py | 103 --- zhenxun/plugins/black_word/model.py | 154 ---- zhenxun/plugins/black_word/utils.py | 374 ---------- zhenxun/plugins/bt/__init__.py | 78 --- zhenxun/plugins/bt/data_source.py | 54 -- zhenxun/plugins/check/__init__.py | 40 -- zhenxun/plugins/check/data_source.py | 83 --- zhenxun/plugins/coser.py | 90 --- zhenxun/plugins/dialogue/__init__.py | 158 ----- zhenxun/plugins/dialogue/_data_source.py | 55 -- zhenxun/plugins/draw_card/__init__.py | 293 -------- zhenxun/plugins/draw_card/config.py | 203 ------ zhenxun/plugins/draw_card/count_manager.py | 149 ---- .../plugins/draw_card/handles/azur_handle.py | 308 -------- .../plugins/draw_card/handles/ba_handle.py | 157 ----- .../plugins/draw_card/handles/base_handle.py | 295 -------- .../plugins/draw_card/handles/fgo_handle.py | 223 ------ .../draw_card/handles/genshin_handle.py | 461 ------------ .../draw_card/handles/guardian_handle.py | 400 ----------- .../draw_card/handles/onmyoji_handle.py | 178 ----- .../plugins/draw_card/handles/pcr_handle.py | 149 ---- .../draw_card/handles/pretty_handle.py | 423 ----------- .../plugins/draw_card/handles/prts_handle.py | 344 --------- zhenxun/plugins/draw_card/rule.py | 10 - zhenxun/plugins/draw_card/util.py | 61 -- zhenxun/plugins/epic/__init__.py | 40 -- zhenxun/plugins/epic/data_source.py | 180 ----- zhenxun/plugins/fudu.py | 151 ---- zhenxun/plugins/gold_redbag/__init__.py | 349 ---------- zhenxun/plugins/gold_redbag/config.py | 372 ---------- zhenxun/plugins/gold_redbag/data_source.py | 234 ------- zhenxun/plugins/gold_redbag/model.py | 63 -- zhenxun/plugins/group_welcome_msg.py | 62 -- zhenxun/plugins/image_management/__init__.py | 69 -- zhenxun/plugins/image_management/_config.py | 14 - .../plugins/image_management/_data_source.py | 196 ------ .../plugins/image_management/delete_image.py | 107 --- .../image_management/image_management_log.py | 27 - .../plugins/image_management/move_image.py | 137 ---- .../plugins/image_management/send_image.py | 0 .../plugins/image_management/upload_image.py | 195 ------ zhenxun/plugins/luxun.py | 74 -- zhenxun/plugins/mute/__init__.py | 5 - zhenxun/plugins/mute/_data_source.py | 124 ---- zhenxun/plugins/mute/mute_message.py | 56 -- zhenxun/plugins/mute/mute_setting.py | 117 ---- zhenxun/plugins/nbnhhsh.py | 62 -- zhenxun/plugins/one_friend/__init__.py | 77 -- zhenxun/plugins/open_cases/__init__.py | 329 --------- zhenxun/plugins/open_cases/build_image.py | 155 ----- zhenxun/plugins/open_cases/command.py | 75 -- zhenxun/plugins/open_cases/config.py | 253 ------- zhenxun/plugins/open_cases/models/__init__.py | 0 .../plugins/open_cases/models/buff_prices.py | 22 - .../plugins/open_cases/models/buff_skin.py | 113 --- .../open_cases/models/buff_skin_log.py | 50 -- .../open_cases/models/open_cases_log.py | 44 -- .../open_cases/models/open_cases_user.py | 60 -- zhenxun/plugins/open_cases/open_cases_c.py | 481 ------------- zhenxun/plugins/open_cases/utils.py | 657 ------------------ zhenxun/plugins/parse_bilibili/__init__.py | 186 ----- zhenxun/plugins/parse_bilibili/get_image.py | 106 --- .../parse_bilibili/information_container.py | 53 -- zhenxun/plugins/parse_bilibili/parse_url.py | 65 -- zhenxun/plugins/pid_search.py | 125 ---- zhenxun/plugins/pix_gallery/__init__.py | 62 -- zhenxun/plugins/pix_gallery/_data_source.py | 426 ------------ .../plugins/pix_gallery/_model/__init__.py | 1 - .../pix_gallery/_model/omega_pixiv_illusts.py | 89 --- zhenxun/plugins/pix_gallery/_model/pixiv.py | 91 --- .../pix_gallery/_model/pixiv_keyword_user.py | 52 -- zhenxun/plugins/pix_gallery/pix.py | 247 ------- .../plugins/pix_gallery/pix_add_keyword.py | 135 ---- .../pix_gallery/pix_pass_del_keyword.py | 218 ------ zhenxun/plugins/pix_gallery/pix_show_info.py | 85 --- zhenxun/plugins/pix_gallery/pix_update.py | 225 ------ zhenxun/plugins/pixiv_rank_search/__init__.py | 225 ------ .../plugins/pixiv_rank_search/data_source.py | 171 ----- zhenxun/plugins/poke/__init__.py | 92 --- zhenxun/plugins/quotations.py | 32 - zhenxun/plugins/roll.py | 66 -- zhenxun/plugins/russian/__init__.py | 201 ------ zhenxun/plugins/russian/command.py | 108 --- zhenxun/plugins/russian/data_source.py | 535 -------------- zhenxun/plugins/russian/model.py | 107 --- zhenxun/plugins/search_anime/__init__.py | 68 -- zhenxun/plugins/search_anime/data_source.py | 53 -- .../search_buff_skin_price/__init__.py | 104 --- .../search_buff_skin_price/data_source.py | 62 -- zhenxun/plugins/search_image/__init__.py | 94 --- zhenxun/plugins/search_image/saucenao.py | 62 -- zhenxun/plugins/send_setu_/__init__.py | 5 - zhenxun/plugins/send_setu_/_model.py | 87 --- .../plugins/send_setu_/send_setu/__init__.py | 243 ------- .../send_setu_/send_setu/_data_source.py | 360 ---------- .../send_setu_/update_setu/__init__.py | 59 -- .../send_setu_/update_setu/data_source.py | 187 ----- zhenxun/plugins/send_voice/__init__.py | 5 - zhenxun/plugins/send_voice/dinggong.py | 51 -- zhenxun/plugins/translate/__init__.py | 91 --- zhenxun/plugins/translate/data_source.py | 83 --- zhenxun/plugins/wbtop/__init__.py | 56 -- zhenxun/plugins/wbtop/data_source.py | 63 -- zhenxun/plugins/what_anime/__init__.py | 60 -- zhenxun/plugins/what_anime/data_source.py | 47 -- zhenxun/plugins/word_bank/__init__.py | 18 - zhenxun/plugins/word_bank/_config.py | 49 -- zhenxun/plugins/word_bank/_data_source.py | 295 -------- zhenxun/plugins/word_bank/_model.py | 591 ---------------- zhenxun/plugins/word_bank/_rule.py | 58 -- zhenxun/plugins/word_bank/command.py | 47 -- zhenxun/plugins/word_bank/message_handle.py | 31 - zhenxun/plugins/word_bank/word_handle.py | 328 --------- zhenxun/utils/common_utils.py | 36 + zhenxun/utils/decorator/shop.py | 27 +- zhenxun/utils/platform.py | 201 +++--- zhenxun/utils/utils.py | 23 +- zhenxun/utils/withdraw_manage.py | 10 +- 146 files changed, 578 insertions(+), 18318 deletions(-) delete mode 100644 zhenxun/plugins/ai/__init__.py delete mode 100644 zhenxun/plugins/ai/data_source.py delete mode 100644 zhenxun/plugins/ai/utils.py delete mode 100644 zhenxun/plugins/alapi/__init__.py delete mode 100644 zhenxun/plugins/alapi/_data_source.py delete mode 100644 zhenxun/plugins/alapi/comments_163.py delete mode 100644 zhenxun/plugins/alapi/cover.py delete mode 100644 zhenxun/plugins/alapi/jitang.py delete mode 100644 zhenxun/plugins/alapi/poetry.py delete mode 100644 zhenxun/plugins/black_word/__init__.py delete mode 100644 zhenxun/plugins/black_word/black_watch.py delete mode 100644 zhenxun/plugins/black_word/black_word.py delete mode 100644 zhenxun/plugins/black_word/data_source.py delete mode 100644 zhenxun/plugins/black_word/model.py delete mode 100644 zhenxun/plugins/black_word/utils.py delete mode 100644 zhenxun/plugins/bt/__init__.py delete mode 100644 zhenxun/plugins/bt/data_source.py delete mode 100644 zhenxun/plugins/check/__init__.py delete mode 100644 zhenxun/plugins/check/data_source.py delete mode 100644 zhenxun/plugins/coser.py delete mode 100644 zhenxun/plugins/dialogue/__init__.py delete mode 100644 zhenxun/plugins/dialogue/_data_source.py delete mode 100644 zhenxun/plugins/draw_card/__init__.py delete mode 100644 zhenxun/plugins/draw_card/config.py delete mode 100644 zhenxun/plugins/draw_card/count_manager.py delete mode 100644 zhenxun/plugins/draw_card/handles/azur_handle.py delete mode 100644 zhenxun/plugins/draw_card/handles/ba_handle.py delete mode 100644 zhenxun/plugins/draw_card/handles/base_handle.py delete mode 100644 zhenxun/plugins/draw_card/handles/fgo_handle.py delete mode 100644 zhenxun/plugins/draw_card/handles/genshin_handle.py delete mode 100644 zhenxun/plugins/draw_card/handles/guardian_handle.py delete mode 100644 zhenxun/plugins/draw_card/handles/onmyoji_handle.py delete mode 100644 zhenxun/plugins/draw_card/handles/pcr_handle.py delete mode 100644 zhenxun/plugins/draw_card/handles/pretty_handle.py delete mode 100644 zhenxun/plugins/draw_card/handles/prts_handle.py delete mode 100644 zhenxun/plugins/draw_card/rule.py delete mode 100644 zhenxun/plugins/draw_card/util.py delete mode 100644 zhenxun/plugins/epic/__init__.py delete mode 100644 zhenxun/plugins/epic/data_source.py delete mode 100644 zhenxun/plugins/fudu.py delete mode 100644 zhenxun/plugins/gold_redbag/__init__.py delete mode 100644 zhenxun/plugins/gold_redbag/config.py delete mode 100644 zhenxun/plugins/gold_redbag/data_source.py delete mode 100644 zhenxun/plugins/gold_redbag/model.py delete mode 100644 zhenxun/plugins/group_welcome_msg.py delete mode 100644 zhenxun/plugins/image_management/__init__.py delete mode 100644 zhenxun/plugins/image_management/_config.py delete mode 100644 zhenxun/plugins/image_management/_data_source.py delete mode 100644 zhenxun/plugins/image_management/delete_image.py delete mode 100644 zhenxun/plugins/image_management/image_management_log.py delete mode 100644 zhenxun/plugins/image_management/move_image.py delete mode 100644 zhenxun/plugins/image_management/send_image.py delete mode 100644 zhenxun/plugins/image_management/upload_image.py delete mode 100644 zhenxun/plugins/luxun.py delete mode 100644 zhenxun/plugins/mute/__init__.py delete mode 100644 zhenxun/plugins/mute/_data_source.py delete mode 100644 zhenxun/plugins/mute/mute_message.py delete mode 100644 zhenxun/plugins/mute/mute_setting.py delete mode 100644 zhenxun/plugins/nbnhhsh.py delete mode 100644 zhenxun/plugins/one_friend/__init__.py delete mode 100644 zhenxun/plugins/open_cases/__init__.py delete mode 100644 zhenxun/plugins/open_cases/build_image.py delete mode 100644 zhenxun/plugins/open_cases/command.py delete mode 100644 zhenxun/plugins/open_cases/config.py delete mode 100644 zhenxun/plugins/open_cases/models/__init__.py delete mode 100644 zhenxun/plugins/open_cases/models/buff_prices.py delete mode 100644 zhenxun/plugins/open_cases/models/buff_skin.py delete mode 100644 zhenxun/plugins/open_cases/models/buff_skin_log.py delete mode 100644 zhenxun/plugins/open_cases/models/open_cases_log.py delete mode 100644 zhenxun/plugins/open_cases/models/open_cases_user.py delete mode 100644 zhenxun/plugins/open_cases/open_cases_c.py delete mode 100644 zhenxun/plugins/open_cases/utils.py delete mode 100644 zhenxun/plugins/parse_bilibili/__init__.py delete mode 100644 zhenxun/plugins/parse_bilibili/get_image.py delete mode 100644 zhenxun/plugins/parse_bilibili/information_container.py delete mode 100644 zhenxun/plugins/parse_bilibili/parse_url.py delete mode 100644 zhenxun/plugins/pid_search.py delete mode 100644 zhenxun/plugins/pix_gallery/__init__.py delete mode 100644 zhenxun/plugins/pix_gallery/_data_source.py delete mode 100644 zhenxun/plugins/pix_gallery/_model/__init__.py delete mode 100644 zhenxun/plugins/pix_gallery/_model/omega_pixiv_illusts.py delete mode 100644 zhenxun/plugins/pix_gallery/_model/pixiv.py delete mode 100644 zhenxun/plugins/pix_gallery/_model/pixiv_keyword_user.py delete mode 100644 zhenxun/plugins/pix_gallery/pix.py delete mode 100644 zhenxun/plugins/pix_gallery/pix_add_keyword.py delete mode 100644 zhenxun/plugins/pix_gallery/pix_pass_del_keyword.py delete mode 100644 zhenxun/plugins/pix_gallery/pix_show_info.py delete mode 100644 zhenxun/plugins/pix_gallery/pix_update.py delete mode 100644 zhenxun/plugins/pixiv_rank_search/__init__.py delete mode 100644 zhenxun/plugins/pixiv_rank_search/data_source.py delete mode 100644 zhenxun/plugins/poke/__init__.py delete mode 100644 zhenxun/plugins/quotations.py delete mode 100644 zhenxun/plugins/roll.py delete mode 100644 zhenxun/plugins/russian/__init__.py delete mode 100644 zhenxun/plugins/russian/command.py delete mode 100644 zhenxun/plugins/russian/data_source.py delete mode 100644 zhenxun/plugins/russian/model.py delete mode 100644 zhenxun/plugins/search_anime/__init__.py delete mode 100644 zhenxun/plugins/search_anime/data_source.py delete mode 100644 zhenxun/plugins/search_buff_skin_price/__init__.py delete mode 100644 zhenxun/plugins/search_buff_skin_price/data_source.py delete mode 100644 zhenxun/plugins/search_image/__init__.py delete mode 100644 zhenxun/plugins/search_image/saucenao.py delete mode 100644 zhenxun/plugins/send_setu_/__init__.py delete mode 100644 zhenxun/plugins/send_setu_/_model.py delete mode 100644 zhenxun/plugins/send_setu_/send_setu/__init__.py delete mode 100644 zhenxun/plugins/send_setu_/send_setu/_data_source.py delete mode 100644 zhenxun/plugins/send_setu_/update_setu/__init__.py delete mode 100644 zhenxun/plugins/send_setu_/update_setu/data_source.py delete mode 100644 zhenxun/plugins/send_voice/__init__.py delete mode 100644 zhenxun/plugins/send_voice/dinggong.py delete mode 100644 zhenxun/plugins/translate/__init__.py delete mode 100644 zhenxun/plugins/translate/data_source.py delete mode 100644 zhenxun/plugins/wbtop/__init__.py delete mode 100644 zhenxun/plugins/wbtop/data_source.py delete mode 100644 zhenxun/plugins/what_anime/__init__.py delete mode 100644 zhenxun/plugins/what_anime/data_source.py delete mode 100644 zhenxun/plugins/word_bank/__init__.py delete mode 100644 zhenxun/plugins/word_bank/_config.py delete mode 100644 zhenxun/plugins/word_bank/_data_source.py delete mode 100644 zhenxun/plugins/word_bank/_model.py delete mode 100644 zhenxun/plugins/word_bank/_rule.py delete mode 100644 zhenxun/plugins/word_bank/command.py delete mode 100644 zhenxun/plugins/word_bank/message_handle.py delete mode 100644 zhenxun/plugins/word_bank/word_handle.py create mode 100644 zhenxun/utils/common_utils.py diff --git a/pyproject.toml b/pyproject.toml index c6ec88a28..5d18841a2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,7 +119,7 @@ fixture-parentheses = false mark-parentheses = false [tool.pyright] -pythonVersion = "3.9" +pythonVersion = "3.10" pythonPlatform = "All" defineConstant = { PYDANTIC_V2 = true } executionEnvironments = [ diff --git a/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py b/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py index 7bda88e72..acb277e5b 100644 --- a/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py +++ b/zhenxun/builtin_plugins/admin/group_member_update/_data_source.py @@ -1,20 +1,18 @@ -import time -from datetime import datetime, timedelta, timezone +from datetime import datetime, timezone, timedelta from nonebot.adapters import Bot -# from nonebot.adapters.discord import Bot as DiscordBot -# from nonebot.adapters.dodo import Bot as DodoBot -from nonebot.adapters.dodo.models import MemberInfo - # from nonebot.adapters.kaiheila import Bot as KaiheilaBot from nonebot.adapters.onebot.v11 import Bot as v11Bot from nonebot.adapters.onebot.v12 import Bot as v12Bot +from zhenxun.services.log import logger from zhenxun.configs.config import Config -from zhenxun.models.group_member_info import GroupInfoUser from zhenxun.models.level_user import LevelUser -from zhenxun.services.log import logger +from zhenxun.models.group_member_info import GroupInfoUser + +# from nonebot.adapters.discord import Bot as DiscordBot +# from nonebot.adapters.dodo import Bot as DodoBot class MemberUpdateManage: @@ -98,29 +96,27 @@ async def v11(cls, bot: v11Bot, group_id: str): user_id = str(user_info["user_id"]) nickname = user_info["card"] or user_info["nickname"] role = user_info["role"] - if default_auth: - if role in ["owner", "admin"] and not await LevelUser.is_group_flag( - user_id, group_id - ): - if role == "owner": - await LevelUser.set_level(user_id, group_id, default_auth + 1) - else: - await LevelUser.set_level(user_id, group_id, default_auth) + if ( + default_auth + and role in ["owner", "admin"] + and not await LevelUser.is_group_flag(user_id, group_id) + ): + if role == "owner": + await LevelUser.set_level(user_id, group_id, default_auth + 1) + else: + await LevelUser.set_level(user_id, group_id, default_auth) if user_id in bot.config.superusers: await LevelUser.set_level(user_id, group_id, 9) - join_time = datetime.strptime( - time.strftime( - "%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"]) - ), - "%Y-%m-%d %H:%M:%S", - ).replace(tzinfo=timezone(timedelta(hours=8))) + join_time = datetime.fromtimestamp( + user_info["join_time"], timezone(timedelta(hours=8)) + ) if cnt := db_user_uid.count(user_id): users = [u for u in db_user if u.user_id == user_id] - user = users[0] if cnt > 1: for u in users[1:]: delete_list.append(u.id) if nickname != uid2name.get(user_id): + user = users[0] user.user_name = nickname update_list.append(user) else: @@ -156,7 +152,7 @@ async def v11(cls, bot: v11Bot, group_id: str): user_id__in=delete_member_list, group_id=group_id ).delete() logger.info( - f"删除已退群用户", "更新群组成员信息", group_id=group_id, platform="qq" + "删除已退群用户", "更新群组成员信息", group_id=group_id, platform="qq" ) @classmethod @@ -165,7 +161,8 @@ async def v12(cls, bot: v12Bot, group_id: str): pass # exist_member_list = [] # default_auth = Config.get_config("admin_bot_manage", "ADMIN_DEFAULT_AUTH") - # group_member_list: list[GetGroupMemberInfoResp] = await bot.get_group_member_list( + # group_member_list: list[GetGroupMemberInfoResp] = + # await bot.get_group_member_list( # group_id=group_id # ) # for user_info in group_member_list: @@ -180,7 +177,8 @@ async def v12(cls, bot: v12Bot, group_id: str): # if str(user_id) in bot.config.superusers: # await LevelUser.set_level(str(user_id), group_id, 9) # join_time = datetime.strptime( - # time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user_info["join_time"])), + # 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( @@ -194,7 +192,8 @@ async def v12(cls, bot: v12Bot, group_id: str): # }, # ) # exist_member_list.append(str(user_id)) - # logger.debug("更新成功", "更新群组成员信息", session=user_id, group_id=group_id) + # logger.debug("更新成功", "更新群组成员信息", + # session=user_id, group_id=group_id) # if delete_member_list := list( # set(exist_member_list).difference( # set(await GroupInfoUser.get_group_member_id_list(group_id)) diff --git a/zhenxun/builtin_plugins/auto_update/__init__.py b/zhenxun/builtin_plugins/auto_update/__init__.py index 1f38d19fb..5bb7505cc 100644 --- a/zhenxun/builtin_plugins/auto_update/__init__.py +++ b/zhenxun/builtin_plugins/auto_update/__init__.py @@ -1,14 +1,14 @@ +from nonebot.rule import to_me from nonebot.adapters import Bot from nonebot.permission import SUPERUSER from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Args, Match, on_alconna from nonebot_plugin_session import EventSession +from nonebot_plugin_alconna import Args, Match, Alconna, on_alconna -from zhenxun.configs.utils import PluginExtraData, RegisterConfig from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType from zhenxun.utils.message import MessageUtils +from zhenxun.configs.utils import RegisterConfig, PluginExtraData from ._data_source import UpdateManage @@ -32,12 +32,6 @@ help="是否检测更新版本", default_value=True, ), - RegisterConfig( - key="UPDATE_REMIND", - value=True, - help="是否检测更新版本", - default_value=True, - ), ], ).dict(), ) diff --git a/zhenxun/builtin_plugins/init/__init__.py b/zhenxun/builtin_plugins/init/__init__.py index 0631c9ed0..f296b1a98 100644 --- a/zhenxun/builtin_plugins/init/__init__.py +++ b/zhenxun/builtin_plugins/init/__init__.py @@ -3,9 +3,9 @@ import nonebot from nonebot.adapters import Bot -from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger from zhenxun.utils.platform import PlatformUtils +from zhenxun.models.group_console import GroupConsole nonebot.load_plugins(str(Path(__file__).parent.resolve())) @@ -20,22 +20,24 @@ async def _(bot: Bot): 参数: bot: Bot """ - if PlatformUtils.get_platform(bot) == "qq": - logger.debug(f"更新Bot: {bot.self_id} 的群认证...") - group_list, _ = await PlatformUtils.get_group_list(bot) - db_group_list = await GroupConsole.all().values_list("group_id", flat=True) - create_list = [] - update_id = [] - for group in group_list: - if group.group_id not in db_group_list: - group.group_flag = 1 - create_list.append(group) - else: - update_id.append(group.group_id) - if create_list: - await GroupConsole.bulk_create(create_list, 10) + if PlatformUtils.get_platform(bot) != "qq": + return + logger.debug(f"更新Bot: {bot.self_id} 的群认证...") + group_list, _ = await PlatformUtils.get_group_list(bot) + db_group_list = await GroupConsole.all().values_list("group_id", flat=True) + create_list = [] + update_id = [] + for group in group_list: + if group.group_id not in db_group_list: + group.group_flag = 1 + create_list.append(group) else: - await GroupConsole.filter(group_id__in=update_id).update(group_flag=1) - logger.debug( - f"更新Bot: {bot.self_id} 的群认证完成,共创建 {len(create_list)} 条数据,共修改 {len(update_id)} 条数据..." - ) + update_id.append(group.group_id) + if create_list: + await GroupConsole.bulk_create(create_list, 10) + else: + await GroupConsole.filter(group_id__in=update_id).update(group_flag=1) + logger.debug( + f"更新Bot: {bot.self_id} 的群认证完成,共创建 {len(create_list)} 条数据," + "共修改 {len(update_id)} 条数据..." + ) diff --git a/zhenxun/builtin_plugins/init/init_plugin.py b/zhenxun/builtin_plugins/init/init_plugin.py index 5650fb476..8581dafe5 100644 --- a/zhenxun/builtin_plugins/init/init_plugin.py +++ b/zhenxun/builtin_plugins/init/init_plugin.py @@ -1,23 +1,24 @@ import nonebot +import aiofiles import ujson as json -from nonebot import get_loaded_plugins -from nonebot.drivers import Driver -from nonebot.plugin import Plugin from ruamel.yaml import YAML +from nonebot.plugin import Plugin +from nonebot.drivers import Driver +from nonebot import get_loaded_plugins +from zhenxun.services.log import logger +from zhenxun.models.task_info import TaskInfo from zhenxun.configs.path_config import DATA_PATH -from zhenxun.configs.utils import PluginExtraData, PluginSetting -from zhenxun.models.group_console import GroupConsole from zhenxun.models.plugin_info import PluginInfo from zhenxun.models.plugin_limit import PluginLimit -from zhenxun.models.task_info import TaskInfo -from zhenxun.services.log import logger +from zhenxun.models.group_console import GroupConsole +from zhenxun.configs.utils import PluginSetting, PluginExtraData from zhenxun.utils.enum import ( BlockType, + PluginType, LimitCheckType, LimitWatchType, PluginLimitType, - PluginType, ) _yaml = YAML(pure=True) @@ -31,7 +32,7 @@ async def _handle_setting( plugin: Plugin, plugin_list: list[PluginInfo], limit_list: list[PluginLimit], - task_list: list[TaskInfo], + task_list: list[tuple[bool, TaskInfo]], ): """处理插件设置 @@ -40,8 +41,7 @@ async def _handle_setting( plugin_list: 插件列表 limit_list: 插件限制列表 """ - metadata = plugin.metadata - if metadata: + if metadata := plugin.metadata: extra = metadata.extra extra_data = PluginExtraData(**extra) logger.debug(f"{metadata.name}:{plugin.name} -> {extra}", "初始化插件数据") @@ -71,30 +71,34 @@ async def _handle_setting( ) ) if extra_data.limits: - for limit in extra_data.limits: - limit_list.append( - PluginLimit( - module=plugin.name, - module_path=plugin.module_name, - limit_type=limit._type, - watch_type=limit.watch_type, - status=limit.status, - check_type=limit.check_type, - result=limit.result, - cd=getattr(limit, "cd", None), - max_count=getattr(limit, "max_count", None), - ) + limit_list.extend( + PluginLimit( + module=plugin.name, + module_path=plugin.module_name, + limit_type=limit._type, + watch_type=limit.watch_type, + status=limit.status, + check_type=limit.check_type, + result=limit.result, + cd=getattr(limit, "cd", None), + max_count=getattr(limit, "max_count", None), ) + for limit in extra_data.limits + ) if extra_data.tasks: - for task in extra_data.tasks: - task_list.append((task.create_status, + task_list.extend( + ( + task.create_status, TaskInfo( module=task.module, name=task.name, status=task.status, run_time=task.run_time, - ) - )) + default_status=task.default_status, + ), + ) + for task in extra_data.tasks + ) @driver.on_startup @@ -104,7 +108,7 @@ async def _(): """ plugin_list: list[PluginInfo] = [] limit_list: list[PluginLimit] = [] - task_list: list[TaskInfo] = [] + task_list = [] module2id = {} load_plugin = [] if module_list := await PluginInfo.all().values("id", "module_path"): @@ -132,14 +136,15 @@ async def _(): update_list.append(plugin) if create_list: await PluginInfo.bulk_create(create_list, 10) - if update_list: - # TODO: 批量更新无法更新plugin_type: tortoise.exceptions.OperationalError: column "superuser" does not exist - pass - # await PluginInfo.bulk_update( - # update_list, - # ["name", "author", "version", "admin_level", "plugin_type"], - # 10, - # ) + # if update_list: + # # TODO: 批量更新无法更新plugin_type: tortoise.exceptions.OperationalError: + # column "superuser" does not exist + # pass + # await PluginInfo.bulk_update( + # update_list, + # ["name", "author", "version", "admin_level", "plugin_type"], + # 10, + # ) if limit_list: limit_create = [] plugins = [] @@ -147,8 +152,8 @@ async def _(): plugins = await PluginInfo.filter(module_path__in=module_path_list).all() if plugins: for limit in limit_list: - if l := [p for p in plugins if p.module_path == limit.module_path]: - plugin = l[0] + if lmt := [p for p in plugins if p.module_path == limit.module_path]: + plugin = lmt[0] limit_type_list = [ _limit.limit_type for _limit in await plugin.plugin_limit.all() # type: ignore ] @@ -170,7 +175,7 @@ async def _(): task.id = module_dict[task.module] update_list.append(task) if create_list: - _create_list = [t[1] for t in create_list] + _create_list = [t[1] for t in create_list] await TaskInfo.bulk_create(_create_list, 10) if block := [t[1].module for t in create_list if not t[0]]: block_task = ",".join(block) + "," @@ -202,14 +207,14 @@ async def limit_migration(): count_file = DATA_PATH / "configs" / "plugins2count.yaml" limit_data: dict[str, list[tuple[str, dict]]] = {} if cd_file.exists(): - with open(cd_file, encoding="utf8") as f: - if data := _yaml.load(f): + async with aiofiles.open(cd_file, encoding="utf8") as f: + if data := _yaml.load(await f.read()): for k in data["PluginCdLimit"]: limit_data[k] = [("CD", data["PluginCdLimit"][k])] cd_file.unlink() if block_file.exists(): - with open(block_file, encoding="utf8") as f: - if data := _yaml.load(f): + async with aiofiles.open(block_file, encoding="utf8") as f: + if data := _yaml.load(await f.read()): for k in data["PluginBlockLimit"]: if k in limit_data: limit_data[k].append(("BLOCK", data["PluginBlockLimit"][k])) @@ -217,8 +222,8 @@ async def limit_migration(): limit_data[k] = [("BLOCK", data["PluginBlockLimit"][k])] block_file.unlink() if count_file.exists(): - with open(count_file, encoding="utf8") as f: - if data := _yaml.load(f): + async with aiofiles.open(count_file, encoding="utf8") as f: + if data := _yaml.load(await f.read()): for k in data["PluginCountLimit"]: if k in limit_data: limit_data[k].append(("COUNT", data["PluginCountLimit"][k])) @@ -294,7 +299,8 @@ async def limit_migration(): max_count=_limit.get("max_count"), ) ) - # TODO: 批量错误 tortoise.exceptions.OperationalError: syntax error at or near "ALL" + # TODO: 批量错误 tortoise.exceptions.OperationalError: + # syntax error at or near "ALL" # if update_list: # await PluginLimit.bulk_update( # update_list, @@ -318,8 +324,8 @@ async def plugin_migration(): setting_file = DATA_PATH / "configs" / "plugins2settings.yaml" plugin_file = DATA_PATH / "manager" / "plugins_manager.json" if setting_file.exists(): - with open(setting_file, encoding="utf8") as f: - if data := _yaml.load(f): + async with aiofiles.open(setting_file, encoding="utf8") as f: + if data := _yaml.load(await f.read()): logger.info("开始迁移插件setting数据...") data = data["PluginSettings"] plugins = await PluginInfo.filter(module__in=data.keys()) @@ -349,8 +355,8 @@ async def plugin_migration(): setting_file.unlink() logger.info("迁移插件setting数据完成!") if plugin_file.exists(): - with open(plugin_file, encoding="utf8") as f: - if data := json.load(f): + async with aiofiles.open(plugin_file, encoding="utf8") as f: + if data := json.loads(await f.read()): logger.info("开始迁移插件数据...") plugins = await PluginInfo.filter(module__in=data.keys()) for plugin in plugins: @@ -366,7 +372,8 @@ async def plugin_migration(): block_type = BlockType.GROUP plugin.block_type = block_type await plugin.save(update_fields=["status", "block_type"]) - # TODO: tortoise.exceptions.OperationalError: syntax error at or near "ALL" + # TODO: tortoise.exceptions.OperationalError: syntax error at + # or near "ALL" # await PluginInfo.bulk_update(plugins, ["status", "block_type"], 10) plugin_file.unlink() logger.info("迁移插件数据完成!") @@ -378,22 +385,20 @@ async def group_migration(): """ group_file = DATA_PATH / "manager" / "group_manager.json" if group_file.exists(): - with open(group_file, encoding="utf8") as f: - if data := json.load(f): + async with aiofiles.open(group_file, encoding="utf8") as f: + if data := json.loads(await f.read()): logger.info("开始迁移群组数据...") update_list = [] create_list = [] white_group = data["white_group"] - close_task = data["close_task"] old_group_list: dict = data["group_manager"] - if close_task: + if close_task := data["close_task"]: """全局被动关闭""" await TaskInfo.filter(module__in=close_task).update(status=False) group_list = await GroupConsole.filter( group_id__in=old_group_list.keys() ) - for old_group_id in old_group_list: - old_group = old_group_list[old_group_id] + for old_group_id, old_group in old_group_list.items(): block_plugin = "" block_task = "" status = old_group.get("status", True) diff --git a/zhenxun/builtin_plugins/platform/qq/group_handle.py b/zhenxun/builtin_plugins/platform/qq/group_handle.py index 29a059162..a2ec5cd8e 100644 --- a/zhenxun/builtin_plugins/platform/qq/group_handle.py +++ b/zhenxun/builtin_plugins/platform/qq/group_handle.py @@ -19,11 +19,11 @@ from zhenxun.services.log import logger from zhenxun.utils.utils import FreqLimiter -from zhenxun.models.task_info import TaskInfo from zhenxun.utils.message import MessageUtils from zhenxun.models.fg_request import FgRequest from zhenxun.models.level_user import LevelUser from zhenxun.models.plugin_info import PluginInfo +from zhenxun.utils.common_utils import CommonUtils from zhenxun.configs.config import Config, BotConfig from zhenxun.models.group_console import GroupConsole from zhenxun.models.group_member_info import GroupInfoUser @@ -242,7 +242,7 @@ async def _(bot: Bot, event: GroupIncreaseNoticeEvent | GroupMemberIncreaseEvent img_file = path / f"{i}.png" if img_file.exists(): msg_list.append(img_file) - if not await TaskInfo.is_block("group_welcome", group_id): + if not await CommonUtils.is_block("group_welcome", group_id): logger.info("发送群欢迎消息...", "入群检测", group_id=group_id) if msg_list: await MessageUtils.build_message(msg_list).send() @@ -314,5 +314,5 @@ async def _(bot: Bot, event: GroupDecreaseNoticeEvent | GroupMemberDecreaseEvent ) operator_name = operator["card"] or operator["nickname"] result = f"{user_name} 被 {operator_name} 送走了." - if not await TaskInfo.is_block("refund_group_remind", str(event.group_id)): + if not await CommonUtils.is_block("refund_group_remind", str(event.group_id)): await group_decrease_handle.send(f"{result}") diff --git a/zhenxun/builtin_plugins/plugin_store/data_source.py b/zhenxun/builtin_plugins/plugin_store/data_source.py index 519bc44d1..5b20ddfdc 100644 --- a/zhenxun/builtin_plugins/plugin_store/data_source.py +++ b/zhenxun/builtin_plugins/plugin_store/data_source.py @@ -3,19 +3,20 @@ import subprocess from pathlib import Path +import aiofiles import ujson as json -from zhenxun.models.plugin_info import PluginInfo from zhenxun.services.log import logger from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage, ImageTemplate, RowStyle +from zhenxun.models.plugin_info import PluginInfo +from zhenxun.utils.image_utils import RowStyle, BuildImage, ImageTemplate from .config import ( BASE_PATH, - CONFIG_INDEX_CDN_URL, - CONFIG_INDEX_URL, CONFIG_URL, DOWNLOAD_URL, + CONFIG_INDEX_URL, + CONFIG_INDEX_CDN_URL, ) @@ -30,17 +31,16 @@ def row_style(column: str, text: str) -> RowStyle: RowStyle: RowStyle """ style = RowStyle() - if column in ["-"]: - if text == "已安装": - style.font_color = "#67C23A" + if column == "-" and text == "已安装": + style.font_color = "#67C23A" return style async def recurrence_get_url( url: str, data_list: list[tuple[str, str]], - ignore_list: list[str] = [], - api_url: str = None, + ignore_list: list[str] | None = None, + api_url: str | None = None, ): """递归获取目录下所有文件 @@ -51,14 +51,15 @@ async def recurrence_get_url( 异常: ValueError: 访问错误 """ + if ignore_list is None: + ignore_list = [] logger.debug(f"访问插件下载信息 URL: {url}", "插件管理") res = await AsyncHttpx.get(url) if res.status_code != 200: raise ValueError(f"访问错误, code: {res.status_code}") json_data = res.json() if isinstance(json_data, list): - for v in json_data: - data_list.append((v.get("download_url"), v["path"])) + data_list.extend((v.get("download_url"), v["path"]) for v in json_data) else: data_list.append((json_data.get("download_url"), json_data["path"])) for download_url, path in data_list: @@ -69,7 +70,7 @@ async def recurrence_get_url( await recurrence_get_url(_url, data_list, ignore_list, api_url) -async def download_file(url: str, _is: bool = False, api_url: str = None): +async def download_file(url: str, _is: bool = False, api_url: str | None = None): """下载文件 参数: @@ -88,14 +89,13 @@ async def download_file(url: str, _is: bool = False, api_url: str = None): base_path = "zhenxun/plugins/" if _is else "zhenxun/" file = Path(f"{base_path}{path}") file.parent.mkdir(parents=True, exist_ok=True) - print(download_url) r = await AsyncHttpx.get(download_url) if r.status_code != 200: raise ValueError(f"文件下载错误, code: {r.status_code}") content = r.text.replace("\r\n", "\n") # 统一换行符为 UNIX 风格 - with open(file, "w", encoding="utf8") as f: + async with aiofiles.open(file, "w", encoding="utf8") as f: logger.debug(f"写入文件: {file}", "插件管理") - f.write(content) + await f.write(content) def install_requirement(plugin_path: Path): @@ -116,22 +116,23 @@ def install_requirement(plugin_path: Path): result = subprocess.run( ["pip", "install", "-r", str(existing_requirements)], check=True, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + capture_output=True, text=True, ) logger.debug( - f"Successfully installed dependencies for plugin: {plugin_path.name}. Output:\n{result.stdout}", + "Successfully installed dependencies for" + f" plugin: {plugin_path.name}. Output:\n{result.stdout}", "插件管理", ) - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError: logger.error( - f"Failed to install dependencies for plugin: {plugin_path.name}. Error:\n{e.stderr}" + f"Failed to install dependencies for plugin: {plugin_path.name}. " + " Error:\n{e.stderr}" ) class ShopManage: - type2name = { + type2name = { # noqa: RUF012 "NORMAL": "普通插件", "ADMIN": "管理员插件", "SUPERUSER": "超级用户插件", @@ -169,9 +170,8 @@ async def __get_data(cls) -> dict: @classmethod def version_check(cls, plugin_info: dict, suc_plugin: dict[str, str]): module = plugin_info["module"] - if module in suc_plugin: - if plugin_info["version"] != suc_plugin[module]: - return f"{suc_plugin[module]} (有更新->{plugin_info['version']})" + if module in suc_plugin and plugin_info["version"] != suc_plugin[module]: + return f"{suc_plugin[module]} (有更新->{plugin_info['version']})" return plugin_info["version"] @classmethod @@ -226,7 +226,7 @@ async def get_plugins_info(cls) -> BuildImage | str: ] return await ImageTemplate.table_page( "插件列表", - f"通过安装/卸载插件 ID 来管理插件", + "通过安装/卸载插件 ID 来管理插件", column_name, data_list, text_style=row_style, @@ -254,7 +254,9 @@ async def add_plugin(cls, plugin_id: int) -> str: logger.debug(f"尝试下载插件 URL: {url_path}", "插件管理") github_url = plugin_info.get("github_url") if github_url: - github_path = re.search(r"github\.com/([^/]+/[^/]+)", github_url).group(1) + if not (r := re.search(r"github\.com/([^/]+/[^/]+)", github_url)): + return "github地址格式错误" + github_path = r[1] api_url = f"https://api.github.com/repos/{github_path}/contents/" download_url = f"{api_url}{url_path}?ref=main" else: @@ -265,28 +267,27 @@ async def add_plugin(cls, plugin_id: int) -> str: # 安装依赖 plugin_path = BASE_PATH / "/".join(module_path_split) - if url_path and github_url: + if url_path and github_url and api_url: plugin_path = BASE_PATH / "plugins" / "/".join(module_path_split) res = await AsyncHttpx.get(api_url) if res.status_code != 200: return f"访问错误, code: {res.status_code}" json_data = res.json() - requirement_file = next( + if requirement_file := next( ( v for v in json_data if v["name"] in ["requirements.txt", "requirement.txt"] ), None, - ) - if requirement_file: + ): r = await AsyncHttpx.get(requirement_file.get("download_url")) if r.status_code != 200: raise ValueError(f"文件下载错误, code: {r.status_code}") requirement_path = plugin_path / requirement_file["name"] - with open(requirement_path, "w", encoding="utf8") as f: + async with aiofiles.open(requirement_path, "w", encoding="utf8") as f: logger.debug(f"写入文件: {requirement_path}", "插件管理") - f.write(r.text) + await f.write(r.text) install_requirement(plugin_path) @@ -308,8 +309,7 @@ async def remove_plugin(cls, plugin_id: int) -> str: plugin_key = list(data.keys())[plugin_id] plugin_info = data[plugin_key] path = BASE_PATH - github_url = plugin_info.get("github_url") - if github_url: + if plugin_info.get("github_url"): path = BASE_PATH / "plugins" for p in plugin_info["module_path"].split("."): path = path / p @@ -335,7 +335,6 @@ async def search_plugin(cls, plugin_name_or_author: str) -> BuildImage | str: BuildImage | str: 返回消息 """ data: dict = await cls.__get_data() - column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"] for k in data.copy(): if data[k]["plugin_type"]: data[k]["plugin_type"] = cls.type2name[data[k]["plugin_type"]] @@ -364,9 +363,10 @@ async def search_plugin(cls, plugin_name_or_author: str) -> BuildImage | str: ] if not data_list: return "未找到相关插件..." + column_name = ["-", "ID", "名称", "简介", "作者", "版本", "类型"] return await ImageTemplate.table_page( "插件列表", - f"通过添加/移除插件 ID 来管理插件", + "通过添加/移除插件 ID 来管理插件", column_name, data_list, text_style=row_style, @@ -394,7 +394,9 @@ async def update_plugin(cls, plugin_id: int) -> str: logger.debug(f"尝试下载插件 URL: {url_path}", "插件管理") github_url = plugin_info.get("github_url") if github_url: - github_path = re.search(r"github\.com/([^/]+/[^/]+)", github_url).group(1) + if not (r := re.search(r"github\.com/([^/]+/[^/]+)", github_url)): + return "github地址格式错误..." + github_path = r[1] api_url = f"https://api.github.com/repos/{github_path}/contents/" download_url = f"{api_url}{url_path}?ref=main" else: @@ -405,28 +407,27 @@ async def update_plugin(cls, plugin_id: int) -> str: # 安装依赖 plugin_path = BASE_PATH / "/".join(module_path_split) - if url_path and github_url: + if url_path and github_url and api_url: plugin_path = BASE_PATH / "plugins" / "/".join(module_path_split) res = await AsyncHttpx.get(api_url) if res.status_code != 200: return f"访问错误, code: {res.status_code}" json_data = res.json() - requirement_file = next( + if requirement_file := next( ( v for v in json_data if v["name"] in ["requirements.txt", "requirement.txt"] ), None, - ) - if requirement_file: + ): r = await AsyncHttpx.get(requirement_file.get("download_url")) if r.status_code != 200: raise ValueError(f"文件下载错误, code: {r.status_code}") requirement_path = plugin_path / requirement_file["name"] - with open(requirement_path, "w", encoding="utf8") as f: + async with aiofiles.open(requirement_path, "w", encoding="utf8") as f: logger.debug(f"写入文件: {requirement_path}", "插件管理") - f.write(r.text) + await f.write(r.text) install_requirement(plugin_path) diff --git a/zhenxun/builtin_plugins/record_request.py b/zhenxun/builtin_plugins/record_request.py index 8a837e7a8..5762c1a3f 100644 --- a/zhenxun/builtin_plugins/record_request.py +++ b/zhenxun/builtin_plugins/record_request.py @@ -1,25 +1,27 @@ import time from datetime import datetime -import nonebot +from nonebot.plugin import PluginMetadata from nonebot import on_message, on_request -from nonebot.adapters.onebot.v11 import ActionFailed +from nonebot_plugin_session import EventSession +from nonebot_plugin_apscheduler import scheduler from nonebot.adapters.onebot.v11 import Bot as v11Bot -from nonebot.adapters.onebot.v11 import FriendRequestEvent, GroupRequestEvent from nonebot.adapters.onebot.v12 import Bot as v12Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession +from nonebot.adapters.onebot.v11 import ( + ActionFailed, + GroupRequestEvent, + FriendRequestEvent, +) -from zhenxun.configs.config import BotConfig, Config -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.models.fg_request import FgRequest -from zhenxun.models.friend_user import FriendUser -from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType, RequestHandleType, RequestType from zhenxun.utils.message import MessageUtils +from zhenxun.models.fg_request import FgRequest from zhenxun.utils.platform import PlatformUtils +from zhenxun.models.friend_user import FriendUser +from zhenxun.configs.config import Config, BotConfig +from zhenxun.models.group_console import GroupConsole +from zhenxun.configs.utils import RegisterConfig, PluginExtraData +from zhenxun.utils.enum import PluginType, RequestType, RequestHandleType base_config = Config.get("invite_manager") @@ -46,13 +48,11 @@ class Timer: - data: dict[str, float] = {} + data: dict[str, float] = {} # noqa: RUF012 @classmethod def check(cls, uid: int | str): - if uid not in cls.data: - return True - return time.time() - cls.data[uid] > 5 * 60 + return True if uid not in cls.data else time.time() - cls.data[uid] > 5 * 60 @classmethod def clear(cls): @@ -71,7 +71,7 @@ def clear(cls): async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSession): superuser = BotConfig.get_superuser("qq") if event.user_id and Timer.check(event.user_id): - logger.debug(f"收录好友请求...", "好友请求", target=event.user_id) + logger.debug("收录好友请求...", "好友请求", target=event.user_id) user = await bot.get_stranger_info(user_id=event.user_id) nickname = user["nickname"] # sex = user["sex"] @@ -87,7 +87,7 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi ).send(target=PlatformUtils.get_target(bot, superuser)) if base_config.get("AUTO_ADD_FRIEND"): logger.debug( - f"已开启好友请求自动同意,成功通过该请求", + "已开启好友请求自动同意,成功通过该请求", "好友请求", target=event.user_id, ) @@ -112,22 +112,33 @@ async def _(bot: v12Bot | v11Bot, event: FriendRequestEvent, session: EventSessi comment=comment, ) else: - logger.debug(f"好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id) + logger.debug("好友请求五分钟内重复, 已忽略", "好友请求", target=event.user_id) @group_req.handle() async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSession): superuser = BotConfig.get_superuser("qq") - # 邀请 if event.sub_type == "invite": if str(event.user_id) in bot.config.superusers: try: logger.debug( - f"超级用户自动同意加入群聊", + "超级用户自动同意加入群聊", "群聊请求", session=event.user_id, target=event.group_id, ) + group, _ = await GroupConsole.update_or_create( + group_id=str(event.group_id), + defaults={ + "group_name": "", + "max_member_count": 0, + "member_count": 0, + "group_flag": 1, + }, + ) + await bot.set_group_add_request( + flag=event.flag, sub_type="invite", approve=True + ) if isinstance(bot, v11Bot): group_info = await bot.get_group_info(group_id=event.group_id) max_member_count = group_info["max_member_count"] @@ -136,17 +147,11 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio group_info = await bot.get_group_info(group_id=str(event.group_id)) max_member_count = 0 member_count = 0 - await GroupConsole.update_or_create( - group_id=str(event.group_id), - defaults={ - "group_name": group_info["group_name"], - "max_member_count": max_member_count, - "member_count": member_count, - "group_flag": 1, - }, - ) - await bot.set_group_add_request( - flag=event.flag, sub_type="invite", approve=True + group.max_member_count = max_member_count + group.member_count = member_count + group.group_name = group_info["group_name"] + await group.save( + update_fields=["group_name", "max_member_count", "member_count"] ) except ActionFailed as e: logger.error( @@ -156,47 +161,47 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio target=event.group_id, e=e, ) + elif Timer.check(f"{event.user_id}:{event.group_id}"): + logger.debug( + f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求", + "群聊请求", + target=event.group_id, + ) + nickname = await FriendUser.get_user_name(str(event.user_id)) + await PlatformUtils.send_superuser( + bot, + f"*****一份入群申请*****\n申请人:{nickname}({event.user_id})\n群聊:" + f"{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}", + superuser, + ) + await bot.send_private_msg( + user_id=event.user_id, + message=f"想要邀请我偷偷入群嘛~已经提醒{BotConfig.self_nickname}的管理员大人了\n" + "请确保已经群主或群管理沟通过!\n" + "等待管理员处理吧!", + ) + # 旧请求全部设置为过期 + await FgRequest.filter( + request_type=RequestType.GROUP, + user_id=str(event.user_id), + group_id=str(event.group_id), + handle_type__isnull=True, + ).update(handle_type=RequestHandleType.EXPIRE) + await FgRequest.create( + request_type=RequestType.GROUP, + platform=session.platform, + bot_id=bot.self_id, + flag=event.flag, + user_id=str(event.user_id), + nickname=nickname, + group_id=str(event.group_id), + ) else: - if Timer.check(f"{event.user_id}:{event.group_id}"): - logger.debug( - f"收录 用户[{event.user_id}] 群聊[{event.group_id}] 群聊请求", - "群聊请求", - target=event.group_id, - ) - nickname = await FriendUser.get_user_name(str(event.user_id)) - await PlatformUtils.send_superuser( - bot, - f"*****一份入群申请*****\n申请人:{nickname}({event.user_id})\n群聊:{event.group_id}\n邀请日期:{datetime.now().replace(microsecond=0)}", - superuser, - ) - await bot.send_private_msg( - user_id=event.user_id, - message=f"想要邀请我偷偷入群嘛~已经提醒{BotConfig.self_nickname}的管理员大人了\n" - "请确保已经群主或群管理沟通过!\n" - "等待管理员处理吧!", - ) - # 旧请求全部设置为过期 - await FgRequest.filter( - request_type=RequestType.GROUP, - user_id=str(event.user_id), - group_id=str(event.group_id), - handle_type__isnull=True, - ).update(handle_type=RequestHandleType.EXPIRE) - await FgRequest.create( - request_type=RequestType.GROUP, - platform=session.platform, - bot_id=bot.self_id, - flag=event.flag, - user_id=str(event.user_id), - nickname=nickname, - group_id=str(event.group_id), - ) - else: - logger.debug( - f"群聊请求五分钟内重复, 已忽略", - "群聊请求", - target=f"{event.user_id}:{event.group_id}", - ) + logger.debug( + "群聊请求五分钟内重复, 已忽略", + "群聊请求", + target=f"{event.user_id}:{event.group_id}", + ) @scheduler.scheduled_job( @@ -205,3 +210,7 @@ async def _(bot: v12Bot | v11Bot, event: GroupRequestEvent, session: EventSessio ) async def _(): Timer.clear() + + +async def _(): + Timer.clear() diff --git a/zhenxun/builtin_plugins/scheduler/auto_backup.py b/zhenxun/builtin_plugins/scheduler/auto_backup.py index 92f1119b0..8b5bc9ac0 100644 --- a/zhenxun/builtin_plugins/scheduler/auto_backup.py +++ b/zhenxun/builtin_plugins/scheduler/auto_backup.py @@ -3,8 +3,8 @@ from nonebot_plugin_apscheduler import scheduler -from zhenxun.configs.config import Config from zhenxun.services.log import logger +from zhenxun.configs.config import Config Config.add_plugin_config( "_backup", @@ -18,14 +18,7 @@ Config.add_plugin_config( "_backup", "BACKUP_DIR_OR_FILE", - [ - "data/black_word", - "data/configs", - "data/statistics", - "data/word_bank", - "data/manager", - "configs", - ], + ["data"], help="备份的文件夹或文件", default_value=[], type=list[str], @@ -39,24 +32,25 @@ minute=25, ) async def _(): - if Config.get_config("_backup", "BACKUP_FLAG"): - _backup_path = Path() / "backup" - _backup_path.mkdir(exist_ok=True, parents=True) - if backup_dir_or_file := Config.get_config("_backup", "BACKUP_DIR_OR_FILE"): - for path_file in backup_dir_or_file: - try: - path = Path(path_file) - _p = _backup_path / path_file - if path.exists(): - if path.is_dir(): - if _p.exists(): - shutil.rmtree(_p, ignore_errors=True) - shutil.copytree(path_file, _p) - else: - if _p.exists(): - _p.unlink() - shutil.copy(path_file, _p) - logger.debug(f"已完成自动备份:{path_file}", "自动备份") - except Exception as e: - logger.error(f"自动备份文件 {path_file} 发生错误", "自动备份", e=e) - logger.info("自动备份成功...", "自动备份") + if not Config.get_config("_backup", "BACKUP_FLAG"): + return + _backup_path = Path() / "backup" + _backup_path.mkdir(exist_ok=True, parents=True) + if backup_dir_or_file := Config.get_config("_backup", "BACKUP_DIR_OR_FILE"): + for path_file in backup_dir_or_file: + try: + path = Path(path_file) + _p = _backup_path / path_file + if path.exists(): + if path.is_dir(): + if _p.exists(): + shutil.rmtree(_p, ignore_errors=True) + shutil.copytree(path_file, _p) + else: + if _p.exists(): + _p.unlink() + shutil.copy(path_file, _p) + logger.debug(f"已完成自动备份:{path_file}", "自动备份") + except Exception as e: + logger.error(f"自动备份文件 {path_file} 发生错误", "自动备份", e=e) + logger.info("自动备份成功...", "自动备份") diff --git a/zhenxun/builtin_plugins/scheduler/morning.py b/zhenxun/builtin_plugins/scheduler/morning.py index dbb3536ac..76138b296 100644 --- a/zhenxun/builtin_plugins/scheduler/morning.py +++ b/zhenxun/builtin_plugins/scheduler/morning.py @@ -2,14 +2,15 @@ from nonebot.plugin import PluginMetadata from nonebot_plugin_apscheduler import scheduler -from zhenxun.configs.config import BotConfig -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.configs.utils import PluginExtraData, Task -from zhenxun.models.task_info import TaskInfo from zhenxun.services.log import logger from zhenxun.utils.enum import PluginType +from zhenxun.configs.config import BotConfig +from zhenxun.models.task_info import TaskInfo from zhenxun.utils.message import MessageUtils +from zhenxun.configs.path_config import IMAGE_PATH +from zhenxun.utils.common_utils import CommonUtils from zhenxun.utils.platform import broadcast_group +from zhenxun.configs.utils import Task, PluginExtraData __plugin_meta__ = PluginMetadata( name="早晚安被动技能", @@ -37,7 +38,7 @@ async def _(): async def check(group_id: str) -> bool: - return not await TaskInfo.is_block("morning_goodnight", group_id) + return not await CommonUtils.is_block("morning_goodnight", group_id) # 早上好 diff --git a/zhenxun/builtin_plugins/superuser/broadcast/_data_source.py b/zhenxun/builtin_plugins/superuser/broadcast/_data_source.py index 617f0d440..1307883cd 100644 --- a/zhenxun/builtin_plugins/superuser/broadcast/_data_source.py +++ b/zhenxun/builtin_plugins/superuser/broadcast/_data_source.py @@ -1,5 +1,6 @@ -import nonebot_plugin_alconna as alc from nonebot.adapters import Bot +import nonebot_plugin_alconna as alc +from nonebot_plugin_session import EventSession # from nonebot.adapters.discord import Bot as DiscordBot # from nonebot.adapters.dodo import Bot as DodoBot @@ -7,12 +8,11 @@ # from nonebot.adapters.onebot.v11 import Bot as v11Bot # from nonebot.adapters.onebot.v12 import Bot as v12Bot from nonebot_plugin_alconna import Image, UniMsg -from nonebot_plugin_session import EventSession -from zhenxun.models.task_info import TaskInfo from zhenxun.services.log import logger from zhenxun.utils.message import MessageUtils from zhenxun.utils.platform import PlatformUtils +from zhenxun.utils.common_utils import CommonUtils class BroadcastManage: @@ -42,7 +42,7 @@ async def send( error_count = 0 for group in group_list: try: - if not await TaskInfo.is_block( + if not await CommonUtils.is_block( group.group_id, "broadcast", # group.channel_id ): diff --git a/zhenxun/builtin_plugins/web_ui/utils.py b/zhenxun/builtin_plugins/web_ui/utils.py index 6fee53885..6d2d22353 100644 --- a/zhenxun/builtin_plugins/web_ui/utils.py +++ b/zhenxun/builtin_plugins/web_ui/utils.py @@ -1,19 +1,19 @@ import os -from datetime import datetime, timedelta +import contextlib from pathlib import Path -import secrets +from datetime import datetime, timezone, timedelta import psutil import ujson as json -from fastapi import Depends, HTTPException -from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt from nonebot.utils import run_sync +from fastapi import Depends, HTTPException +from fastapi.security import OAuth2PasswordBearer from zhenxun.configs.config import Config from zhenxun.configs.path_config import DATA_PATH -from .base_model import SystemFolderSize, SystemStatus, User +from .base_model import User, SystemStatus, SystemFolderSize ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = 30 @@ -24,10 +24,8 @@ token_file.parent.mkdir(parents=True, exist_ok=True) token_data = {"token": []} if token_file.exists(): - try: - token_data = json.load(open(token_file, "r", encoding="utf8")) - except json.JSONDecodeError: - pass + with contextlib.suppress(json.JSONDecodeError): + token_data = json.load(open(token_file, encoding="utf8")) def get_user(uname: str) -> User | None: @@ -52,7 +50,7 @@ def create_token(user: User, expires_delta: timedelta | None = None): user: 用户信息 expires_delta: 过期时间. """ - expire = datetime.utcnow() + (expires_delta or timedelta(minutes=15)) + expire = datetime.now(timezone.utc) + (expires_delta or timedelta(minutes=15)) return jwt.encode( claims={"sub": user.username, "exp": expire}, key=Config.get_config("web-ui", "secret"), @@ -71,8 +69,10 @@ def authentication(): # if token not in token_data["token"]: def inner(token: str = Depends(oauth2_scheme)): try: - payload = jwt.decode(token, Config.get_config("web-ui", "secret"), algorithms=[ALGORITHM]) - username, expire = payload.get("sub"), payload.get("exp") + payload = jwt.decode( + token, Config.get_config("web-ui", "secret"), algorithms=[ALGORITHM] + ) + username, _ = payload.get("sub"), payload.get("exp") user = get_user(username) # type: ignore if user is None: raise JWTError @@ -90,10 +90,10 @@ def _get_dir_size(dir_path: Path) -> float: 参数: dir_path: 文件夹路径 """ - size = 0 - for root, dirs, files in os.walk(dir_path): - size += sum([os.path.getsize(os.path.join(root, name)) for name in files]) - return size + return sum( + sum(os.path.getsize(os.path.join(root, name)) for name in files) + for root, dirs, files in os.walk(dir_path) + ) @run_sync diff --git a/zhenxun/configs/utils/__init__.py b/zhenxun/configs/utils/__init__.py index 7198d93a5..3277222cd 100644 --- a/zhenxun/configs/utils/__init__.py +++ b/zhenxun/configs/utils/__init__.py @@ -1,15 +1,16 @@ import copy +from typing import Any from pathlib import Path -from typing import Any, Callable, Dict, Set, Type +from collections.abc import Callable import cattrs -from pydantic import BaseModel from ruamel.yaml import YAML +from pydantic import BaseModel from ruamel.yaml.scanner import ScannerError -from zhenxun.configs.path_config import DATA_PATH from zhenxun.services.log import logger -from zhenxun.utils.enum import BlockType, LimitWatchType, PluginLimitType, PluginType +from zhenxun.configs.path_config import DATA_PATH +from zhenxun.utils.enum import BlockType, PluginType, LimitWatchType, PluginLimitType _yaml = YAML(pure=True) _yaml.indent = 2 @@ -63,7 +64,7 @@ class ConfigGroup(BaseModel): """模块名""" name: str | None = None """插件名""" - configs: Dict[str, ConfigModel] = {} + configs: dict[str, ConfigModel] = {} """配置项列表""" def get(self, c: str, default: Any = None) -> Any: @@ -139,6 +140,8 @@ class Task(BaseBlock): """全局开关状态""" create_status: bool = False """初次加载默认开关状态""" + default_status: bool = True + """进群时默认状态""" run_time: str | None = None """运行时间""" @@ -168,7 +171,7 @@ class PluginExtraData(BaseModel): """技能被动""" superuser_help: str | None = None """超级用户帮助""" - aliases: Set[str] = set() + aliases: set[str] = set() """额外名称""" sql_list: list[str] | None = None """常用sql""" @@ -184,7 +187,7 @@ class ConfigsManager: """ def __init__(self, file: Path): - self._data: Dict[str, ConfigGroup] = {} + self._data: dict[str, ConfigGroup] = {} self._simple_data: dict = {} self._simple_file = DATA_PATH / "config.yaml" _yaml = YAML() @@ -194,14 +197,14 @@ def __init__(self, file: Path): self.load_data() if self._simple_file.exists(): try: - with open(self._simple_file, "r", encoding="utf8") as f: + with self._simple_file.open(encoding="utf8") as f: self._simple_data = _yaml.load(f) except ScannerError as e: raise ScannerError( f"{e}\n**********************************************\n" f"****** 可能为config.yaml配置文件填写不规范 ******\n" f"**********************************************" - ) + ) from e def set_name(self, module: str, name: str): """设置插件配置中文名出 @@ -226,7 +229,7 @@ def add_plugin_config( *, help: str | None = None, default_value: Any = None, - type: Type | None = None, + type: type | None = None, arg_parser: Callable | None = None, _override: bool = False, ): @@ -314,45 +317,46 @@ def get_config(self, module: str, key: str, default: Any = None) -> Any: Any: 配置值 """ logger.debug( - f"尝试获取配置 MODULE: [{module}] | KEY: [{key}]" + f"尝试获取配置MODULE: [{module}] | KEY: [{key}]" ) key = key.upper() value = None if module in self._data.keys(): - config = self._data[module].configs.get(key) - if not config: - config = self._data[module].configs.get(key) + config = self._data[module].configs.get(key) or self._data[ + module + ].configs.get(key) if not config: raise NoSuchConfig( f"未查询到配置项 MODULE: [ {module} ] | KEY: [ {key} ]" ) - if config.arg_parser: - value = config.arg_parser(value or config.default_value) - else: - try: - if config.value is not None: - value = ( - cattrs.structure(config.value, config.type) - if config.type - else config.value - ) - else: - if config.default_value is not None: - value = ( - cattrs.structure(config.default_value, config.type) - if config.type - else config.default_value - ) - except Exception as e: - logger.warning( - f"配置项类型转换 MODULE: [{module}] | KEY: [{key}]", - e=e, + try: + if config.arg_parser: + value = config.arg_parser(value or config.default_value) + elif config.value is not None: + # try: + value = ( + cattrs.structure(config.value, config.type) + if config.type + else config.value + ) + elif config.default_value is not None: + value = ( + cattrs.structure(config.default_value, config.type) + if config.type + else config.default_value ) - value = config.value or config.default_value + except Exception as e: + logger.warning( + f"配置项类型转换 MODULE: [{module}]" + " | KEY: [{key}]", + e=e, + ) + value = config.value or config.default_value if value is None: value = default logger.debug( - f"获取配置 MODULE: [{module}] | KEY: [{key}] -> [{value}]" + f"获取配置 MODULE: [{module}] | " + " KEY: [{key}] -> [{value}]" ) return value @@ -403,7 +407,7 @@ def reload(self): """重新加载配置文件""" _yaml = YAML() if self._simple_file.exists(): - with open(self._simple_file, "r", encoding="utf8") as f: + with open(self._simple_file, encoding="utf8") as f: self._simple_data = _yaml.load(f) for key in self._simple_data.keys(): for k in self._simple_data[key].keys(): @@ -416,31 +420,31 @@ def load_data(self): 异常: ValueError: 配置文件为空! """ - if self.file.exists(): - with open(self.file, "r", encoding="utf8") as f: - temp_data = _yaml.load(f) - if not temp_data: - self.file.unlink() - raise ValueError( - "配置文件为空!\n" - "***********************************************************\n" - "****** 配置文件 plugins2config.yaml 为空,已删除,请重启 ******\n" - "***********************************************************" - ) - count = 0 - for module in temp_data: - config_group = ConfigGroup(module=module) - for config in temp_data[module]: - config_group.configs[config] = ConfigModel( - **temp_data[module][config] - ) - count += 1 - self._data[module] = config_group - logger.info( - f"加载配置完成,共加载 {len(temp_data)} 个配置组及对应 {count} 个配置项" + if not self.file.exists(): + return + with open(self.file, encoding="utf8") as f: + temp_data = _yaml.load(f) + if not temp_data: + self.file.unlink() + raise ValueError( + "配置文件为空!\n" + "***********************************************************\n" + "****** 配置文件 plugins2config.yaml 为空,已删除,请重启 ******\n" + "***********************************************************" ) + count = 0 + for module in temp_data: + config_group = ConfigGroup(module=module) + for config in temp_data[module]: + config_group.configs[config] = ConfigModel(**temp_data[module][config]) + count += 1 + self._data[module] = config_group + logger.info( + f"加载配置完成,共加载 {len(temp_data)} 个配置组及对应" + " {count} 个配置项" + ) - def get_data(self) -> Dict[str, ConfigGroup]: + def get_data(self) -> dict[str, ConfigGroup]: return copy.deepcopy(self._data) def is_empty(self) -> bool: diff --git a/zhenxun/models/fg_request.py b/zhenxun/models/fg_request.py index 7f1178ac5..6a62fa8d0 100644 --- a/zhenxun/models/fg_request.py +++ b/zhenxun/models/fg_request.py @@ -33,7 +33,7 @@ class FgRequest(Model): ) """处理类型""" - class Meta: + class Meta: # type: ignore table = "fg_request" table_description = "好友群组请求" diff --git a/zhenxun/models/group_console.py b/zhenxun/models/group_console.py index bbcdb6002..8c64d2a42 100644 --- a/zhenxun/models/group_console.py +++ b/zhenxun/models/group_console.py @@ -1,6 +1,10 @@ -from tortoise import fields +from typing import Any from typing_extensions import Self +from tortoise import fields +from tortoise.backends.base.client import BaseDBAsyncClient + +from zhenxun.models.task_info import TaskInfo from zhenxun.services.db_context import Model @@ -35,11 +39,64 @@ class GroupConsole(Model): platform = fields.CharField(255, default="qq", description="所属平台") """所属平台""" - class Meta: + class Meta: # type: ignore table = "group_console" table_description = "群组信息表" unique_together = ("group_id", "channel_id") + @classmethod + async def create( + cls, using_db: BaseDBAsyncClient | None = None, **kwargs: Any + ) -> Self: + """覆盖create方法""" + group = await super().create(using_db=using_db, **kwargs) + if modules := await TaskInfo.filter(default_status=False).values_list( + "module", flat=True + ): + group.block_task = ",".join(modules) + "," # type: ignore + await group.save(using_db=using_db, update_fields=["block_task"]) + return group + + @classmethod + async def get_or_create( + cls, + defaults: dict | None = None, + using_db: BaseDBAsyncClient | None = None, + **kwargs: Any, + ) -> tuple[Self, bool]: + """覆盖get_or_create方法""" + group, is_create = await super().get_or_create( + defaults=defaults, using_db=using_db, **kwargs + ) + if is_create and ( + modules := await TaskInfo.filter(default_status=False).values_list( + "module", flat=True + ) + ): + group.block_task = ",".join(modules) + "," # type: ignore + await group.save(using_db=using_db, update_fields=["block_task"]) + return group, is_create + + @classmethod + async def update_or_create( + cls, + defaults: dict | None = None, + using_db: BaseDBAsyncClient | None = None, + **kwargs: Any, + ) -> tuple[Self, bool]: + """覆盖update_or_create方法""" + group, is_create = await super().update_or_create( + defaults=defaults, using_db=using_db, **kwargs + ) + if is_create and ( + modules := await TaskInfo.filter(default_status=False).values_list( + "module", flat=True + ) + ): + group.block_task = ",".join(modules) + "," # type: ignore + await group.save(using_db=using_db, update_fields=["block_task"]) + return group, is_create + @classmethod async def get_group( cls, group_id: str, channel_id: str | None = None @@ -67,9 +124,7 @@ async def is_super_group(cls, group_id: str) -> bool: 返回: bool: 是否超级用户指定群 """ - if group := await cls.get_group(group_id): - return group.is_super - return False + return group.is_super if (group := await cls.get_group(group_id)) else False @classmethod async def is_super_block_plugin( diff --git a/zhenxun/models/task_info.py b/zhenxun/models/task_info.py index 3ca1fb49a..56cd70157 100644 --- a/zhenxun/models/task_info.py +++ b/zhenxun/models/task_info.py @@ -2,9 +2,6 @@ from zhenxun.services.db_context import Model -from .ban_console import BanConsole -from .group_console import GroupConsole - class TaskInfo(Model): id = fields.IntField(pk=True, generated=True, auto_increment=True) @@ -15,41 +12,20 @@ class TaskInfo(Model): """被动技能名称""" status = fields.BooleanField(default=True, description="全局开关状态") """全局开关状态""" + default_status = fields.BooleanField(default=True, description="进群默认开关状态") + """全局开关状态""" run_time = fields.CharField(255, null=True, description="运行时间") """运行时间""" run_count = fields.IntField(default=0, description="运行次数") """运行次数""" - class Meta: + class Meta: # type: ignore table = "task_info" table_description = "被动技能基本信息" @classmethod - async def is_block(cls, module: str, group_id: str | None) -> bool: - """判断被动技能是否可以发送 - - 参数: - module: 被动技能模块名 - group_id: 群组id - - 返回: - bool: 是否可以发送 - """ - if task := await cls.get_or_none(module=module): - """被动全局状态""" - if not task.status: - return True - if group_id: - if await GroupConsole.is_block_task(group_id, module): - """群组是否禁用被动""" - return True - if g := await GroupConsole.get_or_none( - group_id=group_id, channel_id__isnull=True - ): - """群组权限是否小于0""" - if g.level < 0: - return True - if await BanConsole.is_ban(None, group_id): - """群组是否被ban""" - return True - return False + async def _run_script(cls): + return [ + "ALTER TABLE task_info ADD default_status boolean DEFAULT true;", + # 默认状态 + ] diff --git a/zhenxun/plugins/ai/__init__.py b/zhenxun/plugins/ai/__init__.py deleted file mode 100644 index bc5fd5404..000000000 --- a/zhenxun/plugins/ai/__init__.py +++ /dev/null @@ -1,87 +0,0 @@ -from typing import List - -from nonebot import on_message -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import BotConfig, Config -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.models.friend_user import FriendUser -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.services.log import logger -from zhenxun.utils.depends import UserName -from zhenxun.utils.message import MessageUtils - -from .data_source import get_chat_result, hello, no_result - -__plugin_meta__ = PluginMetadata( - name="AI", - description="屑Ai", - usage=f""" - 与{BotConfig.self_nickname}普普通通的对话吧! - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - configs=[ - RegisterConfig( - module="alapi", - key="ALAPI_TOKEN", - value=None, - help="在 https://admin.alapi.cn/user/login 登录后获取token", - ), - RegisterConfig(key="TL_KEY", value=[], help="图灵Key", type=List[str]), - RegisterConfig( - key="ALAPI_AI_CHECK", - value=False, - help="是否检测青云客骂娘回复", - default_value=False, - type=bool, - ), - RegisterConfig( - key="TEXT_FILTER", - value=["鸡", "口交"], - help="文本过滤器,将敏感词更改为*", - type=List[str], - ), - ], - ).dict(), -) - - -ai = on_message(rule=to_me(), priority=998) - - -@ai.handle() -async def _(message: UniMsg, session: EventSession, uname: str = UserName()): - if not message or message.extract_plain_text() in [ - "你好啊", - "你好", - "在吗", - "在不在", - "您好", - "您好啊", - "你好", - "在", - ]: - await hello().finish() - if not session.id1: - await Text("用户id不存在...").finish() - gid = session.id3 or session.id2 - if gid: - nickname = await GroupInfoUser.get_user_nickname(session.id1, gid) - else: - nickname = await FriendUser.get_user_nickname(session.id1) - if not nickname: - nickname = uname - result = await get_chat_result(message, session.id1, nickname) - logger.info(f"问题:{message} ---- 回答:{result}", "ai", session=session) - if result: - result = str(result) - for t in Config.get_config("ai", "TEXT_FILTER"): - result = result.replace(t, "*") - await MessageUtils.build_message(result).finish() - else: - await no_result().finish() diff --git a/zhenxun/plugins/ai/data_source.py b/zhenxun/plugins/ai/data_source.py deleted file mode 100644 index 8c813c7d6..000000000 --- a/zhenxun/plugins/ai/data_source.py +++ /dev/null @@ -1,241 +0,0 @@ -import os -import random -import re - -import ujson as json -from nonebot_plugin_alconna import UniMessage, UniMsg - -from zhenxun.configs.config import BotConfig, Config -from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -from .utils import ai_message_manager - -url = "http://openapi.tuling123.com/openapi/api/v2" - -check_url = "https://v2.alapi.cn/api/censor/text" - -index = 0 - -anime_data = json.load(open(DATA_PATH / "anime.json", "r", encoding="utf8")) - - -async def get_chat_result( - message: UniMsg, user_id: str, nickname: str -) -> UniMessage | None: - """获取 AI 返回值,顺序: 特殊回复 -> 图灵 -> 青云客 - - 参数: - text: 问题 - img_url: 图片链接 - user_id: 用户id - nickname: 用户昵称 - - 返回 - str: 回答 - """ - global index - text = message.extract_plain_text() - ai_message_manager.add_message(user_id, text) - special_rst = await ai_message_manager.get_result(user_id, nickname) - if special_rst: - ai_message_manager.add_result(user_id, special_rst) - return MessageUtils.build_message(special_rst) - if index == 5: - index = 0 - if len(text) < 6 and random.random() < 0.6: - keys = anime_data.keys() - for key in keys: - if text.find(key) != -1: - return random.choice(anime_data[key]).replace("你", nickname) - rst = await tu_ling(text, "", user_id) - if not rst: - rst = await xie_ai(text) - if not rst: - return None - if nickname: - if len(nickname) < 5: - if random.random() < 0.5: - nickname = "~".join(nickname) + "~" - if random.random() < 0.2: - if nickname.find("大人") == -1: - nickname += "大~人~" - rst = str(rst).replace("小主人", nickname).replace("小朋友", nickname) - ai_message_manager.add_result(user_id, rst) - for t in Config.get_config("ai", "TEXT_FILTER"): - rst = rst.replace(t, "*") - return MessageUtils.build_message(rst) - - -# 图灵接口 -async def tu_ling(text: str, img_url: str, user_id: str) -> str | None: - """获取图灵接口的回复 - - 参数: - text: 问题 - img_url: 图片链接 - user_id: 用户id - - 返回 - str: 图灵回复 - """ - global index - TL_KEY = Config.get_config("ai", "TL_KEY") - req = None - if not TL_KEY: - return None - try: - if text: - req = { - "perception": { - "inputText": {"text": text}, - "selfInfo": { - "location": { - "city": "陨石坑", - "province": "火星", - "street": "第5坑位", - } - }, - }, - "userInfo": {"apiKey": TL_KEY[index], "userId": str(user_id)}, - } - elif img_url: - req = { - "reqType": 1, - "perception": { - "inputImage": {"url": img_url}, - "selfInfo": { - "location": { - "city": "陨石坑", - "province": "火星", - "street": "第5坑位", - } - }, - }, - "userInfo": {"apiKey": TL_KEY[index], "userId": str(user_id)}, - } - except IndexError: - index = 0 - return None - text = "" - response = await AsyncHttpx.post(url, json=req) - if response.status_code != 200: - return None - resp_payload = json.loads(response.text) - if int(resp_payload["intent"]["code"]) in [4003]: - return None - if resp_payload["results"]: - for result in resp_payload["results"]: - if result["resultType"] == "text": - text = result["values"]["text"] - if "请求次数超过" in text: - text = "" - return text - - -# 屑 AI -async def xie_ai(text: str) -> str: - """获取青云客回复 - - 参数: - text: 问题 - - 返回: - str: 青云可回复 - """ - res = await AsyncHttpx.get( - f"http://api.qingyunke.com/api.php?key=free&appid=0&msg={text}" - ) - content = "" - try: - data = json.loads(res.text) - if data["result"] == 0: - content = data["content"] - if "菲菲" in content: - content = content.replace("菲菲", BotConfig.self_nickname) - if "艳儿" in content: - content = content.replace("艳儿", BotConfig.self_nickname) - if "公众号" in content: - content = "" - if "{br}" in content: - content = content.replace("{br}", "\n") - if "提示" in content: - content = content[: content.find("提示")] - if "淘宝" in content or "taobao.com" in content: - return "" - while True: - r = re.search("{face:(.*)}", content) - if r: - id_ = r.group(1) - content = content.replace("{" + f"face:{id_}" + "}", "") - else: - break - return ( - content - if not content and not Config.get_config("ai", "ALAPI_AI_CHECK") - else await check_text(content) - ) - except Exception as e: - logger.error(f"Ai xie_ai 发生错误", e=e) - return "" - - -def hello() -> UniMessage: - """一些打招呼的内容""" - result = random.choice( - ( - "哦豁?!", - "你好!Ov<", - f"库库库,呼唤{BotConfig.self_nickname}做什么呢", - "我在呢!", - "呼呼,叫俺干嘛", - ) - ) - img = random.choice(os.listdir(IMAGE_PATH / "zai")) - return MessageUtils.build_message([IMAGE_PATH / "zai" / img, result]) - - -def no_result() -> UniMessage: - """ - 没有回答时的回复 - """ - return MessageUtils.build_message( - [ - random.choice( - [ - "你在说啥子?", - f"纯洁的{BotConfig.self_nickname}没听懂", - "下次再告诉你(下次一定)", - "你觉得我听懂了吗?嗯?", - "我!不!知!道!", - ] - ), - IMAGE_PATH - / "noresult" - / random.choice(os.listdir(IMAGE_PATH / "noresult")), - ] - ) - - -async def check_text(text: str) -> str: - """ALAPI文本检测,主要针对青云客API,检测为恶俗文本改为无回复的回答 - - 参数: - text: 回复 - - 返回: - str: 检测文本 - """ - if not Config.get_config("alapi", "ALAPI_TOKEN"): - return text - params = {"token": Config.get_config("alapi", "ALAPI_TOKEN"), "text": text} - try: - data = (await AsyncHttpx.get(check_url, timeout=2, params=params)).json() - if data["code"] == 200: - if data["data"]["conclusion_type"] == 2: - return "" - except Exception as e: - logger.error(f"检测违规文本错误...", e=e) - return text diff --git a/zhenxun/plugins/ai/utils.py b/zhenxun/plugins/ai/utils.py deleted file mode 100644 index e22d1fde9..000000000 --- a/zhenxun/plugins/ai/utils.py +++ /dev/null @@ -1,153 +0,0 @@ -import random -import time - -from zhenxun.configs.config import BotConfig -from zhenxun.models.ban_console import BanConsole - - -class AiMessageManager: - def __init__(self): - self._data = {} - self._same_message = [ - "为什么要发一样的话?", - "请不要再重复对我说一句话了,不然我就要生气了!", - "别再发这句话了,我已经知道了...", - "你是只会说这一句话吗?", - "[*],你发我也发!", - "[uname],[*]", - f"救命!有笨蛋一直给{BotConfig.self_nickname}发一样的话!", - "这句话你已经给我发了{}次了,再发就生气!", - ] - self._repeat_message = [ - f"请不要学{BotConfig.self_nickname}说话", - f"为什么要一直学{BotConfig.self_nickname}说话?", - "你再学!你再学我就生气了!", - f"呜呜,你是想欺负{BotConfig.self_nickname}嘛..", - "[uname]不要再学我说话了!", - "再学我说话,我就把你拉进黑名单(生气", - "你再学![uname]是个笨蛋!", - "你已经学我说话{}次了!别再学了!", - ] - - def add_message(self, user_id: str, message: str): - """添加用户消息 - - 参数: - user_id: 用户id - message: 消息内容 - """ - if message: - if self._data.get(user_id) is None: - self._data[user_id] = { - "time": time.time(), - "message": [], - "result": [], - "repeat_count": 0, - } - if time.time() - self._data[user_id]["time"] > 60 * 10: - self._data[user_id]["message"].clear() - self._data[user_id]["time"] = time.time() - self._data[user_id]["message"].append(message.strip()) - - def add_result(self, user_id: str, message: str): - """添加回复用户的消息 - - 参数: - user_id: 用户id - message: 回复消息内容 - """ - if message: - if self._data.get(user_id) is None: - self._data[user_id] = { - "time": time.time(), - "message": [], - "result": [], - "repeat_count": 0, - } - if time.time() - self._data[user_id]["time"] > 60 * 10: - self._data[user_id]["result"].clear() - self._data[user_id]["repeat_count"] = 0 - self._data[user_id]["time"] = time.time() - self._data[user_id]["result"].append(message.strip()) - - async def get_result(self, user_id: str, nickname: str) -> str | None: - """特殊消息特殊回复 - - 参数: - user_id: 用户id - nickname: 用户昵称 - - 返回: - str | None: 回答 - """ - try: - if len(self._data[user_id]["message"]) < 2: - return None - except KeyError: - return None - msg = await self._get_user_repeat_message_result(user_id) - if not msg: - msg = await self._get_user_same_message_result(user_id) - if msg: - if "[uname]" in msg: - msg = msg.replace("[uname]", nickname) - if not msg.startswith("生气了!你好烦,闭嘴!") and "[*]" in msg: - msg = msg.replace("[*]", self._data[user_id]["message"][-1]) - return msg - - async def _get_user_same_message_result(self, user_id: str) -> str | None: - """重复消息回复 - - 参数: - user_id: 用户id - - 返回: - str | None: 回答 - """ - msg = self._data[user_id]["message"][-1] - cnt = 0 - _tmp = self._data[user_id]["message"][:-1] - _tmp.reverse() - for s in _tmp: - if s == msg: - cnt += 1 - else: - break - if cnt > 1: - if random.random() < 0.5 and cnt > 3: - rand = random.randint(60, 300) - await BanConsole.ban(user_id, None, 9, rand, None) - self._data[user_id]["message"].clear() - return f"生气了!你好烦,闭嘴!给我老实安静{rand}秒" - return random.choice(self._same_message).format(cnt) - return None - - async def _get_user_repeat_message_result(self, user_id: str) -> str | None: - """复读真寻的消息回复 - - 参数: - user_id: 用户id - - 返回: - str | None: 回答 - """ - msg = self._data[user_id]["message"][-1] - if self._data[user_id]["result"]: - rst = self._data[user_id]["result"][-1] - else: - return None - if msg == rst: - self._data[user_id]["repeat_count"] += 1 - cnt = self._data[user_id]["repeat_count"] - if cnt > 1: - if random.random() < 0.5 and cnt > 3: - rand = random.randint(60, 300) - await BanConsole.ban(user_id, None, 9, rand, None) - self._data[user_id]["result"].clear() - self._data[user_id]["repeat_count"] = 0 - return f"生气了!你好烦,闭嘴!给我老实安静{rand}秒" - return random.choice(self._repeat_message).format(cnt) - return None - - -ai_message_manager = AiMessageManager() diff --git a/zhenxun/plugins/alapi/__init__.py b/zhenxun/plugins/alapi/__init__.py deleted file mode 100644 index 3efe41132..000000000 --- a/zhenxun/plugins/alapi/__init__.py +++ /dev/null @@ -1,14 +0,0 @@ -from pathlib import Path - -import nonebot - -from zhenxun.configs.config import Config - -Config.add_plugin_config( - "alapi", - "ALAPI_TOKEN", - None, - help="在https://admin.alapi.cn/user/login登录后获取token", -) - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/alapi/_data_source.py b/zhenxun/plugins/alapi/_data_source.py deleted file mode 100644 index 61037ab9c..000000000 --- a/zhenxun/plugins/alapi/_data_source.py +++ /dev/null @@ -1,29 +0,0 @@ -from zhenxun.configs.config import Config -from zhenxun.utils.http_utils import AsyncHttpx - - -async def get_data(url: str, params: dict | None = None) -> tuple[dict | str, int]: - """获取ALAPI数据 - - 参数: - url: 请求链接 - params: 参数 - - 返回: - tuple[dict | str, int]: 返回信息 - """ - if not params: - params = {} - params["token"] = Config.get_config("alapi", "ALAPI_TOKEN") - try: - data = (await AsyncHttpx.get(url, params=params, timeout=5)).json() - if data["code"] == 200: - if not data["data"]: - return "没有搜索到...", 997 - return data, 200 - else: - if data["code"] == 101: - return "缺失ALAPI TOKEN,请在配置文件中填写!", 999 - return f'发生了错误...code:{data["code"]}', 999 - except TimeoutError: - return "超时了....", 998 diff --git a/zhenxun/plugins/alapi/comments_163.py b/zhenxun/plugins/alapi/comments_163.py deleted file mode 100644 index d05a5aa97..000000000 --- a/zhenxun/plugins/alapi/comments_163.py +++ /dev/null @@ -1,57 +0,0 @@ -from nonebot import on_regex -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import get_data - -comments_163_url = "https://v2.alapi.cn/api/comment" - -__plugin_meta__ = PluginMetadata( - name="网易云热评", - description="生了个人,我很抱歉", - usage=""" - 到点了,还是防不了下塔 - 指令: - 网易云热评/到点了/12点了 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_matcher = on_alconna( - Alconna("网易云热评"), - priority=5, - block=True, -) - -_matcher.shortcut( - "(到点了|12点了)", - command="网易云热评", - arguments=[], - prefix=True, -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - data, code = await get_data(comments_163_url) - if code != 200 and isinstance(data, str): - await MessageUtils.build_message(data).finish(reply_to=True) - data = data["data"] # type: ignore - comment = data["comment_content"] # type: ignore - song_name = data["title"] # type: ignore - await MessageUtils.build_message(f"{comment}\n\t——《{song_name}》").send( - reply_to=True - ) - logger.info( - f" 发送网易云热评: {comment} \n\t\t————{song_name}", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/plugins/alapi/cover.py b/zhenxun/plugins/alapi/cover.py deleted file mode 100644 index e26bf79cf..000000000 --- a/zhenxun/plugins/alapi/cover.py +++ /dev/null @@ -1,46 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Image, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import get_data - -cover_url = "https://v2.alapi.cn/api/bilibili/cover" - -__plugin_meta__ = PluginMetadata( - name="b封面", - description="快捷的b站视频封面获取方式", - usage=""" - b封面 [链接/av/bv/cv/直播id] - 示例:b封面 av86863038 - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", menu_type="一些工具" - ).dict(), -) - -_matcher = on_alconna( - Alconna("b封面", Args["url", str]), - priority=5, - block=True, -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma, url: str): - params = {"c": url} - data, code = await get_data(cover_url, params) - if code != 200 and isinstance(data, str): - await MessageUtils.build_message(data).finish(reply_to=True) - data = data["data"] # type: ignore - title = data["title"] # type: ignore - img = data["cover"] # type: ignore - await MessageUtils.build_message([f"title:{title}\n", Image(url=img)]).send( - reply_to=True - ) - logger.info( - f" 获取b站封面: {title} url:{img}", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/alapi/jitang.py b/zhenxun/plugins/alapi/jitang.py deleted file mode 100644 index 63dc93e29..000000000 --- a/zhenxun/plugins/alapi/jitang.py +++ /dev/null @@ -1,48 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import get_data - -url = "https://v2.alapi.cn/api/soul" - -__plugin_meta__ = PluginMetadata( - name="鸡汤", - description="喏,亲手为你煮的鸡汤", - usage=""" - 不喝点什么感觉有点不舒服 - 指令: - 鸡汤 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_matcher = on_alconna( - Alconna("鸡汤"), - priority=5, - block=True, -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - try: - data, code = await get_data(url) - if code != 200 and isinstance(data, str): - await MessageUtils.build_message(data).finish(reply_to=True) - await MessageUtils.build_message(data["data"]["content"]).send(reply_to=True) # type: ignore - logger.info( - f" 发送鸡汤:" + data["data"]["content"], # type:ignore - arparma.header_result, - session=session, - ) - except Exception as e: - await MessageUtils.build_message("鸡汤煮坏掉了...").send() - logger.error(f"鸡汤煮坏掉了", e=e) diff --git a/zhenxun/plugins/alapi/poetry.py b/zhenxun/plugins/alapi/poetry.py deleted file mode 100644 index 4d3594980..000000000 --- a/zhenxun/plugins/alapi/poetry.py +++ /dev/null @@ -1,57 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import get_data - -__plugin_meta__ = PluginMetadata( - name="古诗", - description="为什么突然文艺起来了!", - usage=""" - 平白无故念首诗 - 示例:念诗/来首诗/念首诗 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_matcher = on_alconna( - Alconna("念诗"), - priority=5, - block=True, -) - -_matcher.shortcut( - "(来首诗|念首诗)", - command="念诗", - arguments=[], - prefix=True, -) - - -poetry_url = "https://v2.alapi.cn/api/shici" - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - data, code = await get_data(poetry_url) - if code != 200 and isinstance(data, str): - await MessageUtils.build_message(data).finish(reply_to=True) - data = data["data"] # type: ignore - content = data["content"] # type: ignore - title = data["origin"] # type: ignore - author = data["author"] # type: ignore - await MessageUtils.build_message(f"{content}\n\t——{author}《{title}》").send( - reply_to=True - ) - logger.info( - f" 发送古诗: f'{content}\n\t--{author}《{title}》'", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/plugins/black_word/__init__.py b/zhenxun/plugins/black_word/__init__.py deleted file mode 100644 index eb35e275d..000000000 --- a/zhenxun/plugins/black_word/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from pathlib import Path - -import nonebot - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/black_word/black_watch.py b/zhenxun/plugins/black_word/black_watch.py deleted file mode 100644 index 133352a64..000000000 --- a/zhenxun/plugins/black_word/black_watch.py +++ /dev/null @@ -1,62 +0,0 @@ -from nonebot.adapters import Bot, Event -from nonebot.matcher import Matcher -from nonebot.message import run_preprocessor -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.ban_console import BanConsole -from zhenxun.models.group_console import GroupConsole -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType - -from .utils import black_word_manager - -__plugin_meta__ = PluginMetadata( - name="敏感词文本监听", - description="敏感词文本监听", - usage="".strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.DEPENDANT, - ).dict(), -) - -base_config = Config.get("black_word") - - -# 黑名单词汇检测 -@run_preprocessor -async def _( - bot: Bot, message: UniMsg, matcher: Matcher, event: Event, session: EventSession -): - gid = session.id3 or session.id2 - if session.id1: - if ( - event.is_tome() - and matcher.plugin_name == "black_word" - and not await BanConsole.is_ban(session.id1, gid) - ): - msg = message.extract_plain_text() - if session.id1 in bot.config.superusers: - return logger.debug( - f"超级用户跳过黑名单词汇检查 Message: {msg}", target=session.id1 - ) - if gid: - """屏蔽群权限-1的群""" - group, _ = await GroupConsole.get_or_create( - group_id=gid, channel_id__isnull=True - ) - if group.level < 0: - return - if await BanConsole.is_ban(None, gid): - """屏蔽群被ban的群""" - return - if await black_word_manager.check(bot, session, msg) and base_config.get( - "CONTAIN_BLACK_STOP_PROPAGATION" - ): - matcher.stop_propagation() diff --git a/zhenxun/plugins/black_word/black_word.py b/zhenxun/plugins/black_word/black_word.py deleted file mode 100644 index 8164851f0..000000000 --- a/zhenxun/plugins/black_word/black_word.py +++ /dev/null @@ -1,235 +0,0 @@ -from datetime import datetime -from typing import List - -from nonebot.adapters import Bot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Option, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import BotConfig -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from .data_source import set_user_punish, show_black_text_image - -__plugin_meta__ = PluginMetadata( - name="敏感词检测", - description="请注意你的发言!", - usage=""" - 惩罚机制: 检测内容提示 - 设置惩罚 [uid] [id] [level]: 设置惩罚内容, 此id需要通过`记录名单 -u:uid`来获取 - 记录名单: 查看检测记录名单 - 记录名单: - -u [uid] 指定用户记录名单 - -g [gid] 指定群组记录名单 - -d [date] 指定日期 - -dt ['=', '>', '<'] 大于小于等于指定日期 - - 示例: - 设置惩罚 123123123 0 1 - 记录名单 -u 123123123 - 记录名单 -g 333333 - 记录名单 -d 2022-11-11 - 记录名单 -d 2022-11-11 -dt > - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.SUPERUSER, - menu_type="其他", - configs=[ - RegisterConfig( - key="CYCLE_DAYS", - value=30, - help="黑名单词汇记录周期", - default_value=30, - type=int, - ), - RegisterConfig( - key="TOLERATE_COUNT", - value=[5, 1, 1, 1, 1], - help="各个级别惩罚的容忍次数, 依次为: 1, 2, 3, 4, 5", - default_value=[5, 1, 1, 1, 1], - type=List[int], - ), - RegisterConfig( - key="AUTO_PUNISH", - value=True, - help="是否启动自动惩罚机制", - default_value=True, - type=bool, - ), - RegisterConfig( - key="BAN_4_DURATION", - value=360, - help="Ban时长(分钟),四级惩罚,可以为指定数字或指定列表区间(随机),例如 [30, 360]", - default_value=360, - type=int, - ), - RegisterConfig( - key="BAN_3_DURATION", - value=7, - help="Ban时长(天),三级惩罚,可以为指定数字或指定列表区间(随机),例如 [7, 30]", - default_value=7, - type=int, - ), - RegisterConfig( - key="WARNING_RESULT", - value=f"请注意对{BotConfig.self_nickname}的发言内容", - help="口头警告内容", - default_value=None, - ), - RegisterConfig( - key="AUTO_ADD_PUNISH_LEVEL", - value=360, - help="自动提级机制,当周期内处罚次数大于某一特定值就提升惩罚等级", - default_value=360, - type=int, - ), - RegisterConfig( - key="ADD_PUNISH_LEVEL_TO_COUNT", - value=3, - help="在CYCLE_DAYS周期内触发指定惩罚次数后提升惩罚等级", - default_value=3, - type=int, - ), - RegisterConfig( - key="ALAPI_CHECK_FLAG", - value=False, - help="当未检测到已收录的敏感词时,开启ALAPI文本检测并将疑似文本发送给超级用户", - default_value=False, - type=bool, - ), - RegisterConfig( - key="CONTAIN_BLACK_STOP_PROPAGATION", - value=True, - help="当文本包含任意敏感词时,停止向下级插件传递,即不触发ai", - default_value=True, - type=bool, - ), - ], - ).dict(), -) - - -_punish_matcher = on_alconna( - Alconna("设置惩罚", Args["uid", str]["id", int]["punish_level", int]), - priority=1, - permission=SUPERUSER, - block=True, -) - - -_show_matcher = on_alconna( - Alconna( - "记录名单", - Option("-u|--uid", Args["uid", str]), - Option("-g|--group", Args["gid", str]), - Option("-d|--date", Args["date", str]), - Option("-dt|--type", Args["date_type", ["=", ">", "<"]], default="="), - ), - priority=1, - permission=SUPERUSER, - block=True, -) - -_show_punish_matcher = on_alconna( - Alconna("惩罚机制"), aliases={"敏感词检测"}, priority=1, block=True -) - - -@_show_matcher.handle() -async def _( - bot: Bot, uid: Match[str], gid: Match[str], date: Match[str], date_type: Match[str] -): - user_id = None - group_id = None - date_ = None - date_str = None - date_type_ = "=" - if uid.available: - user_id = uid.result - if gid.available: - group_id = gid.result - if date.available: - date_str = date.result - if date_type.available: - date_type_ = date_type.result - if date_str: - try: - date_ = datetime.strptime(date_str, "%Y-%m-%d") - except ValueError: - await MessageUtils.build_message("日期格式错误,需要:年-月-日").finish() - result = await show_black_text_image( - user_id, - group_id, - date_, - date_type_, - ) - await MessageUtils.build_message(result).send() - - -@_show_punish_matcher.handle() -async def _(): - text = f""" - ** 惩罚机制 ** - - 惩罚前包含容忍机制,在指定周期内会容忍偶尔少次数的敏感词只会进行警告提醒 - - 多次触发同级惩罚会使惩罚等级提高,即惩罚自动提级机制 - - 目前公开的惩罚等级: - - 1级:永久ban - - 2级:删除好友 - - 3级:ban指定/随机天数 - - 4级:ban指定/随机时长 - - 5级:警告 - - 备注: - - 该功能为测试阶段,如果你有被误封情况,请联系管理员,会从数据库中提取出你的数据进行审核后判断 - - 目前该功能暂不完善,部分情况会由管理员鉴定,请注意对真寻的发言 - - 关于敏感词: - - 记住不要骂{BotConfig.self_nickname}就对了! - """.strip() - max_width = 0 - for m in text.split("\n"): - max_width = len(m) * 20 if len(m) * 20 > max_width else max_width - max_height = len(text.split("\n")) * 24 - A = BuildImage( - max_width, max_height, font="CJGaoDeGuo.otf", font_size=24, color="#E3DBD1" - ) - await A.text((10, 10), text) - await MessageUtils.build_message(A).send() - - -@_punish_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - uid: str, - id: int, - punish_level: int, -): - result = await set_user_punish( - bot, uid, session.id2 or session.id3, id, punish_level - ) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info( - f"设置惩罚 uid:{uid} id_:{id} punish_level:{punish_level} --> {result}", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/plugins/black_word/data_source.py b/zhenxun/plugins/black_word/data_source.py deleted file mode 100644 index e985facc2..000000000 --- a/zhenxun/plugins/black_word/data_source.py +++ /dev/null @@ -1,103 +0,0 @@ -from datetime import datetime - -from nonebot.adapters import Bot - -from zhenxun.models.friend_user import FriendUser -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.utils.image_utils import BuildImage, ImageTemplate - -from .model import BlackWord -from .utils import Config, _get_punish - - -async def show_black_text_image( - user_id: str | None, - group_id: str | None, - date: datetime | None, - data_type: str = "=", -) -> BuildImage: - """展示记录名单 - - 参数: - bot: bot - user: 用户id - group_id: 群组id - date: 日期 - data_type: 日期搜索类型 - - 返回: - BuildImage: 数据图片 - """ - data_list = await BlackWord.get_black_data(user_id, group_id, date, data_type) - column_name = [ - "ID", - "昵称", - "UID", - "GID", - "文本", - "检测内容", - "检测等级", - "惩罚", - "平台", - "记录日期", - ] - column_list = [] - uid_list = [u for u in data_list] - uid2name = { - u.user_id: u.user_name for u in await FriendUser.filter(user_id__in=uid_list) - } - for i, data in enumerate(data_list): - uname = uid2name.get(data.user_id) - if not uname: - if u := await GroupInfoUser.get_or_none( - user_id=data.user_id, group_id=data.group_id - ): - uname = u.user_name - if len(data.plant_text) > 30: - data.plant_text = data.plant_text[:30] + "..." - column_list.append( - [ - i, - uname or data.user_id, - data.user_id, - data.group_id, - data.plant_text, - data.black_word, - data.punish_level, - data.punish, - data.platform, - data.create_time, - ] - ) - A = await ImageTemplate.table_page( - "记录名单", "一个都不放过!", column_name, column_list - ) - return A - - -async def set_user_punish( - bot: Bot, user_id: str, group_id: str | None, id_: int, punish_level: int -) -> str: - """设置惩罚 - - 参数: - user_id: 用户id - group_id: 群组id或频道id - id_: 记录下标 - punish_level: 惩罚等级 - - 返回: - str: 结果 - """ - result = await _get_punish(bot, punish_level, user_id, group_id) - punish = { - 1: "永久ban", - 2: "删除好友", - 3: f"ban {result} 天", - 4: f"ban {result} 分钟", - 5: "口头警告", - } - if await BlackWord.set_user_punish(user_id, punish[punish_level], id_=id_): - return f"已对 USER {user_id} 进行 {punish[punish_level]} 处罚。" - else: - return "操作失败,可能未找到用户,id或敏感词" diff --git a/zhenxun/plugins/black_word/model.py b/zhenxun/plugins/black_word/model.py deleted file mode 100644 index ef81c0ba7..000000000 --- a/zhenxun/plugins/black_word/model.py +++ /dev/null @@ -1,154 +0,0 @@ -from datetime import datetime, timedelta -from email.policy import default - -import pytz -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class BlackWord(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255, 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) - """创建时间""" - platform = fields.CharField(255, null=True) - """平台""" - - class Meta: - table = "black_word" - table_description = "惩罚机制数据表" - - @classmethod - async def set_user_punish( - cls, - user_id: str, - punish: str, - black_word: str | None = None, - id_: int | None = None, - ) -> bool: - """设置处罚 - - 参数: - user_id: 用户id - punish: 处罚 - black_word: 黑名单词汇 - id_: 记录下标 - """ - user = None - if (not black_word and id_ is None) or not punish: - return False - if black_word: - user = ( - await cls.filter(user_id=user_id, black_word=black_word, punish="") - .order_by("id") - .first() - ) - elif id_ is not None: - user_list = await cls.filter(user_id=user_id).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 - user.punish = f"{user.punish}{punish} " - await user.save(update_fields=["punish"]) - return True - - @classmethod - async def get_user_count( - cls, user_id: str, days: int = 7, punish_level: int | None = None - ) -> int: - """获取用户规定周期内的犯事次数 - - 参数: - user_id: 用户id - days: 周期天数 - punish_level: 惩罚等级 - """ - query = cls.filter( - user_id=user_id, - create_time__gte=datetime.now() - timedelta(days=days), - punish_level__not_in=[-1], - ) - if punish_level is not None: - query = query.filter(punish_level=punish_level) - return await query.count() - - @classmethod - async def get_user_punish_level(cls, user_id: str, days: int = 7) -> int | None: - """获取用户最近一次的惩罚记录等级 - - 参数: - user_id: 用户id - days: 周期天数 - """ - if ( - user := await cls.filter( - user_id=user_id, - create_time__gte=datetime.now() - timedelta(days=days), - ) - .order_by("id") - .first() - ): - return user.punish_level - return None - - @classmethod - async def get_black_data( - cls, - user_id: str | None, - group_id: str | None, - date: datetime | None, - date_type: str = "=", - ) -> list["BlackWord"]: - """通过指定条件查询数据 - - 参数: - user_id: 用户id - group_id: 群号 - date: 日期 - date_type: 日期查询类型 - """ - query = cls - if user_id: - query = query.filter(user_id=user_id) - if group_id: - query = query.filter(group_id=group_id) - if date: - if date_type == "=": - query = query.filter( - create_time__range=[date, date + timedelta(days=1)] - ) - elif date_type == ">": - query = query.filter(create_time__gte=date) - elif date_type == "<": - query = query.filter(create_time__lte=date) - data_list = await query.all().order_by("id") - for data in data_list: - data.create_time = data.create_time.astimezone( - pytz.timezone("Asia/Shanghai") - ) - return data_list # type: ignore - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE black_word RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE black_word ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE black_word ALTER COLUMN group_id TYPE character varying(255);", - "ALTER TABLE black_word ADD COLUMN platform character varying(255);", - ] diff --git a/zhenxun/plugins/black_word/utils.py b/zhenxun/plugins/black_word/utils.py deleted file mode 100644 index 53526bd0d..000000000 --- a/zhenxun/plugins/black_word/utils.py +++ /dev/null @@ -1,374 +0,0 @@ -import random -from pathlib import Path - -import ujson as json -from nonebot.adapters import Bot -from nonebot.adapters.onebot.v11 import ActionFailed -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import DATA_PATH -from zhenxun.models.ban_console import BanConsole -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.utils import cn2py - -from .model import BlackWord - - -class BlackWordManager: - """ - 敏感词管理( 拒绝恶意 - """ - - def __init__(self, word_file: Path, py_file: Path): - self._word_list = { - "1": [], - "2": [], - "3": [], - "4": ["sb", "nmsl", "mdzz", "2b", "jb", "操", "废物", "憨憨", "cnm", "rnm"], - "5": [], - } - self._py_list = { - "1": [], - "2": [], - "3": [], - "4": [ - "shabi", - "wocaonima", - "sima", - "sabi", - "zhizhang", - "naocan", - "caonima", - "rinima", - "simadongxi", - "simawanyi", - "hanbi", - "hanpi", - "laji", - "fw", - ], - "5": [], - } - word_file.parent.mkdir(parents=True, exist_ok=True) - if word_file.exists(): - # 清空默认配置 - with open(word_file, "r", encoding="utf8") as f: - self._word_list = json.load(f) - else: - with open(word_file, "w", encoding="utf8") as f: - json.dump( - self._word_list, - f, - ensure_ascii=False, - indent=4, - ) - if py_file.exists(): - # 清空默认配置 - with open(py_file, "r", encoding="utf8") as f: - self._py_list = json.load(f) - else: - with open(py_file, "w", encoding="utf8") as f: - json.dump( - self._py_list, - f, - ensure_ascii=False, - indent=4, - ) - - async def check( - self, bot: Bot, session: EventSession, message: str - ) -> str | bool | None: - """检查是否包含黑名单词汇 - - 参数: - bot: Bot - session: EventSession - message: 消息 - """ - logger.debug( - f"检查文本是否含有黑名单词汇: {message}", "敏感词检测", session=session - ) - if session.id1: - if data := self._check(message): - if data[0]: - await _add_user_black_word( - bot, - session.id1, - session.id2 or session.id3, - data[0], - message, - int(data[1]), - ) - return True - if Config.get_config( - "black_word", "ALAPI_CHECK_FLAG" - ) and not await check_text(message): - await send_msg( - bot, - "", - None, - f"用户 {session.id1} 群组 {session.id3 or session.id2} ALAPI 疑似检测:{message}", - ) - return False - - def _check(self, message: str) -> tuple[str | None, int]: - """检测文本是否违规 - - 参数: - message: 检测消息 - """ - # 移除空格 - message = message.replace(" ", "") - py_msg = cn2py(message).lower() - # 完全匹配 - for x in [self._word_list, self._py_list]: - for level in x: - if message in x[level] or py_msg in x[level]: - return message if message in x[level] else py_msg, int(level) - # 模糊匹配 - for x in [self._word_list, self._py_list]: - for level in x: - for m in x[level]: - if m in message or m in py_msg: - return m, -1 - return None, 0 - - -async def _add_user_black_word( - bot: Bot, - user_id: str, - group_id: str | None, - black_word: str, - message: str, - punish_level: int, -): - """添加敏感词数据 - - 参数: - bot: Bot - user_id: 用户id - group_id: 群组id或频道id - black_word: 触发的黑名单词汇 - message: 原始文本 - punish_level: 惩罚等级 - """ - cycle_days = Config.get_config("black_word", "CYCLE_DAYS") or 7 - user_count = await BlackWord.get_user_count(user_id, cycle_days, punish_level) - add_punish_level_to_count = Config.get_config( - "black_word", "ADD_PUNISH_LEVEL_TO_COUNT" - ) - # 周期内超过次数直接提升惩罚 - if ( - Config.get_config("black_word", "AUTO_ADD_PUNISH_LEVEL") - and add_punish_level_to_count - ): - punish_level -= 1 - await BlackWord.create( - user_id=user_id, - group_id=group_id, - plant_text=message, - black_word=black_word, - punish_level=punish_level, - platform=PlatformUtils.get_platform(bot), - ) - logger.info( - f"已将 USER {user_id} GROUP {group_id} 添加至黑名单词汇记录 Black_word:{black_word} Plant_text:{message}" - ) - # 自动惩罚 - if Config.get_config("black_word", "AUTO_PUNISH") and punish_level != -1: - await _punish_handle(bot, user_id, group_id, punish_level, black_word) - - -async def _punish_handle( - bot: Bot, - user_id: str, - group_id: str | None, - punish_level: int, - black_word: str, -): - """惩罚措施,级别越低惩罚越严 - - 参数: - bot: Bot - user_id: 用户id - group_id: 群组id或频道id - black_word: 触发的黑名单词汇 - channel_id: 频道id - """ - logger.info(f"BlackWord USER {user_id} 触发 {punish_level} 级惩罚...") - # 周期天数 - cycle_days = Config.get_config("black_word", "CYCLE_DAYS") or 7 - # 用户周期内触发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 - ) - # 容忍次数:List[int] - tolerate_count = Config.get_config("black_word", "TOLERATE_COUNT") - if not tolerate_count or len(tolerate_count) < 5: - tolerate_count = [5, 2, 2, 2, 2] - if punish_level == 1 and user_count > tolerate_count[punish_level - 1]: - # 永久ban - await _get_punish(bot, 1, user_id, group_id) - await BlackWord.set_user_punish(user_id, "永久ban 删除好友", black_word) - elif punish_level == 2 and user_count > tolerate_count[punish_level - 1]: - # 删除好友 - await _get_punish(bot, 2, user_id, group_id) - await BlackWord.set_user_punish(user_id, "删除好友", black_word) - elif punish_level == 3 and user_count > tolerate_count[punish_level - 1]: - # 永久ban - ban_day = await _get_punish(bot, 3, user_id, group_id) - await BlackWord.set_user_punish(user_id, f"ban {ban_day} 天", black_word) - elif punish_level == 4 and user_count > tolerate_count[punish_level - 1]: - # ban指定时长 - ban_time = await _get_punish(bot, 4, user_id, group_id) - await BlackWord.set_user_punish(user_id, f"ban {ban_time} 分钟", black_word) - elif punish_level == 5 and user_count > tolerate_count[punish_level - 1]: - # 口头警告 - warning_result = await _get_punish(bot, 5, user_id, group_id) - await BlackWord.set_user_punish( - user_id, f"口头警告:{warning_result}", black_word - ) - else: - await BlackWord.set_user_punish(user_id, f"提示!", black_word) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker:该条发言已被记录,目前你在{cycle_days}天内的发表{punish_level}级" - f"言论记录次数为:{user_count}次,请注意你的发言\n" - f"* 如果你不清楚惩罚机制,请发送“惩罚机制” *", - ) - - -async def _get_punish( - bot: Bot, - id_: int, - user_id: str, - group_id: str | None = None, -) -> int | str | None: - """通过id_获取惩罚 - - 参数: - bot: Bot - id_: id - user_id: 用户id - group_id: 群组id或频道id - """ - # 忽略的群聊 - # _ignore_group = Config.get_config("black_word", "IGNORE_GROUP") - # 处罚 id 4 ban 时间:int,List[int] - ban_3_duration = Config.get_config("black_word", "BAN_3_DURATION") or 7 - # 处罚 id 4 ban 时间:int,List[int] - ban_4_duration = Config.get_config("black_word", "BAN_4_DURATION") or 360 - # 口头警告内容 - warning_result = Config.get_config("black_word", "WARNING_RESULT") - if user := await GroupInfoUser.get_or_none(user_id=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 BanConsole.ban(user_id, group_id, 10, -1, None) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker 永久ban USER {uname}({user_id})", - ) - logger.info(f"BlackWord 永久封禁 USER {user_id}...") - # 删除好友(有的话 - elif id_ == 2: - if str(user_id) not in bot.config.superusers: - try: - await bot.delete_friend(user_id=user_id) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker 删除好友 USER {uname}({user_id})", - ) - logger.info(f"BlackWord 删除好友 {user_id}...") - except ActionFailed: - pass - # 封禁用户指定时间,默认7天 - elif id_ == 3: - if isinstance(ban_3_duration, list): - ban_3_duration = random.randint(ban_3_duration[0], ban_3_duration[1]) - await BanConsole.ban(user_id, group_id, 9, ban_4_duration * 60 * 24) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker 对用户 USER {uname}({user_id}) 进行封禁 {ban_3_duration} 天处罚。", - ) - logger.info(f"BlackWord 封禁 USER {uname}({user_id}) {ban_3_duration} 天...") - return ban_3_duration - # 封禁用户指定时间,默认360分钟 - elif id_ == 4: - if isinstance(ban_4_duration, list): - ban_4_duration = random.randint(ban_4_duration[0], ban_4_duration[1]) - await BanConsole.ban(user_id, group_id, 9, ban_4_duration * 60) - await send_msg( - bot, - user_id, - group_id, - f"BlackWordChecker 对用户 USER {uname}({user_id}) 进行封禁 {ban_4_duration} 分钟处罚。", - ) - logger.info(f"BlackWord 封禁 USER {uname}({user_id}) {ban_4_duration} 分钟...") - return ban_4_duration - # 口头警告 - elif id_ == 5: - await PlatformUtils.send_message(bot, user_id, group_id, warning_result) - logger.info(f"BlackWord 口头警告 USER {user_id}") - return warning_result - return None - - -async def send_msg(bot: Bot, user_id: str, group_id: str | None, message: str): - """发送消息 - - 参数: - bot: Bot - user_id: user_id - group_id: group_id - message: message - """ - if not user_id: - platform = PlatformUtils.get_platform(bot) - user_id = bot.config.platform_superusers[platform][0] - await PlatformUtils.send_message(bot, user_id, group_id, message) - - -async def check_text(text: str) -> bool: - """ALAPI文本检测,检测输入违规 - - 参数: - text: 回复 - """ - if not Config.get_config("alapi", "ALAPI_TOKEN"): - return True - params = {"token": Config.get_config("alapi", "ALAPI_TOKEN"), "text": text} - try: - data = ( - await AsyncHttpx.get( - "https://v2.alapi.cn/api/censor/text", timeout=4, params=params - ) - ).json() - if data["code"] == 200: - return data["data"]["conclusion_type"] == 2 - except Exception as e: - logger.error(f"检测违规文本错误...", e=e) - return True - - -black_word_manager = BlackWordManager( - DATA_PATH / "black_word" / "black_word.json", - DATA_PATH / "black_word" / "black_py.json", -) diff --git a/zhenxun/plugins/bt/__init__.py b/zhenxun/plugins/bt/__init__.py deleted file mode 100644 index 3aff4f8b1..000000000 --- a/zhenxun/plugins/bt/__init__.py +++ /dev/null @@ -1,78 +0,0 @@ -from asyncio.exceptions import TimeoutError - -from httpx import ConnectTimeout -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import ensure_private - -from .data_source import get_bt_info - -__plugin_meta__ = PluginMetadata( - name="磁力搜索", - description="bt(磁力搜索)[仅支持私聊,懂的都懂]", - usage=""" - * 拒绝反冲斗士! * - 指令: - bt [关键词] ?[页数] - 示例: bt 钢铁侠 - 示例: bt 钢铁侠 3 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - configs=[ - RegisterConfig( - key="BT_MAX_NUM", - value=10, - help="单次BT搜索返回最大消息数量", - default_value=10, - type=int, - ), - ], - ).dict(), -) - - -_matcher = on_alconna( - Alconna("bt", Args["keyword", str]["page?", int]), - rule=ensure_private, - priority=5, - block=True, -) - - -@_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, - keyword: str, - page: Match[int], -): - send_flag = False - try: - async for title, type_, create_time, file_size, link in get_bt_info( - keyword, page.result if page.available else 1 - ): - await MessageUtils.build_message( - f"标题:{title}\n" - f"类型:{type_}\n" - f"创建时间:{create_time}\n" - f"文件大小:{file_size}\n" - f"种子:{link}" - ).send() - send_flag = True - except (TimeoutError, ConnectTimeout): - await MessageUtils.build_message(f"搜索 {keyword} 超时...").finish() - except Exception as e: - logger.error(f"bt 错误", arparma.header_result, session=session, e=e) - await MessageUtils.build_message(f"bt 其他未知错误..").finish() - if not send_flag: - await MessageUtils.build_message(f"{keyword} 未搜索到...").send() - logger.info( - f"BT搜索 {keyword} 第 {page} 页", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/bt/data_source.py b/zhenxun/plugins/bt/data_source.py deleted file mode 100644 index ad02a5d59..000000000 --- a/zhenxun/plugins/bt/data_source.py +++ /dev/null @@ -1,54 +0,0 @@ -from bs4 import BeautifulSoup - -from zhenxun.configs.config import Config -from zhenxun.utils.http_utils import AsyncHttpx, AsyncPlaywright - -url = "http://www.eclzz.ink" - - -async def get_bt_info(keyword: str, page: int): - """获取资源信息 - - 参数: - keyword: 关键词 - page: 页数 - """ - global url - text = (await AsyncHttpx.get(f"{url}/s/{keyword}_rel_{page}.html", timeout=30)).text - if "301 Moved Permanently" in text: - async with AsyncPlaywright.new_page() as _page: - await _page.goto(url) - url = _page.url - text = ( - await AsyncHttpx.get(f"{url}/s/{keyword}_rel_{page}.html", timeout=30) - ).text - if "大约0条结果" in text: - return - soup = BeautifulSoup(text, "lxml") - item_lst = soup.find_all("div", {"class": "search-item"}) - bt_max_num = Config.get_config("bt", "BT_MAX_NUM") or 10 - bt_max_num = bt_max_num if bt_max_num < len(item_lst) else len(item_lst) - for item in item_lst[:bt_max_num]: - divs = item.find_all("div") - title = ( - str(divs[0].find("a").text).replace("", "").replace("", "").strip() - ) - spans = divs[2].find_all("span") - type_ = spans[0].text - create_time = spans[1].find("b").text - file_size = spans[2].find("b").text - link = await get_download_link(divs[0].find("a")["href"]) - yield title, type_, create_time, file_size, link - - -async def get_download_link(_url: str) -> str | None: - """获取资源下载地址 - - 参数: - _url: 链接 - """ - text = (await AsyncHttpx.get(f"{url}{_url}")).text - soup = BeautifulSoup(text, "lxml") - if fd := soup.find("a", {"id": "down-url"}): - return fd["href"] # type: ignore - return None diff --git a/zhenxun/plugins/check/__init__.py b/zhenxun/plugins/check/__init__.py deleted file mode 100644 index ee63b50db..000000000 --- a/zhenxun/plugins/check/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from .data_source import Check - -__plugin_meta__ = PluginMetadata( - name="服务器自我检查", - description="查看服务器当前状态", - usage=""" - 查看服务器当前状态 - 指令: - 自检 - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER - ).dict(), -) - - -check = Check() - - -_matcher = on_alconna( - Alconna("自检"), rule=to_me(), permission=SUPERUSER, block=True, priority=1 -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - image = await check.show() - await MessageUtils.build_message(image).send() - logger.info("自检", arparma.header_result, session=session) diff --git a/zhenxun/plugins/check/data_source.py b/zhenxun/plugins/check/data_source.py deleted file mode 100644 index 78fbe7bae..000000000 --- a/zhenxun/plugins/check/data_source.py +++ /dev/null @@ -1,83 +0,0 @@ -import asyncio -import time -from datetime import datetime - -import psutil - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage - - -class Check: - def __init__(self): - self.cpu = None - self.memory = None - self.disk = None - self.user = None - self.baidu = 200 - self.google = 200 - - async def check_all(self): - await self.check_network() - await asyncio.sleep(0.1) - self.check_system() - self.check_user() - - def check_system(self): - self.cpu = psutil.cpu_percent() - self.memory = psutil.virtual_memory().percent - self.disk = psutil.disk_usage("/").percent - - async def check_network(self): - try: - await AsyncHttpx.get("https://www.baidu.com/", timeout=5) - except Exception as e: - logger.warning(f"访问BaiDu失败... {type(e)}: {e}") - self.baidu = 404 - try: - await AsyncHttpx.get("https://www.google.com/", timeout=5) - except Exception as e: - logger.warning(f"访问Google失败... {type(e)}: {e}") - self.google = 404 - - def check_user(self): - result = "" - for user in psutil.users(): - result += f'[{user.name}] {time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(user.started))}\n' - self.user = result[:-1] - - async def show(self) -> BuildImage: - await self.check_all() - font = BuildImage.load_font(font_size=24) - result = ( - f'[Time] {str(datetime.now()).split(".")[0]}\n' - f"-----System-----\n" - f"[CPU] {self.cpu}%\n" - f"[Memory] {self.memory}%\n" - f"[Disk] {self.disk}%\n" - f"-----Network-----\n" - f"[BaiDu] {self.baidu}\n" - f"[Google] {self.google}\n" - ) - if self.user: - result += "-----User-----\n" + self.user - width = 0 - height = 0 - for x in result.split("\n"): - w, h = BuildImage.get_text_size(x, font) - if w > width: - width = w - height += 30 - A = BuildImage(width + 50, height + 10, font_size=24) - await A.transparent(1) - await A.text((10, 10), result) - max_width = max(width, height) - bk = BuildImage( - max_width + 100, - max_width + 100, - background=IMAGE_PATH / "background" / "check" / "0.jpg", - ) - await bk.paste(A, center_type="center") - return bk diff --git a/zhenxun/plugins/coser.py b/zhenxun/plugins/coser.py deleted file mode 100644 index 90062d517..000000000 --- a/zhenxun/plugins/coser.py +++ /dev/null @@ -1,90 +0,0 @@ -import time -from typing import Tuple - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.withdraw_manage import WithdrawManager - -__plugin_meta__ = PluginMetadata( - name="coser", - description="三次元也不戳,嘿嘿嘿", - usage=""" - ?N连cos/coser - 示例: cos - 示例: 5连cos (单次请求张数小于9) - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - configs=[ - RegisterConfig( - key="WITHDRAW_COS_MESSAGE", - value=(0, 1), - help="自动撤回,参1:延迟撤回色图时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)", - default_value=(0, 1), - type=Tuple[int, int], - ), - ], - ).dict(), -) - -_matcher = on_alconna(Alconna("get-cos", Args["num", int, 1]), priority=5, block=True) - -_matcher.shortcut( - r"cos", - command="get-cos", - arguments=["1"], - prefix=True, -) - -_matcher.shortcut( - r"(?P\d)(张|个|条|连)cos", - command="get-cos", - arguments=["{num}"], - prefix=True, -) - - -# 纯cos,较慢:https://picture.yinux.workers.dev -# 比较杂,有福利姬,较快:https://api.jrsgslb.cn/cos/url.php?return=img -url = "https://picture.yinux.workers.dev" - - -@_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - num: int, -): - withdraw_time = Config.get_config("coser", "WITHDRAW_COS_MESSAGE") - for _ in range(num): - path = TEMP_PATH / f"cos_cc{int(time.time())}.jpeg" - try: - await AsyncHttpx.download_file(url, path) - receipt = await MessageUtils.build_message(path).send() - message_id = receipt.msg_ids[0]["message_id"] - if message_id and WithdrawManager.check(session, withdraw_time): - WithdrawManager.append( - bot, - message_id, - withdraw_time[0], - ) - logger.info(f"发送cos", arparma.header_result, session=session) - except Exception as e: - await MessageUtils.build_message("你cos给我看!").send() - logger.error( - f"cos错误", - arparma.header_result, - session=session, - e=e, - ) diff --git a/zhenxun/plugins/dialogue/__init__.py b/zhenxun/plugins/dialogue/__init__.py deleted file mode 100644 index a99bc0558..000000000 --- a/zhenxun/plugins/dialogue/__init__.py +++ /dev/null @@ -1,158 +0,0 @@ -import nonebot -from nonebot import on_command -from nonebot.adapters import Bot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import Target, Text, UniMsg -from nonebot_plugin_session import EventSession -from nonebot_plugin_userinfo import EventUserInfo, UserInfo - -from zhenxun.configs.config import BotConfig -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.group_console import GroupConsole -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from ._data_source import DialogueManage - -__plugin_meta__ = PluginMetadata( - name="联系管理员", - description="跨越空间与时间跟管理员对话", - usage=""" - 滴滴滴- ?[文本] ?[图片] - 示例:滴滴滴- 我喜欢你 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="联系管理员", - superuser_help=""" - /t: 查看当前存储的消息 - /t [user_id] [group_id] [文本]: 在group回复指定用户 - /t [user_id] [文本]: 私聊用户 - /t -1 [group_id] [文本]: 在group内发送消息 - /t [id] [文本]: 回复指定id的对话,id在 /t 中获取 - 示例:/t 73747222 32848432 你好啊 - 示例:/t 73747222 你好不好 - 示例:/t -1 32848432 我不太好 - 示例:/t 0 我收到你的话了 - """.strip(), - ).dict(), -) - -config = nonebot.get_driver().config - - -_dialogue_matcher = on_command("滴滴滴-", priority=5, block=True) -_reply_matcher = on_command("/t", priority=1, permission=SUPERUSER, block=True) - - -@_dialogue_matcher.handle() -async def _( - bot: Bot, - message: UniMsg, - session: EventSession, - user_info: UserInfo = EventUserInfo(), -): - if session.id1: - message[0] = Text(str(message[0]).replace("滴滴滴-", "", 1)) - platform = PlatformUtils.get_platform(bot) - superuser_id = None - try: - if platform: - superuser_id = BotConfig.get_superuser(platform) - except IndexError: - await MessageUtils.build_message("管理员失联啦...").finish() - if not superuser_id: - await MessageUtils.build_message("管理员失联啦...").finish() - uname = user_info.user_displayname or user_info.user_name - group_name = "" - gid = session.id3 or session.id2 - if gid: - if g := await GroupConsole.get(group_id=gid): - group_name = g.group_name - logger.info( - f"发送消息至{platform}管理员: {message}", "滴滴滴-", session=session - ) - message.insert(0, Text("消息:\n")) - if gid: - message.insert(0, Text(f"群组: {group_name}({gid})\n")) - message.insert(0, Text(f"昵称: {uname}({session.id1})\n")) - message.insert(0, Text(f"Id: {DialogueManage._index}\n")) - message.insert(0, Text("*****一份交流报告*****\n")) - DialogueManage.add(uname, session.id1, gid, group_name, message, platform) - await message.send(bot=bot, target=Target(superuser_id, private=True)) - await MessageUtils.build_message("已成功发送给管理员啦!").send(reply_to=True) - else: - await MessageUtils.build_message("用户id为空...").send() - - -@_reply_matcher.handle() -async def _( - bot: Bot, - message: UniMsg, - session: EventSession, -): - message[0] = Text(str(message[0]).replace("/t", "", 1).strip()) - if session.id1: - msg = message.extract_plain_text() - if not msg: - platform = PlatformUtils.get_platform(bot) - data = DialogueManage._data - if not data: - await MessageUtils.build_message("暂无待回复消息...").finish() - if platform: - data = [data[d] for d in data if data[d].platform == platform] - for d in data: - await d.message.send( - bot=bot, target=Target(session.id1, private=True) - ) - else: - msg = msg.split() - group_id = "" - user_id = "" - if msg[0].replace("-", "", 1).isdigit(): - if len(msg[0]) < 4: - _id = int(msg[0]) - if _id >= 0: - if model := DialogueManage.get(_id): - user_id = model.user_id - group_id = model.group_id - else: - return MessageUtils.build_message("未获取此id数据").finish() - message[0] = Text(" ".join(str(message[0]).split(" ")[1:])) - else: - user_id = 0 - if msg[1].isdigit(): - group_id = msg[1] - message[0] = Text(" ".join(str(message[0]).split(" ")[2:])) - else: - await MessageUtils.build_message("群组id错误...").finish( - at_sender=True - ) - DialogueManage.remove(_id) - else: - user_id = msg[0] - if msg[1].isdigit() and len(msg[1]) > 5: - group_id = msg[1] - message[0] = Text(" ".join(str(message[0]).split(" ")[2:])) - else: - group_id = 0 - message[0] = Text(" ".join(str(message[0]).split(" ")[1:])) - else: - await MessageUtils.build_message("参数错误...").finish(at_sender=True) - if group_id: - if user_id: - message.insert(0, alcAt("user", user_id)) - message.insert(1, Text("\n管理员回复\n=======\n")) - await message.send(Target(group_id), bot) - await MessageUtils.build_message("消息发送成功!").finish(at_sender=True) - elif user_id: - await message.send(Target(user_id, private=True), bot) - await MessageUtils.build_message("消息发送成功!").finish(at_sender=True) - else: - await MessageUtils.build_message("群组id与用户id为空...").send() - else: - await MessageUtils.build_message("用户id为空...").send() diff --git a/zhenxun/plugins/dialogue/_data_source.py b/zhenxun/plugins/dialogue/_data_source.py deleted file mode 100644 index 440c8176f..000000000 --- a/zhenxun/plugins/dialogue/_data_source.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Dict - -from nonebot_plugin_alconna import UniMsg -from pydantic import BaseModel - - -class DialogueData(BaseModel): - - name: str - """用户名称""" - user_id: str - """用户id""" - group_id: str | None - """群组id""" - group_name: str | None - """群组名称""" - message: UniMsg - """UniMsg""" - platform: str | None - """平台""" - - -class DialogueManage: - - _data: Dict[int, DialogueData] = {} - _index = 0 - - @classmethod - def add( - cls, - name: str, - uid: str, - gid: str | None, - group_name: str | None, - message: UniMsg, - platform: str | None, - ): - cls._data[cls._index] = DialogueData( - name=name, - user_id=uid, - group_id=gid, - group_name=group_name, - message=message, - platform=platform, - ) - cls._index += 1 - - @classmethod - def remove(cls, index: int): - if index in cls._data: - del cls._data[index] - - @classmethod - def get(cls, k: int): - return cls._data.get(k) diff --git a/zhenxun/plugins/draw_card/__init__.py b/zhenxun/plugins/draw_card/__init__.py deleted file mode 100644 index 2aeeb30eb..000000000 --- a/zhenxun/plugins/draw_card/__init__.py +++ /dev/null @@ -1,293 +0,0 @@ -import asyncio -import traceback -from dataclasses import dataclass -from typing import Any - -import nonebot -from cn2an import cn2an -from nonebot import on_keyword, on_message, on_regex -from nonebot.log import logger -from nonebot.matcher import Matcher -from nonebot.params import RegexGroup -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.typing import T_Handler -from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.utils.message import MessageUtils - -from .handles.azur_handle import AzurHandle -from .handles.ba_handle import BaHandle -from .handles.base_handle import BaseHandle -from .handles.fgo_handle import FgoHandle -from .handles.genshin_handle import GenshinHandle -from .handles.guardian_handle import GuardianHandle -from .handles.onmyoji_handle import OnmyojiHandle -from .handles.pcr_handle import PcrHandle -from .handles.pretty_handle import PrettyHandle -from .handles.prts_handle import PrtsHandle -from .rule import rule - -__plugin_meta__ = PluginMetadata( - name="游戏抽卡", - description="就算是模拟抽卡也不能改变自己是个非酋", - usage=""" - usage: - 模拟赛马娘,原神,明日方舟,坎公骑冠剑,公主连结(国/台),碧蓝航线,FGO,阴阳师,碧蓝档案进行抽卡 - 指令: - 原神[1-180]抽: 原神常驻池 - 原神角色[1-180]抽: 原神角色UP池子 - 原神角色2池[1-180]抽: 原神角色UP池子 - 原神武器[1-180]抽: 原神武器UP池子 - 重置原神抽卡: 清空当前卡池的抽卡次数[即从0开始计算UP概率] - 方舟[1-300]抽: 方舟卡池,当有当期UP时指向UP池 - 赛马娘[1-200]抽: 赛马娘卡池,当有当期UP时指向UP池 - 坎公骑冠剑[1-300]抽: 坎公骑冠剑卡池,当有当期UP时指向UP池 - pcr/公主连接[1-300]抽: 公主连接卡池 - 碧蓝航线/碧蓝[重型/轻型/特型/活动][1-300]抽: 碧蓝航线重型/轻型/特型/活动卡池 - fgo[1-300]抽: fgo卡池 (已失效) - 阴阳师[1-300]抽: 阴阳师卡池 - ba/碧蓝档案[1-200]抽:碧蓝档案卡池 - * 以上指令可以通过 XX一井 来指定最大抽取数量 * - * 示例:原神一井 * - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="抽卡相关", - superuser_help=""" - 更新方舟信息 - 重载方舟卡池 - 更新原神信息 - 重载原神卡池 - 更新赛马娘信息 - 重载赛马娘卡池 - 更新坎公骑冠剑信息 - 更新碧蓝航线信息 - 更新fgo信息 - 更新阴阳师信息 - """, - ).dict(), -) - -_hidden = on_message(rule=lambda: False) - - -@dataclass -class Game: - keywords: set[str] - handle: BaseHandle - flag: bool - config_name: str - max_count: int = 300 # 一次最大抽卡数 - reload_time: int | None = None # 重载UP池时间(小时) - has_other_pool: bool = False - - -games = ( - Game( - {"azur", "碧蓝航线"}, - AzurHandle(), - Config.get_config("draw_card", "AZUR_FLAG", True), - "AZUR_FLAG", - ), - Game( - {"fgo", "命运冠位指定"}, - FgoHandle(), - Config.get_config("draw_card", "FGO_FLAG", True), - "FGO_FLAG", - ), - Game( - {"genshin", "原神"}, - GenshinHandle(), - Config.get_config("draw_card", "GENSHIN_FLAG", True), - "GENSHIN_FLAG", - max_count=180, - reload_time=18, - has_other_pool=True, - ), - Game( - {"guardian", "坎公骑冠剑"}, - GuardianHandle(), - Config.get_config("draw_card", "GUARDIAN_FLAG", True), - "GUARDIAN_FLAG", - reload_time=4, - ), - Game( - {"onmyoji", "阴阳师"}, - OnmyojiHandle(), - Config.get_config("draw_card", "ONMYOJI_FLAG", True), - "ONMYOJI_FLAG", - ), - Game( - {"pcr", "公主连结", "公主连接", "公主链接", "公主焊接"}, - PcrHandle(), - Config.get_config("draw_card", "PCR_FLAG", True), - "PCR_FLAG", - ), - Game( - {"pretty", "马娘", "赛马娘"}, - PrettyHandle(), - Config.get_config("draw_card", "PRETTY_FLAG", True), - "PRETTY_FLAG", - max_count=200, - reload_time=4, - ), - Game( - {"prts", "方舟", "明日方舟"}, - PrtsHandle(), - Config.get_config("draw_card", "PRTS_FLAG", True), - "PRTS_FLAG", - reload_time=4, - ), - Game( - {"ba", "碧蓝档案"}, - BaHandle(), - Config.get_config("draw_card", "BA_FLAG", True), - "BA_FLAG", - ), -) - - -def create_matchers(): - def draw_handler(game: Game) -> T_Handler: - async def handler( - session: EventSession, - args: tuple[Any, ...] = RegexGroup(), - ): - pool_name, pool_type_, num, unit = args - if num == "单": - num = 1 - else: - try: - num = int(cn2an(num, mode="smart")) - except ValueError: - await MessageUtils.build_message("必!须!是!数!字!").finish( - reply_to=True - ) - if unit == "井": - num *= game.max_count - if num < 1: - await MessageUtils.build_message("虚空抽卡???").finish(reply_to=True) - elif num > game.max_count: - await MessageUtils.build_message( - "一井都满不足不了你嘛!快爬开!" - ).finish(reply_to=True) - pool_name = ( - pool_name.replace("池", "") - .replace("武器", "arms") - .replace("角色", "char") - .replace("卡牌", "card") - .replace("卡", "card") - ) - try: - if pool_type_ in ["2池", "二池"]: - pool_name = pool_name + "1" - res = await game.handle.draw( - num, pool_name=pool_name, user_id=session.id1 - ) - logger.info( - f"游戏抽卡 类型: {list(game.keywords)[1]} 卡池: {pool_name} 数量: {num}", - "游戏抽卡", - session=session, - ) - except: - logger.warning(traceback.format_exc()) - await MessageUtils.build_message("出错了...").finish(reply_to=True) - await res.send() - - return handler - - def update_handler(game: Game) -> T_Handler: - async def handler(matcher: Matcher): - await game.handle.update_info() - await matcher.finish("更新完成!") - - return handler - - def reload_handler(game: Game) -> T_Handler: - async def handler(matcher: Matcher): - res = await game.handle.reload_pool() - if res: - await res.send() - - return handler - - def reset_handler(game: Game) -> T_Handler: - async def handler(matcher: Matcher, session: EventSession): - if not session.id1: - await MessageUtils.build_message("获取用户id失败...").finish() - if game.handle.reset_count(session.id1): - await MessageUtils.build_message("重置成功!").send() - - return handler - - def scheduled_job(game: Game) -> T_Handler: - async def handler(): - await game.handle.reload_pool() - - return handler - - for game in games: - pool_pattern = r"([^\s单0-9零一二三四五六七八九百十]{0,3})" - num_pattern = r"(单|[0-9零一二三四五六七八九百十]{1,3})" - unit_pattern = r"([抽|井|连])" - pool_type = "()" - if game.has_other_pool: - pool_type = r"([2二]池)?" - draw_regex = r".*?(?:{})\s*{}\s*{}\s*{}\s*{}".format( - "|".join(game.keywords), pool_pattern, pool_type, num_pattern, unit_pattern - ) - update_keywords = {f"更新{keyword}信息" for keyword in game.keywords} - reload_keywords = {f"重载{keyword}卡池" for keyword in game.keywords} - reset_keywords = {f"重置{keyword}抽卡" for keyword in game.keywords} - on_regex(draw_regex, priority=5, block=True, rule=rule(game)).append_handler( - draw_handler(game) - ) - on_keyword( - update_keywords, priority=1, block=True, permission=SUPERUSER - ).append_handler(update_handler(game)) - on_keyword( - reload_keywords, priority=1, block=True, permission=SUPERUSER - ).append_handler(reload_handler(game)) - on_keyword(reset_keywords, priority=5, block=True).append_handler( - reset_handler(game) - ) - if game.reload_time: - scheduler.add_job( - scheduled_job(game), trigger="cron", hour=game.reload_time, minute=1 - ) - - -create_matchers() - - -# 更新资源 -@scheduler.scheduled_job( - "cron", - hour=4, - minute=1, -) -async def _(): - tasks = [] - for game in games: - if game.flag: - tasks.append(asyncio.ensure_future(game.handle.update_info())) - await asyncio.gather(*tasks) - - -driver = nonebot.get_driver() - - -@driver.on_startup -async def _(): - tasks = [] - for game in games: - if game.flag: - game.handle.init_data() - if not game.handle.data_exists(): - tasks.append(asyncio.ensure_future(game.handle.update_info())) - await asyncio.gather(*tasks) diff --git a/zhenxun/plugins/draw_card/config.py b/zhenxun/plugins/draw_card/config.py deleted file mode 100644 index 0aff3ef8b..000000000 --- a/zhenxun/plugins/draw_card/config.py +++ /dev/null @@ -1,203 +0,0 @@ -import nonebot -import ujson as json -from pydantic import BaseModel, Extra, ValidationError - -from zhenxun.configs.config import Config as AConfig -from zhenxun.configs.path_config import DATA_PATH, IMAGE_PATH -from zhenxun.services.log import logger - - -# 原神 -class GenshinConfig(BaseModel, extra=Extra.ignore): - GENSHIN_FIVE_P: float = 0.006 - GENSHIN_FOUR_P: float = 0.051 - GENSHIN_THREE_P: float = 0.43 - GENSHIN_G_FIVE_P: float = 0.016 - GENSHIN_G_FOUR_P: float = 0.13 - I72_ADD: float = 0.0585 - - -# 明日方舟 -class PrtsConfig(BaseModel, extra=Extra.ignore): - PRTS_SIX_P: float = 0.02 - PRTS_FIVE_P: float = 0.08 - PRTS_FOUR_P: float = 0.48 - PRTS_THREE_P: float = 0.42 - - -# 赛马娘 -class PrettyConfig(BaseModel, extra=Extra.ignore): - PRETTY_THREE_P: float = 0.03 - PRETTY_TWO_P: float = 0.18 - PRETTY_ONE_P: float = 0.79 - - -# 坎公骑冠剑 -class GuardianConfig(BaseModel, extra=Extra.ignore): - GUARDIAN_THREE_CHAR_P: float = 0.0275 - GUARDIAN_TWO_CHAR_P: float = 0.19 - GUARDIAN_ONE_CHAR_P: float = 0.7825 - GUARDIAN_THREE_CHAR_UP_P: float = 0.01375 - GUARDIAN_THREE_CHAR_OTHER_P: float = 0.01375 - GUARDIAN_EXCLUSIVE_ARMS_P: float = 0.03 - GUARDIAN_FIVE_ARMS_P: float = 0.03 - GUARDIAN_FOUR_ARMS_P: float = 0.09 - GUARDIAN_THREE_ARMS_P: float = 0.27 - GUARDIAN_TWO_ARMS_P: float = 0.58 - GUARDIAN_EXCLUSIVE_ARMS_UP_P: float = 0.01 - GUARDIAN_EXCLUSIVE_ARMS_OTHER_P: float = 0.02 - - -# 公主连结 -class PcrConfig(BaseModel, extra=Extra.ignore): - PCR_THREE_P: float = 0.025 - PCR_TWO_P: float = 0.18 - PCR_ONE_P: float = 0.795 - PCR_G_THREE_P: float = 0.025 - PCR_G_TWO_P: float = 0.975 - - -# 碧蓝航线 -class AzurConfig(BaseModel, extra=Extra.ignore): - AZUR_FIVE_P: float = 0.012 - AZUR_FOUR_P: float = 0.07 - AZUR_THREE_P: float = 0.12 - AZUR_TWO_P: float = 0.51 - AZUR_ONE_P: float = 0.3 - - -# 命运-冠位指定 -class FgoConfig(BaseModel, extra=Extra.ignore): - FGO_SERVANT_FIVE_P: float = 0.01 - FGO_SERVANT_FOUR_P: float = 0.03 - FGO_SERVANT_THREE_P: float = 0.4 - FGO_CARD_FIVE_P: float = 0.04 - FGO_CARD_FOUR_P: float = 0.12 - FGO_CARD_THREE_P: float = 0.4 - - -# 阴阳师 -class OnmyojiConfig(BaseModel, extra=Extra.ignore): - ONMYOJI_SP: float = 0.0025 - ONMYOJI_SSR: float = 0.01 - ONMYOJI_SR: float = 0.2 - ONMYOJI_R: float = 0.7875 - - -# 碧蓝档案 -class BaConfig(BaseModel, extra=Extra.ignore): - BA_THREE_P: float = 0.025 - BA_TWO_P: float = 0.185 - BA_ONE_P: float = 0.79 - BA_G_TWO_P: float = 0.975 - - -class Config(BaseModel, extra=Extra.ignore): - # 开关 - PRTS_FLAG: bool = True - GENSHIN_FLAG: bool = True - PRETTY_FLAG: bool = True - GUARDIAN_FLAG: bool = True - PCR_FLAG: bool = True - AZUR_FLAG: bool = True - FGO_FLAG: bool = True - ONMYOJI_FLAG: bool = True - BA_FLAG: bool = True - - # 其他配置 - PCR_TAI: bool = False - SEMAPHORE: int = 5 - - # 抽卡概率 - prts: PrtsConfig = PrtsConfig() - genshin: GenshinConfig = GenshinConfig() - pretty: PrettyConfig = PrettyConfig() - guardian: GuardianConfig = GuardianConfig() - pcr: PcrConfig = PcrConfig() - azur: AzurConfig = AzurConfig() - fgo: FgoConfig = FgoConfig() - onmyoji: OnmyojiConfig = OnmyojiConfig() - ba: BaConfig = BaConfig() - - -driver = nonebot.get_driver() - -# DRAW_PATH = Path("data/draw_card").absolute() -DRAW_PATH = IMAGE_PATH / "draw_card" -# try: -# DRAW_PATH = Path(global_config.draw_path).absolute() -# except: -# pass -config_path = DATA_PATH / "draw_card" / "draw_card_config" / "draw_card_config.json" - -draw_config: Config = Config() - -for game_flag, game_name in zip( - [ - "PRTS_FLAG", - "GENSHIN_FLAG", - "PRETTY_FLAG", - "GUARDIAN_FLAG", - "PCR_FLAG", - "AZUR_FLAG", - "FGO_FLAG", - "ONMYOJI_FLAG", - "PCR_TAI", - "BA_FLAG", - ], - [ - "明日方舟", - "原神", - "赛马娘", - "坎公骑冠剑", - "公主连结", - "碧蓝航线", - "命运-冠位指定(FGO)", - "阴阳师", - "pcr台服卡池", - "碧蓝档案", - ], -): - AConfig.add_plugin_config( - "draw_card", - game_flag, - True, - help=f"{game_name} 抽卡开关", - default_value=True, - type=bool, - ) -AConfig.add_plugin_config( - "draw_card", - "SEMAPHORE", - 5, - help=f"异步数据下载数量限制", - default_value=5, - type=int, -) -AConfig.set_name("draw_card", "游戏抽卡") - - -@driver.on_startup -def check_config(): - global draw_config - - if not config_path.exists(): - config_path.parent.mkdir(parents=True, exist_ok=True) - draw_config = Config() - logger.warning("draw_card:配置文件不存在,已重新生成配置文件...") - else: - with config_path.open("r", encoding="utf8") as fp: - data = json.load(fp) - try: - draw_config = Config.parse_obj({**data}) - except ValidationError: - draw_config = Config() - logger.warning("draw_card:配置文件格式错误,已重新生成配置文件...") - - with config_path.open("w", encoding="utf8") as fp: - json.dump( - draw_config.dict(), - fp, - indent=4, - ensure_ascii=False, - ) diff --git a/zhenxun/plugins/draw_card/count_manager.py b/zhenxun/plugins/draw_card/count_manager.py deleted file mode 100644 index 7768b057c..000000000 --- a/zhenxun/plugins/draw_card/count_manager.py +++ /dev/null @@ -1,149 +0,0 @@ -from typing import Optional, TypeVar, Generic -from pydantic import BaseModel -from cachetools import TTLCache - - -class BaseUserCount(BaseModel): - count: int = 0 # 当前抽卡次数 - - -TCount = TypeVar("TCount", bound="BaseUserCount") - - -class DrawCountManager(Generic[TCount]): - """ - 抽卡统计保底 - """ - - def __init__( - self, game_draw_count_rule: tuple, star2name: tuple, max_draw_count: int - ): - """ - 初始化保底统计 - - 例如:DrawCountManager((10, 90, 180), ("4", "5", "5")) - - 抽卡保底需要的次数和返回的对应名称,例如星级等 - - :param game_draw_count_rule:抽卡规则 - :param star2name:星级对应的名称 - :param max_draw_count:最大累计抽卡次数,当下次单次抽卡超过该次数时将会清空数据 - - """ - # 只有保底 - # 超过60秒重置抽卡次数 - self._data: TTLCache[int, TCount] = TTLCache(maxsize=1000, ttl=60) - self._guarantee_tuple = game_draw_count_rule - self._star2name = star2name - self._max_draw_count = max_draw_count - - @classmethod - def get_count_class(cls) -> TCount: - raise NotImplementedError - - def _get_count(self, key: int) -> TCount: - if self._data.get(key) is None: - self._data[key] = self.get_count_class() - else: - self._data[key] = self._data[key] - return self._data[key] - - def increase(self, key: int, value: int = 1): - """ - 用户抽卡次数加1 - """ - self._get_count(key).count += value - - def get_max_guarantee(self): - """ - 获取最大保底抽卡次数 - """ - return self._guarantee_tuple[-1] - - def get_user_count(self, key: int) -> int: - """ - 获取当前抽卡次数 - """ - return self._get_count(key).count - - def reset(self, key: int): - """ - 清空记录 - """ - self._data.pop(key, None) - - -class GenshinUserCount(BaseUserCount): - five_index: int = 0 # 获取五星时的抽卡次数 - four_index: int = 0 # 获取四星时的抽卡次数 - is_up: bool = False # 下次五星是否必定为up - - -class GenshinCountManager(DrawCountManager[GenshinUserCount]): - @classmethod - def get_count_class(cls) -> GenshinUserCount: - return GenshinUserCount() - - def set_is_up(self, key: int, value: bool): - """ - 设置下次是否必定up - """ - self._get_count(key).is_up = value - - def is_up(self, key: int) -> bool: - """ - 判断该次保底是否必定为up - """ - return self._get_count(key).is_up - - def get_user_five_index(self, key: int) -> int: - """ - 获取用户上次获取五星的次数 - """ - return self._get_count(key).five_index - - def get_user_four_index(self, key: int) -> int: - """ - 获取用户上次获取四星的次数 - """ - return self._get_count(key).four_index - - def mark_five_index(self, key: int): - """ - 标记用户该次次数为五星 - """ - self._get_count(key).five_index = self._get_count(key).count - - def mark_four_index(self, key: int): - """ - 标记用户该次次数为四星 - """ - self._get_count(key).four_index = self._get_count(key).count - - def check_count(self, key: int, count: int): - """ - 检查用户该次抽卡次数累计是否超过最大限制次数 - """ - if self._get_count(key).count + count > self._max_draw_count: - self._data.pop(key, None) - - def get_user_guarantee_count(self, key: int) -> int: - user = self._get_count(key) - return ( - self.get_max_guarantee() - - (user.count % self.get_max_guarantee() - user.five_index) - ) % self.get_max_guarantee() or self.get_max_guarantee() - - def check(self, key: int) -> Optional[int]: - """ - 是否保底 - """ - # print(self._data) - user = self._get_count(key) - if user.count - user.five_index == 90: - user.five_index = user.count - return 5 - if user.count - user.four_index == 10: - user.four_index = user.count - return 4 - return None diff --git a/zhenxun/plugins/draw_card/handles/azur_handle.py b/zhenxun/plugins/draw_card/handles/azur_handle.py deleted file mode 100644 index 67242a774..000000000 --- a/zhenxun/plugins/draw_card/handles/azur_handle.py +++ /dev/null @@ -1,308 +0,0 @@ -import random -from urllib.parse import unquote - -import dateparser -import ujson as json -from lxml import etree -from nonebot_plugin_alconna import UniMessage -from PIL import ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle -from .base_handle import UpChar as _UpChar -from .base_handle import UpEvent as _UpEvent - - -class AzurChar(BaseData): - type_: str # 舰娘类型 - - @property - def star_str(self) -> str: - return ["白", "蓝", "紫", "金"][self.star - 1] - - -class UpChar(_UpChar): - type_: str # 舰娘类型 - - -class UpEvent(_UpEvent): - up_char: list[UpChar] # up对象 - - -class AzurHandle(BaseHandle[AzurChar]): - def __init__(self): - super().__init__("azur", "碧蓝航线") - self.max_star = 4 - self.config = draw_config.azur - self.ALL_CHAR: list[AzurChar] = [] - self.UP_EVENT: UpEvent | None = None - - def get_card(self, pool_name: str, **kwargs) -> AzurChar: - if pool_name == "轻型": - type_ = ["驱逐", "轻巡", "维修"] - elif pool_name == "重型": - type_ = ["重巡", "战列", "战巡", "重炮"] - else: - type_ = ["维修", "潜艇", "重巡", "轻航", "航母"] - up_pool_flag = pool_name == "活动" - # Up - up_ship = ( - [x for x in self.UP_EVENT.up_char if x.zoom > 0] if self.UP_EVENT else [] - ) - # print(up_ship) - acquire_char = None - if up_ship and up_pool_flag: - up_zoom: list[tuple[float, float]] = [(0, up_ship[0].zoom / 100)] - # 初始化概率 - cur_ = up_ship[0].zoom / 100 - for i in range(len(up_ship)): - try: - up_zoom.append((cur_, cur_ + up_ship[i + 1].zoom / 100)) - cur_ += up_ship[i + 1].zoom / 100 - except IndexError: - pass - rand = random.random() - # 抽取up - for i, zoom in enumerate(up_zoom): - if zoom[0] <= rand <= zoom[1]: - try: - acquire_char = [ - x for x in self.ALL_CHAR if x.name == up_ship[i].name - ][0] - except IndexError: - pass - # 没有up或者未抽取到up - if not acquire_char: - star = self.get_star( - # [4, 3, 2, 1], - [4, 3, 2, 2], - [ - self.config.AZUR_FOUR_P, - self.config.AZUR_THREE_P, - self.config.AZUR_TWO_P, - self.config.AZUR_ONE_P, - ], - ) - acquire_char = random.choice( - [ - x - for x in self.ALL_CHAR - if x.star == star and x.type_ in type_ and not x.limited - ] - ) - return acquire_char - - async def draw(self, count: int, **kwargs) -> UniMessage: - index2card = self.get_cards(count, **kwargs) - cards = [card[0] for card in index2card] - up_list = [x.name for x in self.UP_EVENT.up_char] if self.UP_EVENT else [] - result = self.format_result(index2card, **{**kwargs, "up_list": up_list}) - gen_img = await self.generate_img(cards) - return MessageUtils.build_message([gen_img.pic2bytes(), result]) - - async def generate_card_img(self, card: AzurChar) -> BuildImage: - sep_w = 5 - sep_t = 5 - sep_b = 20 - w = 100 - h = 100 - bg = BuildImage(w + sep_w * 2, h + sep_t + sep_b) - frame_path = str(self.img_path / f"{card.star}_star.png") - frame = BuildImage(w, h, background=frame_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(w, h, background=img_path) - # 加圆角 - await frame.circle_corner(6) - await img.circle_corner(6) - await bg.paste(img, (sep_w, sep_t)) - await bg.paste(frame, (sep_w, sep_t)) - # 加名字 - text = card.name[:6] + "..." if len(card.name) > 7 else card.name - font = load_font(fontsize=14) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (w - text_w) / 2, h + sep_t + (sep_b - text_h) / 2), - text, - font=font, - fill=["#808080", "#3b8bff", "#8000ff", "#c90", "#ee494c"][card.star - 1], - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - AzurChar( - name=value["名称"], - star=int(value["星级"]), - limited="可以建造" not in value["获取途径"], - type_=value["类型"], - ) - for value in self.load_data().values() - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - self.UP_EVENT = UpEvent.parse_obj(data.get("char", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_EVENT: - data = {"char": json.loads(self.UP_EVENT.json())} - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - info = {} - # 更新图鉴 - url = "https://wiki.biligame.com/blhx/舰娘图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - contents = dom.xpath( - "//div[@class='mw-body-content mw-content-ltr']/div[@class='mw-parser-output']" - ) - for index, content in enumerate(contents): - char_list = content.xpath("./div[@id='CardSelectTr']/div") - for char in char_list: - try: - name = char.xpath("./span/a/text()")[0] - frame = char.xpath("./div/div/a/img/@alt")[0] - avatar = char.xpath("./div/img/@srcset")[0] - except IndexError: - continue - member_dict = { - "名称": remove_prohibited_str(name), - "头像": unquote(str(avatar).split(" ")[-2]), - "星级": self.parse_star(frame), - "类型": char.xpath("./@data-param1")[0].split(",")[-1], - } - info[member_dict["名称"]] = member_dict - # 更新额外信息 - for key in info.keys(): - # TODO: 各种舰娘·改获取错误 - url = f"https://wiki.biligame.com/blhx/{key}" - result = await self.get_url(url) - if not result: - info[key]["获取途径"] = [] - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - continue - try: - dom = etree.HTML(result, etree.HTMLParser()) - time = dom.xpath( - "//table[@class='wikitable sv-general']/tbody[1]/tr[4]/td[2]//text()" - )[0] - sources = [] - if "无法建造" in time: - sources.append("无法建造") - elif "活动已关闭" in time: - sources.append("活动限定") - else: - sources.append("可以建造") - info[key]["获取途径"] = sources - except IndexError: - info[key]["获取途径"] = [] - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载头像框 - idx = 1 - BLHX_URL = "https://patchwiki.biligame.com/images/blhx" - for url in [ - "/1/15/pxho13xsnkyb546tftvh49etzdh74cf.png", - "/a/a9/k8t7nx6c8pan5vyr8z21txp45jxeo66.png", - "/a/a5/5whkzvt200zwhhx0h0iz9qo1kldnidj.png", - "/a/a2/ptog1j220x5q02hytpwc8al7f229qk9.png", - "/6/6d/qqv5oy3xs40d3055cco6bsm0j4k4gzk.png", - ]: - await self.download_img(BLHX_URL + url, f"{idx}_star") - idx += 1 - await self.update_up_char() - - @staticmethod - def parse_star(star: str) -> int: - if star in ["舰娘头像外框普通.png", "舰娘头像外框白色.png"]: - return 1 - elif star in ["舰娘头像外框稀有.png", "舰娘头像外框蓝色.png"]: - return 2 - elif star in ["舰娘头像外框精锐.png", "舰娘头像外框紫色.png"]: - return 3 - elif star in ["舰娘头像外框超稀有.png", "舰娘头像外框金色.png"]: - return 4 - elif star in ["舰娘头像外框海上传奇.png", "舰娘头像外框彩色.png"]: - return 5 - elif star in [ - "舰娘头像外框最高方案.png", - "舰娘头像外框决战方案.png", - "舰娘头像外框超稀有META.png", - "舰娘头像外框精锐META.png", - ]: - return 6 - else: - return 6 - - async def update_up_char(self): - url = "https://wiki.biligame.com/blhx/游戏活动表" - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取活动表出错") - return - try: - dom = etree.HTML(result, etree.HTMLParser()) - dd = dom.xpath("//div[@class='timeline2']/dl/dd/a")[0] - url = "https://wiki.biligame.com" + dd.xpath("./@href")[0] - title = dd.xpath("string(.)") - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取活动页面出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - timer = dom.xpath("//span[@class='eventTimer']")[0] - start_time = dateparser.parse(timer.xpath("./@data-start")[0]) - end_time = dateparser.parse(timer.xpath("./@data-end")[0]) - ships = dom.xpath("//table[@class='shipinfo']") - up_chars = [] - for ship in ships: - name = ship.xpath("./tbody/tr/td[2]/p/a/@title")[0] - type_ = ship.xpath("./tbody/tr/td[2]/p/small/text()")[0] # 舰船类型 - try: - p = float(str(ship.xpath(".//sup/text()")[0]).strip("%")) - except (IndexError, ValueError): - p = 0 - star = self.parse_star( - ship.xpath("./tbody/tr/td[1]/div/div/div/a/img/@alt")[0] - ) - up_chars.append( - UpChar(name=name, star=star, limited=False, zoom=p, type_=type_) - ) - self.UP_EVENT = UpEvent( - title=title, - pool_img="", - start_time=start_time, - end_time=end_time, - up_char=up_chars, - ) - self.dump_up_char() - except Exception as e: - logger.warning(f"{self.game_name_cn}UP更新出错", e=e) - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_EVENT: - return MessageUtils.build_message( - f"重载成功!\n当前活动:{self.UP_EVENT.title}" - ) diff --git a/zhenxun/plugins/draw_card/handles/ba_handle.py b/zhenxun/plugins/draw_card/handles/ba_handle.py deleted file mode 100644 index d347504af..000000000 --- a/zhenxun/plugins/draw_card/handles/ba_handle.py +++ /dev/null @@ -1,157 +0,0 @@ -import random - -from PIL import ImageDraw - -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage - -from ..config import draw_config -from ..util import cn2py, load_font -from .base_handle import BaseData, BaseHandle - - -class BaChar(BaseData): - pass - - -class BaHandle(BaseHandle[BaChar]): - def __init__(self): - super().__init__("ba", "碧蓝档案") - self.max_star = 3 - self.config = draw_config.ba - self.ALL_CHAR: list[BaChar] = [] - - def get_card(self, mode: int = 1) -> BaChar: - if mode == 2: - star = self.get_star( - [3, 2], [self.config.BA_THREE_P, self.config.BA_G_TWO_P] - ) - else: - star = self.get_star( - [3, 2, 1], - [self.config.BA_THREE_P, self.config.BA_TWO_P, self.config.BA_ONE_P], - ) - chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited] - return random.choice(chars) - - def get_cards(self, count: int, **kwargs) -> list[tuple[BaChar, int]]: - card_list = [] - card_count = 0 # 保底计算 - for i in range(count): - card_count += 1 - # 十连保底 - if card_count == 10: - card = self.get_card(2) - card_count = 0 - else: - card = self.get_card(1) - if card.star > self.max_star - 2: - card_count = 0 - card_list.append((card, i + 1)) - return card_list - - async def generate_card_img(self, card: BaChar) -> BuildImage: - sep_w = 5 - sep_h = 5 - star_h = 15 - img_w = 90 - img_h = 100 - font_h = 20 - bar_h = 20 - bar_w = 90 - bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5") - img_path = self.img_path / f"{cn2py(card.name)}.png" - img = BuildImage( - img_w, img_h, background=img_path if img_path.exists() else None - ) - bar = BuildImage(bar_w, bar_h, color="#6495ED") - await bg.paste(img, (sep_w, sep_h)) - await bg.paste(bar, (sep_w, img_h - bar_h + sep_h)) - if card.star == 1: - star_path = str(self.img_path / "star-1.png") - star_w = 15 - elif card.star == 2: - star_path = str(self.img_path / "star-2.png") - star_w = 30 - else: - star_path = str(self.img_path / "star-3.png") - star_w = 45 - star = BuildImage(star_w, star_h, background=star_path) - await bg.paste(star, (img_w // 2 - 15 * (card.star - 1) // 2, img_h - star_h)) - text = card.name[:5] + "..." if len(card.name) > 6 else card.name - font = load_font(fontsize=14) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2), - text, - font=font, - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - BaChar( - name=value["名称"], - star=int(value["星级"]), - limited=True if "(" in key else False, - ) - for key, value in self.load_data().items() - ] - - def title2star(self, title: int): - if title == "Star-3.png": - return 3 - elif title == "Star-2.png": - return 2 - else: - return 1 - - async def _update_info(self): - # TODO: ba获取链接失效 - info = {} - url = "https://schale.gg/data/cn/students.min.json?v=49" - result = (await AsyncHttpx.get(url)).json() - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - else: - for char in result: - try: - name = char["Name"] - id = str(char["Id"]) - avatar = ( - "https://github.com/SchaleDB/SchaleDB/raw/main/images/student/icon/" - + id - + ".webp" - ) - star = char["StarGrade"] - star = char["StarGrade"] - except IndexError: - continue - member_dict = { - "头像": avatar, - "名称": name, - "星级": star, - } - info[member_dict["名称"]] = member_dict - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - await self.download_img( - "https://patchwiki.biligame.com/images/bluearchive/thumb/e/e0/82nj2x9sxko473g7782r14fztd4zyky.png/15px-Star-1.png", - "star-1", - ) - await self.download_img( - "https://patchwiki.biligame.com/images/bluearchive/thumb/0/0b/msaff2g0zk6nlyl1rrn7n1ri4yobcqc.png/30px-Star-2.png", - "star-2", - ) - await self.download_img( - "https://patchwiki.biligame.com/images/bluearchive/thumb/8/8a/577yv79x1rwxk8efdccpblo0lozl158.png/46px-Star-3.png", - "star-3", - ) diff --git a/zhenxun/plugins/draw_card/handles/base_handle.py b/zhenxun/plugins/draw_card/handles/base_handle.py deleted file mode 100644 index 3483d246e..000000000 --- a/zhenxun/plugins/draw_card/handles/base_handle.py +++ /dev/null @@ -1,295 +0,0 @@ -import asyncio -import random -from asyncio.exceptions import TimeoutError -from datetime import datetime -from typing import Generic, TypeVar - -import aiohttp -import anyio -import ujson as json -from nonebot_plugin_alconna import UniMessage -from PIL import Image -from pydantic import BaseModel, Extra - -from zhenxun.configs.path_config import DATA_PATH -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import DRAW_PATH, draw_config -from ..util import circled_number, cn2py - - -class BaseData(BaseModel, extra=Extra.ignore): - name: str # 名字 - star: int # 星级 - limited: bool # 限定 - - def __eq__(self, other: "BaseData"): - return self.name == other.name - - def __hash__(self): - return hash(self.name) - - @property - def star_str(self) -> str: - return "".join(["★" for _ in range(self.star)]) - - -class UpChar(BaseData): - zoom: float # up提升倍率 - - -class UpEvent(BaseModel): - title: str # up池标题 - pool_img: str # up池封面 - start_time: datetime | None # 开始时间 - end_time: datetime | None # 结束时间 - up_char: list[UpChar] # up对象 - - -TC = TypeVar("TC", bound="BaseData") - - -class BaseHandle(Generic[TC]): - def __init__(self, game_name: str, game_name_cn: str): - self.game_name = game_name - self.game_name_cn = game_name_cn - self.max_star = 1 # 最大星级 - self.game_card_color: str = "#ffffff" - self.data_path = DATA_PATH / "draw_card" - self.img_path = DRAW_PATH / f"{self.game_name}" - self.up_path = DATA_PATH / "draw_card" / "draw_card_up" - self.img_path.mkdir(parents=True, exist_ok=True) - self.up_path.mkdir(parents=True, exist_ok=True) - self.data_files: list[str] = [f"{self.game_name}.json"] - - async def draw(self, count: int, **kwargs) -> UniMessage: - index2card = self.get_cards(count, **kwargs) - cards = [card[0] for card in index2card] - result = self.format_result(index2card) - gen_img = await self.generate_img(cards) - return MessageUtils.build_message([gen_img, result]) - - # 抽取卡池 - def get_card(self, **kwargs) -> TC: - raise NotImplementedError - - def get_cards(self, count: int, **kwargs) -> list[tuple[TC, int]]: - return [(self.get_card(**kwargs), i) for i in range(count)] - - # 获取星级 - @staticmethod - def get_star(star_list: list[int], probability_list: list[float]) -> int: - return random.choices(star_list, weights=probability_list, k=1)[0] - - def format_result(self, index2card: list[tuple[TC, int]], **kwargs) -> str: - card_list = [card[0] for card in index2card] - results = [ - self.format_star_result(card_list, **kwargs), - self.format_max_star(index2card, **kwargs), - self.format_max_card(card_list, **kwargs), - ] - results = [rst for rst in results if rst] - return "\n".join(results) - - def format_star_result(self, card_list: list[TC], **kwargs) -> str: - star_dict: dict[str, int] = {} # 记录星级及其次数 - - card_list_sorted = sorted(card_list, key=lambda c: c.star, reverse=True) - for card in card_list_sorted: - try: - star_dict[card.star_str] += 1 - except KeyError: - star_dict[card.star_str] = 1 - - rst = "" - for star_str, count in star_dict.items(): - rst += f"[{star_str}×{count}] " - return rst.strip() - - def format_max_star( - self, card_list: list[tuple[TC, int]], up_list: list[str] = [], **kwargs - ) -> str: - up_list = up_list or kwargs.get("up_list", []) - rst = "" - for card, index in card_list: - if card.star == self.max_star: - if card.name in up_list: - rst += f"第 {index} 抽获取UP {card.name}\n" - else: - rst += f"第 {index} 抽获取 {card.name}\n" - return rst.strip() - - def format_max_card(self, card_list: list[TC], **kwargs) -> str: - card_dict: dict[TC, int] = {} # 记录卡牌抽取次数 - - for card in card_list: - try: - card_dict[card] += 1 - except KeyError: - card_dict[card] = 1 - - max_count = max(card_dict.values()) - max_card = list(card_dict.keys())[list(card_dict.values()).index(max_count)] - if max_count <= 1: - return "" - return f"抽取到最多的是{max_card.name},共抽取了{max_count}次" - - async def generate_img( - self, - cards: list[TC], - num_per_line: int = 5, - max_per_line: tuple[int, int] = (40, 10), - ) -> BuildImage: - """ - 生成统计图片 - cards: 卡牌列表 - num_per_line: 单行角色显示数量 - max_per_line: 当card_list超过一定数值时,更改单行数量 - """ - if len(cards) > max_per_line[0]: - num_per_line = max_per_line[1] - if len(cards) > 90: - card_dict: dict[TC, int] = {} # 记录卡牌抽取次数 - for card in cards: - try: - card_dict[card] += 1 - except KeyError: - card_dict[card] = 1 - card_list = list(card_dict) - num_list = list(card_dict.values()) - else: - card_list = cards - num_list = [1] * len(cards) - - card_imgs: list[BuildImage] = [] - for card, num in zip(card_list, num_list): - card_img = await self.generate_card_img(card) - # 数量 > 1 时加数字上标 - if num > 1: - label = circled_number(num) - label_w = int(min(card_img.width, card_img.height) / 7) - label = label.resize( - ( - int(label_w * label.width / label.height), - label_w, - ), - Image.ANTIALIAS, # type: ignore - ) - await card_img.paste(label) - - card_imgs.append(card_img) - - # img_w = card_imgs[0].width - # img_h = card_imgs[0].height - # if len(card_imgs) < num_per_line: - # w = img_w * len(card_imgs) - # else: - # w = img_w * num_per_line - # h = img_h * math.ceil(len(card_imgs) / num_per_line) - # img = BuildImage(w, h, img_w, img_h, color=self.game_card_color) - # for card_img in card_imgs: - # await img.paste(card_img) - return await BuildImage.auto_paste(card_imgs, 10, color=self.game_card_color) # type: ignore - - async def generate_card_img(self, card: TC) -> BuildImage: - img = str(self.img_path / f"{cn2py(card.name)}.png") - return BuildImage(100, 100, background=img) - - def load_data(self, filename: str = "") -> dict: - if not filename: - filename = f"{self.game_name}.json" - filepath = self.data_path / filename - if not filepath.exists(): - return {} - with filepath.open("r", encoding="utf8") as f: - return json.load(f) - - def dump_data(self, data: dict, filename: str = ""): - if not filename: - filename = f"{self.game_name}.json" - filepath = self.data_path / filename - with filepath.open("w", encoding="utf8") as f: - json.dump(data, f, ensure_ascii=False, indent=4) - - def data_exists(self) -> bool: - for file in self.data_files: - if not (self.data_path / file).exists(): - return False - return True - - def _init_data(self): - raise NotImplementedError - - def init_data(self): - try: - self._init_data() - except Exception as e: - logger.warning(f"{self.game_name_cn} 导入角色数据错误:{type(e)}:{e}") - - async def _update_info(self): - raise NotImplementedError - - def client(self) -> aiohttp.ClientSession: - headers = { - "User-Agent": '"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; TencentTraveler 4.0)"' - } - return aiohttp.ClientSession(headers=headers) - - async def update_info(self): - try: - async with asyncio.Semaphore(draw_config.SEMAPHORE): - async with self.client() as session: - self.session = session - await self._update_info() - except Exception as e: - logger.warning(f"{self.game_name_cn} 更新数据错误:{type(e)}:{e}") - self.init_data() - - async def get_url(self, url: str) -> str: - result = "" - retry = 5 - for i in range(retry): - try: - async with self.session.get(url, timeout=10) as response: - result = await response.text() - break - except TimeoutError: - logger.warning(f"访问 {url} 超时, 重试 {i + 1}/{retry}") - await asyncio.sleep(1) - return result - - async def download_img(self, url: str, name: str) -> bool: - img_path = self.img_path / f"{cn2py(name)}.png" - if img_path.exists(): - return True - try: - async with self.session.get(url, timeout=10) as response: - async with await anyio.open_file(img_path, "wb") as f: - await f.write(await response.read()) - return True - except TimeoutError: - logger.warning( - f"下载 {self.game_name_cn} 图片超时,名称:{name},url:{url}" - ) - return False - except: - logger.warning( - f"下载 {self.game_name_cn} 链接错误,名称:{name},url:{url}" - ) - return False - - async def _reload_pool(self) -> UniMessage | None: - return None - - async def reload_pool(self) -> UniMessage | None: - try: - async with self.client() as session: - self.session = session - return await self._reload_pool() - except Exception as e: - logger.warning(f"{self.game_name_cn} 重载UP池错误", e=e) - - def reset_count(self, user_id: str) -> bool: - return False diff --git a/zhenxun/plugins/draw_card/handles/fgo_handle.py b/zhenxun/plugins/draw_card/handles/fgo_handle.py deleted file mode 100644 index 5acb8c5f7..000000000 --- a/zhenxun/plugins/draw_card/handles/fgo_handle.py +++ /dev/null @@ -1,223 +0,0 @@ -import random - -import ujson as json -from lxml import etree -from PIL import ImageDraw - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle - - -class FgoData(BaseData): - pass - - -class FgoChar(FgoData): - pass - - -class FgoCard(FgoData): - pass - - -class FgoHandle(BaseHandle[FgoData]): - def __init__(self): - super().__init__("fgo", "命运-冠位指定") - self.data_files.append("fgo_card.json") - self.max_star = 5 - self.config = draw_config.fgo - self.ALL_CHAR: list[FgoChar] = [] - self.ALL_CARD: list[FgoCard] = [] - - def get_card(self, mode: int = 1) -> FgoData: - if mode == 1: - star = self.get_star( - [8, 7, 6, 5, 4, 3], - [ - self.config.FGO_SERVANT_FIVE_P, - self.config.FGO_SERVANT_FOUR_P, - self.config.FGO_SERVANT_THREE_P, - self.config.FGO_CARD_FIVE_P, - self.config.FGO_CARD_FOUR_P, - self.config.FGO_CARD_THREE_P, - ], - ) - elif mode == 2: - star = self.get_star( - [5, 4], [self.config.FGO_CARD_FIVE_P, self.config.FGO_CARD_FOUR_P] - ) - else: - star = self.get_star( - [8, 7, 6], - [ - self.config.FGO_SERVANT_FIVE_P, - self.config.FGO_SERVANT_FOUR_P, - self.config.FGO_SERVANT_THREE_P, - ], - ) - if star > 5: - star -= 3 - chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited] - else: - chars = [x for x in self.ALL_CARD if x.star == star and not x.limited] - return random.choice(chars) - - def get_cards(self, count: int, **kwargs) -> list[tuple[FgoData, int]]: - card_list = [] # 获取所有角色 - servant_count = 0 # 保底计算 - card_count = 0 # 保底计算 - for i in range(count): - servant_count += 1 - card_count += 1 - if card_count == 9: # 四星卡片保底 - mode = 2 - elif servant_count == 10: # 三星从者保底 - mode = 3 - else: # 普通抽 - mode = 1 - card = self.get_card(mode) - if isinstance(card, FgoCard) and card.star > self.max_star - 2: - card_count = 0 - if isinstance(card, FgoChar): - servant_count = 0 - card_list.append((card, i + 1)) - return card_list - - async def generate_card_img(self, card: FgoData) -> BuildImage: - sep_w = 5 - sep_t = 5 - sep_b = 20 - w = 128 - h = 140 - bg = BuildImage(w + sep_w * 2, h + sep_t + sep_b) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(w, h, background=img_path) - await bg.paste(img, (sep_w, sep_t)) - # 加名字 - text = card.name[:6] + "..." if len(card.name) > 7 else card.name - font = load_font(fontsize=16) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (w - text_w) / 2, h + sep_t + (sep_b - text_h) / 2), - text, - font=font, - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - FgoChar( - name=value["名称"], - star=int(value["星级"]), - limited=( - True - if not ( - "圣晶石召唤" in value["入手方式"] - or "圣晶石召唤(Story卡池)" in value["入手方式"] - ) - else False - ), - ) - for value in self.load_data().values() - ] - self.ALL_CARD = [ - FgoCard(name=value["名称"], star=int(value["星级"]), limited=False) - for value in self.load_data("fgo_card.json").values() - ] - - async def _update_info(self): - # TODO: fgo获取链接失效 - fgo_info = {} - for i in range(500): - url = f"http://fgo.vgtime.com/servant/ajax?card=&wd=&ids=&sort=12777&o=desc&pn={i}" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} page {i} 出错") - continue - fgo_data = json.loads(result) - if int(fgo_data["nums"]) <= 0: - break - for x in fgo_data["data"]: - name = remove_prohibited_str(x["name"]) - member_dict = { - "id": x["id"], - "card_id": x["charid"], - "头像": x["icon"], - "名称": remove_prohibited_str(x["name"]), - "职阶": x["classes"], - "星级": int(x["star"]), - "hp": x["lvmax4hp"], - "atk": x["lvmax4atk"], - "card_quick": x["cardquick"], - "card_arts": x["cardarts"], - "card_buster": x["cardbuster"], - "宝具": x["tprop"], - } - fgo_info[name] = member_dict - # 更新额外信息 - for key in fgo_info.keys(): - url = f'http://fgo.vgtime.com/servant/{fgo_info[key]["id"]}' - result = await self.get_url(url) - if not result: - fgo_info[key]["入手方式"] = ["圣晶石召唤"] - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - continue - try: - dom = etree.HTML(result, etree.HTMLParser()) - obtain = dom.xpath( - "//table[contains(string(.),'入手方式')]/tr[8]/td[3]/text()" - )[0] - obtain = str(obtain).strip() - if "限时活动免费获取 活动结束后无法获得" in obtain: - obtain = ["活动获取"] - elif "非限时UP无法获得" in obtain: - obtain = ["限时召唤"] - else: - if "&" in obtain: - obtain = obtain.split("&") - else: - obtain = obtain.split(" ") - obtain = [s.strip() for s in obtain if s.strip()] - fgo_info[key]["入手方式"] = obtain - except IndexError: - fgo_info[key]["入手方式"] = ["圣晶石召唤"] - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - self.dump_data(fgo_info) - logger.info(f"{self.game_name_cn} 更新成功") - # fgo_card.json - fgo_card_info = {} - for i in range(500): - url = f"http://fgo.vgtime.com/equipment/ajax?wd=&ids=&sort=12958&o=desc&pn={i}" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn}卡牌 page {i} 出错") - continue - fgo_data = json.loads(result) - if int(fgo_data["nums"]) <= 0: - break - for x in fgo_data["data"]: - name = remove_prohibited_str(x["name"]) - member_dict = { - "id": x["id"], - "card_id": x["equipid"], - "头像": x["icon"], - "名称": name, - "星级": int(x["star"]), - "hp": x["lvmax_hp"], - "atk": x["lvmax_atk"], - "skill_e": str(x["skill_e"]).split("
")[:-1], - } - fgo_card_info[name] = member_dict - self.dump_data(fgo_card_info, "fgo_card.json") - logger.info(f"{self.game_name_cn} 卡牌更新成功") - # 下载头像 - for value in fgo_info.values(): - await self.download_img(value["头像"], value["名称"]) - for value in fgo_card_info.values(): - await self.download_img(value["头像"], value["名称"]) diff --git a/zhenxun/plugins/draw_card/handles/genshin_handle.py b/zhenxun/plugins/draw_card/handles/genshin_handle.py deleted file mode 100644 index 61edcf30f..000000000 --- a/zhenxun/plugins/draw_card/handles/genshin_handle.py +++ /dev/null @@ -1,461 +0,0 @@ -import random -from datetime import datetime, timedelta -from urllib.parse import unquote - -import dateparser -import ujson as json -from lxml import etree -from nonebot_plugin_alconna import UniMessage -from PIL import Image, ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..count_manager import GenshinCountManager -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle, UpChar, UpEvent - - -class GenshinData(BaseData): - pass - - -class GenshinChar(GenshinData): - pass - - -class GenshinArms(GenshinData): - pass - - -class GenshinHandle(BaseHandle[GenshinData]): - def __init__(self): - super().__init__("genshin", "原神") - self.data_files.append("genshin_arms.json") - self.max_star = 5 - self.game_card_color = "#ebebeb" - self.config = draw_config.genshin - - self.ALL_CHAR: list[GenshinData] = [] - self.ALL_ARMS: list[GenshinData] = [] - self.UP_CHAR: UpEvent | None = None - self.UP_CHAR_LIST: UpEvent | None = [] - self.UP_ARMS: UpEvent | None = None - - self.count_manager = GenshinCountManager((10, 90), ("4", "5"), 180) - - # 抽取卡池 - def get_card( - self, - pool_name: str, - mode: int = 1, - add: float = 0.0, - is_up: bool = False, - card_index: int = 0, - ): - """ - mode 1:普通抽 2:四星保底 3:五星保底 - """ - if mode == 1: - star = self.get_star( - [5, 4, 3], - [ - self.config.GENSHIN_FIVE_P + add, - self.config.GENSHIN_FOUR_P, - self.config.GENSHIN_THREE_P, - ], - ) - elif mode == 2: - star = self.get_star( - [5, 4], - [self.config.GENSHIN_G_FIVE_P + add, self.config.GENSHIN_G_FOUR_P], - ) - else: - star = 5 - - if pool_name == "char": - up_event = self.UP_CHAR_LIST[card_index] - all_list = self.ALL_CHAR + [ - x for x in self.ALL_ARMS if x.star == star and x.star < 5 - ] - elif pool_name == "arms": - up_event = self.UP_ARMS - all_list = self.ALL_ARMS + [ - x for x in self.ALL_CHAR if x.star == star and x.star < 5 - ] - else: - up_event = None - all_list = self.ALL_ARMS + self.ALL_CHAR - - acquire_char = None - # 是否UP - if up_event and star > 3: - # 获取up角色列表 - up_list = [x.name for x in up_event.up_char if x.star == star] - # 成功获取up角色 - if random.random() < 0.5 or is_up: - up_name = random.choice(up_list) - try: - acquire_char = [x for x in all_list if x.name == up_name][0] - except IndexError: - pass - if not acquire_char: - chars = [x for x in all_list if x.star == star and not x.limited] - acquire_char = random.choice(chars) - return acquire_char - - def get_cards( - self, count: int, user_id: int, pool_name: str, card_index: int = 0 - ) -> list[tuple[GenshinData, int]]: - card_list = [] # 获取角色列表 - add = 0.0 - count_manager = self.count_manager - count_manager.check_count(user_id, count) # 检查次数累计 - pool = self.UP_CHAR_LIST[card_index] if pool_name == "char" else self.UP_ARMS - for i in range(count): - count_manager.increase(user_id) - star = count_manager.check(user_id) # 是否有四星或五星保底 - if ( - count_manager.get_user_count(user_id) - - count_manager.get_user_five_index(user_id) - ) % count_manager.get_max_guarantee() >= 72: - add += draw_config.genshin.I72_ADD - if star: - if star == 4: - card = self.get_card(pool_name, 2, add=add, card_index=card_index) - else: - card = self.get_card( - pool_name, - 3, - add, - count_manager.is_up(user_id), - card_index=card_index, - ) - else: - card = self.get_card( - pool_name, - 1, - add, - count_manager.is_up(user_id), - card_index=card_index, - ) - # print(f"{count_manager.get_user_count(user_id)}:", - # count_manager.get_user_five_index(user_id), star, card.star, add) - # 四星角色 - if card.star == 4: - count_manager.mark_four_index(user_id) - # 五星角色 - elif card.star == self.max_star: - add = 0 - count_manager.mark_five_index(user_id) # 记录五星保底 - count_manager.mark_four_index(user_id) # 记录四星保底 - if pool and card.name in [ - x.name for x in pool.up_char if x.star == self.max_star - ]: - count_manager.set_is_up(user_id, True) - else: - count_manager.set_is_up(user_id, False) - card_list.append((card, count_manager.get_user_count(user_id))) - return card_list - - async def generate_card_img(self, card: GenshinData) -> BuildImage: - sep_w = 10 - sep_h = 5 - frame_w = 112 - frame_h = 132 - img_w = 106 - img_h = 106 - bg = BuildImage(frame_w + sep_w * 2, frame_h + sep_h * 2, color="#EBEBEB") - frame_path = str(self.img_path / "avatar_frame.png") - frame = Image.open(frame_path) - # 加名字 - text = card.name - font = load_font(fontsize=14) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(frame) - draw.text( - ((frame_w - text_w) / 2, frame_h - 15 - text_h / 2), - text, - font=font, - fill="gray", - ) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - if isinstance(card, GenshinArms): - # 武器卡背景不是透明的,切去上方两个圆弧 - r = 12 - circle = Image.new("L", (r * 2, r * 2), 0) - alpha = Image.new("L", img.size, 255) - alpha.paste(circle, (-r - 3, -r - 3)) # 左上角 - alpha.paste(circle, (img_h - r + 3, -r - 3)) # 右上角 - img.markImg.putalpha(alpha) - star_path = str(self.img_path / f"{card.star}_star.png") - star = Image.open(star_path) - await bg.paste(frame, (sep_w, sep_h)) - await bg.paste(img, (sep_w + 3, sep_h + 3)) - await bg.paste(star, (sep_w + int((frame_w - star.width) / 2), sep_h - 6)) - return bg - - def format_pool_info(self, pool_name: str, card_index: int = 0) -> str: - info = "" - up_event = None - if pool_name == "char": - up_event = self.UP_CHAR_LIST[card_index] - elif pool_name == "arms": - up_event = self.UP_ARMS - if up_event: - star5_list = [x.name for x in up_event.up_char if x.star == 5] - star4_list = [x.name for x in up_event.up_char if x.star == 4] - if star5_list: - info += f"五星UP:{' '.join(star5_list)}\n" - if star4_list: - info += f"四星UP:{' '.join(star4_list)}\n" - info = f"当前up池:{up_event.title}\n{info}" - return info.strip() - - async def draw( - self, count: int, user_id: int, pool_name: str = "", **kwargs - ) -> UniMessage: - card_index = 0 - if "1" in pool_name: - card_index = 1 - pool_name = pool_name.replace("1", "") - index2cards = self.get_cards(count, user_id, pool_name, card_index) - cards = [card[0] for card in index2cards] - up_event = None - if pool_name == "char": - if card_index == 1 and len(self.UP_CHAR_LIST) == 1: - return MessageUtils.build_message("当前没有第二个角色UP池") - up_event = self.UP_CHAR_LIST[card_index] - elif pool_name == "arms": - up_event = self.UP_ARMS - up_list = [x.name for x in up_event.up_char] if up_event else [] - result = self.format_star_result(cards) - result += ( - "\n" + max_star_str - if (max_star_str := self.format_max_star(index2cards, up_list=up_list)) - else "" - ) - result += f"\n距离保底发还剩 {self.count_manager.get_user_guarantee_count(user_id)} 抽" - # result += "\n【五星:0.6%,四星:5.1%,第72抽开始五星概率每抽加0.585%】" - pool_info = self.format_pool_info(pool_name, card_index) - img = await self.generate_img(cards) - bk = BuildImage(img.width, img.height + 50, font_size=20, color="#ebebeb") - await bk.paste(img) - await bk.text( - (0, img.height + 10), - "【五星:0.6%,四星:5.1%,第72抽开始五星概率每抽加0.585%】", - ) - return MessageUtils.build_message([pool_info, bk, result]) - - def _init_data(self): - self.ALL_CHAR = [ - GenshinChar( - name=value["名称"], - star=int(value["星级"]), - limited=value["常驻/限定"] == "限定UP", - ) - for key, value in self.load_data().items() - if "旅行者" not in key - ] - self.ALL_ARMS = [ - GenshinArms( - name=value["名称"], - star=int(value["星级"]), - limited="祈愿" not in value["获取途径"], - ) - for value in self.load_data("genshin_arms.json").values() - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - self.UP_CHAR_LIST.append(UpEvent.parse_obj(data.get("char", {}))) - self.UP_CHAR_LIST.append(UpEvent.parse_obj(data.get("char1", {}))) - self.UP_ARMS = UpEvent.parse_obj(data.get("arms", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_CHAR_LIST and self.UP_ARMS: - data = { - "char": json.loads(self.UP_CHAR_LIST[0].json()), - "arms": json.loads(self.UP_ARMS.json()), - } - if len(self.UP_CHAR_LIST) > 1: - data["char1"] = json.loads(self.UP_CHAR_LIST[1].json()) - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - # genshin.json - char_info = {} - url = "https://wiki.biligame.com/ys/角色筛选" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/a/@title")[0] - avatar = char.xpath("./td[1]/a/img/@srcset")[0] - star = char.xpath("./td[3]/text()")[0] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()[:1]), - } - char_info[member_dict["名称"]] = member_dict - # 更新额外信息 - for key in char_info.keys(): - result = await self.get_url(f"https://wiki.biligame.com/ys/{key}") - if not result: - char_info[key]["常驻/限定"] = "未知" - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - continue - try: - dom = etree.HTML(result, etree.HTMLParser()) - limit = dom.xpath( - "//table[contains(string(.),'常驻/限定')]/tbody/tr[6]/td/text()" - )[0] - char_info[key]["常驻/限定"] = str(limit).strip() - except IndexError: - char_info[key]["常驻/限定"] = "未知" - logger.warning(f"{self.game_name_cn} 获取额外信息错误 {key}") - self.dump_data(char_info) - logger.info(f"{self.game_name_cn} 更新成功") - # genshin_arms.json - arms_info = {} - url = "https://wiki.biligame.com/ys/武器图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/a/@title")[0] - avatar = char.xpath("./td[1]/a/img/@srcset")[0] - star = char.xpath("./td[4]/img/@alt")[0] - sources = str(char.xpath("./td[5]/text()")[0]).split(",") - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()[:1]), - "获取途径": [s.strip() for s in sources if s.strip()], - } - arms_info[member_dict["名称"]] = member_dict - self.dump_data(arms_info, "genshin_arms.json") - logger.info(f"{self.game_name_cn} 武器更新成功") - # 下载头像 - for value in char_info.values(): - await self.download_img(value["头像"], value["名称"]) - for value in arms_info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - idx = 1 - YS_URL = "https://patchwiki.biligame.com/images/ys" - for url in [ - "/1/13/7xzg7tgf8dsr2hjpmdbm5gn9wvzt2on.png", - "/b/bc/sd2ige6d7lvj7ugfumue3yjg8gyi0d1.png", - "/e/ec/l3mnhy56pyailhn3v7r873htf2nofau.png", - "/9/9c/sklp02ffk3aqszzvh8k1c3139s0awpd.png", - "/c/c7/qu6xcndgj6t14oxvv7yz2warcukqv1m.png", - ]: - await self.download_img(YS_URL + url, f"{idx}_star") - idx += 1 - # 下载头像框 - await self.download_img( - YS_URL + "/2/2e/opbcst4xbtcq0i4lwerucmosawn29ti.png", f"avatar_frame" - ) - await self.update_up_char() - - async def update_up_char(self): - self.UP_CHAR_LIST = [] - url = "https://wiki.biligame.com/ys/祈愿" - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取祈愿页面出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - tables = dom.xpath( - "//div[@class='mw-parser-output']/div[@class='row']/div/table[@class='wikitable']/tbody" - ) - if not tables or len(tables) < 2: - logger.warning(f"{self.game_name_cn}获取活动祈愿出错") - return - try: - for index, table in enumerate(tables): - title = table.xpath("./tr[1]/th/img/@title")[0] - title = str(title).split("」")[0] + "」" if "」" in title else title - pool_img = str(table.xpath("./tr[1]/th/img/@srcset")[0]).split(" ")[-2] - time = table.xpath("./tr[2]/td/text()")[0] - star5_list = table.xpath("./tr[3]/td/a/@title") - star4_list = table.xpath("./tr[4]/td/a/@title") - start, end = str(time).split("~") - start_time = dateparser.parse(start) - end_time = dateparser.parse(end) - if not start_time and end_time: - start_time = end_time - timedelta(days=20) - if start_time and end_time and start_time <= datetime.now() <= end_time: - up_event = UpEvent( - title=title, - pool_img=pool_img, - start_time=start_time, - end_time=end_time, - up_char=[ - UpChar(name=name, star=5, limited=False, zoom=50) - for name in star5_list - ] - + [ - UpChar(name=name, star=4, limited=False, zoom=50) - for name in star4_list - ], - ) - if "神铸赋形" not in title: - self.UP_CHAR_LIST.append(up_event) - else: - self.UP_ARMS = up_event - if self.UP_CHAR_LIST and self.UP_ARMS: - self.dump_up_char() - char_title = " & ".join([x.title for x in self.UP_CHAR_LIST]) - logger.info( - f"成功获取{self.game_name_cn}当前up信息...当前up池: {char_title} & {self.UP_ARMS.title}" - ) - except Exception as e: - logger.warning(f"{self.game_name_cn}UP更新出错", e=e) - - def reset_count(self, user_id: str) -> bool: - self.count_manager.reset(user_id) - return True - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_CHAR_LIST and self.UP_ARMS: - if len(self.UP_CHAR_LIST) > 1: - return MessageUtils.build_message( - [ - f"重载成功!\n当前UP池子:{self.UP_CHAR_LIST[0].title} & {self.UP_CHAR_LIST[1].title} & {self.UP_ARMS.title}", - self.UP_CHAR_LIST[0].pool_img, - self.UP_CHAR_LIST[1].pool_img, - self.UP_ARMS.pool_img, - ] - ) - return UniMessage( - [ - f"重载成功!\n当前UP池子:{char_title} & {self.UP_ARMS.title}", - self.UP_CHAR_LIST[0].pool_img, - self.UP_ARMS.pool_img, - ] - ) diff --git a/zhenxun/plugins/draw_card/handles/guardian_handle.py b/zhenxun/plugins/draw_card/handles/guardian_handle.py deleted file mode 100644 index 517f126d9..000000000 --- a/zhenxun/plugins/draw_card/handles/guardian_handle.py +++ /dev/null @@ -1,400 +0,0 @@ -import random -import re -from datetime import datetime -from urllib.parse import unquote - -import dateparser -import ujson as json -from lxml import etree -from nonebot_plugin_alconna import UniMessage -from PIL import ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle, UpChar, UpEvent - - -class GuardianData(BaseData): - pass - - -class GuardianChar(GuardianData): - pass - - -class GuardianArms(GuardianData): - pass - - -class GuardianHandle(BaseHandle[GuardianData]): - def __init__(self): - super().__init__("guardian", "坎公骑冠剑") - self.data_files.append("guardian_arms.json") - self.config = draw_config.guardian - - self.ALL_CHAR: list[GuardianChar] = [] - self.ALL_ARMS: list[GuardianArms] = [] - self.UP_CHAR: UpEvent | None = None - self.UP_ARMS: UpEvent | None = None - - def get_card(self, pool_name: str, mode: int = 1) -> GuardianData: - if pool_name == "char": - if mode == 1: - star = self.get_star( - [3, 2, 1], - [ - self.config.GUARDIAN_THREE_CHAR_P, - self.config.GUARDIAN_TWO_CHAR_P, - self.config.GUARDIAN_ONE_CHAR_P, - ], - ) - else: - star = self.get_star( - [3, 2], - [ - self.config.GUARDIAN_THREE_CHAR_P, - self.config.GUARDIAN_TWO_CHAR_P, - ], - ) - up_event = self.UP_CHAR - self.max_star = 3 - all_data = self.ALL_CHAR - else: - if mode == 1: - star = self.get_star( - [5, 4, 3, 2], - [ - self.config.GUARDIAN_FIVE_ARMS_P, - self.config.GUARDIAN_FOUR_ARMS_P, - self.config.GUARDIAN_THREE_ARMS_P, - self.config.GUARDIAN_TWO_ARMS_P, - ], - ) - else: - star = self.get_star( - [5, 4], - [ - self.config.GUARDIAN_FIVE_ARMS_P, - self.config.GUARDIAN_FOUR_ARMS_P, - ], - ) - up_event = self.UP_ARMS - self.max_star = 5 - all_data = self.ALL_ARMS - - acquire_char = None - # 是否UP - if up_event and star == self.max_star and pool_name: - # 获取up角色列表 - up_list = [x.name for x in up_event.up_char if x.star == star] - # 成功获取up角色 - if random.random() < 0.5: - up_name = random.choice(up_list) - try: - acquire_char = [x for x in all_data if x.name == up_name][0] - except IndexError: - pass - if not acquire_char: - chars = [x for x in all_data if x.star == star and not x.limited] - acquire_char = random.choice(chars) - return acquire_char - - def get_cards(self, count: int, pool_name: str) -> list[tuple[GuardianData, int]]: - card_list = [] - card_count = 0 # 保底计算 - for i in range(count): - card_count += 1 - # 十连保底 - if card_count == 10: - card = self.get_card(pool_name, 2) - card_count = 0 - else: - card = self.get_card(pool_name, 1) - if card.star > self.max_star - 2: - card_count = 0 - card_list.append((card, i + 1)) - return card_list - - def format_pool_info(self, pool_name: str) -> str: - info = "" - up_event = self.UP_CHAR if pool_name == "char" else self.UP_ARMS - if up_event: - if pool_name == "char": - up_list = [x.name for x in up_event.up_char if x.star == 3] - info += f'三星UP:{" ".join(up_list)}\n' - else: - up_list = [x.name for x in up_event.up_char if x.star == 5] - info += f'五星UP:{" ".join(up_list)}\n' - info = f"当前up池:{up_event.title}\n{info}" - return info.strip() - - async def draw(self, count: int, pool_name: str, **kwargs) -> UniMessage: - index2card = self.get_cards(count, pool_name) - cards = [card[0] for card in index2card] - up_event = self.UP_CHAR if pool_name == "char" else self.UP_ARMS - up_list = [x.name for x in up_event.up_char] if up_event else [] - result = self.format_result(index2card, up_list=up_list) - pool_info = self.format_pool_info(pool_name) - img = await self.generate_img(cards) - return MessageUtils.build_message([pool_info, img, result]) - - async def generate_card_img(self, card: GuardianData) -> BuildImage: - sep_w = 1 - sep_h = 1 - block_w = 170 - block_h = 90 - img_w = 90 - img_h = 90 - if isinstance(card, GuardianChar): - block_color = "#2e2923" - font_color = "#e2ccad" - star_w = 90 - star_h = 30 - star_name = f"{card.star}_star.png" - frame_path = "" - else: - block_color = "#EEE4D5" - font_color = "#A65400" - star_w = 45 - star_h = 45 - star_name = f"{card.star}_star_rank.png" - frame_path = str(self.img_path / "avatar_frame.png") - bg = BuildImage(block_w + sep_w * 2, block_h + sep_h * 2, color="#F6F4ED") - block = BuildImage(block_w, block_h, color=block_color) - star_path = str(self.img_path / star_name) - star = BuildImage(star_w, star_h, background=star_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - await block.paste(img, (0, 0)) - if frame_path: - frame = BuildImage(img_w, img_h, background=frame_path) - await block.paste(frame, (0, 0)) - await block.paste( - star, - (int((block_w + img_w - star_w) / 2), block_h - star_h - 30), - ) - # 加名字 - text = card.name[:4] + "..." if len(card.name) > 5 else card.name - font = load_font(fontsize=14) - text_w, _ = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(block.markImg) - draw.text( - ((block_w + img_w - text_w) / 2, 55), - text, - font=font, - fill=font_color, - ) - await bg.paste(block, (sep_w, sep_h)) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - GuardianChar(name=value["名称"], star=int(value["星级"]), limited=False) - for value in self.load_data().values() - ] - self.ALL_ARMS = [ - GuardianArms(name=value["名称"], star=int(value["星级"]), limited=False) - for value in self.load_data("guardian_arms.json").values() - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - self.UP_CHAR = UpEvent.parse_obj(data.get("char", {})) - self.UP_ARMS = UpEvent.parse_obj(data.get("arms", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_CHAR and self.UP_ARMS: - data = { - "char": json.loads(self.UP_CHAR.json()), - "arms": json.loads(self.UP_ARMS.json()), - } - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - # guardian.json - guardian_info = {} - url = "https://wiki.biligame.com/gt/英雄筛选表" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - # name = char.xpath("./td[1]/a/@title")[0] - # avatar = char.xpath("./td[1]/a/img/@src")[0] - # star = char.xpath("./td[1]/span/img/@alt")[0] - name = char.xpath("./th[1]/a[1]/@title")[0] - avatar = char.xpath("./th[1]/a/img/@src")[0] - star = char.xpath("./th[1]/span/img/@alt")[0] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar)), - "名称": remove_prohibited_str(name), - "星级": int(str(star).split(" ")[0].replace("Rank", "")), - } - guardian_info[member_dict["名称"]] = member_dict - self.dump_data(guardian_info) - logger.info(f"{self.game_name_cn} 更新成功") - # guardian_arms.json - guardian_arms_info = {} - url = "https://wiki.biligame.com/gt/武器" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 武器出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[2]/a/@title")[0] - avatar = char.xpath("./td[1]/div/div/div/a/img/@src")[0] - url = char.xpath("./td[3]/img/@srcset")[0] - if r := re.search(r"Rank-mini-star_(\d).png", url): - star = r.group(1) - else: - continue - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar)), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()), - } - guardian_arms_info[member_dict["名称"]] = member_dict - self.dump_data(guardian_arms_info, "guardian_arms.json") - logger.info(f"{self.game_name_cn} 武器更新成功") - url = "https://wiki.biligame.com/gt/盾牌" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 盾牌出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath( - "//div[@class='resp-tabs-container']/div[2]/div/table[1]/tbody/tr" - ) - for char in char_list: - try: - name = char.xpath("./td[2]/a/@title")[0] - avatar = char.xpath("./td[1]/div/div/div/a/img/@src")[0] - star = char.xpath("./td[3]/text()")[0] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar)), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()), - } - guardian_arms_info[member_dict["名称"]] = member_dict - self.dump_data(guardian_arms_info, "guardian_arms.json") - logger.info(f"{self.game_name_cn} 盾牌更新成功") - # 下载头像 - for value in guardian_info.values(): - await self.download_img(value["头像"], value["名称"]) - for value in guardian_arms_info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - idx = 1 - GT_URL = "https://patchwiki.biligame.com/images/gt" - for url in [ - "/4/4b/ardr3bi2yf95u4zomm263tc1vke6i3i.png", - "/5/55/6vow7lh76gzus6b2g9cfn325d1sugca.png", - "/b/b9/du8egrd2vyewg0cuyra9t8jh0srl0ds.png", - ]: - await self.download_img(GT_URL + url, f"{idx}_star") - idx += 1 - # 另一种星星 - idx = 1 - for url in [ - "/6/66/4e2tfa9kvhfcbikzlyei76i9crva145.png", - "/1/10/r9ihsuvycgvsseyneqz4xs22t53026m.png", - "/7/7a/o0k86ru9k915y04azc26hilxead7xp1.png", - "/c/c9/rxz99asysz0rg391j3b02ta09mnpa7v.png", - "/2/2a/sfxz0ucv1s6ewxveycz9mnmrqs2rw60.png", - ]: - await self.download_img(GT_URL + url, f"{idx}_star_rank") - idx += 1 - # 头像框 - await self.download_img( - GT_URL + "/8/8e/ogbqslbhuykjhnc8trtoa0p0nhfzohs.png", f"avatar_frame" - ) - await self.update_up_char() - - async def update_up_char(self): - url = "https://wiki.biligame.com/gt/首页" - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取公告出错") - return - try: - dom = etree.HTML(result, etree.HTMLParser()) - announcement = dom.xpath( - "//div[@class='mw-parser-output']/div/div[3]/div[2]/div/div[2]/div[3]" - )[0] - title = announcement.xpath("./font/p/b/text()")[0] - match = re.search(r"从(.*?)开始.*?至(.*?)结束", title) - if not match: - logger.warning(f"{self.game_name_cn}找不到UP时间") - return - start, end = match.groups() - start_time = dateparser.parse(start.replace("月", "/").replace("日", "")) - end_time = dateparser.parse(end.replace("月", "/").replace("日", "")) - if not (start_time and end_time) or not ( - start_time <= datetime.now() <= end_time - ): - return - divs = announcement.xpath("./font/div") - char_index = 0 - arms_index = 0 - for index, div in enumerate(divs): - if div.xpath("string(.)") == "角色": - char_index = index - elif div.xpath("string(.)") == "武器": - arms_index = index - chars = divs[char_index + 1 : arms_index] - arms = divs[arms_index + 1 :] - up_chars = [] - up_arms = [] - for char in chars: - name = char.xpath("./p/a/@title")[0] - up_chars.append(UpChar(name=name, star=3, limited=False, zoom=0)) - for arm in arms: - name = arm.xpath("./p/a/@title")[0] - up_arms.append(UpChar(name=name, star=5, limited=False, zoom=0)) - self.UP_CHAR = UpEvent( - title=title, - pool_img="", - start_time=start_time, - end_time=end_time, - up_char=up_chars, - ) - self.UP_ARMS = UpEvent( - title=title, - pool_img="", - start_time=start_time, - end_time=end_time, - up_char=up_arms, - ) - self.dump_up_char() - logger.info(f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}") - except Exception as e: - logger.warning(f"{self.game_name_cn}UP更新出错 {type(e)}:{e}") - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_CHAR and self.UP_ARMS: - return MessageUtils.build_message( - f"重载成功!\n当前UP池子:{self.UP_CHAR.title}" - ) diff --git a/zhenxun/plugins/draw_card/handles/onmyoji_handle.py b/zhenxun/plugins/draw_card/handles/onmyoji_handle.py deleted file mode 100644 index 25d05c388..000000000 --- a/zhenxun/plugins/draw_card/handles/onmyoji_handle.py +++ /dev/null @@ -1,178 +0,0 @@ -import random - -import ujson as json -from lxml import etree -from PIL import Image, ImageDraw -from PIL.Image import Image as IMG - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle - - -class OnmyojiChar(BaseData): - @property - def star_str(self) -> str: - return ["N", "R", "SR", "SSR", "SP"][self.star - 1] - - -class OnmyojiHandle(BaseHandle[OnmyojiChar]): - def __init__(self): - super().__init__("onmyoji", "阴阳师") - self.max_star = 5 - self.config = draw_config.onmyoji - self.ALL_CHAR: list[OnmyojiChar] = [] - - def get_card(self, **kwargs) -> OnmyojiChar: - star = self.get_star( - [5, 4, 3, 2], - [ - self.config.ONMYOJI_SP, - self.config.ONMYOJI_SSR, - self.config.ONMYOJI_SR, - self.config.ONMYOJI_R, - ], - ) - chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited] - return random.choice(chars) - - def format_max_star(self, card_list: list[tuple[OnmyojiChar, int]]) -> str: - rst = "" - for card, index in card_list: - if card.star == self.max_star: - rst += f"第 {index} 抽获取SP {card.name}\n" - elif card.star == self.max_star - 1: - rst += f"第 {index} 抽获取SSR {card.name}\n" - return rst.strip() - - @staticmethod - def star_label(star: int) -> IMG: - text, color1, color2 = [ - ("N", "#7E7E82", "#F5F6F7"), - ("R", "#014FA8", "#37C6FD"), - ("SR", "#6E0AA4", "#E94EFD"), - ("SSR", "#E5511D", "#FAF905"), - ("SP", "#FA1F2D", "#FFBBAF"), - ][star - 1] - w = 200 - h = 110 - # 制作渐变色图片 - base = Image.new("RGBA", (w, h), color1) - top = Image.new("RGBA", (w, h), color2) - mask = Image.new("L", (w, h)) - mask_data = [] - for y in range(h): - mask_data.extend([int(255 * (y / h))] * w) - mask.putdata(mask_data) - base.paste(top, (0, 0), mask) - # 透明图层 - font = load_font("gorga.otf", 100) - alpha = Image.new("L", (w, h)) - draw = ImageDraw.Draw(alpha) - draw.text((20, -30), text, fill="white", font=font) - base.putalpha(alpha) - # stroke - bg = Image.new("RGBA", (w, h)) - draw = ImageDraw.Draw(bg) - draw.text( - (20, -30), - text, - font=font, - fill="gray", - stroke_width=3, - stroke_fill="gray", - ) - bg.paste(base, (0, 0), base) - return bg - - async def generate_img(self, card_list: list[OnmyojiChar]) -> BuildImage: - return await super().generate_img(card_list, num_per_line=10) - - async def generate_card_img(self, card: OnmyojiChar) -> BuildImage: - bg = BuildImage(73, 240, color="#F1EFE9") - img_path = str(self.img_path / f"{cn2py(card.name)}_mark_btn.png") - img = BuildImage(0, 0, background=img_path) - img = Image.open(img_path).convert("RGBA") - label = self.star_label(card.star).resize((60, 33), Image.ANTIALIAS) - await bg.paste(img, (0, 0)) - await bg.paste(label, (0, 135)) - font = load_font("msyh.ttf", 16) - draw = ImageDraw.Draw(bg.markImg) - text = "\n".join([t for t in card.name[:4]]) - _, text_h = font.getsize_multiline(text, spacing=0) - draw.text( - (40, 150 + (90 - text_h) / 2), text, font=font, fill="gray", spacing=0 - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - OnmyojiChar( - name=value["名称"], - star=["N", "R", "SR", "SSR", "SP"].index(value["星级"]) + 1, - limited=( - True - if key - in [ - "奴良陆生", - "卖药郎", - "鬼灯", - "阿香", - "蜜桃&芥子", - "犬夜叉", - "杀生丸", - "桔梗", - "朽木露琪亚", - "黑崎一护", - "灶门祢豆子", - "灶门炭治郎", - ] - else False - ), - ) - for key, value in self.load_data().items() - ] - - async def _update_info(self): - info = {} - url = "https://yys.res.netease.com/pc/zt/20161108171335/js/app/all_shishen.json?v74=" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - data = json.loads(result) - for x in data: - name = remove_prohibited_str(x["name"]) - member_dict = { - "id": x["id"], - "名称": name, - "星级": x["level"], - } - info[name] = member_dict - # logger.info(f"{name} is update...") - # 更新头像 - for key in info.keys(): - url = f'https://yys.163.com/shishen/{info[key]["id"]}.html' - result = await self.get_url(url) - if not result: - info[key]["头像"] = "" - continue - try: - dom = etree.HTML(result, etree.HTMLParser()) - avatar = dom.xpath("//div[@class='pic_wrap']/img/@src")[0] - avatar = "https:" + avatar - info[key]["头像"] = avatar - except IndexError: - info[key]["头像"] = "" - logger.warning(f"{self.game_name_cn} 获取头像错误 {key}") - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载书签形式的头像 - url = f"https://yys.res.netease.com/pc/zt/20161108171335/data/mark_btn/{value['id']}.png" - await self.download_img(url, value["名称"] + "_mark_btn") diff --git a/zhenxun/plugins/draw_card/handles/pcr_handle.py b/zhenxun/plugins/draw_card/handles/pcr_handle.py deleted file mode 100644 index 666a68426..000000000 --- a/zhenxun/plugins/draw_card/handles/pcr_handle.py +++ /dev/null @@ -1,149 +0,0 @@ -import random -from urllib.parse import unquote - -from lxml import etree -from PIL import ImageDraw - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle - - -class PcrChar(BaseData): - pass - - -class PcrHandle(BaseHandle[PcrChar]): - def __init__(self): - super().__init__("pcr", "公主连结") - self.max_star = 3 - self.config = draw_config.pcr - self.ALL_CHAR: list[PcrChar] = [] - - def get_card(self, mode: int = 1) -> PcrChar: - if mode == 2: - star = self.get_star( - [3, 2], [self.config.PCR_G_THREE_P, self.config.PCR_G_TWO_P] - ) - else: - star = self.get_star( - [3, 2, 1], - [self.config.PCR_THREE_P, self.config.PCR_TWO_P, self.config.PCR_ONE_P], - ) - chars = [x for x in self.ALL_CHAR if x.star == star and not x.limited] - return random.choice(chars) - - def get_cards(self, count: int, **kwargs) -> list[tuple[PcrChar, int]]: - card_list = [] - card_count = 0 # 保底计算 - for i in range(count): - card_count += 1 - # 十连保底 - if card_count == 10: - card = self.get_card(2) - card_count = 0 - else: - card = self.get_card(1) - if card.star > self.max_star - 2: - card_count = 0 - card_list.append((card, i + 1)) - return card_list - - async def generate_card_img(self, card: PcrChar) -> BuildImage: - sep_w = 5 - sep_h = 5 - star_h = 15 - img_w = 90 - img_h = 90 - font_h = 20 - bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5") - star_path = str(self.img_path / "star.png") - star = BuildImage(star_h, star_h, background=star_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - await bg.paste(img, (sep_w, sep_h)) - for i in range(card.star): - await bg.paste(star, (sep_w + img_w - star_h * (i + 1), sep_h)) - # 加名字 - text = card.name[:5] + "..." if len(card.name) > 6 else card.name - font = load_font(fontsize=14) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2), - text, - font=font, - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - PcrChar( - name=value["名称"], - star=int(value["星级"]), - limited=True if "(" in key else False, - ) - for key, value in self.load_data().items() - ] - - async def _update_info(self): - info = {} - if draw_config.PCR_TAI: - url = "https://wiki.biligame.com/pcr/角色图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - # TODO: PCR台湾更新失败 - char_list = dom.xpath( - "//*[@id='CardSelectCard']/div[@class='unit-icon trcard']" - ) - for char in char_list: - try: - name = char.xpath("./a/@title")[0] - avatar = char.xpath("./a/img/@srcset")[0] - star = len(char.xpath("./div[1]/img")) - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "星级": star, - } - info[member_dict["名称"]] = member_dict - else: - url = "https://wiki.biligame.com/pcr/角色筛选表" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/a/@title")[0] - avatar = char.xpath("./td[1]/a/img/@srcset")[0] - star = char.xpath("./td[4]/text()")[0] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "星级": int(str(star).strip()), - } - info[member_dict["名称"]] = member_dict - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - await self.download_img( - "https://patchwiki.biligame.com/images/pcr/0/02/s75ys2ecqhu2xbdw1wf1v9ccscnvi5g.png", - "star", - ) diff --git a/zhenxun/plugins/draw_card/handles/pretty_handle.py b/zhenxun/plugins/draw_card/handles/pretty_handle.py deleted file mode 100644 index 535e2b19d..000000000 --- a/zhenxun/plugins/draw_card/handles/pretty_handle.py +++ /dev/null @@ -1,423 +0,0 @@ -import random -import re -from datetime import datetime -from urllib.parse import unquote - -import dateparser -import ujson as json -from bs4 import BeautifulSoup -from lxml import etree -from nonebot_plugin_alconna import UniMessage -from PIL import ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle, UpChar, UpEvent - - -class PrettyData(BaseData): - pass - - -class PrettyChar(PrettyData): - pass - - -class PrettyCard(PrettyData): - @property - def star_str(self) -> str: - return ["R", "SR", "SSR"][self.star - 1] - - -class PrettyHandle(BaseHandle[PrettyData]): - def __init__(self): - super().__init__("pretty", "赛马娘") - self.data_files.append("pretty_card.json") - self.max_star = 3 - self.game_card_color = "#eff2f5" - self.config = draw_config.pretty - - self.ALL_CHAR: list[PrettyChar] = [] - self.ALL_CARD: list[PrettyCard] = [] - self.UP_CHAR: UpEvent | None = None - self.UP_CARD: UpEvent | None = None - - def get_card(self, pool_name: str, mode: int = 1) -> PrettyData: - if mode == 1: - star = self.get_star( - [3, 2, 1], - [ - self.config.PRETTY_THREE_P, - self.config.PRETTY_TWO_P, - self.config.PRETTY_ONE_P, - ], - ) - else: - star = self.get_star( - [3, 2], [self.config.PRETTY_THREE_P, self.config.PRETTY_TWO_P] - ) - up_pool = None - if pool_name == "char": - up_pool = self.UP_CHAR - all_list = self.ALL_CHAR - else: - up_pool = self.UP_CARD - all_list = self.ALL_CARD - - all_char = [x for x in all_list if x.star == star and not x.limited] - acquire_char = None - # 有UP池子 - if up_pool and star in [x.star for x in up_pool.up_char]: - up_list = [x.name for x in up_pool.up_char if x.star == star] - # 抽到UP - if random.random() < 1 / len(all_char) * (0.7 / 0.1385): - up_name = random.choice(up_list) - try: - acquire_char = [x for x in all_list if x.name == up_name][0] - except IndexError: - pass - if not acquire_char: - acquire_char = random.choice(all_char) - return acquire_char - - def get_cards(self, count: int, pool_name: str) -> list[tuple[PrettyData, int]]: - card_list = [] - card_count = 0 # 保底计算 - for i in range(count): - card_count += 1 - # 十连保底 - if card_count == 10: - card = self.get_card(pool_name, 2) - card_count = 0 - else: - card = self.get_card(pool_name, 1) - if card.star > self.max_star - 2: - card_count = 0 - card_list.append((card, i + 1)) - return card_list - - def format_pool_info(self, pool_name: str) -> str: - info = "" - up_event = self.UP_CHAR if pool_name == "char" else self.UP_CARD - if up_event: - star3_list = [x.name for x in up_event.up_char if x.star == 3] - star2_list = [x.name for x in up_event.up_char if x.star == 2] - star1_list = [x.name for x in up_event.up_char if x.star == 1] - if star3_list: - if pool_name == "char": - info += f'三星UP:{" ".join(star3_list)}\n' - else: - info += f'SSR UP:{" ".join(star3_list)}\n' - if star2_list: - if pool_name == "char": - info += f'二星UP:{" ".join(star2_list)}\n' - else: - info += f'SR UP:{" ".join(star2_list)}\n' - if star1_list: - if pool_name == "char": - info += f'一星UP:{" ".join(star1_list)}\n' - else: - info += f'R UP:{" ".join(star1_list)}\n' - info = f"当前up池:{up_event.title}\n{info}" - return info.strip() - - async def draw(self, count: int, pool_name: str, **kwargs) -> UniMessage: - pool_name = "char" if not pool_name else pool_name - index2card = self.get_cards(count, pool_name) - cards = [card[0] for card in index2card] - up_event = self.UP_CHAR if pool_name == "char" else self.UP_CARD - up_list = [x.name for x in up_event.up_char] if up_event else [] - result = self.format_result(index2card, up_list=up_list) - pool_info = self.format_pool_info(pool_name) - img = await self.generate_img(cards) - return MessageUtils.build_message([pool_info, img, result]) - - async def generate_card_img(self, card: PrettyData) -> BuildImage: - if isinstance(card, PrettyChar): - star_h = 30 - img_w = 200 - img_h = 219 - font_h = 50 - bg = BuildImage(img_w, img_h + font_h, color="#EFF2F5") - star_path = str(self.img_path / "star.png") - star = BuildImage(star_h, star_h, background=star_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - star_w = star_h * card.star - for i in range(card.star): - await bg.paste(star, (int((img_w - star_w) / 2) + star_h * i, 0)) - await bg.paste(img, (0, 0)) - # 加名字 - text = card.name[:5] + "..." if len(card.name) > 6 else card.name - font = load_font(fontsize=30) - text_w, _ = font.getsize(text) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - ((img_w - text_w) / 2, img_h), - text, - font=font, - fill="gray", - ) - return bg - else: - sep_w = 10 - img_w = 200 - img_h = 267 - font_h = 75 - bg = BuildImage(img_w + sep_w * 2, img_h + font_h, color="#EFF2F5") - label_path = str(self.img_path / f"{card.star}_label.png") - label = BuildImage(40, 40, background=label_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - await bg.paste(img, (sep_w, 0)) - await bg.paste(label, (30, 3)) - # 加名字 - text = "" - texts = [] - font = load_font(fontsize=25) - for t in card.name: - if BuildImage.get_text_size((text + t), font)[0] > 190: - texts.append(text) - text = "" - if len(texts) >= 2: - texts[-1] += "..." - break - else: - text += t - if text: - texts.append(text) - text = "\n".join(texts) - text_w, _ = font.getsize_multiline(text) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - ((img_w - text_w) / 2, img_h), - text, - font=font, - align="center", - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_CHAR = [ - PrettyChar( - name=value["名称"], - star=int(value["初始星级"]), - limited=False, - ) - for value in self.load_data().values() - ] - self.ALL_CARD = [ - PrettyCard( - name=value["中文名"], - star=["R", "SR", "SSR"].index(value["稀有度"]) + 1, - limited=True if "卡池" not in value["获取方式"] else False, - ) - for value in self.load_data("pretty_card.json").values() - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - self.UP_CHAR = UpEvent.parse_obj(data.get("char", {})) - self.UP_CARD = UpEvent.parse_obj(data.get("card", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_CHAR and self.UP_CARD: - data = { - "char": json.loads(self.UP_CHAR.json()), - "card": json.loads(self.UP_CARD.json()), - } - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - # pretty.json - pretty_info = {} - url = "https://wiki.biligame.com/umamusume/赛马娘图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/a/@title")[0] - avatar = char.xpath("./td[1]/a/img/@srcset")[0] - star = len(char.xpath("./td[3]/img")) - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "初始星级": star, - } - pretty_info[member_dict["名称"]] = member_dict - self.dump_data(pretty_info) - logger.info(f"{self.game_name_cn} 更新成功") - # pretty_card.json - pretty_card_info = {} - url = "https://wiki.biligame.com/umamusume/支援卡图鉴" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 卡牌出错") - else: - dom = etree.HTML(result, etree.HTMLParser()) - char_list = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - name = char.xpath("./td[1]/div/a/@title")[0] - name_cn = char.xpath("./td[3]/a/text()")[0] - avatar = char.xpath("./td[1]/div/a/img/@srcset")[0] - star = str(char.xpath("./td[5]/text()")[0]).strip() - sources = str(char.xpath("./td[7]/text()")[0]).strip() - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(name), - "中文名": remove_prohibited_str(name_cn), - "稀有度": star, - "获取方式": [sources] if sources else [], - } - pretty_card_info[member_dict["中文名"]] = member_dict - self.dump_data(pretty_card_info, "pretty_card.json") - logger.info(f"{self.game_name_cn} 卡牌更新成功") - # 下载头像 - for value in pretty_info.values(): - await self.download_img(value["头像"], value["名称"]) - for value in pretty_card_info.values(): - await self.download_img(value["头像"], value["中文名"]) - # 下载星星 - PRETTY_URL = "https://patchwiki.biligame.com/images/umamusume" - await self.download_img( - PRETTY_URL + "/1/13/e1hwjz4vmhtvk8wlyb7c0x3ld1s2ata.png", "star" - ) - # 下载稀有度标志 - idx = 1 - for url in [ - "/f/f7/afqs7h4snmvovsrlifq5ib8vlpu2wvk.png", - "/3/3b/d1jmpwrsk4irkes1gdvoos4ic6rmuht.png", - "/0/06/q23szwkbtd7pfkqrk3wcjlxxt9z595o.png", - ]: - await self.download_img(PRETTY_URL + url, f"{idx}_label") - idx += 1 - await self.update_up_char() - - async def update_up_char(self): - announcement_url = "https://wiki.biligame.com/umamusume/公告" - result = await self.get_url(announcement_url) - if not result: - logger.warning(f"{self.game_name_cn}获取公告出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - announcements = dom.xpath("//div[@id='mw-content-text']/div/div/span/a") - title = "" - url = "" - for announcement in announcements: - try: - title = announcement.xpath("./@title")[0] - url = "https://wiki.biligame.com/" + announcement.xpath("./@href")[0] - if re.match(r".*?\d{8}$", title) or re.match( - r"^\d{1,2}月\d{1,2}日.*?", title - ): - break - except IndexError: - continue - if not title: - logger.warning(f"{self.game_name_cn}未找到新UP公告") - return - result = await self.get_url(url) - if not result: - logger.warning(f"{self.game_name_cn}获取UP公告出错") - return - try: - start_time = None - end_time = None - char_img = "" - card_img = "" - up_chars = [] - up_cards = [] - soup = BeautifulSoup(result, "lxml") - heads = soup.find_all("span", {"class": "mw-headline"}) - for head in heads: - if "时间" in head.text: - time = head.find_next("p").text.split("\n")[0] - if "~" in time: - start, end = time.split("~") - start_time = dateparser.parse(start) - end_time = dateparser.parse(end) - elif "赛马娘" in head.text: - char_img = head.find_next("a", {"class": "image"}).find("img")[ - "src" - ] - lines = str(head.find_next("p").text).split("\n") - chars = [ - line - for line in lines - if "★" in line and "(" in line and ")" in line - ] - for char in chars: - star = char.count("★") - name = re.split(r"[()]", char)[-2].strip() - up_chars.append( - UpChar(name=name, star=star, limited=False, zoom=70) - ) - elif "支援卡" in head.text: - card_img = head.find_next("a", {"class": "image"}).find("img")[ - "src" - ] - lines = str(head.find_next("p").text).split("\n") - cards = [ - line - for line in lines - if "R" in line and "(" in line and ")" in line - ] - for card in cards: - star = 3 if "SSR" in card else 2 if "SR" in card else 1 - name = re.split(r"[()]", card)[-2].strip() - up_cards.append( - UpChar(name=name, star=star, limited=False, zoom=70) - ) - if start_time and end_time: - if start_time <= datetime.now() <= end_time: - self.UP_CHAR = UpEvent( - title=title, - pool_img=char_img, - start_time=start_time, - end_time=end_time, - up_char=up_chars, - ) - self.UP_CARD = UpEvent( - title=title, - pool_img=card_img, - start_time=start_time, - end_time=end_time, - up_char=up_cards, - ) - self.dump_up_char() - logger.info( - f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}" - ) - except Exception as e: - logger.warning(f"{self.game_name_cn}UP更新出错", e=e) - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_CHAR and self.UP_CARD: - return MessageUtils.build_message( - [ - f"重载成功!\n当前UP池子:{self.UP_CHAR.title}", - self.UP_CHAR.pool_img, - self.UP_CARD.pool_img, - ] - ) diff --git a/zhenxun/plugins/draw_card/handles/prts_handle.py b/zhenxun/plugins/draw_card/handles/prts_handle.py deleted file mode 100644 index 18a86fc3a..000000000 --- a/zhenxun/plugins/draw_card/handles/prts_handle.py +++ /dev/null @@ -1,344 +0,0 @@ -import random -import re -from datetime import datetime -from urllib.parse import unquote - -import dateparser -import ujson as json -from lxml import etree -from lxml.etree import _Element -from nonebot_plugin_alconna import UniMessage -from PIL import ImageDraw -from pydantic import ValidationError - -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -from ..config import draw_config -from ..util import cn2py, load_font, remove_prohibited_str -from .base_handle import BaseData, BaseHandle, UpChar, UpEvent - - -class Operator(BaseData): - recruit_only: bool # 公招限定 - event_only: bool # 活动获得干员 - core_only: bool # 中坚干员 - # special_only: bool # 升变/异格干员 - - -class PrtsHandle(BaseHandle[Operator]): - def __init__(self): - super().__init__(game_name="prts", game_name_cn="明日方舟") - self.max_star = 6 - self.game_card_color = "#eff2f5" - self.config = draw_config.prts - - self.ALL_OPERATOR: list[Operator] = [] - self.UP_EVENT: UpEvent | None = None - - def get_card(self, add: float) -> Operator: - star = self.get_star( - star_list=[6, 5, 4, 3], - probability_list=[ - self.config.PRTS_SIX_P + add, - self.config.PRTS_FIVE_P, - self.config.PRTS_FOUR_P, - self.config.PRTS_THREE_P, - ], - ) - - all_operators = [ - x - for x in self.ALL_OPERATOR - if x.star == star - and not any([x.limited, x.recruit_only, x.event_only, x.core_only]) - ] - acquire_operator = None - - if self.UP_EVENT: - up_operators = [x for x in self.UP_EVENT.up_char if x.star == star] - # UPs - try: - zooms = [x.zoom for x in up_operators] - zoom_sum = sum(zooms) - if random.random() < zoom_sum: - up_name = random.choices(up_operators, weights=zooms, k=1)[0].name - acquire_operator = [ - x for x in self.ALL_OPERATOR if x.name == up_name - ][0] - except IndexError: - pass - if not acquire_operator: - acquire_operator = random.choice(all_operators) - return acquire_operator - - def get_cards(self, count: int, **kwargs) -> list[tuple[Operator, int]]: - card_list = [] # 获取所有角色 - add = 0.0 - count_idx = 0 - for i in range(count): - count_idx += 1 - card = self.get_card(add) - if card.star == self.max_star: - add = 0.0 - count_idx = 0 - elif count_idx > 50: - add += 0.02 - card_list.append((card, i + 1)) - return card_list - - def format_pool_info(self) -> str: - info = "" - if self.UP_EVENT: - star6_list = [x.name for x in self.UP_EVENT.up_char if x.star == 6] - star5_list = [x.name for x in self.UP_EVENT.up_char if x.star == 5] - star4_list = [x.name for x in self.UP_EVENT.up_char if x.star == 4] - if star6_list: - info += f"六星UP:{' '.join(star6_list)}\n" - if star5_list: - info += f"五星UP:{' '.join(star5_list)}\n" - if star4_list: - info += f"四星UP:{' '.join(star4_list)}\n" - info = f"当前up池: {self.UP_EVENT.title}\n{info}" - return info.strip() - - async def draw(self, count: int, **kwargs) -> UniMessage: - index2card = self.get_cards(count) - """这里cards修复了抽卡图文不符的bug""" - cards = [card[0] for card in index2card] - up_list = [x.name for x in self.UP_EVENT.up_char] if self.UP_EVENT else [] - result = self.format_result(index2card, up_list=up_list) - pool_info = self.format_pool_info() - img = await self.generate_img(cards) - return MessageUtils.build_message([pool_info, img, result]) - - async def generate_card_img(self, card: Operator) -> BuildImage: - sep_w = 5 - sep_h = 5 - star_h = 15 - img_w = 120 - img_h = 120 - font_h = 20 - bg = BuildImage(img_w + sep_w * 2, img_h + font_h + sep_h * 2, color="#EFF2F5") - star_path = str(self.img_path / "star.png") - star = BuildImage(star_h, star_h, background=star_path) - img_path = str(self.img_path / f"{cn2py(card.name)}.png") - img = BuildImage(img_w, img_h, background=img_path) - await bg.paste(img, (sep_w, sep_h)) - for i in range(card.star): - await bg.paste(star, (sep_w + img_w - 5 - star_h * (i + 1), sep_h)) - # 加名字 - text = card.name[:7] + "..." if len(card.name) > 8 else card.name - font = load_font(fontsize=16) - text_w, text_h = BuildImage.get_text_size(text, font) - draw = ImageDraw.Draw(bg.markImg) - draw.text( - (sep_w + (img_w - text_w) / 2, sep_h + img_h + (font_h - text_h) / 2), - text, - font=font, - fill="gray", - ) - return bg - - def _init_data(self): - self.ALL_OPERATOR = [ - Operator( - name=value["名称"], - star=int(value["星级"]), - limited="标准寻访" not in value["获取途径"] - and "中坚寻访" not in value["获取途径"], - recruit_only=( - True - if "标准寻访" not in value["获取途径"] - and "中坚寻访" not in value["获取途径"] - and "公开招募" in value["获取途径"] - else False - ), - event_only=True if "活动获取" in value["获取途径"] else False, - core_only=( - True - if "标准寻访" not in value["获取途径"] - and "中坚寻访" in value["获取途径"] - else False - ), - ) - for key, value in self.load_data().items() - if "阿米娅" not in key - ] - self.load_up_char() - - def load_up_char(self): - try: - data = self.load_data(f"draw_card_up/{self.game_name}_up_char.json") - """这里的 waring 有点模糊,更新游戏信息时没有up池的情况下也会报错,所以细分了一下""" - if not data: - logger.warning(f"当前无UP池或 {self.game_name}_up_char.json 文件不存在") - else: - self.UP_EVENT = UpEvent.parse_obj(data.get("char", {})) - except ValidationError: - logger.warning(f"{self.game_name}_up_char 解析出错") - - def dump_up_char(self): - if self.UP_EVENT: - data = {"char": json.loads(self.UP_EVENT.json())} - self.dump_data(data, f"draw_card_up/{self.game_name}_up_char.json") - - async def _update_info(self): - """更新信息""" - info = {} - url = "https://wiki.biligame.com/arknights/干员数据表" - result = await self.get_url(url) - if not result: - logger.warning(f"更新 {self.game_name_cn} 出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - char_list: list[_Element] = dom.xpath("//table[@id='CardSelectTr']/tbody/tr") - for char in char_list: - try: - avatar = char.xpath("./td[1]/div/div/div/a/img/@srcset")[0] - name = char.xpath("./td[1]/center/a/text()")[0] - star = char.xpath("./td[2]/text()")[0] - """这里sources修好了干员获取标签有问题的bug,如三星只能抽到卡缇就是这个原因""" - sources = [_.strip("\n") for _ in char.xpath("./td[7]/text()")] - except IndexError: - continue - member_dict = { - "头像": unquote(str(avatar).split(" ")[-2]), - "名称": remove_prohibited_str(str(name).strip()), - "星级": int(str(star).strip()), - "获取途径": sources, - } - info[member_dict["名称"]] = member_dict - self.dump_data(info) - logger.info(f"{self.game_name_cn} 更新成功") - # 下载头像 - for value in info.values(): - await self.download_img(value["头像"], value["名称"]) - # 下载星星 - await self.download_img( - "https://patchwiki.biligame.com/images/pcr/0/02/s75ys2ecqhu2xbdw1wf1v9ccscnvi5g.png", - "star", - ) - await self.update_up_char() - - async def update_up_char(self): - """重载卡池""" - announcement_url = "https://ak.hypergryph.com/news.html" - result = await self.get_url(announcement_url) - if not result: - logger.warning(f"{self.game_name_cn}获取公告出错") - return - dom = etree.HTML(result, etree.HTMLParser()) - activity_urls = dom.xpath( - "//ol[@class='articlelist' and @data-category-key='ACTIVITY']/li/a/@href" - ) - start_time = None - end_time = None - up_chars = [] - pool_img = "" - for activity_url in activity_urls[:10]: # 减少响应时间, 10个就够了 - activity_url = f"https://ak.hypergryph.com{activity_url}" - result = await self.get_url(activity_url) - if not result: - logger.warning(f"{self.game_name_cn}获取公告 {activity_url} 出错") - continue - - """因为鹰角的前端太自由了,这里重写了匹配规则以尽可能避免因为前端乱七八糟而导致的重载失败""" - dom = etree.HTML(result, etree.HTMLParser()) - contents = dom.xpath( - "//div[@class='article-content']/p/text() | //div[@class='article-content']/p/span/text() | //div[@class='article-content']/div[@class='media-wrap image-wrap']/img/@src" - ) - title = "" - time = "" - chars: list[str] = [] - for index, content in enumerate(contents): - if re.search("(.*)(寻访|复刻).*?开启", content): - title = re.split(r"[【】]", content) - title = "".join(title[1:-1]) if "-" in title else title[1] - lines = [ - contents[index - 2 + _] for _ in range(8) - ] # 从 -2 开始是因为xpath获取的时间有的会在寻访开启这一句之前 - lines.append("") # 防止IndexError,加个空字符串 - for idx, line in enumerate(lines): - match = re.search( - r"(\d{1,2}月\d{1,2}日.*?-.*?\d{1,2}月\d{1,2}日.*?$)", line - ) - if match: - time = match.group(1) - """因为

的诡异排版,所以有了下面的一段""" - if ("★★" in line and "%" in line) or ( - "★★" in line and "%" in lines[idx + 1] - ): - ( - chars.append(line) - if ("★★" in line and "%" in line) - else chars.append(line + lines[idx + 1]) - ) - if not time: - continue - start, end = ( - time.replace("月", "/").replace("日", " ").split("-")[:2] - ) # 日替换为空格是因为有日后面不接空格的情况,导致 split 出问题 - start_time = dateparser.parse(start) - end_time = dateparser.parse(end) - pool_img = contents[index - 2] - r"""两类格式:用/分割,用\分割;★+(概率)+名字,★+名字+(概率)""" - for char in chars: - star = char.split("(")[0].count("★") - name = ( - re.split(r"[:(]", char)[1] - if "★(" not in char - else re.split("):", char)[1] - ) # 有的括号在前面有的在后面 - dual_up = False - if "\\" in name: - names = name.split("\\") - dual_up = True - elif "/" in name: - names = name.split("/") - dual_up = True - else: - names = [name] # 既有用/分割的,又有用\分割的 - - names = [name.replace("[限定]", "").strip() for name in names] - zoom = 1 - if "权值" in char: - zoom = 0.03 - else: - match = re.search(r"(占.*?的.*?(\d+).*?%)", char) - if dual_up == True: - zoom = float(match.group(1)) / 2 - else: - zoom = float(match.group(1)) - zoom = zoom / 100 if zoom > 1 else zoom - for name in names: - up_chars.append( - UpChar(name=name, star=star, limited=False, zoom=zoom) - ) - break # 这里break会导致个问题:如果一个公告里有两个池子,会漏掉下面的池子,比如 5.19 的定向寻访。但目前我也没啥好想法解决 - if title and start_time and end_time: - if start_time <= datetime.now() <= end_time: - self.UP_EVENT = UpEvent( - title=title, - pool_img=pool_img, - start_time=start_time, - end_time=end_time, - up_char=up_chars, - ) - self.dump_up_char() - logger.info( - f"成功获取{self.game_name_cn}当前up信息...当前up池: {title}" - ) - break - - async def _reload_pool(self) -> UniMessage | None: - await self.update_up_char() - self.load_up_char() - if self.UP_EVENT: - return MessageUtils.build_message( - [ - f"重载成功!\n当前UP池子:{self.UP_EVENT.title}", - self.UP_EVENT.pool_img, - ] - ) diff --git a/zhenxun/plugins/draw_card/rule.py b/zhenxun/plugins/draw_card/rule.py deleted file mode 100644 index 49746d95b..000000000 --- a/zhenxun/plugins/draw_card/rule.py +++ /dev/null @@ -1,10 +0,0 @@ -from nonebot.internal.rule import Rule - -from zhenxun.configs.config import Config - - -def rule(game) -> Rule: - async def _rule() -> bool: - return Config.get_config("draw_card", game.config_name, True) - - return Rule(_rule) diff --git a/zhenxun/plugins/draw_card/util.py b/zhenxun/plugins/draw_card/util.py deleted file mode 100644 index d0cefc91c..000000000 --- a/zhenxun/plugins/draw_card/util.py +++ /dev/null @@ -1,61 +0,0 @@ -import platform -from pathlib import Path - -import pypinyin -from PIL import Image, ImageDraw, ImageFont -from PIL.Image import Image as IMG -from PIL.ImageFont import FreeTypeFont - -from zhenxun.configs.path_config import FONT_PATH -from zhenxun.utils._build_image import BuildImage - -dir_path = Path(__file__).parent.absolute() - - -def cn2py(word) -> str: - """保存声调,防止出现类似方舟干员红与吽拼音相同声调不同导致红照片无法保存的问题""" - temp = "" - for i in pypinyin.pinyin(word, style=pypinyin.Style.TONE3): - temp += "".join(i) - return temp - - -# 移除windows和linux下特殊字符 -def remove_prohibited_str(name: str) -> str: - if platform.system().lower() == "windows": - tmp = "" - for i in name: - if i not in ["\\", "/", ":", "*", "?", '"', "<", ">", "|"]: - tmp += i - name = tmp - else: - name = name.replace("/", "\\") - return name - - -def load_font(fontname: str = "msyh.ttf", fontsize: int = 16) -> FreeTypeFont: - return ImageFont.truetype( - str(FONT_PATH / f"{fontname}"), fontsize, encoding="utf-8" - ) - - -def circled_number(num: int) -> IMG: - font = load_font(fontsize=450) - text = str(num) - text_w = BuildImage.get_text_size(text, font=font)[0] - w = 240 + text_w - w = w if w >= 500 else 500 - img = Image.new("RGBA", (w, 500)) - draw = ImageDraw.Draw(img) - draw.ellipse(((0, 0), (500, 500)), fill="red") - draw.ellipse(((w - 500, 0), (w, 500)), fill="red") - draw.rectangle(((250, 0), (w - 250, 500)), fill="red") - draw.text( - (120, -60), - text, - font=font, - fill="white", - stroke_width=10, - stroke_fill="white", - ) - return img diff --git a/zhenxun/plugins/epic/__init__.py b/zhenxun/plugins/epic/__init__.py deleted file mode 100644 index 23c084aad..000000000 --- a/zhenxun/plugins/epic/__init__.py +++ /dev/null @@ -1,40 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.adapters.onebot.v11 import Bot as v11Bot -from nonebot.adapters.onebot.v12 import Bot as v12Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, UniMessage, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from .data_source import get_epic_free - -__plugin_meta__ = PluginMetadata( - name="epic免费游戏", - description="可以不玩,不能没有,每日白嫖", - usage=""" - epic - """.strip(), - extra=PluginExtraData( - author="AkashiCoin", - version="0.1", - ).dict(), -) - -_matcher = on_alconna(Alconna("epic"), priority=5, block=True) - - -@_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma): - gid = session.id3 or session.id2 - type_ = "Group" if gid else "Private" - msg_list, code = await get_epic_free(bot, type_) - if code == 404 and isinstance(msg_list, str): - await MessageUtils.build_message(msg_list).finish() - elif isinstance(bot, (v11Bot, v12Bot)) and isinstance(msg_list, list): - await bot.send_group_forward_msg(group_id=gid, messages=msg_list) - elif isinstance(msg_list, UniMessage): - await msg_list.send() - logger.info(f"获取epic免费游戏", arparma.header_result, session=session) diff --git a/zhenxun/plugins/epic/data_source.py b/zhenxun/plugins/epic/data_source.py deleted file mode 100644 index 583221fe8..000000000 --- a/zhenxun/plugins/epic/data_source.py +++ /dev/null @@ -1,180 +0,0 @@ -from datetime import datetime - -from nonebot.adapters import Bot -from nonebot.adapters.onebot.v11 import Bot as v11Bot -from nonebot.adapters.onebot.v12 import Bot as v12Bot -from nonebot_plugin_alconna import Image, UniMessage - -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - - -# 获取所有 Epic Game Store 促销游戏 -# 方法参考:RSSHub /epicgames 路由 -# https://github.com/DIYgod/RSSHub/blob/master/lib/v2/epicgames/index.js -async def get_epic_game() -> dict | None: - epic_url = "https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions?locale=zh-CN&country=CN&allowCountries=CN" - headers = { - "Referer": "https://www.epicgames.com/store/zh-CN/", - "Content-Type": "application/json; charset=utf-8", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36", - } - try: - res = await AsyncHttpx.get(epic_url, headers=headers, timeout=10) - res_json = res.json() - games = res_json["data"]["Catalog"]["searchStore"]["elements"] - return games - except Exception as e: - logger.error(f"Epic 访问接口错误", e=e) - return None - - -# 此处用于获取游戏简介 -async def get_epic_game_desp(name) -> dict | None: - desp_url = ( - "https://store-content-ipv4.ak.epicgames.com/api/zh-CN/content/products/" - + str(name) - ) - headers = { - "Referer": "https://store.epicgames.com/zh-CN/p/" + str(name), - "Content-Type": "application/json; charset=utf-8", - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.80 Safari/537.36", - } - try: - res = await AsyncHttpx.get(desp_url, headers=headers, timeout=10) - res_json = res.json() - gamesDesp = res_json["pages"][0]["data"]["about"] - return gamesDesp - except Exception as e: - logger.error(f"Epic 访问接口错误", e=e) - return None - - -# 获取 Epic Game Store 免费游戏信息 -# 处理免费游戏的信息方法借鉴 pip 包 epicstore_api 示例 -# https://github.com/SD4RK/epicstore_api/blob/master/examples/free_games_example.py -async def get_epic_free( - bot: Bot, type_event: str -) -> tuple[UniMessage | list | str, int]: - games = await get_epic_game() - if not games: - return "Epic 可能又抽风啦,请稍后再试(", 404 - else: - msg_list = [] - for game in games: - game_name = game["title"] - game_corp = game["seller"]["name"] - game_price = game["price"]["totalPrice"]["fmtPrice"]["originalPrice"] - # 赋初值以避免 local variable referenced before assignment - game_thumbnail, game_dev, game_pub = None, game_corp, game_corp - try: - game_promotions = game["promotions"]["promotionalOffers"] - upcoming_promotions = game["promotions"]["upcomingPromotionalOffers"] - if not game_promotions and upcoming_promotions: - # 促销暂未上线,但即将上线 - promotion_data = upcoming_promotions[0]["promotionalOffers"][0] - start_date_iso, end_date_iso = ( - promotion_data["startDate"][:-1], - promotion_data["endDate"][:-1], - ) - # 删除字符串中最后一个 "Z" 使 Python datetime 可处理此时间 - start_date = datetime.fromisoformat(start_date_iso).strftime( - "%b.%d %H:%M" - ) - end_date = datetime.fromisoformat(end_date_iso).strftime( - "%b.%d %H:%M" - ) - if type_event == "Group": - _message = f"\n由 {game_corp} 公司发行的游戏 {game_name} ({game_price}) 在 UTC 时间 {start_date} 即将推出免费游玩,预计截至 {end_date}。" - msg_list.append(_message) - else: - msg = "\n由 {} 公司发行的游戏 {} ({}) 在 UTC 时间 {} 即将推出免费游玩,预计截至 {}。".format( - game_corp, game_name, game_price, start_date, end_date - ) - msg_list.append(msg) - else: - for image in game["keyImages"]: - if ( - image.get("url") - and not game_thumbnail - and image["type"] - in [ - "Thumbnail", - "VaultOpened", - "DieselStoreFrontWide", - "OfferImageWide", - ] - ): - game_thumbnail = image["url"] - break - for pair in game["customAttributes"]: - if pair["key"] == "developerName": - game_dev = pair["value"] - if pair["key"] == "publisherName": - game_pub = pair["value"] - if game.get("productSlug"): - if gamesDesp := await get_epic_game_desp(game["productSlug"]): - try: - # 是否存在简短的介绍 - if "shortDescription" in gamesDesp: - game_desp = gamesDesp["shortDescription"] - except KeyError: - game_desp = gamesDesp["description"] - else: - game_desp = game["description"] - try: - end_date_iso = game["promotions"]["promotionalOffers"][0][ - "promotionalOffers" - ][0]["endDate"][:-1] - end_date = datetime.fromisoformat(end_date_iso).strftime( - "%b.%d %H:%M" - ) - except IndexError: - end_date = "未知" - # API 返回不包含游戏商店 URL,此处自行拼接,可能出现少数游戏 404 请反馈 - if game.get("productSlug"): - game_url = "https://store.epicgames.com/zh-CN/p/{}".format( - game["productSlug"].replace("/home", "") - ) - elif game.get("url"): - game_url = game["url"] - else: - slugs = ( - [ - x["pageSlug"] - for x in game.get("offerMappings", []) - if x.get("pageType") == "productHome" - ] - + [ - x["pageSlug"] - for x in game.get("catalogNs", {}).get("mappings", []) - if x.get("pageType") == "productHome" - ] - + [ - x["value"] - for x in game.get("customAttributes", []) - if "productSlug" in x.get("key") - ] - ) - game_url = "https://store.epicgames.com/zh-CN{}".format( - f"/p/{slugs[0]}" if len(slugs) else "" - ) - if isinstance(bot, (v11Bot, v12Bot)) and type_event == "Group": - _message = [ - Image(url=game_thumbnail), - f"\nFREE now :: {game_name} ({game_price})\n{game_desp}\n此游戏由 {game_dev} 开发、{game_pub} 发行,将在 UTC 时间 {end_date} 结束免费游玩,戳链接速度加入你的游戏库吧~\n{game_url}\n", - ] - msg_list.append(_message) - else: - _message = [] - if game_thumbnail: - _message.append(Image(url=game_thumbnail)) - _message.append( - f"\n\nFREE now :: {game_name} ({game_price})\n{game_desp}\n此游戏由 {game_dev} 开发、{game_pub} 发行,将在 UTC 时间 {end_date} 结束免费游玩,戳链接速度加入你的游戏库吧~\n{game_url}\n" - ) - return MessageUtils.build_message(_message), 200 - except TypeError as e: - # logger.info(str(e)) - pass - return MessageUtils.template2forward(msg_list, bot.self_id), 200 diff --git a/zhenxun/plugins/fudu.py b/zhenxun/plugins/fudu.py deleted file mode 100644 index bf4555f40..000000000 --- a/zhenxun/plugins/fudu.py +++ /dev/null @@ -1,151 +0,0 @@ -import random - -from nonebot import on_message -from nonebot.adapters import Event -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Image as alcImg -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import BotConfig, Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task -from zhenxun.models.task_info import TaskInfo -from zhenxun.utils.enum import PluginType -from zhenxun.utils.image_utils import get_download_image_hash -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import ensure_group - -__plugin_meta__ = PluginMetadata( - name="复读", - description="群友的本质是什么?是复读机哒!", - usage=""" - usage: - 重复3次相同的消息时会复读 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.DEPENDANT, - tasks=[Task(module="fudu", name="复读")], - configs=[ - RegisterConfig( - key="FUDU_PROBABILITY", - value=0.7, - help="复读概率", - default_value=0.7, - type=float, - ), - RegisterConfig( - module="_task", - key="DEFAULT_FUDU", - value=True, - help="被动 复读 进群默认开关状态", - default_value=True, - type=bool, - ), - ], - ).dict(), -) - - -class Fudu: - def __init__(self): - self.data = {} - - def append(self, key, content): - self._create(key) - self.data[key]["data"].append(content) - - def clear(self, key): - self._create(key) - self.data[key]["data"] = [] - self.data[key]["is_repeater"] = False - - def size(self, key) -> int: - self._create(key) - return len(self.data[key]["data"]) - - def check(self, key, content) -> bool: - self._create(key) - return self.data[key]["data"][0] == content - - def get(self, key): - self._create(key) - return self.data[key]["data"][0] - - def is_repeater(self, key): - self._create(key) - return self.data[key]["is_repeater"] - - def set_repeater(self, key): - self._create(key) - self.data[key]["is_repeater"] = True - - def _create(self, key): - if self.data.get(key) is None: - self.data[key] = {"is_repeater": False, "data": []} - - -_manage = Fudu() - - -base_config = Config.get("fudu") - - -_matcher = on_message(rule=ensure_group, priority=999) - - -@_matcher.handle() -async def _(message: UniMsg, event: Event, session: EventSession): - group_id = session.id2 or "" - if await TaskInfo.is_block("fudu", group_id): - return - if event.is_tome(): - return - plain_text = message.extract_plain_text() - image_list = [] - for m in message: - if isinstance(m, alcImg): - if m.url: - image_list.append(m.url) - if not plain_text and not image_list: - return - if plain_text and plain_text.startswith(f"@可爱的{BotConfig.self_nickname}"): - await MessageUtils.build_message("复制粘贴的虚空艾特?").send(reply_to=True) - if image_list: - img_hash = await get_download_image_hash(image_list[0], group_id) - else: - img_hash = "" - add_msg = plain_text + "|-|" + img_hash - if _manage.size(group_id) == 0: - _manage.append(group_id, add_msg) - elif _manage.check(group_id, add_msg): - _manage.append(group_id, add_msg) - else: - _manage.clear(group_id) - _manage.append(group_id, add_msg) - if _manage.size(group_id) > 2: - if random.random() < base_config.get( - "FUDU_PROBABILITY" - ) and not _manage.is_repeater(group_id): - if random.random() < 0.2: - if plain_text.startswith("打断施法"): - await MessageUtils.build_message("打断" + plain_text).finish() - else: - await MessageUtils.build_message("打断施法!").finish() - _manage.set_repeater(group_id) - rst = None - if image_list and plain_text: - rst = MessageUtils.build_message( - [plain_text, TEMP_PATH / f"compare_download_{group_id}_img.jpg"] - ) - elif image_list: - rst = MessageUtils.build_message( - TEMP_PATH / f"compare_download_{group_id}_img.jpg" - ) - elif plain_text: - rst = MessageUtils.build_message(plain_text) - if rst: - await rst.finish() diff --git a/zhenxun/plugins/gold_redbag/__init__.py b/zhenxun/plugins/gold_redbag/__init__.py deleted file mode 100644 index 7e6a1c285..000000000 --- a/zhenxun/plugins/gold_redbag/__init__.py +++ /dev/null @@ -1,349 +0,0 @@ -import time -import uuid -from datetime import datetime, timedelta - -from apscheduler.jobstores.base import JobLookupError -from nonebot.adapters import Bot -from nonebot.exception import ActionFailed -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Args, Arparma, At, Match, Option, on_alconna -from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import BotConfig -from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.depends import GetConfig, UserName -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.rules import ensure_group - -from .config import FESTIVE_KEY, FestiveRedBagManage -from .data_source import RedBagManager - -__plugin_meta__ = PluginMetadata( - name="金币红包", - description="运气项目又来了", - usage=""" - 塞红包 [金币数] ?[红包数=5] ?[at指定人]: 塞入红包 - 开/抢: 打开红包 - 退回红包: 退回未开完的红包,必须在一分钟后使用 - - * 不同群组同一个节日红包用户只能开一次 - - 示例: - 塞红包 1000 - 塞红包 1000 10 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - superuser_help=""" - 节日红包 [金额] [红包数] ?[指定主题文字] ? -g [群id] [群id] ... - - * 不同群组同一个节日红包用户只能开一次 - - 示例: - 节日红包 10000 20 今日出道贺金 - 节日红包 10000 20 明日出道贺金 -g 123123123 - - """, - configs=[ - RegisterConfig( - key="DEFAULT_TIMEOUT", - value=600, - help="普通红包默认超时时间", - default_value=600, - type=int, - ), - RegisterConfig( - key="DEFAULT_INTERVAL", - value=60, - help="用户发送普通红包最小间隔时间", - default_value=60, - type=int, - ), - RegisterConfig( - key="RANK_NUM", - value=10, - help="结算排行显示前N位", - default_value=10, - type=int, - ), - ], - limits=[PluginCdBlock(result="急什么急什么,待会再发!")], - ).dict(), -) - - -# def rule(session: EventSession) -> bool: -# if gid := session.id3 or session.id2: -# if group_red_bag := RedBagManager.get_group_data(gid): -# return group_red_bag.check_open(gid) -# return False - - -# async def rule_group(session: EventSession): -# return rule(session) and ensure_group(session) - - -_red_bag_matcher = on_alconna( - Alconna("塞红包", Args["amount", int]["num", int, 5]["user?", At]), - aliases={"金币红包"}, - priority=5, - block=True, - rule=ensure_group, -) - -_open_matcher = on_alconna( - Alconna("开"), - aliases={"抢", "开红包", "抢红包"}, - priority=5, - block=True, - rule=ensure_group, -) - -_return_matcher = on_alconna( - Alconna("退回红包"), aliases={"退还红包"}, priority=5, block=True, rule=ensure_group -) - -_festive_matcher = on_alconna( - Alconna( - "节日红包", - Args["amount", int]["num", int]["text?", str], - Option("-g|--group", Args["groups", str] / "\n", help_text="指定群"), - ), - priority=1, - block=True, - permission=SUPERUSER, - rule=to_me(), -) - - -@_red_bag_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, - amount: int, - num: int, - user: Match[At], - default_interval: int = GetConfig(config="DEFAULT_INTERVAL"), - user_name: str = UserName(), -): - at_user = None - if user.available: - at_user = user.result.target - # group_id = session.id3 or session.id2 - group_id = session.id2 - """以频道id为键""" - user_id = session.id1 - if not user_id: - await MessageUtils.build_message("用户id为空").finish() - if not group_id: - await MessageUtils.build_message("群组id为空").finish() - group_red_bag = RedBagManager.get_group_data(group_id) - # 剩余过期时间 - time_remaining = group_red_bag.check_timeout(user_id) - if time_remaining != -1: - # 判断用户红包是否存在且是否过时覆盖 - if user_red_bag := group_red_bag.get_user_red_bag(user_id): - now = time.time() - if now < user_red_bag.start_time + default_interval: - await MessageUtils.build_message( - f"你的红包还没消化完捏...还剩下 {user_red_bag.num - len(user_red_bag.open_user)} 个! 请等待红包领取完毕..." - f"(或等待{time_remaining}秒红包cd)" - ).finish() - result = await RedBagManager.check_gold(user_id, amount, session.platform) - if result: - await MessageUtils.build_message(result).finish(at_sender=True) - await group_red_bag.add_red_bag( - f"{user_name}的红包", - int(amount), - 1 if at_user else num, - user_name, - user_id, - assigner=at_user, - platform=session.platform, - ) - image = await RedBagManager.random_red_bag_background( - user_id, platform=session.platform - ) - message_list: list = [f"{user_name}发起了金币红包\n金额: {amount}\n数量: {num}\n"] - if at_user: - message_list.append("指定人: ") - message_list.append(At(flag="user", target=at_user)) - message_list.append("\n") - message_list.append(image) - await MessageUtils.build_message(message_list).send() - - logger.info( - f"塞入 {num} 个红包,共 {amount} 金币", arparma.header_result, session=session - ) - - -@_open_matcher.handle() -async def _( - session: EventSession, - rank_num: int = GetConfig(config="RANK_NUM"), -): - # group_id = session.id3 or session.id2 - group_id = session.id2 - """以频道id为键""" - user_id = session.id1 - if not user_id: - await MessageUtils.build_message("用户id为空").finish() - if not group_id: - await MessageUtils.build_message("群组id为空").finish() - if group_red_bag := RedBagManager.get_group_data(group_id): - open_data, settlement_list = await group_red_bag.open(user_id, session.platform) - # send_msg = Text("没有红包给你开!") - send_msg = [] - for _, item in open_data.items(): - amount, red_bag = item - result_image = await RedBagManager.build_open_result_image( - red_bag, user_id, amount, session.platform - ) - send_msg.append(f"开启了 {red_bag.promoter} 的红包, 获取 {amount} 个金币\n") - send_msg.append(result_image) - send_msg.append("\n") - logger.info( - f"抢到了 {red_bag.promoter}({red_bag.promoter_id}) 的红包,获取了{amount}个金币", - "开红包", - session=session, - ) - send_msg = ( - MessageUtils.build_message(send_msg[:-1]) - if send_msg - else MessageUtils.build_message("没有红包给你开!") - ) - await send_msg.send(reply_to=True) - if settlement_list: - for red_bag in settlement_list: - result_image = await red_bag.build_amount_rank( - rank_num, session.platform - ) - await MessageUtils.build_message( - [f"{red_bag.name}已结算\n", result_image] - ).send() - - -@_return_matcher.handle() -async def _( - session: EventSession, - default_interval: int = GetConfig(config="DEFAULT_INTERVAL"), - rank_num: int = GetConfig(config="RANK_NUM"), -): - group_id = session.id3 or session.id2 - user_id = session.id1 - if not user_id: - await MessageUtils.build_message("用户id为空").finish() - if not group_id: - await MessageUtils.build_message("群组id为空").finish() - if group_red_bag := RedBagManager.get_group_data(group_id): - if user_red_bag := group_red_bag.get_user_red_bag(user_id): - now = time.time() - if now - user_red_bag.start_time < default_interval: - await MessageUtils.build_message( - f"你的红包还没有过时, 在 {int(default_interval - now + user_red_bag.start_time)} " - f"秒后可以退回..." - ).finish(reply_to=True) - user_red_bag = group_red_bag.get_user_red_bag(user_id) - if user_red_bag and ( - data := await group_red_bag.settlement(user_id, session.platform) - ): - image_result = await user_red_bag.build_amount_rank( - rank_num, session.platform - ) - logger.info(f"退回了红包 {data[0]} 金币", "红包退回", session=session) - await MessageUtils.build_message( - [ - f"已成功退还了 " f"{data[0]} 金币\n", - image_result, - ] - ).finish(reply_to=True) - await MessageUtils.build_message("目前没有红包可以退回...").finish(reply_to=True) - - -@_festive_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - amount: int, - num: int, - text: Match[str], - groups: Match[str], -): - greetings = "恭喜发财 大吉大利" - if text.available: - greetings = text.result - gl = [] - if groups.available: - gl = groups.result.strip().split() - else: - g_l, platform = await PlatformUtils.get_group_list(bot) - gl = [g.channel_id or g.group_id for g in g_l] - _uuid = str(uuid.uuid1()) - FestiveRedBagManage.add(_uuid) - _suc_cnt = 0 - for g in gl: - if target := PlatformUtils.get_target(bot, group_id=g): - group_red_bag = RedBagManager.get_group_data(g) - if festive_red_bag := group_red_bag.get_festive_red_bag(): - group_red_bag.remove_festive_red_bag() - if festive_red_bag.uuid: - FestiveRedBagManage.remove(festive_red_bag.uuid) - rank_image = await festive_red_bag.build_amount_rank(10, platform) - try: - await MessageUtils.build_message( - [ - f"{BotConfig.self_nickname}的节日红包过时了,一共开启了 " - f"{len(festive_red_bag.open_user)}" - f" 个红包,共 {sum(festive_red_bag.open_user.values())} 金币\n", - rank_image, - ] - ).send(target=target, bot=bot) - except ActionFailed: - pass - try: - scheduler.remove_job(f"{FESTIVE_KEY}_{g}") - await RedBagManager.end_red_bag( - g, is_festive=True, platform=session.platform - ) - except JobLookupError: - pass - await group_red_bag.add_red_bag( - f"{BotConfig.self_nickname}的红包", - amount, - num, - BotConfig.self_nickname, - FESTIVE_KEY, - _uuid, - platform=session.platform, - ) - scheduler.add_job( - RedBagManager._auto_end_festive_red_bag, - "date", - run_date=(datetime.now() + timedelta(hours=24)).replace(microsecond=0), - id=f"{FESTIVE_KEY}_{g}", - args=[bot, g, session.platform], - ) - try: - image_result = await RedBagManager.random_red_bag_background( - bot.self_id, greetings, session.platform - ) - await MessageUtils.build_message( - [ - f"{BotConfig.self_nickname}发起了节日金币红包\n金额: {amount}\n数量: {num}\n", - image_result, - ] - ).send(target=target, bot=bot) - _suc_cnt += 1 - logger.debug("节日红包图片信息发送成功...", "节日红包", group_id=g) - except ActionFailed: - logger.warning(f"节日红包图片信息发送失败...", "节日红包", group_id=g) - if gl: - await MessageUtils.build_message( - f"节日红包发送成功,累计成功发送 {_suc_cnt} 个群组!" - ).send() diff --git a/zhenxun/plugins/gold_redbag/config.py b/zhenxun/plugins/gold_redbag/config.py deleted file mode 100644 index da8c0d396..000000000 --- a/zhenxun/plugins/gold_redbag/config.py +++ /dev/null @@ -1,372 +0,0 @@ -import random -import time -from io import BytesIO -from typing import Dict - -from pydantic import BaseModel - -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.models.user_console import UserConsole -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.utils import get_user_avatar - -from .model import RedbagUser - -FESTIVE_KEY = "FESTIVE" -"""节日红包KEY""" - - -class FestiveRedBagManage: - - _data: Dict[str, list[str]] = {} - - @classmethod - def add(cls, uuid: str): - cls._data[uuid] = [] - - @classmethod - def open(cls, uuid: str, uid: str): - if uuid in cls._data and uid not in cls._data[uuid]: - cls._data[uuid].append(uid) - - @classmethod - def remove(cls, uuid: str): - if uuid in cls._data: - del cls._data[uuid] - - @classmethod - def check(cls, uuid: str, uid: str): - if uuid in cls._data: - return uid not in cls._data[uuid] - return False - - -class RedBag(BaseModel): - """ - 红包 - """ - - group_id: str - """所属群聊""" - name: str - """红包名称""" - amount: int - """总金币""" - num: int - """红包数量""" - promoter: str - """发起人昵称""" - promoter_id: str - """发起人id""" - is_festival: bool - """是否为节日红包""" - timeout: int - """过期时间""" - assigner: str | None = None - """指定人id""" - start_time: float - """红包发起时间""" - open_user: Dict[str, int] = {} - """开启用户""" - red_bag_list: list[int] - """红包金额列表""" - uuid: str | None - """uuid""" - - async def build_amount_rank(self, num: int, platform: str) -> BuildImage: - """生成结算红包图片 - - 参数: - num: 查看的排名数量. - platform: 平台. - - 返回: - BuildImage: 结算红包图片 - """ - user_image_list = [] - if self.open_user: - sort_data = sorted( - self.open_user.items(), key=lambda item: item[1], reverse=True - ) - num = num if num < len(self.open_user) else len(self.open_user) - user_id_list = [sort_data[i][0] for i in range(num)] - group_user_list = await GroupInfoUser.filter( - group_id=self.group_id, user_id__in=user_id_list - ).all() - for i in range(num): - user_background = BuildImage(600, 100, font_size=30) - user_id, amount = sort_data[i] - user_ava_bytes = await PlatformUtils.get_user_avatar(user_id, platform) - user_ava = None - if user_ava_bytes: - user_ava = BuildImage(80, 80, background=BytesIO(user_ava_bytes)) - else: - user_ava = BuildImage(80, 80) - await user_ava.circle_corner(10) - await user_background.paste(user_ava, (130, 10)) - no_image = BuildImage(100, 100, font_size=65, font="CJGaoDeGuo.otf") - await no_image.text((0, 0), f"{i+1}", center_type="center") - await no_image.line((99, 10, 99, 90), "#b9b9b9") - await user_background.paste(no_image) - name = [ - user.user_name - for user in group_user_list - if user_id == user.user_id - ] - await user_background.text((225, 15), name[0] if name else "") - amount_image = await BuildImage.build_text_image( - f"{amount} 元", size=30, font_color="#cdac72" - ) - await user_background.paste( - amount_image, (user_background.width - amount_image.width - 20, 50) - ) - await user_background.line((225, 99, 590, 99), "#b9b9b9") - user_image_list.append(user_background) - background = BuildImage(600, 150 + len(user_image_list) * 100) - top = BuildImage(600, 100, color="#f55545", font_size=30) - promoter_ava_bytes = await PlatformUtils.get_user_avatar( - self.promoter_id, platform - ) - promoter_ava = None - if promoter_ava_bytes: - promoter_ava = BuildImage(60, 60, background=BytesIO(promoter_ava_bytes)) - else: - promoter_ava = BuildImage(60, 60) - await promoter_ava.circle() - await top.paste(promoter_ava, (10, 0), "height") - await top.text((80, 33), self.name, (255, 255, 255)) - right_text = BuildImage(150, 100, color="#f55545", font_size=30) - await right_text.text((10, 33), "结算排行", (255, 255, 255)) - await right_text.line((4, 10, 4, 90), (255, 255, 255), 2) - await top.paste(right_text, (460, 0)) - await background.paste(top) - cur_h = 110 - for user_image in user_image_list: - await background.paste(user_image, (0, cur_h)) - cur_h += user_image.height - return background - - -class GroupRedBag: - """ - 群组红包管理 - """ - - def __init__(self, group_id: str): - self.group_id = group_id - self._data: Dict[str, RedBag] = {} - """红包列表""" - - def remove_festive_red_bag(self): - """删除节日红包""" - _key = None - for k, red_bag in self._data.items(): - if red_bag.is_festival: - _key = k - break - if _key: - del self._data[_key] - - def get_festive_red_bag(self) -> RedBag | None: - """获取节日红包 - - 返回: - RedBag | None: 节日红包 - """ - for _, red_bag in self._data.items(): - if red_bag.is_festival: - return red_bag - return None - - def get_user_red_bag(self, user_id: str) -> RedBag | None: - """获取用户塞红包数据 - - 参数: - user_id: 用户id - - 返回: - RedBag | None: RedBag - """ - return self._data.get(str(user_id)) - - def check_open(self, user_id: str) -> bool: - """检查是否有可开启的红包 - - 参数: - user_id: 用户id - - 返回: - bool: 是否有可开启的红包 - """ - user_id = str(user_id) - for _, red_bag in self._data.items(): - if red_bag.assigner: - if red_bag.assigner == user_id: - return True - else: - if user_id not in red_bag.open_user: - return True - return False - - def check_timeout(self, user_id: str) -> int: - """判断用户红包是否过期 - - 参数: - user_id: 用户id - - 返回: - int: 距离过期时间 - """ - if user_id in self._data: - reg_bag = self._data[user_id] - now = time.time() - if now < reg_bag.timeout + reg_bag.start_time: - return int(reg_bag.timeout + reg_bag.start_time - now) - return -1 - - async def open( - self, user_id: str, platform: str | None = None - ) -> tuple[Dict[str, tuple[int, RedBag]], list[RedBag]]: - """开启红包 - - 参数: - user_id: 用户id - platform: 所属平台 - - 返回: - Dict[str, tuple[int, RedBag]]: 键为发起者id, 值为开启金额以及对应RedBag - list[RedBag]: 开完的红包 - """ - open_data = {} - settlement_list: list[RedBag] = [] - for _, red_bag in self._data.items(): - if red_bag.num > len(red_bag.open_user): - if red_bag.is_festival and red_bag.uuid: - if not FestiveRedBagManage.check(red_bag.uuid, user_id): - continue - FestiveRedBagManage.open(red_bag.uuid, user_id) - is_open = False - if red_bag.assigner: - is_open = red_bag.assigner == user_id - else: - is_open = user_id not in red_bag.open_user - if is_open: - random_amount = red_bag.red_bag_list.pop() - await RedbagUser.add_redbag_data( - user_id, self.group_id, "get", random_amount - ) - await UserConsole.add_gold( - user_id, random_amount, "gold_redbag", platform - ) - red_bag.open_user[user_id] = random_amount - open_data[red_bag.promoter_id] = (random_amount, red_bag) - if red_bag.num == len(red_bag.open_user): - # 红包开完,结算 - settlement_list.append(red_bag) - if settlement_list: - for uid in [red_bag.promoter_id for red_bag in settlement_list]: - if uid in self._data: - del self._data[uid] - return open_data, settlement_list - - def festive_red_bag_expire(self) -> RedBag | None: - """节日红包过期 - - 返回: - RedBag | None: 过期的节日红包 - """ - if FESTIVE_KEY in self._data: - red_bag = self._data[FESTIVE_KEY] - del self._data[FESTIVE_KEY] - return red_bag - return None - - async def settlement( - self, user_id: str, platform: str | None = None - ) -> tuple[int | None, RedBag | None]: - """红包退回 - - 参数: - user_id: 用户id, 指定id时结算指定用户红包. - platform: 用户平台 - - 返回: - tuple[int | None, RedBag | None]: 退回金币, 红包 - """ - if red_bag := self._data.get(user_id): - del self._data[user_id] - if red_bag.is_festival and red_bag.uuid: - FestiveRedBagManage.remove(red_bag.uuid) - if red_bag.red_bag_list: - """退还剩余金币""" - if amount := sum(red_bag.red_bag_list): - await UserConsole.add_gold(user_id, amount, "gold_redbag", platform) - return amount, red_bag - return None, None - - async def add_red_bag( - self, - name: str, - amount: int, - num: int, - promoter: str, - promoter_id: str, - festival_uuid: str | None = None, - timeout: int = 60, - assigner: str | None = None, - platform: str | None = None, - ): - """添加红包 - - 参数: - name: 红包名称 - amount: 金币数量 - num: 红包数量 - promoter: 发起人昵称 - promoter_id: 发起人id - festival_uuid: 节日红包uuid. - timeout: 超时时间. - assigner: 指定人. - platform: 用户平台. - """ - user = await UserConsole.get_user(promoter_id, platform) - if not festival_uuid and (amount < 1 or user.gold < amount): - raise ValueError("红包金币不足或用户金币不足") - red_bag_list = self._random_red_bag(amount, num) - if not festival_uuid: - user.gold -= amount - await RedbagUser.add_redbag_data(promoter_id, self.group_id, "send", amount) - await user.save(update_fields=["gold"]) - self._data[promoter_id] = RedBag( - group_id=self.group_id, - name=name, - amount=amount, - num=num, - promoter=promoter, - promoter_id=promoter_id, - is_festival=bool(festival_uuid), - timeout=timeout, - start_time=time.time(), - assigner=assigner, - red_bag_list=red_bag_list, - uuid=festival_uuid, - ) - - def _random_red_bag(self, amount: int, num: int) -> list[int]: - """初始化红包金币 - - 参数: - amount: 金币数量 - num: 红包数量 - - 返回: - list[int]: 红包列表 - """ - red_bag_list = [] - for _ in range(num - 1): - tmp = int(amount / random.choice(range(3, num + 3))) - red_bag_list.append(tmp) - amount -= tmp - red_bag_list.append(amount) - return red_bag_list diff --git a/zhenxun/plugins/gold_redbag/data_source.py b/zhenxun/plugins/gold_redbag/data_source.py deleted file mode 100644 index ecc7dd380..000000000 --- a/zhenxun/plugins/gold_redbag/data_source.py +++ /dev/null @@ -1,234 +0,0 @@ -import asyncio -import os -import random -from io import BytesIO -from typing import Dict - -from nonebot.adapters import Bot -from nonebot.exception import ActionFailed -from nonebot_plugin_alconna import UniMessage - -from zhenxun.configs.config import BotConfig, Config -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.models.user_console import UserConsole -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from .config import FestiveRedBagManage, GroupRedBag, RedBag - - -class RedBagManager: - - _data: Dict[str, GroupRedBag] = {} - - @classmethod - def get_group_data(cls, group_id: str) -> GroupRedBag: - """获取群组红包数据 - - 参数: - group_id: 群组id - - 返回: - GroupRedBag | None: GroupRedBag - """ - if group_id not in cls._data: - cls._data[group_id] = GroupRedBag(group_id) - return cls._data[group_id] - - @classmethod - async def _auto_end_festive_red_bag(cls, bot: Bot, group_id: str, platform: str): - """自动结算节日红包 - - 参数: - bot: Bot - group_id: 群组id - platform: 平台 - """ - if target := PlatformUtils.get_target(bot, group_id=group_id): - rank_num = Config.get_config("gold_redbag", "RANK_NUM") or 10 - group_red_bag = cls.get_group_data(group_id) - red_bag = group_red_bag.get_festive_red_bag() - if not red_bag: - return - rank_image = await red_bag.build_amount_rank(rank_num, platform) - if red_bag.is_festival and red_bag.uuid: - FestiveRedBagManage.remove(red_bag.uuid) - await asyncio.sleep(random.randint(1, 5)) - try: - await MessageUtils.build_message( - [ - f"{BotConfig.self_nickname}的节日红包过时了,一共开启了 " - f"{len(red_bag.open_user)}" - f" 个红包,共 {sum(red_bag.open_user.values())} 金币\n", - rank_image, - ] - ).send(target=target, bot=bot) - except ActionFailed: - pass - - @classmethod - async def end_red_bag( - cls, - group_id: str, - user_id: str | None = None, - is_festive: bool = False, - platform: str = "", - ) -> UniMessage | None: - """结算红包 - - 参数: - group_id: 群组id或频道id - user_id: 用户id - is_festive: 是否节日红包 - platform: 用户平台 - """ - rank_num = Config.get_config("gold_redbag", "RANK_NUM") or 10 - group_red_bag = cls.get_group_data(group_id) - if not group_red_bag: - return None - if is_festive: - if festive_red_bag := group_red_bag.festive_red_bag_expire(): - rank_image = await festive_red_bag.build_amount_rank(rank_num, platform) - return MessageUtils.build_message( - [ - f"{BotConfig.self_nickname}的节日红包过时了,一共开启了 " - f"{len(festive_red_bag.open_user)}" - f" 个红包,共 {sum(festive_red_bag.open_user.values())} 金币\n", - rank_image, - ] - ) - else: - if not user_id: - return None - return_gold, red_bag = await group_red_bag.settlement(user_id, platform) - if red_bag: - rank_image = await red_bag.build_amount_rank(rank_num, platform) - return MessageUtils.build_message( - [ - f"已成功退还了 " f"{return_gold} 金币\n", - rank_image.pic2bytes(), - ] - ) - - @classmethod - async def check_gold(cls, user_id: str, amount: int, platform: str) -> str | None: - """检查金币数量是否合法 - - 参数: - user_id: 用户id - amount: 金币数量 - platform: 所属平台 - - 返回: - tuple[bool, str]: 是否合法以及提示语 - """ - user = await UserConsole.get_user(user_id, platform) - if amount < 1: - return "小气鬼,要别人倒贴金币给你嘛!" - if user.gold < amount: - return "没有金币的话请不要发红包..." - return None - - @classmethod - async def random_red_bag_background( - cls, user_id: str, msg: str = "恭喜发财 大吉大利", platform: str = "" - ) -> BuildImage: - """构造发送红包图片 - - 参数: - user_id: 用户id - msg: 红包消息. - platform: 平台. - - 异常: - ValueError: 图片背景列表为空 - - 返回: - BuildImage: 构造后的图片 - """ - background_list = os.listdir(f"{IMAGE_PATH}/prts/redbag_2") - if not background_list: - raise ValueError("prts/redbag_1 背景图列表为空...") - random_redbag = random.choice(background_list) - redbag = BuildImage( - 0, - 0, - font_size=38, - background=IMAGE_PATH / "prts" / "redbag_2" / random_redbag, - ) - ava_byte = await PlatformUtils.get_user_avatar(user_id, platform) - ava = None - if ava_byte: - ava = BuildImage(65, 65, background=BytesIO(ava_byte)) - else: - ava = BuildImage(65, 65, color=(0, 0, 0)) - await ava.circle() - await redbag.text( - (int((redbag.size[0] - redbag.getsize(msg)[0]) / 2), 210), - msg, - (240, 218, 164), - ) - await redbag.paste(ava, (int((redbag.size[0] - ava.size[0]) / 2), 130)) - return redbag - - @classmethod - async def build_open_result_image( - cls, red_bag: RedBag, user_id: str, amount: int, platform: str - ) -> BuildImage: - """构造红包开启图片 - - 参数: - red_bag: RedBag - user_id: 开启红包用户id - amount: 开启红包获取的金额 - platform: 平台 - - 异常: - ValueError: 图片背景列表为空 - - 返回: - BuildImage: 构造后的图片 - """ - background_list = os.listdir(f"{IMAGE_PATH}/prts/redbag_1") - if not background_list: - raise ValueError("prts/redbag_1 背景图列表为空...") - random_redbag = random.choice(background_list) - head = BuildImage( - 1000, - 980, - font_size=30, - background=IMAGE_PATH / "prts" / "redbag_1" / random_redbag, - ) - size = BuildImage.get_text_size(red_bag.name, font_size=50) - ava_bk = BuildImage(100 + size[0], 66, (255, 255, 255, 0), font_size=50) - - ava_byte = await PlatformUtils.get_user_avatar(user_id, platform) - ava = None - if ava_byte: - ava = BuildImage(66, 66, background=BytesIO(ava_byte)) - else: - ava = BuildImage(66, 66, color=(0, 0, 0)) - await ava_bk.paste(ava) - await ava_bk.text((100, 7), red_bag.name) - ava_bk_w, ava_bk_h = ava_bk.size - await head.paste(ava_bk, (int((1000 - ava_bk_w) / 2), 300)) - size = BuildImage.get_text_size(str(amount), font_size=150) - amount_image = BuildImage(size[0], size[1], (255, 255, 255, 0), font_size=150) - await amount_image.text((0, 0), str(amount), fill=(209, 171, 108)) - # 金币中文 - await head.paste(amount_image, (int((1000 - size[0]) / 2) - 50, 460)) - await head.text( - (int((1000 - size[0]) / 2 + size[0]) - 50, 500 + size[1] - 70), - "金币", - fill=(209, 171, 108), - ) - # 剩余数量和金额 - text = ( - f"已领取" - f"{red_bag.num - len(red_bag.open_user)}" - f"/{red_bag.num}个," - f"共{sum(red_bag.open_user.values())}/{red_bag.amount}金币" - ) - await head.text((350, 900), text, (198, 198, 198)) - return head diff --git a/zhenxun/plugins/gold_redbag/model.py b/zhenxun/plugins/gold_redbag/model.py deleted file mode 100644 index a8e9359a4..000000000 --- a/zhenxun/plugins/gold_redbag/model.py +++ /dev/null @@ -1,63 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class RedbagUser(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊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_id", "group_id") - - @classmethod - async def add_redbag_data( - cls, user_id: str, group_id: str, i_type: str, money: int - ): - """添加收发红包数据 - - 参数: - user_id: 用户id - group_id: 群号 - i_type: 收或发 - money: 金钱数量 - """ - - user, _ = await cls.get_or_create(user_id=user_id, 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: - 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", - ] - ) - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE redbag_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE redbag_users ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE redbag_users ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/group_welcome_msg.py b/zhenxun/plugins/group_welcome_msg.py deleted file mode 100644 index 7148e8e9b..000000000 --- a/zhenxun/plugins/group_welcome_msg.py +++ /dev/null @@ -1,62 +0,0 @@ -import re - -import ujson as json -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import DATA_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import ensure_group - -__plugin_meta__ = PluginMetadata( - name="查看群欢迎消息", - description="查看群欢迎消息", - usage=""" - usage: - 查看群欢迎消息 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - ).dict(), -) - -_matcher = on_alconna(Alconna("群欢迎消息"), rule=ensure_group, priority=5, block=True) - - -BASE_PATH = DATA_PATH / "welcome_message" - - -@_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, -): - path = BASE_PATH / f"{session.platform or session.bot_type}" / f"{session.id2}" - if session.id3: - path = ( - BASE_PATH - / f"{session.platform or session.bot_type}" - / f"{session.id3}" - / f"{session.id2}" - ) - file = path / "text.json" - if not file.exists(): - await MessageUtils.build_message("未设置群欢迎消息...").finish(reply_to=True) - message = json.load(open(file, encoding="utf8"))["message"] - message_split = re.split(r"\[image:\d+\]", message) - if len(message_split) == 1: - await MessageUtils.build_message(message_split[0]).finish(reply_to=True) - idx = 0 - data_list = [] - for msg in message_split[:-1]: - data_list.append(msg) - data_list.append(path / f"{idx}.png") - idx += 1 - data_list.append(message_split[-1]) - await MessageUtils.build_message(data_list).send(reply_to=True) - logger.info("查看群欢迎消息", arparma.header_result, session=session) diff --git a/zhenxun/plugins/image_management/__init__.py b/zhenxun/plugins/image_management/__init__.py deleted file mode 100644 index 8f24386d8..000000000 --- a/zhenxun/plugins/image_management/__init__.py +++ /dev/null @@ -1,69 +0,0 @@ -from pathlib import Path -from typing import List, Tuple - -import nonebot - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import IMAGE_PATH - -Config.add_plugin_config( - "image_management", - "IMAGE_DIR_LIST", - ["美图", "萝莉", "壁纸"], - help="公开图库列表,可自定义添加 [如果含有send_setu插件,请不要添加色图库]", - default_value=[], - type=List[str], -) - -Config.add_plugin_config( - "image_management", - "WITHDRAW_IMAGE_MESSAGE", - (0, 1), - help="自动撤回,参1:延迟撤回发送图库图片的时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)", - default_value=(0, 1), - type=Tuple[int, int], -) - -Config.add_plugin_config( - "image_management:delete_image", - "DELETE_IMAGE_LEVEL", - 7, - help="删除图库图片需要的管理员等级", - default_value=7, - type=int, -) - -Config.add_plugin_config( - "image_management:move_image", - "MOVE_IMAGE_LEVEL", - 7, - help="移动图库图片需要的管理员等级", - default_value=7, - type=int, -) - -Config.add_plugin_config( - "image_management:upload_image", - "UPLOAD_IMAGE_LEVEL", - 6, - help="上传图库图片需要的管理员等级", - default_value=6, - type=int, -) - -Config.add_plugin_config( - "image_management", - "SHOW_ID", - True, - help="是否消息显示图片下标id", - default_value=True, - type=bool, -) - -Config.set_name("image_management", "图库操作") - - -(IMAGE_PATH / "image_management").mkdir(parents=True, exist_ok=True) - - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/image_management/_config.py b/zhenxun/plugins/image_management/_config.py deleted file mode 100644 index d5e01f587..000000000 --- a/zhenxun/plugins/image_management/_config.py +++ /dev/null @@ -1,14 +0,0 @@ -from strenum import StrEnum - - -class ImageHandleType(StrEnum): - """ - 图片处理类型 - """ - - UPLOAD = "UPLOAD" - """上传""" - DELETE = "DELETE" - """删除""" - MOVE = "MOVE" - """移动""" diff --git a/zhenxun/plugins/image_management/_data_source.py b/zhenxun/plugins/image_management/_data_source.py deleted file mode 100644 index bc26b74f7..000000000 --- a/zhenxun/plugins/image_management/_data_source.py +++ /dev/null @@ -1,196 +0,0 @@ -import os -import random -from pathlib import Path - -import aiofiles - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.utils import cn2py - -from .image_management_log import ImageHandleType, ImageManagementLog - -BASE_PATH = IMAGE_PATH / "image_management" - - -class ImageManagementManage: - - @classmethod - async def random_image(cls, name: str, file_id: int | None = None) -> Path | None: - """随机图片 - - 参数: - name: 图库名称 - file_id: 图片id. - - 返回: - Path | None: 图片路径 - """ - path = BASE_PATH / name - file_name = f"{file_id}.jpg" - if file_id is None: - if file_list := os.listdir(path): - file_name = random.choice(file_list) - _file = path / file_name - if not _file.exists(): - return None - return _file - - @classmethod - async def upload_image( - cls, - image_data: bytes | str, - name: str, - user_id: str, - platform: str | None = None, - ) -> str | None: - """上传图片 - - 参数: - image_data: 图片bytes - name: 图库名称 - user_id: 用户id - platform: 所属平台 - - 返回: - str | None: 文件名称 - """ - path = BASE_PATH / cn2py(name) - path.mkdir(exist_ok=True, parents=True) - _file_name = 0 - if file_list := os.listdir(path): - file_list.sort() - _file_name = int(file_list[-1].split(".")[0]) + 1 - _file_path = path / f"{_file_name}.jpg" - try: - await ImageManagementLog.create( - user_id=user_id, - path=_file_path, - handle_type=ImageHandleType.UPLOAD, - platform=platform, - ) - if isinstance(image_data, str): - await AsyncHttpx.download_file(image_data, _file_path) - else: - async with aiofiles.open(_file_path, "wb") as f: - await f.write(image_data) - logger.info( - f"上传图片至 {name}, 路径: {_file_path}", - "上传图片", - session=user_id, - ) - return f"{_file_name}.jpg" - except Exception as e: - logger.error("上传图片错误", "上传图片", e=e) - return None - - @classmethod - async def delete_image( - cls, name: str, file_id: int, user_id: str, platform: str | None = None - ) -> bool: - """删除图片 - - 参数: - name: 图库名称 - file_id: 图片id - user_id: 用户id - platform: 所属平台. - - 返回: - bool: 是否删除成功 - """ - path = BASE_PATH / cn2py(name) - if not path.exists(): - return False - _file_path = path / f"{file_id}.jpg" - if not _file_path.exists(): - return False - try: - await ImageManagementLog.create( - user_id=user_id, - path=_file_path, - handle_type=ImageHandleType.DELETE, - platform=platform, - ) - _file_path.unlink() - logger.info( - f"图库: {name}, 删除图片路径: {_file_path}", "删除图片", session=user_id - ) - if file_list := os.listdir(path): - file_list.sort() - _file_name = file_list[-1].split(".")[0] - _move_file = path / f"{_file_name}.jpg" - _move_file.rename(_file_path) - logger.info( - f"图库: {name}, 移动图片名称: {_file_name}.jpg -> {file_id}.jpg", - "删除图片", - session=user_id, - ) - except Exception as e: - logger.error("删除图片错误", "删除图片", e=e) - return False - return True - - @classmethod - async def move_image( - cls, - a_name: str, - b_name: str, - file_id: int, - user_id: str, - platform: str | None = None, - ) -> str | None: - """移动图片 - - 参数: - a_name: 源图库 - b_name: 模板图库 - file_id: 图片id - user_id: 用户id - platform: 所属平台. - - 返回: - bool: 是否移动成功 - """ - source_path = BASE_PATH / cn2py(a_name) - if not source_path.exists(): - return None - destination_path = BASE_PATH / cn2py(b_name) - destination_path.mkdir(exist_ok=True, parents=True) - source_file = source_path / f"{file_id}.jpg" - if not source_file.exists(): - return None - _destination_name = 0 - if file_list := os.listdir(destination_path): - file_list.sort() - _destination_name = int(file_list[-1].split(".")[0]) + 1 - destination_file = destination_path / f"{_destination_name}.jpg" - try: - await ImageManagementLog.create( - user_id=user_id, - path=source_file, - move=destination_file, - handle_type=ImageHandleType.MOVE, - platform=platform, - ) - source_file.rename(destination_file) - logger.info( - f"图库: {a_name} -> {b_name}, 移动图片路径: {source_file} -> {destination_file}", - "移动图片", - session=user_id, - ) - if file_list := os.listdir(source_path): - file_list.sort() - _file_name = file_list[-1].split(".")[0] - _move_file = source_path / f"{_file_name}.jpg" - _move_file.rename(source_file) - logger.info( - f"图库: {a_name}, 移动图片名称: {_file_name}.jpg -> {file_id}.jpg", - "移动图片", - session=user_id, - ) - except Exception as e: - logger.error("移动图片错误", "移动图片", e=e) - return None - return f"{source_file} -> {destination_file}" diff --git a/zhenxun/plugins/image_management/delete_image.py b/zhenxun/plugins/image_management/delete_image.py deleted file mode 100644 index 6cabb8e9d..000000000 --- a/zhenxun/plugins/image_management/delete_image.py +++ /dev/null @@ -1,107 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot.typing import T_State -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, UniMessage, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from ._data_source import ImageManagementManage - -base_config = Config.get("image_management") - -__plugin_meta__ = PluginMetadata( - name="删除图片", - description="不好看的图片删掉删掉!", - usage=""" - 指令: - 删除图片 [图库] [id] - 查看图库 - 示例:删除图片 美图 666 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.ADMIN, - admin_level=base_config.get("DELETE_IMAGE_LEVEL"), - ).dict(), -) - - -_matcher = on_alconna( - Alconna("删除图片", Args["name?", str]["index?", str]), - rule=to_me(), - priority=5, - block=True, -) - - -@_matcher.handle() -async def _( - name: Match[str], - index: Match[str], - state: T_State, -): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - _text = "" - for i, dir in enumerate(image_dir_list): - _text += f"{i}. {dir}\n" - state["dir_list"] = _text[:-1] - if name.available: - _matcher.set_path_arg("name", name.result) - if index.available: - _matcher.set_path_arg("index", index.result) - - -@_matcher.got_path( - "name", - prompt=UniMessage.template( - "请输入要删除的目标图库(id 或 名称)【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -async def _(name: str): - if name in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if name.isdigit(): - index = int(name) - if index <= len(image_dir_list) - 1: - name = image_dir_list[index] - if name not in image_dir_list: - await _matcher.reject_path("name", "此目录不正确,请重新输入目录!") - _matcher.set_path_arg("name", name) - - -@_matcher.got_path("index", "请输入要删除的图片id?【发送'取消', '算了'来取消操作】") -async def _( - session: EventSession, - arparma: Arparma, - index: str, -): - if index in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - if not index.isdigit(): - await _matcher.reject_path("index", "图片id需要输入数字...") - name = _matcher.get_path_arg("name", None) - if not name: - await MessageUtils.build_message("图库名称为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if file_name := await ImageManagementManage.delete_image( - name, int(index), session.id1, session.platform - ): - logger.info( - f"删除图片成功 图库: {name} --- 名称: {file_name}", - arparma.header_result, - session=session, - ) - await MessageUtils.build_message( - f"删除图片成功!\n图库: {name}\n名称: {index}.jpg" - ).finish() - await MessageUtils.build_message("图片删除失败...").finish() diff --git a/zhenxun/plugins/image_management/image_management_log.py b/zhenxun/plugins/image_management/image_management_log.py deleted file mode 100644 index 756e58f25..000000000 --- a/zhenxun/plugins/image_management/image_management_log.py +++ /dev/null @@ -1,27 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - -from ._config import ImageHandleType - - -class ImageManagementLog(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255, description="用户id") - """用户id""" - path = fields.TextField(description="图片路径") - """图片路径""" - move = fields.TextField(null=True, description="移动路径") - """移动路径""" - handle_type = fields.CharEnumField(ImageHandleType, description="操作类型") - """操作类型""" - create_time = fields.DatetimeField(auto_now_add=True, description="创建时间") - """创建时间""" - platform = fields.CharField(255, null=True, description="平台") - """平台""" - - class Meta: - table = "image_management_log" - table_description = "画廊操作记录" diff --git a/zhenxun/plugins/image_management/move_image.py b/zhenxun/plugins/image_management/move_image.py deleted file mode 100644 index 6c1ec5e95..000000000 --- a/zhenxun/plugins/image_management/move_image.py +++ /dev/null @@ -1,137 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot.typing import T_State -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, UniMessage, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from ._data_source import ImageManagementManage - -base_config = Config.get("image_management") - -__plugin_meta__ = PluginMetadata( - name="移动图片", - description="图库间的图片移动操作", - usage=""" - 指令: - 移动图片 [源图库] [目标图库] [id] - 查看图库 - 示例:移动图片 萝莉 美图 234 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.ADMIN, - admin_level=base_config.get("MOVE_IMAGE_LEVEL"), - ).dict(), -) - - -_matcher = on_alconna( - Alconna("移动图片", Args["source?", str]["destination?", str]["index?", str]), - rule=to_me(), - priority=5, - block=True, -) - - -@_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - source: Match[str], - destination: Match[str], - index: Match[str], - state: T_State, -): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - _text = "" - for i, dir in enumerate(image_dir_list): - _text += f"{i}. {dir}\n" - state["dir_list"] = _text[:-1] - if source.available: - _matcher.set_path_arg("source", source.result) - if destination.available: - _matcher.set_path_arg("destination", destination.result) - if index.available: - _matcher.set_path_arg("index", index.result) - - -@_matcher.got_path( - "source", - prompt=UniMessage.template( - "要从哪个图库移出?【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -async def _(source: str): - if source in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if source.isdigit(): - index = int(source) - if index <= len(image_dir_list) - 1: - name = image_dir_list[index] - if name not in image_dir_list: - await _matcher.reject_path("source", "此目录不正确,请重新输入目录!") - _matcher.set_path_arg("source", name) - - -@_matcher.got_path( - "destination", - prompt=UniMessage.template( - "要移动到哪个图库?【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -async def _(destination: str): - if destination in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - image_dir_list = base_config.get("IMAGE_DIR_LIST") - name = None - if destination.isdigit(): - index = int(destination) - if index <= len(image_dir_list) - 1: - name = image_dir_list[index] - if name not in image_dir_list: - await _matcher.reject_path("destination", "此目录不正确,请重新输入目录!") - _matcher.set_path_arg("destination", name) - - -@_matcher.got_path("index", "要移动的图片id是?【发送'取消', '算了'来取消操作】") -async def _( - session: EventSession, - arparma: Arparma, - index: str, -): - if index in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - if not index.isdigit(): - await _matcher.reject_path("index", "图片id需要输入数字...") - source = _matcher.get_path_arg("source", None) - destination = _matcher.get_path_arg("destination", None) - if not source: - await MessageUtils.build_message("转出图库名称为空...").finish() - if not destination: - await MessageUtils.build_message("转入图库名称为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if file_name := await ImageManagementManage.move_image( - source, destination, int(index), session.id1, session.platform - ): - logger.info( - f"移动图片成功 图库: {source} -> {destination} --- 名称: {file_name}", - arparma.header_result, - session=session, - ) - await MessageUtils.build_message( - f"移动图片成功!\n图库: {source} -> {destination}" - ).finish() - await MessageUtils.build_message("图片删除失败...").finish() diff --git a/zhenxun/plugins/image_management/send_image.py b/zhenxun/plugins/image_management/send_image.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/zhenxun/plugins/image_management/upload_image.py b/zhenxun/plugins/image_management/upload_image.py deleted file mode 100644 index b69281a4f..000000000 --- a/zhenxun/plugins/image_management/upload_image.py +++ /dev/null @@ -1,195 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot.typing import T_State -from nonebot_plugin_alconna import Alconna, Args, Arparma -from nonebot_plugin_alconna import Image as alcImage -from nonebot_plugin_alconna import Match, UniMessage, UniMsg, image_fetch, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -from ._data_source import ImageManagementManage - -base_config = Config.get("image_management") - -__plugin_meta__ = PluginMetadata( - name="上传图片", - description="上传图片至指定图库", - usage=""" - 指令: - 查看图库 - 上传图片 [图库] [图片] - 示例:上传图片 美图 [图片] - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.ADMIN, - admin_level=base_config.get("UPLOAD_IMAGE_LEVEL"), - ).dict(), -) - - -_upload_matcher = on_alconna( - Alconna("上传图片", Args["name?", str]["img?", alcImage]), - rule=to_me(), - priority=5, - block=True, -) - -_continuous_upload_matcher = on_alconna( - Alconna("连续上传图片", Args["name?", str]), - rule=to_me(), - priority=5, - block=True, -) - -_show_matcher = on_alconna(Alconna("查看公开图库"), priority=1, block=True) - - -@_show_matcher.handle() -async def _(): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - text = "公开图库列表:\n" - for i, e in enumerate(image_dir_list): - text += f"\t{i+1}.{e}\n" - await MessageUtils.build_message(text[:-1]).send() - - -@_upload_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - name: Match[str], - img: Match[bytes], - state: T_State, -): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - _text = "" - for i, dir in enumerate(image_dir_list): - _text += f"{i}. {dir}\n" - state["dir_list"] = _text[:-1] - if name.available: - _upload_matcher.set_path_arg("name", name.result) - if img.available: - result = await AsyncHttpx.get(img.result.url) # type: ignore - _upload_matcher.set_path_arg("img", result.content) - - -@_continuous_upload_matcher.handle() -async def _(bot: Bot, state: T_State, name: Match[str]): - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if not image_dir_list: - await MessageUtils.build_message("未发现任何图库").finish() - _text = "" - for i, dir in enumerate(image_dir_list): - _text += f"{i}. {dir}\n" - state["dir_list"] = _text[:-1] - if name.available: - _upload_matcher.set_path_arg("name", name.result) - - -@_continuous_upload_matcher.got_path( - "name", - prompt=UniMessage.template( - "请选择要上传的图库(id 或 名称)【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -@_upload_matcher.got_path( - "name", - prompt=UniMessage.template( - "请选择要上传的图库(id 或 名称)【发送'取消', '算了'来取消操作】\n{dir_list}" - ), -) -async def _(name: str, state: T_State): - if name in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - image_dir_list = base_config.get("IMAGE_DIR_LIST") - if name.isdigit(): - index = int(name) - if index <= len(image_dir_list) - 1: - name = image_dir_list[index] - if name not in image_dir_list: - await _upload_matcher.reject_path("name", "此目录不正确,请重新输入目录!") - _upload_matcher.set_path_arg("name", name) - - -@_upload_matcher.got_path("img", "图呢图呢图呢图呢!GKD!", image_fetch) -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - img: bytes, -): - name = _upload_matcher.get_path_arg("name", None) - if not name: - await MessageUtils.build_message("图库名称为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if file_name := await ImageManagementManage.upload_image( - img, name, session.id1, session.platform - ): - logger.info( - f"图库: {name} --- 名称: {file_name}", - arparma.header_result, - session=session, - ) - await MessageUtils.build_message( - f"上传图片成功!\n图库: {name}\n名称: {file_name}" - ).finish() - await MessageUtils.build_message("图片上传失败...").finish() - - -@_continuous_upload_matcher.got( - "img", "图呢图呢图呢图呢!GKD!【在最后一张图片中+‘stop’为停止】" -) -async def _( - bot: Bot, - arparma: Arparma, - session: EventSession, - state: T_State, - message: UniMsg, -): - name = _continuous_upload_matcher.get_path_arg("name", None) - if not name: - await MessageUtils.build_message("图库名称为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not state.get("img_list"): - state["img_list"] = [] - msg = message.extract_plain_text().strip().replace(arparma.header_result, "", 1) - if msg in ["取消", "算了"]: - await MessageUtils.build_message("已取消操作...").finish() - if msg != "stop": - for msg in message: - if isinstance(msg, alcImage): - state["img_list"].append(msg.url) - await _continuous_upload_matcher.reject("图再来!!【发送‘stop’为停止】") - if state["img_list"]: - await MessageUtils.build_message("正在下载, 请稍后...").send() - file_list = [] - for img in state["img_list"]: - if file_name := await ImageManagementManage.upload_image( - img, name, session.id1, session.platform - ): - file_list.append(img) - logger.info( - f"图库: {name} --- 名称: {file_name}", - "上传图片", - session=session, - ) - await MessageUtils.build_message( - f"上传图片成功!共上传了{len(file_list)}张图片\n图库: {name}\n名称: {', '.join(file_list)}" - ).finish() - await MessageUtils.build_message("图片上传失败...").finish() diff --git a/zhenxun/plugins/luxun.py b/zhenxun/plugins/luxun.py deleted file mode 100644 index 1c1ab09c1..000000000 --- a/zhenxun/plugins/luxun.py +++ /dev/null @@ -1,74 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.configs.utils import BaseBlock, PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="鲁迅说", - description="鲁迅说了啥?", - usage=""" - 鲁迅说 [文本] - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - limits=[BaseBlock(result="你的鲁迅正在说,等会")], - ).dict(), -) - -_matcher = on_alconna( - Alconna("luxun", Args["content", str]), - priority=5, - block=True, -) - -_matcher.shortcut( - "鲁迅说", - command="luxun", - arguments=["{%0}"], - prefix=True, -) - - -_sign = None - - -@_matcher.handle() -async def _(content: Match[str]): - if content.available: - _matcher.set_path_arg("content", content.result) - - -@_matcher.got_path("content", prompt="你让鲁迅说点啥?") -async def _(content: str, session: EventSession, arparma: Arparma): - global _sign - if content.startswith(",") or content.startswith(","): - content = content[1:] - A = BuildImage( - font_size=37, background=f"{IMAGE_PATH}/other/luxun.jpg", font="msyh.ttf" - ) - text = "" - if len(content) > 40: - await MessageUtils.build_message("太长了,鲁迅说不完...").finish() - while A.getsize(content)[0] > A.width - 50: - n = int(len(content) / 2) - text += content[:n] + "\n" - content = content[n:] - text += content - if len(text.split("\n")) > 2: - await MessageUtils.build_message("太长了,鲁迅说不完...").finish() - await A.text( - (int((480 - A.getsize(text.split("\n")[0])[0]) / 2), 300), text, (255, 255, 255) - ) - if not _sign: - _sign = await BuildImage.build_text_image( - "--鲁迅", "msyh.ttf", 30, (255, 255, 255) - ) - await A.paste(_sign, (320, 400)) - await MessageUtils.build_message(A).send() - logger.info(f"鲁迅说: {content}", arparma.header_result, session=session) diff --git a/zhenxun/plugins/mute/__init__.py b/zhenxun/plugins/mute/__init__.py deleted file mode 100644 index eb35e275d..000000000 --- a/zhenxun/plugins/mute/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from pathlib import Path - -import nonebot - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/mute/_data_source.py b/zhenxun/plugins/mute/_data_source.py deleted file mode 100644 index 13c1cadf9..000000000 --- a/zhenxun/plugins/mute/_data_source.py +++ /dev/null @@ -1,124 +0,0 @@ -import time - -import ujson as json -from pydantic import BaseModel - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import DATA_PATH - -base_config = Config.get("mute_setting") - - -class GroupData(BaseModel): - - count: int - """次数""" - time: int - """检测时长""" - duration: int - """禁言时长""" - message_data: dict = {} - """消息存储""" - - -class MuteManage: - - file = DATA_PATH / "group_mute_data.json" - - def __init__(self) -> None: - self._group_data: dict[str, GroupData] = {} - if self.file.exists(): - _data = json.load(open(self.file)) - for gid in _data: - self._group_data[gid] = GroupData( - count=_data[gid]["count"], - time=_data[gid]["time"], - duration=_data[gid]["duration"], - ) - - def get_group_data(self, group_id: str) -> GroupData: - """获取群组数据 - - 参数: - group_id: 群组id - - 返回: - GroupData: GroupData - """ - if group_id not in self._group_data: - self._group_data[group_id] = GroupData( - count=base_config.get("MUTE_DEFAULT_COUNT", 10) or 10, - time=base_config.get("MUTE_DEFAULT_TIME", 7) or 7, - duration=base_config.get("MUTE_DEFAULT_DURATION", 10) or 10, - ) - return self._group_data[group_id] - - def reset(self, user_id: str, group_id: str): - """重置用户检查次数 - - 参数: - user_id: 用户id - group_id: 群组id - """ - if group_data := self._group_data.get(group_id): - if user_id in group_data.message_data: - group_data.message_data[user_id]["count"] = 0 - - def save_data(self): - """保存数据""" - data = {} - for gid in self._group_data: - data[gid] = { - "count": self._group_data[gid].count, - "time": self._group_data[gid].time, - "duration": self._group_data[gid].duration, - } - with open(self.file, "w") as f: - json.dump(data, f, indent=4, ensure_ascii=False) - - def add_message(self, user_id: str, group_id: str, message: str) -> int: - """添加消息 - - 参数: - user_id: 用户id - group_id: 群组id - message: 消息内容 - - 返回: - int: 禁言时长 - """ - if group_id not in self._group_data: - self._group_data[group_id] = GroupData( - count=base_config.get("MUTE_DEFAULT_COUNT"), - time=base_config.get("MUTE_DEFAULT_TIME"), - duration=base_config.get("MUTE_DEFAULT_DURATION"), - ) - group_data = self._group_data[group_id] - if group_data.duration == 0: - return 0 - message_data = group_data.message_data - if not message_data.get(user_id): - message_data[user_id] = { - "time": time.time(), - "count": 1, - "message": message, - } - else: - if message.find(message_data[user_id]["message"]) != -1: - message_data[user_id]["count"] += 1 - else: - message_data[user_id]["time"] = time.time() - message_data[user_id]["count"] = 1 - message_data[user_id]["message"] = message - if time.time() - message_data[user_id]["time"] > group_data.time: - message_data[user_id]["time"] = time.time() - message_data[user_id]["count"] = 1 - if ( - message_data[user_id]["count"] > group_data.count - and time.time() - message_data[user_id]["time"] < group_data.time - ): - return group_data.duration - return 0 - - -mute_manage = MuteManage() diff --git a/zhenxun/plugins/mute/mute_message.py b/zhenxun/plugins/mute/mute_message.py deleted file mode 100644 index 19e9326ae..000000000 --- a/zhenxun/plugins/mute/mute_message.py +++ /dev/null @@ -1,56 +0,0 @@ -from nonebot import on_message -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Image as alcImage -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import BotConfig -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.ban_console import BanConsole -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.image_utils import get_download_image_hash -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from ._data_source import mute_manage - -__plugin_meta__ = PluginMetadata( - name="刷屏监听", - description="", - usage="", - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.DEPENDANT, - ).dict(), -) - -_matcher = on_message(priority=1, block=False) - - -@_matcher.handle() -async def _(bot: Bot, session: EventSession, message: UniMsg): - group_id = session.id2 - if not session.id1 or not group_id: - return - if await BanConsole.is_ban(session.id1, group_id): - return - plain_text = message.extract_plain_text() - image_list = [m.url for m in message if isinstance(m, alcImage) and m.url] - img_hash = "" - for url in image_list: - img_hash += await get_download_image_hash(url, "_mute_") - _message = plain_text + img_hash - if duration := mute_manage.add_message(session.id1, group_id, _message): - try: - await PlatformUtils.ban_user(bot, session.id1, group_id, duration) - await MessageUtils.build_message( - f"检测到恶意刷屏,{BotConfig.self_nickname}要把你关进小黑屋!" - ).send(at_sender=True) - mute_manage.reset(session.id1, group_id) - logger.info(f"检测刷屏 被禁言 {duration} 分钟", "禁言检查", session=session) - except Exception as e: - logger.error("禁言发送错误", "禁言检测", session=session, e=e) diff --git a/zhenxun/plugins/mute/mute_setting.py b/zhenxun/plugins/mute/mute_setting.py deleted file mode 100644 index b7947ed69..000000000 --- a/zhenxun/plugins/mute/mute_setting.py +++ /dev/null @@ -1,117 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Option, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import BotConfig -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.rules import ensure_group - -from ._data_source import base_config, mute_manage - -__plugin_meta__ = PluginMetadata( - name="刷屏禁言", - description="刷屏禁言相关操作", - usage=""" - 刷屏禁言相关操作,需要 {BotConfig.self_nickname} 有群管理员权限 - 指令: - 设置刷屏: 查看当前设置 - -c [count]: 检测最大次数 - -t [time]: 规定时间内 - -d [duration]: 禁言时长 - 示例: - 设置刷屏 -c 10: 设置最大次数为10 - 设置刷屏 -t 100 -d 20: 设置规定时间和禁言时长 - 设置刷屏 -d 10: 设置禁言时长为10 - * 即 X 秒内发送同样消息 N 次,禁言 M 分钟 * - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.ADMIN, - admin_level=base_config.get("MUTE_LEVEL", 5), - configs=[ - RegisterConfig( - key="MUTE_LEVEL", - value=5, - help="更改禁言设置的管理权限", - default_value=5, - type=int, - ), - RegisterConfig( - key="MUTE_DEFAULT_COUNT", - value=10, - help="刷屏禁言默认检测次数", - default_value=10, - type=int, - ), - RegisterConfig( - key="MUTE_DEFAULT_TIME", - value=7, - help="刷屏检测默认规定时间", - default_value=7, - type=int, - ), - RegisterConfig( - key="MUTE_DEFAULT_DURATION", - value=10, - help="刷屏检测默禁言时长(分钟)", - default_value=10, - type=int, - ), - ], - ).dict(), -) - - -_setting_matcher = on_alconna( - Alconna( - "刷屏设置", - Option("-t|--time", Args["time", int], help_text="检测时长"), - Option("-c|--count", Args["count", int], help_text="检测次数"), - Option("-d|--duration", Args["duration", int], help_text="禁言时长"), - ), - rule=ensure_group, - block=True, - priority=5, -) - - -@_setting_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, - time: Match[int], - count: Match[int], - duration: Match[int], -): - group_id = session.id2 - if not session.id1 or not group_id: - return - _time = time.result if time.available else None - _count = count.result if count.available else None - _duration = duration.result if duration.available else None - group_data = mute_manage.get_group_data(group_id) - if _time is None and _count is None and _duration is None: - await MessageUtils.build_message( - f"最大次数:{group_data.count} 次\n" - f"规定时间:{group_data.time} 秒\n" - f"禁言时长:{group_data.duration:.2f} 分钟\n" - f"【在规定时间内发送相同消息超过最大次数则禁言\n当禁言时长为0时关闭此功能】" - ).finish(reply_to=True) - if _time is not None: - group_data.time = _time - if _count is not None: - group_data.count = _count - if _duration is not None: - group_data.duration = _duration - await MessageUtils.build_message("设置成功!").send(reply_to=True) - logger.info( - f"设置禁言配置 time: {_time}, count: {_count}, duration: {_duration}", - arparma.header_result, - session=session, - ) - mute_manage.save_data() diff --git a/zhenxun/plugins/nbnhhsh.py b/zhenxun/plugins/nbnhhsh.py deleted file mode 100644 index 7ab78aaad..000000000 --- a/zhenxun/plugins/nbnhhsh.py +++ /dev/null @@ -1,62 +0,0 @@ -import ujson as json -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="能不能好好说话", - description="能不能好好说话,说人话", - usage=""" - 说人话 - 指令: - nbnhhsh [文本] - 能不能好好说话 [文本] - 示例: - nbnhhsh xsx - """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1", aliases={"nbnhhsh"}).dict(), -) - -URL = "https://lab.magiconch.com/api/nbnhhsh/guess" - -_matcher = on_alconna( - Alconna("nbnhhsh", Args["text", str]), - aliases={"能不能好好说话"}, - priority=5, - block=True, -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma, text: str): - response = await AsyncHttpx.post( - URL, - data=json.dumps({"text": text}), # type: ignore - timeout=5, - headers={"content-type": "application/json"}, - ) - try: - data = response.json() - tmp = "" - result = "" - for x in data: - trans = "" - if x.get("trans"): - trans = x["trans"][0] - elif x.get("inputting"): - trans = ",".join(x["inputting"]) - tmp += f'{x["name"]} -> {trans}\n' - result += trans - logger.info( - f" 发送能不能好好说话: {text} -> {result}", - arparma.header_result, - session=session, - ) - await MessageUtils.build_message(f"{tmp}={result}").send(reply_to=True) - except (IndexError, KeyError): - await MessageUtils.build_message("没有找到对应的翻译....").send() diff --git a/zhenxun/plugins/one_friend/__init__.py b/zhenxun/plugins/one_friend/__init__.py deleted file mode 100644 index 7fa7c8bfd..000000000 --- a/zhenxun/plugins/one_friend/__init__.py +++ /dev/null @@ -1,77 +0,0 @@ -import random -from io import BytesIO - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, At, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -__plugin_meta__ = PluginMetadata( - name="我有一个朋友", - description="我有一个朋友想问问...", - usage=""" - 指令: - 我有一个朋友想问问 [文本] ?[at]: 当at时你的朋友就是艾特对象 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_matcher = on_alconna( - Alconna("one-friend", Args["text", str]["at?", At]), priority=5, block=True -) - -_matcher.shortcut( - "^我.{0,4}朋友.{0,2}(?:想问问|说|让我问问|想问|让我问|想知道|让我帮他问问|让我帮他问|让我帮忙问|让我帮忙问问|问)(?P.{0,30})$", - command="one-friend", - arguments=["{content}"], - prefix=True, -) - - -@_matcher.handle() -async def _(bot: Bot, text: str, at: Match[At], session: EventSession): - gid = session.id3 or session.id2 - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - at_user = None - if at.available: - at_user = at.result.target - user = None - if at_user: - user = await PlatformUtils.get_user(bot, at_user, gid) - else: - if member_list := await PlatformUtils.get_group_member_list(bot, gid): - text = text.replace("他", "我").replace("她", "我").replace("它", "我") - user = random.choice(member_list) - if user: - ava_data = None - if PlatformUtils.get_platform(bot) == "qq": - ava_data = await PlatformUtils.get_user_avatar(user.user_id, "qq") - elif user.avatar_url: - ava_data = (await AsyncHttpx.get(user.avatar_url)).content - ava_img = BuildImage(200, 100, color=(0, 0, 0, 0)) - if ava_data: - ava_img = BuildImage(200, 100, background=BytesIO(ava_data)) - await ava_img.circle() - user_name = "朋友" - content = BuildImage(400, 30, font_size=30) - await content.text((0, 0), user_name) - A = BuildImage(700, 150, font_size=25, color="white") - await A.paste(ava_img, (30, 25)) - await A.paste(content, (150, 38)) - await A.text((150, 85), text, (125, 125, 125)) - logger.info(f"发送有一个朋友: {text}", "我有一个朋友", session=session) - await MessageUtils.build_message(A).finish() - await MessageUtils.build_message("获取用户信息失败...").send() diff --git a/zhenxun/plugins/open_cases/__init__.py b/zhenxun/plugins/open_cases/__init__.py deleted file mode 100644 index 2798942e8..000000000 --- a/zhenxun/plugins/open_cases/__init__.py +++ /dev/null @@ -1,329 +0,0 @@ -import asyncio -import random -from datetime import datetime, timedelta -from typing import List - -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Arparma, Match -from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig, Task -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import text2image -from zhenxun.utils.message import MessageUtils - -from .command import ( - _group_open_matcher, - _knifes_matcher, - _multiple_matcher, - _my_open_matcher, - _open_matcher, - _price_matcher, - _reload_matcher, - _show_case_matcher, - _update_image_matcher, - _update_matcher, -) -from .open_cases_c import ( - auto_update, - get_my_knifes, - group_statistics, - open_case, - open_multiple_case, - total_open_statistics, -) -from .utils import ( - CASE2ID, - KNIFE2ID, - CaseManager, - build_case_image, - download_image, - get_skin_case, - init_skin_trends, - reset_count_daily, - update_skin_data, -) - -__plugin_meta__ = PluginMetadata( - name="CSGO开箱", - description="csgo模拟开箱[戒赌]", - usage=""" - 指令: - 开箱 ?[武器箱] - [1-30]连开箱 ?[武器箱] - 我的开箱 - 我的金色 - 群开箱统计 - 查看武器箱?[武器箱] - * 不包含[武器箱]时随机开箱 * - 示例: 查看武器箱 - 示例: 查看武器箱英勇 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - superuser_help=""" - 更新皮肤指令 - 重置开箱: 重置今日开箱所有次数 - 指令: - 更新武器箱 ?[武器箱/ALL] - 更新皮肤 ?[名称/ALL1] - 更新皮肤 ?[名称/ALL1] -S: (必定更新罕见皮肤所属箱子) - 更新武器箱图片 - * 不指定武器箱时则全部更新 * - * 过多的爬取会导致账号API被封 * - """.strip(), - menu_type="抽卡相关", - tasks=[Task(module="open_case_reset_remind", name="每日开箱重置提醒")], - limits=[PluginCdBlock(result="着什么急啊,慢慢来!")], - configs=[ - RegisterConfig( - key="INITIAL_OPEN_CASE_COUNT", - value=20, - help="初始每日开箱次数", - default_value=20, - type=int, - ), - RegisterConfig( - key="EACH_IMPRESSION_ADD_COUNT", - value=3, - help="每 * 点好感度额外增加开箱次数", - default_value=3, - type=int, - ), - RegisterConfig(key="COOKIE", value=None, help="BUFF的cookie"), - RegisterConfig( - key="DAILY_UPDATE", - value=None, - help="每日自动更新的武器箱,存在'ALL'时则更新全部武器箱", - type=List[str], - ), - RegisterConfig( - key="DEFAULT_OPEN_CASE_RESET_REMIND", - module="_task", - value=True, - help="被动 每日开箱重置提醒 进群默认开关状态", - default_value=True, - type=bool, - ), - ], - ).dict(), -) - - -@_price_matcher.handle() -async def _( - session: EventSession, - arparma: Arparma, - name: str, - skin: str, - abrasion: str, - day: Match[int], -): - name = name.replace("武器箱", "").strip() - _day = 7 - if day.available: - _day = day.result - if _day > 180: - await MessageUtils.build_message("天数必须大于0且小于180").finish() - result = await init_skin_trends(name, skin, abrasion, _day) - if not result: - await MessageUtils.build_message("未查询到数据...").finish(reply_to=True) - await MessageUtils.build_message(result).send() - logger.info( - f"查看 [{name}:{skin}({abrasion})] 价格趋势", - arparma.header_result, - session=session, - ) - - -@_reload_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - await reset_count_daily() - logger.info("重置开箱次数", arparma.header_result, session=session) - - -@_open_matcher.handle() -async def _(session: EventSession, arparma: Arparma, name: Match[str]): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - case_name = None - if name.available: - case_name = name.result.replace("武器箱", "").strip() - result = await open_case(session.id1, gid, case_name, session) - await result.finish(reply_to=True) - - -@_my_open_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - await MessageUtils.build_message( - await total_open_statistics(session.id1, gid), - ).send(reply_to=True) - logger.info("查询我的开箱", arparma.header_result, session=session) - - -@_group_open_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id3 or session.id2 - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = await group_statistics(gid) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info("查询群开箱统计", arparma.header_result, session=session) - - -@_knifes_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = await get_my_knifes(session.id1, gid) - await result.send(reply_to=True) - logger.info("查询我的金色", arparma.header_result, session=session) - - -@_multiple_matcher.handle() -async def _(session: EventSession, arparma: Arparma, num: int, name: Match[str]): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - if num > 30: - await MessageUtils.build_message("开箱次数不要超过30啊笨蛋!").finish() - if num < 0: - await MessageUtils.build_message("再负开箱就扣你明天开箱数了!").finish() - case_name = None - if name.available: - case_name = name.result.replace("武器箱", "").strip() - result = await open_multiple_case(session.id1, gid, case_name, num, session) - await result.send(reply_to=True) - logger.info(f"{num}连开箱", arparma.header_result, session=session) - - -@_update_matcher.handle() -async def _(session: EventSession, arparma: Arparma, name: Match[str]): - case_name = None - if name.available: - case_name = name.result.strip() - if not case_name: - case_list = [] - skin_list = [] - for i, case_name in enumerate(CASE2ID): - if case_name in CaseManager.CURRENT_CASES: - case_list.append(f"{i+1}.{case_name} [已更新]") - else: - case_list.append(f"{i+1}.{case_name}") - for skin_name in KNIFE2ID: - skin_list.append(f"{skin_name}") - text = "武器箱:\n" + "\n".join(case_list) + "\n皮肤:\n" + ", ".join(skin_list) - img = await text2image(text, padding=20, color="#f9f6f2") - await MessageUtils.build_message( - ["未指定武器箱, 当前已包含武器箱/皮肤\n", img] - ).finish() - if case_name in ["ALL", "ALL1"]: - if case_name == "ALL": - case_list = list(CASE2ID.keys()) - type_ = "武器箱" - else: - case_list = list(KNIFE2ID.keys()) - type_ = "罕见皮肤" - await MessageUtils.build_message(f"即将更新所有{type_}, 请稍等").send() - for i, case_name in enumerate(case_list): - try: - info = await update_skin_data(case_name, arparma.find("s")) - if "请先登录" in info: - await MessageUtils.build_message( - f"未登录, 已停止更新, 请配置BUFF token..." - ).send() - return - rand = random.randint(300, 500) - result = f"更新全部{type_}完成" - if i < len(case_list) - 1: - next_case = case_list[i + 1] - result = f"将在 {rand} 秒后更新下一{type_}: {next_case}" - await MessageUtils.build_message(f"{info}, {result}").send() - logger.info(f"info, {result}", "更新武器箱", session=session) - await asyncio.sleep(rand) - except Exception as e: - logger.error(f"更新{type_}: {case_name}", session=session, e=e) - await MessageUtils.build_message( - f"更新{type_}: {case_name} 发生错误: {type(e)}: {e}" - ).send() - await MessageUtils.build_message(f"更新全部{type_}完成").send() - else: - await MessageUtils.build_message( - f"开始{arparma.header_result}: {case_name}, 请稍等" - ).send() - try: - await MessageUtils.build_message( - await update_skin_data(case_name, arparma.find("s")) - ).send(at_sender=True) - except Exception as e: - logger.error(f"{arparma.header_result}: {case_name}", session=session, e=e) - await MessageUtils.build_message( - f"成功{arparma.header_result}: {case_name} 发生错误: {type(e)}: {e}" - ).send() - - -@_show_case_matcher.handle() -async def _(session: EventSession, arparma: Arparma, name: Match[str]): - case_name = None - if name.available: - case_name = name.result.strip() - result = await build_case_image(case_name) - if isinstance(result, str): - await MessageUtils.build_message(result).send() - else: - await MessageUtils.build_message(result).send() - logger.info("查看武器箱", arparma.header_result, session=session) - - -@_update_image_matcher.handle() -async def _(session: EventSession, arparma: Arparma, name: Match[str]): - case_name = None - if name.available: - case_name = name.result.strip() - await MessageUtils.build_message("开始更新图片...").send(reply_to=True) - await download_image(case_name) - await MessageUtils.build_message("更新图片完成...").send(at_sender=True) - logger.info("更新武器箱图片", arparma.header_result, session=session) - - -# 重置开箱 -@scheduler.scheduled_job( - "cron", - hour=0, - minute=1, -) -async def _(): - await reset_count_daily() - - -@scheduler.scheduled_job( - "cron", - hour=0, - minute=10, -) -async def _(): - now = datetime.now() - hour = random.choice([0, 1, 2, 3]) - date = now + timedelta(hours=hour) - logger.debug(f"将在 {date} 时自动更新武器箱...", "更新武器箱") - scheduler.add_job( - auto_update, - "date", - run_date=date.replace(microsecond=0), - id=f"auto_update_csgo_cases", - ) diff --git a/zhenxun/plugins/open_cases/build_image.py b/zhenxun/plugins/open_cases/build_image.py deleted file mode 100644 index 8b8db8e2e..000000000 --- a/zhenxun/plugins/open_cases/build_image.py +++ /dev/null @@ -1,155 +0,0 @@ -from datetime import timedelta, timezone - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.utils import cn2py - -from .config import COLOR2COLOR, COLOR2NAME -from .models.buff_skin import BuffSkin - -BASE_PATH = IMAGE_PATH / "csgo_cases" - -ICON_PATH = IMAGE_PATH / "_icon" - - -async def draw_card(skin: BuffSkin, rand: str) -> BuildImage: - """构造抽取图片 - - 参数: - skin (BuffSkin): BuffSkin - rand (str): 磨损 - - 返回: - BuildImage: BuildImage - """ - name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - file_path = BASE_PATH / cn2py(skin.case_name.split(",")[0]) / f"{cn2py(name)}.jpg" - if not file_path.exists(): - logger.warning(f"皮肤图片: {name} 不存在", "开箱") - skin_bk = BuildImage( - 460, 200, color=(25, 25, 25, 100), font_size=25, font="CJGaoDeGuo.otf" - ) - if file_path.exists(): - skin_image = BuildImage(205, 153, background=file_path) - await skin_bk.paste(skin_image, (10, 30)) - await skin_bk.line((220, 10, 220, 180)) - await skin_bk.text((10, 10), skin.name, (255, 255, 255)) - name_icon = BuildImage(20, 20, background=ICON_PATH / "name_white.png") - await skin_bk.paste(name_icon, (240, 13)) - await skin_bk.text((265, 15), f"名称:", (255, 255, 255), font_size=20) - await skin_bk.text( - (310, 15), - f"{skin.skin_name + ('(St)' if skin.is_stattrak else '')}", - (255, 255, 255), - ) - tone_icon = BuildImage(20, 20, background=ICON_PATH / "tone_white.png") - await skin_bk.paste(tone_icon, (240, 45)) - await skin_bk.text((265, 45), "品质:", (255, 255, 255), font_size=20) - await skin_bk.text((310, 45), COLOR2NAME[skin.color][:2], COLOR2COLOR[skin.color]) - type_icon = BuildImage(20, 20, background=ICON_PATH / "type_white.png") - await skin_bk.paste(type_icon, (240, 73)) - await skin_bk.text((265, 75), "类型:", (255, 255, 255), font_size=20) - await skin_bk.text((310, 75), skin.weapon_type, (255, 255, 255)) - price_icon = BuildImage(20, 20, background=ICON_PATH / "price_white.png") - await skin_bk.paste(price_icon, (240, 103)) - await skin_bk.text((265, 105), "价格:", (255, 255, 255), font_size=20) - await skin_bk.text((310, 105), str(skin.sell_min_price), (0, 255, 98)) - abrasion_icon = BuildImage(20, 20, background=ICON_PATH / "abrasion_white.png") - await skin_bk.paste(abrasion_icon, (240, 133)) - await skin_bk.text((265, 135), "磨损:", (255, 255, 255), font_size=20) - await skin_bk.text((310, 135), skin.abrasion, (255, 255, 255)) - await skin_bk.text((228, 165), f"({rand})", (255, 255, 255)) - return skin_bk - - -async def generate_skin(skin: BuffSkin, update_count: int) -> BuildImage | None: - """构造皮肤图片 - - 参数: - skin (BuffSkin): BuffSkin - - 返回: - BuildImage | None: 图片 - """ - name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - file_path = BASE_PATH / cn2py(skin.case_name.split(",")[0]) / f"{cn2py(name)}.jpg" - if not file_path.exists(): - logger.warning(f"皮肤图片: {name} 不存在", "查看武器箱") - if skin.color == "CASE": - case_bk = BuildImage( - 700, 200, color=(25, 25, 25, 100), font_size=25, font="CJGaoDeGuo.otf" - ) - if file_path.exists(): - skin_img = BuildImage(200, 200, background=file_path) - await case_bk.paste(skin_img, (10, 10)) - await case_bk.line((250, 10, 250, 190)) - await case_bk.line((280, 160, 660, 160)) - name_icon = BuildImage(30, 30, background=ICON_PATH / "box_white.png") - await case_bk.paste(name_icon, (260, 25)) - await case_bk.text((295, 30), "名称:", (255, 255, 255)) - await case_bk.text((345, 30), skin.case_name, (255, 0, 38), font_size=30) - - type_icon = BuildImage(30, 30, background=ICON_PATH / "type_white.png") - await case_bk.paste(type_icon, (260, 70)) - await case_bk.text((295, 75), "类型:", (255, 255, 255)) - await case_bk.text((345, 75), "武器箱", (0, 157, 255), font_size=30) - - price_icon = BuildImage(30, 30, background=ICON_PATH / "price_white.png") - await case_bk.paste(price_icon, (260, 114)) - await case_bk.text((295, 120), "单价:", (255, 255, 255)) - await case_bk.text( - (340, 120), str(skin.sell_min_price), (0, 255, 98), font_size=30 - ) - - update_count_icon = BuildImage( - 40, 40, background=ICON_PATH / "reload_white.png" - ) - await case_bk.paste(update_count_icon, (575, 10)) - await case_bk.text((625, 12), str(update_count), (255, 255, 255), font_size=45) - - num_icon = BuildImage(30, 30, background=ICON_PATH / "num_white.png") - await case_bk.paste(num_icon, (455, 70)) - await case_bk.text((490, 75), "在售:", (255, 255, 255)) - await case_bk.text((535, 75), str(skin.sell_num), (144, 0, 255), font_size=30) - - want_buy_icon = BuildImage(30, 30, background=ICON_PATH / "want_buy_white.png") - await case_bk.paste(want_buy_icon, (455, 114)) - await case_bk.text((490, 120), "求购:", (255, 255, 255)) - await case_bk.text((535, 120), str(skin.buy_num), (144, 0, 255), font_size=30) - - await case_bk.text((275, 165), "更新时间", (255, 255, 255), font_size=22) - date = str( - skin.update_time.replace(microsecond=0).astimezone( - timezone(timedelta(hours=8)) - ) - ).split("+")[0] - await case_bk.text( - (350, 165), - date, - (255, 255, 255), - font_size=30, - ) - return case_bk - else: - skin_bk = BuildImage( - 235, 250, color=(25, 25, 25, 100), font_size=25, font="CJGaoDeGuo.otf" - ) - if file_path.exists(): - skin_image = BuildImage(205, 153, background=file_path) - await skin_bk.paste(skin_image, (10, 30)) - update_count_icon = BuildImage( - 35, 35, background=ICON_PATH / "reload_white.png" - ) - await skin_bk.line((10, 180, 220, 180)) - await skin_bk.text((10, 10), skin.name, (255, 255, 255)) - await skin_bk.paste(update_count_icon, (140, 10)) - await skin_bk.text((175, 15), str(update_count), (255, 255, 255)) - await skin_bk.text((10, 185), f"{skin.skin_name}", (255, 255, 255), "width") - await skin_bk.text((10, 218), "品质:", (255, 255, 255)) - await skin_bk.text( - (55, 218), COLOR2NAME[skin.color][:2], COLOR2COLOR[skin.color] - ) - await skin_bk.text((100, 218), "类型:", (255, 255, 255)) - await skin_bk.text((145, 218), skin.weapon_type, (255, 255, 255)) - return skin_bk diff --git a/zhenxun/plugins/open_cases/command.py b/zhenxun/plugins/open_cases/command.py deleted file mode 100644 index ea86c2fc6..000000000 --- a/zhenxun/plugins/open_cases/command.py +++ /dev/null @@ -1,75 +0,0 @@ -from nonebot.permission import SUPERUSER -from nonebot_plugin_alconna import Alconna, Args, Option, on_alconna, store_true - -from zhenxun.utils.rules import ensure_group - -_open_matcher = on_alconna( - Alconna("开箱", Args["name?", str]), priority=5, block=True, rule=ensure_group -) - -_reload_matcher = on_alconna( - Alconna("重置开箱"), priority=5, block=True, permission=SUPERUSER, rule=ensure_group -) - -_my_open_matcher = on_alconna( - Alconna("我的开箱"), - aliases={"开箱统计", "开箱查询", "查询开箱"}, - priority=5, - block=True, - rule=ensure_group, -) - -_group_open_matcher = on_alconna( - Alconna("群开箱统计"), priority=5, block=True, rule=ensure_group -) - -_multiple_matcher = on_alconna( - Alconna("multiple-open", Args["num", int]["name?", str]), - priority=5, - block=True, - rule=ensure_group, -) - -_multiple_matcher.shortcut( - r"(?P\d+)连开箱(?P.*?)", - command="multiple-open", - arguments=["{num}", "{name}"], - prefix=True, -) - -_update_matcher = on_alconna( - Alconna( - "更新武器箱", - Args["name?", str], - Option("-s", action=store_true, help_text="是否必定更新所属箱子"), - ), - aliases={"更新皮肤"}, - priority=1, - permission=SUPERUSER, - block=True, -) - -_update_image_matcher = on_alconna( - Alconna("更新武器箱图片", Args["name?", str]), - priority=1, - permission=SUPERUSER, - block=True, -) - -_show_case_matcher = on_alconna( - Alconna("查看武器箱", Args["name?", str]), priority=5, block=True -) - -_knifes_matcher = on_alconna( - Alconna("我的金色"), priority=5, block=True, rule=ensure_group -) - -_show_skin_matcher = on_alconna(Alconna("查看皮肤"), priority=5, block=True) - -_price_matcher = on_alconna( - Alconna( - "价格趋势", Args["name", str]["skin", str]["abrasion", str]["day?", int, 7] - ), - priority=5, - block=True, -) diff --git a/zhenxun/plugins/open_cases/config.py b/zhenxun/plugins/open_cases/config.py deleted file mode 100644 index cefa7384d..000000000 --- a/zhenxun/plugins/open_cases/config.py +++ /dev/null @@ -1,253 +0,0 @@ -import random -from enum import Enum - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger - -from .models.buff_skin import BuffSkin - -BLUE = 0.7981 -BLUE_ST = 0.0699 -PURPLE = 0.1626 -PURPLE_ST = 0.0164 -PINK = 0.0315 -PINK_ST = 0.0048 -RED = 0.0057 -RED_ST = 0.00021 -KNIFE = 0.0021 -KNIFE_ST = 0.000041 - -# 崭新 -FACTORY_NEW_S = 0 -FACTORY_NEW_E = 0.0699999 -# 略磨 -MINIMAL_WEAR_S = 0.07 -MINIMAL_WEAR_E = 0.14999 -# 久经 -FIELD_TESTED_S = 0.15 -FIELD_TESTED_E = 0.37999 -# 破损 -WELL_WORN_S = 0.38 -WELL_WORN_E = 0.44999 -# 战痕 -BATTLE_SCARED_S = 0.45 -BATTLE_SCARED_E = 0.99999 - - -class UpdateType(Enum): - """ - 更新类型 - """ - - CASE = "case" - WEAPON_TYPE = "weapon_type" - - -NAME2COLOR = { - "消费级": "WHITE", - "工业级": "LIGHTBLUE", - "军规级": "BLUE", - "受限": "PURPLE", - "保密": "PINK", - "隐秘": "RED", - "非凡": "KNIFE", -} - -COLOR2NAME = { - "WHITE": "消费级", - "LIGHTBLUE": "工业级", - "BLUE": "军规级", - "PURPLE": "受限", - "PINK": "保密", - "RED": "隐秘", - "KNIFE": "非凡", -} - -COLOR2COLOR = { - "WHITE": (255, 255, 255), - "LIGHTBLUE": (0, 179, 255), - "BLUE": (0, 85, 255), - "PURPLE": (149, 0, 255), - "PINK": (255, 0, 162), - "RED": (255, 34, 0), - "KNIFE": (255, 225, 0), -} - -ABRASION_SORT = ["崭新出厂", "略有磨损", "久经沙场", "破损不堪", "战横累累"] - -CASE_BACKGROUND = IMAGE_PATH / "csgo_cases" / "_background" / "shu" - -# 刀 -KNIFE2ID = { - "鲍伊猎刀": "weapon_knife_survival_bowie", - "蝴蝶刀": "weapon_knife_butterfly", - "弯刀": "weapon_knife_falchion", - "折叠刀": "weapon_knife_flip", - "穿肠刀": "weapon_knife_gut", - "猎杀者匕首": "weapon_knife_tactical", - "M9刺刀": "weapon_knife_m9_bayonet", - "刺刀": "weapon_bayonet", - "爪子刀": "weapon_knife_karambit", - "暗影双匕": "weapon_knife_push", - "短剑": "weapon_knife_stiletto", - "熊刀": "weapon_knife_ursus", - "折刀": "weapon_knife_gypsy_jackknife", - "锯齿爪刀": "weapon_knife_widowmaker", - "海豹短刀": "weapon_knife_css", - "系绳匕首": "weapon_knife_cord", - "求生匕首": "weapon_knife_canis", - "流浪者匕首": "weapon_knife_outdoor", - "骷髅匕首": "weapon_knife_skeleton", - "血猎手套": "weapon_bloodhound_gloves", - "驾驶手套": "weapon_driver_gloves", - "手部束带": "weapon_hand_wraps", - "摩托手套": "weapon_moto_gloves", - "专业手套": "weapon_specialist_gloves", - "运动手套": "weapon_sport_gloves", - "九头蛇手套": "weapon_hydra_gloves", - "狂牙手套": "weapon_brokenfang_gloves", -} - -WEAPON2ID = {} - -# 武器箱 -CASE2ID = { - "变革": "set_community_32", - "反冲": "set_community_31", - "梦魇": "set_community_30", - "激流": "set_community_29", - "蛇噬": "set_community_28", - "狂牙大行动": "set_community_27", - "裂空": "set_community_26", - "棱彩2号": "set_community_25", - "CS20": "set_community_24", - "裂网大行动": "set_community_23", - "棱彩": "set_community_22", - "头号特训": "set_community_21", - "地平线": "set_community_20", - "命悬一线": "set_community_19", - "光谱2号": "set_community_18", - "九头蛇大行动": "set_community_17", - "光谱": "set_community_16", - "手套": "set_community_15", - "伽玛2号": "set_gamma_2", - "伽玛": "set_community_13", - "幻彩3号": "set_community_12", - "野火大行动": "set_community_11", - "左轮": "set_community_10", - "暗影": "set_community_9", - "弯曲猎手": "set_community_8", - "幻彩2号": "set_community_7", - "幻彩": "set_community_6", - "先锋": "set_community_5", - "电竞2014夏季": "set_esports_iii", - "突围大行动": "set_community_4", - "猎杀者": "set_community_3", - "凤凰": "set_community_2", - "电竞2013冬季": "set_esports_ii", - "冬季攻势": "set_community_1", - "军火交易3号": "set_weapons_iii", - "英勇": "set_bravo_i", - "电竞2013": "set_esports", - "军火交易2号": "set_weapons_ii", - "军火交易": "set_weapons_i", -} - - -def get_wear(rand: float) -> str: - """判断磨损度 - - Args: - rand (float): 随机rand - - Returns: - str: 磨损名称 - """ - if rand <= FACTORY_NEW_E: - return "崭新出厂" - if MINIMAL_WEAR_S <= rand <= MINIMAL_WEAR_E: - return "略有磨损" - if FIELD_TESTED_S <= rand <= FIELD_TESTED_E: - return "久经沙场" - if WELL_WORN_S <= rand <= WELL_WORN_E: - return "破损不堪" - return "战痕累累" - - -def random_color_and_st(rand: float) -> tuple[str, bool]: - """获取皮肤品质及是否暗金 - - 参数: - rand (float): 随机rand - - 返回: - tuple[str, bool]: 品质,是否暗金 - """ - if rand <= KNIFE: - if random.random() <= KNIFE_ST: - return ("KNIFE", True) - return ("KNIFE", False) - elif KNIFE < rand <= RED: - if random.random() <= RED_ST: - return ("RED", True) - return ("RED", False) - elif RED < rand <= PINK: - if random.random() <= PINK_ST: - return ("PINK", True) - return ("PINK", False) - elif PINK < rand <= PURPLE: - if random.random() <= PURPLE_ST: - return ("PURPLE", True) - return ("PURPLE", False) - else: - if random.random() <= BLUE_ST: - return ("BLUE", True) - return ("BLUE", False) - - -async def random_skin(num: int, case_name: str) -> list[tuple[BuffSkin, float]]: - """ - 随机抽取皮肤 - """ - case_name = case_name.replace("武器箱", "").replace(" ", "") - color_map = {} - for _ in range(num): - rand = random.random() - # 尝试降低磨损 - if rand > MINIMAL_WEAR_E: - for _ in range(2): - if random.random() < 0.5: - logger.debug(f"[START]开箱随机磨损触发降低磨损条件: {rand}") - if random.random() < 0.2: - rand /= 3 - else: - rand /= 2 - logger.debug(f"[END]开箱随机磨损触发降低磨损条件: {rand}") - break - abrasion = get_wear(rand) - logger.debug(f"开箱随机磨损: {rand} | {abrasion}") - color, is_stattrak = random_color_and_st(rand) - if not color_map.get(color): - color_map[color] = {} - if is_stattrak: - if not color_map[color].get(f"{abrasion}_st"): - color_map[color][f"{abrasion}_st"] = [] - color_map[color][f"{abrasion}_st"].append(rand) - else: - if not color_map[color].get(abrasion): - color_map[color][f"{abrasion}"] = [] - color_map[color][f"{abrasion}"].append(rand) - skin_list = [] - for color in color_map: - for abrasion in color_map[color]: - rand_list = color_map[color][abrasion] - is_stattrak = "_st" in abrasion - abrasion = abrasion.replace("_st", "") - skin_list_ = await BuffSkin.random_skin( - len(rand_list), color, abrasion, is_stattrak, case_name - ) - skin_list += [(skin, rand) for skin, rand in zip(skin_list_, rand_list)] - return skin_list - - -# M249(StatTrak™) | 等高线 diff --git a/zhenxun/plugins/open_cases/models/__init__.py b/zhenxun/plugins/open_cases/models/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/zhenxun/plugins/open_cases/models/buff_prices.py b/zhenxun/plugins/open_cases/models/buff_prices.py deleted file mode 100644 index 9f53de0e6..000000000 --- a/zhenxun/plugins/open_cases/models/buff_prices.py +++ /dev/null @@ -1,22 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - -# 1.狂牙武器箱 - - -class BuffPrice(Model): - - 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/zhenxun/plugins/open_cases/models/buff_skin.py b/zhenxun/plugins/open_cases/models/buff_skin.py deleted file mode 100644 index 7f51221a4..000000000 --- a/zhenxun/plugins/open_cases/models/buff_skin.py +++ /dev/null @@ -1,113 +0,0 @@ -from tortoise import fields -from tortoise.contrib.postgres.functions import Random - -from zhenxun.services.db_context import Model - - -class BuffSkin(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - case_name: str = fields.CharField(255) # type: ignore - """箱子名称""" - name: str = fields.CharField(255) # type: ignore - """武器/手套/刀名称""" - skin_name: str = fields.CharField(255) # type: ignore - """皮肤名称""" - is_stattrak = fields.BooleanField(default=False) - """是否暗金(计数)""" - abrasion = fields.CharField(255) - """磨损度""" - color = fields.CharField(255) - """颜色(品质)""" - skin_id = fields.CharField(255, null=True, unique=True) - """皮肤id""" - - img_url = fields.CharField(255) - """图片url""" - steam_price = fields.FloatField(default=0) - """steam价格""" - weapon_type = fields.CharField(255) - """枪械类型""" - buy_max_price = fields.FloatField(default=0) - """最大求购价格""" - buy_num = fields.IntField(default=0) - """求购数量""" - sell_min_price = fields.FloatField(default=0) - """售卖最低价格""" - sell_num = fields.IntField(default=0) - """出售个数""" - sell_reference_price = fields.FloatField(default=0) - """参考价格""" - - create_time = fields.DatetimeField(auto_add_now=True) - """创建日期""" - update_time = fields.DatetimeField(auto_add=True) - """更新日期""" - - class Meta: - table = "buff_skin" - table_description = "Buff皮肤数据表" - # unique_together = ("case_name", "name", "skin_name", "abrasion", "is_stattrak") - - def __eq__(self, other: "BuffSkin"): - - return self.skin_id == other.skin_id - - def __hash__(self): - - return hash(self.case_name + self.name + self.skin_name + str(self.is_stattrak)) - - @classmethod - async def random_skin( - cls, - num: int, - color: str, - abrasion: str, - is_stattrak: bool = False, - case_name: str | None = None, - ) -> list["BuffSkin"]: # type: ignore - """随机皮肤 - - 参数: - num: 数量 - color: 品质 - abrasion: 磨损度 - is_stattrak: 是否暗金 - case_name: 箱子名称 - - 返回: - list["BuffSkin"]: 皮肤列表 - """ - query = cls - if case_name: - query = query.filter(case_name__contains=case_name) - query = query.filter(abrasion=abrasion, is_stattrak=is_stattrak, color=color) - skin_list = await query.annotate(rand=Random()).limit(num) # type:ignore - num_ = num - cnt = 0 - while len(skin_list) < num: - cnt += 1 - num_ = num - len(skin_list) - skin_list += await query.annotate(rand=Random()).limit(num_) - if cnt > 10: - break - return skin_list # type: ignore - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE buff_skin ADD img_url varchar(255);", # 新增img_url - "ALTER TABLE buff_skin ADD skin_id varchar(255);", # 新增skin_id - "ALTER TABLE buff_skin ADD steam_price float DEFAULT 0;", # 新增steam_price - "ALTER TABLE buff_skin ADD weapon_type varchar(255);", # 新增type - "ALTER TABLE buff_skin ADD buy_max_price float DEFAULT 0;", # 新增buy_max_price - "ALTER TABLE buff_skin ADD buy_num Integer DEFAULT 0;", # 新增buy_max_price - "ALTER TABLE buff_skin ADD sell_min_price float DEFAULT 0;", # 新增sell_min_price - "ALTER TABLE buff_skin ADD sell_num Integer DEFAULT 0;", # 新增sell_num - "ALTER TABLE buff_skin ADD sell_reference_price float DEFAULT 0;", # 新增sell_reference_price - "ALTER TABLE buff_skin DROP COLUMN skin_price;", # 删除skin_price - "alter table buff_skin drop constraint if EXISTS uid_buff_skin_case_na_c35c93;", # 删除唯一约束 - "UPDATE buff_skin set case_name='手套' where case_name='手套武器箱'", - "UPDATE buff_skin set case_name='左轮' where case_name='左轮武器箱'", - ] diff --git a/zhenxun/plugins/open_cases/models/buff_skin_log.py b/zhenxun/plugins/open_cases/models/buff_skin_log.py deleted file mode 100644 index ac9fec952..000000000 --- a/zhenxun/plugins/open_cases/models/buff_skin_log.py +++ /dev/null @@ -1,50 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class BuffSkinLog(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - case_name = fields.CharField(255) - """箱子名称""" - name = fields.CharField(255) - """武器/手套/刀名称""" - skin_name = fields.CharField(255) - """皮肤名称""" - is_stattrak = fields.BooleanField(default=False) - """是否暗金(计数)""" - abrasion = fields.CharField(255) - """磨损度""" - color = fields.CharField(255) - """颜色(品质)""" - - steam_price = fields.FloatField(default=0) - """steam价格""" - weapon_type = fields.CharField(255) - """枪械类型""" - buy_max_price = fields.FloatField(default=0) - """最大求购价格""" - buy_num = fields.IntField(default=0) - """求购数量""" - sell_min_price = fields.FloatField(default=0) - """售卖最低价格""" - sell_num = fields.IntField(default=0) - """出售个数""" - sell_reference_price = fields.FloatField(default=0) - """参考价格""" - - create_time = fields.DatetimeField(auto_add_now=True) - """创建日期""" - - class Meta: - table = "buff_skin_log" - table_description = "Buff皮肤更新日志表" - - @classmethod - async def _run_script(cls): - return [ - "UPDATE buff_skin_log set case_name='手套' where case_name='手套武器箱'", - "UPDATE buff_skin_log set case_name='左轮' where case_name='左轮武器箱'", - ] diff --git a/zhenxun/plugins/open_cases/models/open_cases_log.py b/zhenxun/plugins/open_cases/models/open_cases_log.py deleted file mode 100644 index 0c4f87bb6..000000000 --- a/zhenxun/plugins/open_cases/models/open_cases_log.py +++ /dev/null @@ -1,44 +0,0 @@ -from tortoise import fields -from tortoise.contrib.postgres.functions import Random - -from zhenxun.services.db_context import Model - - -class OpenCasesLog(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊id""" - case_name = fields.CharField(255) - """箱子名称""" - name = fields.CharField(255) - """武器/手套/刀名称""" - skin_name = fields.CharField(255) - """皮肤名称""" - is_stattrak = fields.BooleanField(default=False) - """是否暗金(计数)""" - abrasion = fields.CharField(255) - """磨损度""" - abrasion_value = fields.FloatField() - """磨损数值""" - color = fields.CharField(255) - """颜色(品质)""" - price = fields.FloatField(default=0) - """价格""" - create_time = fields.DatetimeField(auto_add_now=True) - """创建日期""" - - class Meta: - table = "open_cases_log" - table_description = "开箱日志表" - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE open_cases_log RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE open_cases_log ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE open_cases_log ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/open_cases/models/open_cases_user.py b/zhenxun/plugins/open_cases/models/open_cases_user.py deleted file mode 100644 index 3ed439372..000000000 --- a/zhenxun/plugins/open_cases/models/open_cases_user.py +++ /dev/null @@ -1,60 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class OpenCasesUser(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊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.FloatField(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_id", "group_id") - - @classmethod - async def _run_script(cls): - return [ - "alter table open_cases_users alter COLUMN make_money type float;", # 将make_money字段改为float - "alter table open_cases_users alter COLUMN spend_money type float;", # 将spend_money字段改为float - "ALTER TABLE open_cases_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE open_cases_users ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE open_cases_users ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/open_cases/open_cases_c.py b/zhenxun/plugins/open_cases/open_cases_c.py deleted file mode 100644 index f56aa9fef..000000000 --- a/zhenxun/plugins/open_cases/open_cases_c.py +++ /dev/null @@ -1,481 +0,0 @@ -import asyncio -import random -import re -from datetime import datetime - -from nonebot_plugin_alconna import UniMessage -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.models.sign_user import SignUser -from zhenxun.services.log import logger -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import cn2py - -from .build_image import draw_card -from .config import * -from .models.open_cases_log import OpenCasesLog -from .models.open_cases_user import OpenCasesUser -from .utils import CaseManager, update_skin_data - -RESULT_MESSAGE = { - "BLUE": ["这样看着才舒服", "是自己人,大伙把刀收好", "非常舒适~"], - "PURPLE": ["还行吧,勉强接受一下下", "居然不是蓝色,太假了", "运气-1-1-1-1-1..."], - "PINK": ["开始不适....", "你妈妈买菜必涨价!涨三倍!", "你最近不适合出门,真的"], - "RED": [ - "已经非常不适", - "好兄弟你开的什么箱子啊,一般箱子不是只有蓝色的吗", - "开始拿阳寿开箱子了?", - ], - "KNIFE": [ - "你的好运我收到了,你可以去喂鲨鱼了", - "最近该吃啥就迟点啥吧,哎,好好的一个人怎么就....哎", - "众所周知,欧皇寿命极短.", - ], -} - -COLOR2NAME = { - "BLUE": "军规", - "PURPLE": "受限", - "PINK": "保密", - "RED": "隐秘", - "KNIFE": "罕见", -} - -COLOR2CN = {"BLUE": "蓝", "PURPLE": "紫", "PINK": "粉", "RED": "红", "KNIFE": "金"} - - -def add_count(user: OpenCasesUser, skin: BuffSkin, case_price: float): - if skin.color == "BLUE": - if skin.is_stattrak: - user.blue_st_count += 1 - else: - user.blue_count += 1 - elif skin.color == "PURPLE": - if skin.is_stattrak: - user.purple_st_count += 1 - else: - user.purple_count += 1 - elif skin.color == "PINK": - if skin.is_stattrak: - user.pink_st_count += 1 - else: - user.pink_count += 1 - elif skin.color == "RED": - if skin.is_stattrak: - user.red_st_count += 1 - else: - user.red_count += 1 - elif skin.color == "KNIFE": - if skin.is_stattrak: - user.knife_st_count += 1 - else: - user.knife_count += 1 - user.make_money += skin.sell_min_price - user.spend_money += int(17 + case_price) - - -async def get_user_max_count(user_id: str) -> int: - """获取用户每日最大开箱次数 - - 参数: - user_id: 用户id - - 返回: - int: 最大开箱次数 - """ - user, _ = await SignUser.get_or_create(user_id=user_id) - impression = int(user.impression) - initial_open_case_count = Config.get_config("open_cases", "INITIAL_OPEN_CASE_COUNT") - each_impression_add_count = Config.get_config( - "open_cases", "EACH_IMPRESSION_ADD_COUNT" - ) - return int(initial_open_case_count + impression / each_impression_add_count) # type: ignore - - -async def open_case( - user_id: str, group_id: str, case_name: str | None, session: EventSession -) -> UniMessage: - """开箱 - - 参数: - user_id: 用户id - group_id : 群号 - case_name: 武器箱名称. Defaults to "狂牙大行动". - session: EventSession - - 返回: - Union[str, Message]: 回复消息 - """ - user_id = str(user_id) - group_id = str(group_id) - if not CaseManager.CURRENT_CASES: - return MessageUtils.build_message("未收录任何武器箱") - if not case_name: - case_name = random.choice(CaseManager.CURRENT_CASES) # type: ignore - if case_name not in CaseManager.CURRENT_CASES: - return "武器箱未收录, 当前可用武器箱:\n" + ", ".join(CaseManager.CURRENT_CASES) # type: ignore - logger.debug( - f"尝试开启武器箱: {case_name}", "开箱", session=user_id, group_id=group_id - ) - case = cn2py(case_name) # type: ignore - user = await OpenCasesUser.get_or_none(user_id=user_id, group_id=group_id) - if not user: - user = await OpenCasesUser.create( - user_id=user_id, group_id=group_id, open_cases_time_last=datetime.now() - ) - max_count = await get_user_max_count(user_id) - # 一天次数上限 - if user.today_open_total >= max_count: - return MessageUtils.build_message( - f"今天已达开箱上限了喔,明天再来吧\n(提升好感度可以增加每日开箱数 #疯狂暗示)" - ) - skin_list = await random_skin(1, case_name) # type: ignore - if not skin_list: - return MessageUtils.build_message("未抽取到任何皮肤") - skin, rand = skin_list[0] - rand = str(rand)[:11] - case_price = 0 - if case_skin := await BuffSkin.get_or_none(case_name=case_name, color="CASE"): - case_price = case_skin.sell_min_price - user.today_open_total += 1 - user.total_count += 1 - user.open_cases_time_last = datetime.now() - await user.save( - update_fields=["today_open_total", "total_count", "open_cases_time_last"] - ) - add_count(user, skin, case_price) - ridicule_result = random.choice(RESULT_MESSAGE[skin.color]) - price_result = skin.sell_min_price - name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - img_path = IMAGE_PATH / "csgo_cases" / case / f"{cn2py(name)}.jpg" - logger.info( - f"开启{case_name}武器箱获得 {skin.name}{'(StatTrak™)' if skin.is_stattrak else ''} | {skin.skin_name} ({skin.abrasion}) 磨损: [{rand}] 价格: {skin.sell_min_price}", - "开箱", - session=session, - ) - await user.save() - await OpenCasesLog.create( - user_id=user_id, - group_id=group_id, - case_name=case_name, - name=skin.name, - skin_name=skin.skin_name, - is_stattrak=skin.is_stattrak, - abrasion=skin.abrasion, - color=skin.color, - price=skin.sell_min_price, - abrasion_value=rand, - create_time=datetime.now(), - ) - logger.debug(f"添加 1 条开箱日志", "开箱", session=session) - over_count = max_count - user.today_open_total - img = await draw_card(skin, rand) - return MessageUtils.build_message( - [ - f"开启{case_name}武器箱.\n剩余开箱次数:{over_count}.\n", - img, - f"\n箱子单价:{case_price}\n花费:{17 + case_price:.2f}\n:{ridicule_result}", - ] - ) - - -async def open_multiple_case( - user_id: str, - group_id: str, - case_name: str | None, - num: int = 10, - session: EventSession | None = None, -) -> UniMessage: - """多连开箱 - - 参数: - user_id (int): 用户id - group_id (int): 群号 - case_name (str): 箱子名称 - num (int, optional): 数量. Defaults to 10. - session: EventSession - - 返回: - _type_: _description_ - """ - user_id = str(user_id) - group_id = str(group_id) - if not CaseManager.CURRENT_CASES: - return MessageUtils.build_message("未收录任何武器箱") - if not case_name: - case_name = random.choice(CaseManager.CURRENT_CASES) # type: ignore - if case_name not in CaseManager.CURRENT_CASES: - return MessageUtils.build_message( - "武器箱未收录, 当前可用武器箱:\n" + ", ".join(CaseManager.CURRENT_CASES) - ) - user, _ = await OpenCasesUser.get_or_create( - user_id=user_id, - group_id=group_id, - defaults={"open_cases_time_last": datetime.now()}, - ) - max_count = await get_user_max_count(user_id) - if user.today_open_total >= max_count: - return MessageUtils.build_message( - f"今天已达开箱上限了喔,明天再来吧\n(提升好感度可以增加每日开箱数 #疯狂暗示)" - ) - if max_count - user.today_open_total < num: - return MessageUtils.build_message( - f"今天开箱次数不足{num}次噢,请单抽试试看(也许单抽运气更好?)" - f"\n剩余开箱次数:{max_count - user.today_open_total}" - ) - logger.debug(f"尝试开启武器箱: {case_name}", "开箱", session=session) - case = cn2py(case_name) # type: ignore - skin_count = {} - img_list = [] - skin_list = await random_skin(num, case_name) # type: ignore - if not skin_list: - return MessageUtils.build_message("未抽取到任何皮肤...") - total_price = 0 - log_list = [] - now = datetime.now() - user.today_open_total += num - user.total_count += num - user.open_cases_time_last = datetime.now() - await user.save( - update_fields=["today_open_total", "total_count", "open_cases_time_last"] - ) - case_price = 0 - if case_skin := await BuffSkin.get_or_none(case_name=case_name, color="CASE"): - case_price = case_skin.sell_min_price - img_w, img_h = 0, 0 - for skin, rand in skin_list: - img = await draw_card(skin, str(rand)[:11]) - img_w, img_h = img.size - total_price += skin.sell_min_price - color_name = COLOR2CN[skin.color] - if not skin_count.get(color_name): - skin_count[color_name] = 0 - skin_count[color_name] += 1 - add_count(user, skin, case_price) - img_list.append(img) - logger.info( - f"开启{case_name}武器箱获得 {skin.name}{'(StatTrak™)' if skin.is_stattrak else ''} | {skin.skin_name} ({skin.abrasion}) 磨损: [{rand:.11f}] 价格: {skin.sell_min_price}", - "开箱", - session=session, - ) - log_list.append( - OpenCasesLog( - user_id=user_id, - group_id=group_id, - case_name=case_name, - name=skin.name, - skin_name=skin.skin_name, - is_stattrak=skin.is_stattrak, - abrasion=skin.abrasion, - color=skin.color, - price=skin.sell_min_price, - abrasion_value=rand, - create_time=now, - ) - ) - await user.save() - if log_list: - await OpenCasesLog.bulk_create(log_list, 10) - logger.debug(f"添加 {len(log_list)} 条开箱日志", "开箱", session=session) - img_w += 10 - img_h += 10 - w = img_w * 5 - if num < 5: - h = img_h - 10 - w = img_w * num - elif not num % 5: - h = img_h * int(num / 5) - else: - h = img_h * int(num / 5) + img_h - mark_image = BuildImage(w - 10, h - 10, color=(255, 255, 255)) - mark_image = await mark_image.auto_paste(img_list, 5, padding=20) - over_count = max_count - user.today_open_total - result = "" - for color_name in skin_count: - result += f"[{color_name}:{skin_count[color_name]}] " - return MessageUtils.build_message( - [ - f"开启{case_name}武器箱\n剩余开箱次数:{over_count}\n", - mark_image, - f"\n{result[:-1]}\n箱子单价:{case_price}\n总获取金额:{total_price:.2f}\n总花费:{(17 + case_price) * num:.2f}", - ] - ) - - -async def total_open_statistics(user_id: str, group_id: str) -> str: - user, _ = await OpenCasesUser.get_or_create(user_id=user_id, group_id=group_id) - 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_id: str): - user_list = await OpenCasesUser.filter(group_id=str(group_id)).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: - uplist[0] += user.blue_count - uplist[1] += user.blue_st_count - uplist[2] += user.purple_count - uplist[3] += user.purple_st_count - uplist[4] += user.pink_count - uplist[5] += user.pink_st_count - uplist[6] += user.red_count - uplist[7] += user.red_st_count - uplist[8] += user.knife_count - uplist[9] += user.knife_st_count - uplist[10] += user.make_money - uplist[11] += user.total_count - uplist[12] += user.today_open_total - return ( - f"群开箱总数:{uplist[11]}\n" - f"群今日开箱:{uplist[12]}\n" - f"蓝色军规:{uplist[0]}\n" - f"蓝色暗金:{uplist[1]}\n" - f"紫色受限:{uplist[2]}\n" - f"紫色暗金:{uplist[3]}\n" - f"粉色保密:{uplist[4]}\n" - f"粉色暗金:{uplist[5]}\n" - f"红色隐秘:{uplist[6]}\n" - f"红色暗金:{uplist[7]}\n" - f"金色罕见:{uplist[8]}\n" - f"金色暗金:{uplist[9]}\n" - f"花费金额:{uplist[11] * 17}\n" - f"获取金额:{uplist[10]:.2f}" - ) - - -async def get_my_knifes(user_id: str, group_id: str) -> UniMessage: - """获取我的金色 - - 参数: - user_id (str): 用户id - group_id (str): 群号 - - 返回: - MessageFactory: 回复消息或图片 - """ - data_list = await get_old_knife(str(user_id), str(group_id)) - data_list += await OpenCasesLog.filter( - user_id=user_id, group_id=group_id, color="KNIFE" - ).all() - if not data_list: - return MessageUtils.build_message("您木有开出金色级别的皮肤喔...") - length = len(data_list) - if length < 5: - h = 600 - w = length * 540 - elif length % 5 == 0: - h = 600 * int(length / 5) - w = 540 * 5 - else: - h = 600 * int(length / 5) + 600 - w = 540 * 5 - A = BuildImage(w, h) - image_list = [] - for skin in data_list: - name = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - img_path = ( - IMAGE_PATH / "csgo_cases" / cn2py(skin.case_name) / f"{cn2py(name)}.jpg" - ) - knife_img = BuildImage(470, 600, font_size=20) - await knife_img.paste( - BuildImage(470, 470, background=img_path if img_path.exists() else None), - (0, 0), - ) - await knife_img.text( - (5, 500), f"\t{skin.name}|{skin.skin_name}({skin.abrasion})" - ) - await knife_img.text((5, 530), f"\t磨损:{skin.abrasion_value}") - await knife_img.text((5, 560), f"\t价格:{skin.price}") - image_list.append(knife_img) - A = await A.auto_paste(image_list, 5) - return MessageUtils.build_message(A) - - -async def get_old_knife(user_id: str, group_id: str) -> list[OpenCasesLog]: - """获取旧数据字段 - - 参数: - user_id (str): 用户id - group_id (str): 群号 - - 返回: - list[OpenCasesLog]: 旧数据兼容 - """ - user, _ = await OpenCasesUser.get_or_create(user_id=user_id, group_id=group_id) - knifes_name = user.knifes_name - data_list = [] - if knifes_name: - knifes_list = knifes_name[:-1].split(",") - for knife in knifes_list: - try: - if r := re.search( - "(.*)\|\|(.*) \| (.*)\((.*)\) 磨损:(.*), 价格:(.*)", knife - ): - case_name_py = r.group(1) - name = r.group(2) - skin_name = r.group(3) - abrasion = r.group(4) - abrasion_value = r.group(5) - price = r.group(6) - name = name.replace("(StatTrak™)", "") - data_list.append( - OpenCasesLog( - user_id=user_id, - group_id=group_id, - name=name.strip(), - case_name=case_name_py.strip(), - skin_name=skin_name.strip(), - abrasion=abrasion.strip(), - abrasion_value=abrasion_value, - price=price, - ) - ) - except Exception as e: - logger.error( - f"获取兼容旧数据错误: {knife}", - "我的金色", - session=user_id, - group_id=group_id, - e=e, - ) - return data_list - - -async def auto_update(): - """自动更新武器箱""" - if case_list := Config.get_config("open_cases", "DAILY_UPDATE"): - logger.debug("尝试自动更新武器箱", "更新武器箱") - if "ALL" in case_list: - case_list = CASE2ID.keys() - logger.debug(f"预计自动更新武器箱 {len(case_list)} 个", "更新武器箱") - for case_name in case_list: - logger.debug(f"开始自动更新武器箱: {case_name}", "更新武器箱") - try: - await update_skin_data(case_name) - rand = random.randint(300, 500) - logger.info( - f"成功自动更新武器箱: {case_name}, 将在 {rand} 秒后再次更新下一武器箱", - "更新武器箱", - ) - await asyncio.sleep(rand) - except Exception as e: - logger.error(f"自动更新武器箱: {case_name}", e=e) diff --git a/zhenxun/plugins/open_cases/utils.py b/zhenxun/plugins/open_cases/utils.py deleted file mode 100644 index 6fda2265c..000000000 --- a/zhenxun/plugins/open_cases/utils.py +++ /dev/null @@ -1,657 +0,0 @@ -import asyncio -import os -import random -import re -import time -from datetime import datetime, timedelta - -import nonebot -from tortoise.functions import Count - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage, BuildMat, MatType -from zhenxun.utils.utils import cn2py - -from .build_image import generate_skin -from .config import ( - CASE2ID, - CASE_BACKGROUND, - COLOR2NAME, - KNIFE2ID, - NAME2COLOR, - UpdateType, -) -from .models.buff_skin import BuffSkin -from .models.buff_skin_log import BuffSkinLog -from .models.open_cases_user import OpenCasesUser - -# from zhenxun.utils.utils import broadcast_group, cn2py - - -URL = "https://buff.163.com/api/market/goods" - -SELL_URL = "https://buff.163.com/goods" - - -driver = nonebot.get_driver() - -BASE_PATH = IMAGE_PATH / "csgo_cases" - - -class CaseManager: - - CURRENT_CASES = [] - - @classmethod - async def reload(cls): - cls.CURRENT_CASES = [] - case_list = await BuffSkin.filter(color="CASE").values_list( - "case_name", flat=True - ) - for case_name in ( - await BuffSkin.filter(case_name__not="未知武器箱") - .annotate() - .distinct() - .values_list("case_name", flat=True) - ): - for name in case_name.split(","): # type: ignore - if name not in cls.CURRENT_CASES and name in case_list: - cls.CURRENT_CASES.append(name) - - -async def update_skin_data(name: str, is_update_case_name: bool = False) -> str: - """更新箱子内皮肤数据 - - 参数: - name (str): 箱子名称 - is_update_case_name (bool): 是否必定更新所属箱子 - - 返回: - str: 回复内容 - """ - type_ = None - if name in CASE2ID: - type_ = UpdateType.CASE - if name in KNIFE2ID: - type_ = UpdateType.WEAPON_TYPE - if not type_: - return "未在指定武器箱或指定武器类型内" - session = Config.get_config("open_cases", "COOKIE") - if not session: - return "BUFF COOKIE为空捏!" - weapon2case = {} - if type_ == UpdateType.WEAPON_TYPE: - db_data = await BuffSkin.filter(name__contains=name).all() - weapon2case = { - item.name + item.skin_name: item.case_name - for item in db_data - if item.case_name != "未知武器箱" - } - data_list, total = await search_skin_page(name, 1, type_) - if isinstance(data_list, str): - return data_list - for page in range(2, total + 1): - rand_time = random.randint(20, 50) - logger.debug(f"访问随机等待时间: {rand_time}", "开箱更新") - await asyncio.sleep(rand_time) - data_list_, total = await search_skin_page(name, page, type_) - if isinstance(data_list_, list): - data_list += data_list_ - create_list: list[BuffSkin] = [] - update_list: list[BuffSkin] = [] - log_list = [] - now = datetime.now() - exists_id_list = [] - new_weapon2case = {} - for skin in data_list: - if skin.skin_id in exists_id_list: - continue - if skin.case_name: - skin.case_name = ( - skin.case_name.replace("”", "") - .replace("“", "") - .replace("武器箱", "") - .replace(" ", "") - ) - skin.name = skin.name.replace("(★ StatTrak™)", "").replace("(★)", "") - exists_id_list.append(skin.skin_id) - key = skin.name + skin.skin_name - name_ = skin.name + skin.skin_name + skin.abrasion - skin.create_time = now - skin.update_time = now - if UpdateType.WEAPON_TYPE and not skin.case_name: - if is_update_case_name: - case_name = new_weapon2case.get(key) - else: - case_name = weapon2case.get(key) - if not case_name: - if case_list := await get_skin_case(skin.skin_id): - case_name = ",".join(case_list) - rand = random.randint(10, 20) - logger.debug( - f"获取 {skin.name} | {skin.skin_name} 皮肤所属武器箱: {case_name}, 访问随机等待时间: {rand}", - "开箱更新", - ) - await asyncio.sleep(rand) - if not case_name: - case_name = "未知武器箱" - else: - weapon2case[key] = case_name - new_weapon2case[key] = case_name - if skin.case_name == "反恐精英20周年": - skin.case_name = "CS20" - skin.case_name = case_name - if await BuffSkin.exists(skin_id=skin.skin_id): - update_list.append(skin) - else: - create_list.append(skin) - log_list.append( - BuffSkinLog( - name=skin.name, - case_name=skin.case_name, - skin_name=skin.skin_name, - is_stattrak=skin.is_stattrak, - abrasion=skin.abrasion, - color=skin.color, - steam_price=skin.steam_price, - weapon_type=skin.weapon_type, - buy_max_price=skin.buy_max_price, - buy_num=skin.buy_num, - sell_min_price=skin.sell_min_price, - sell_num=skin.sell_num, - sell_reference_price=skin.sell_reference_price, - create_time=now, - ) - ) - name_ = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - for c_name_ in skin.case_name.split(","): - file_path = BASE_PATH / cn2py(c_name_) / f"{cn2py(name_)}.jpg" - if not file_path.exists(): - logger.debug(f"下载皮肤 {name} 图片: {skin.img_url}...", "开箱更新") - await AsyncHttpx.download_file(skin.img_url, file_path) - rand_time = random.randint(1, 10) - await asyncio.sleep(rand_time) - logger.debug(f"图片下载随机等待时间: {rand_time}", "开箱更新") - else: - logger.debug(f"皮肤 {name_} 图片已存在...", "开箱更新") - if create_list: - logger.debug( - f"更新武器箱/皮肤: [{name}], 创建 {len(create_list)} 个皮肤!" - ) - await BuffSkin.bulk_create(set(create_list), 10) - if update_list: - abrasion_list = [] - name_list = [] - skin_name_list = [] - for skin in update_list: - if skin.abrasion not in abrasion_list: - abrasion_list.append(skin.abrasion) - if skin.name not in name_list: - name_list.append(skin.name) - if skin.skin_name not in skin_name_list: - skin_name_list.append(skin.skin_name) - db_data = await BuffSkin.filter( - case_name__contains=name, - skin_name__in=skin_name_list, - name__in=name_list, - abrasion__in=abrasion_list, - ).all() - _update_list = [] - for data in db_data: - for skin in update_list: - if ( - data.name == skin.name - and data.skin_name == skin.skin_name - and data.abrasion == skin.abrasion - ): - data.steam_price = skin.steam_price - data.buy_max_price = skin.buy_max_price - data.buy_num = skin.buy_num - data.sell_min_price = skin.sell_min_price - data.sell_num = skin.sell_num - data.sell_reference_price = skin.sell_reference_price - data.update_time = skin.update_time - _update_list.append(data) - logger.debug( - f"更新武器箱/皮肤: [{name}], 更新 {len(create_list)} 个皮肤!" - ) - await BuffSkin.bulk_update( - _update_list, - [ - "steam_price", - "buy_max_price", - "buy_num", - "sell_min_price", - "sell_num", - "sell_reference_price", - "update_time", - ], - 10, - ) - if log_list: - logger.debug( - f"更新武器箱/皮肤: [{name}], 新增 {len(log_list)} 条皮肤日志!" - ) - await BuffSkinLog.bulk_create(log_list) - if name not in CaseManager.CURRENT_CASES: - CaseManager.CURRENT_CASES.append(name) # type: ignore - return f"更新武器箱/皮肤: [{name}] 成功, 共更新 {len(update_list)} 个皮肤, 新创建 {len(create_list)} 个皮肤!" - - -async def search_skin_page( - s_name: str, page_index: int, type_: UpdateType -) -> tuple[list[BuffSkin] | str, int]: - """查询箱子皮肤 - - 参数: - s_name (str): 箱子/皮肤名称 - page_index (int): 页数 - - 返回: - tuple[list[BuffSkin] | str, int]: BuffSkin - """ - logger.debug( - f"尝试访问武器箱/皮肤: [{s_name}] 页数: [{page_index}]", - "开箱更新", - ) - cookie = {"session": Config.get_config("open_cases", "COOKIE")} - params = { - "game": "csgo", - "page_num": page_index, - "page_size": 80, - "_": time.time(), - "use_suggestio": 0, - } - if type_ == UpdateType.CASE: - params["itemset"] = CASE2ID[s_name] - elif type_ == UpdateType.WEAPON_TYPE: - params["category"] = KNIFE2ID[s_name] - proxy = None - if ip := Config.get_config("open_cases", "BUFF_PROXY"): - proxy = {"http://": ip, "https://": ip} - response = None - error = "" - for i in range(3): - try: - response = await AsyncHttpx.get( - URL, - proxy=proxy, - params=params, - cookies=cookie, # type: ignore - ) - if response.status_code == 200: - break - rand = random.randint(3, 7) - logger.debug( - f"尝试访问武器箱/皮肤第 {i+1} 次访问异常, code: {response.status_code}", - "开箱更新", - ) - await asyncio.sleep(rand) - except Exception as e: - logger.debug( - f"尝试访问武器箱/皮肤第 {i+1} 次访问发生错误 {type(e)}: {e}", "开箱更新" - ) - error = f"{type(e)}: {e}" - if not response: - return f"访问发生异常: {error}", -1 - if response.status_code == 200: - # logger.debug(f"访问BUFF API: {response.text}", "开箱更新") - json_data = response.json() - update_data = [] - if json_data["code"] == "OK": - data_list = json_data["data"]["items"] - for data in data_list: - obj = {} - if type_ == UpdateType.CASE: - obj["case_name"] = s_name - name = data["name"] - try: - logger.debug( - f"武器箱: [{s_name}] 页数: [{page_index}] 正在收录皮肤: [{name}]...", - "开箱更新", - ) - obj["skin_id"] = str(data["id"]) - obj["buy_max_price"] = data["buy_max_price"] # 求购最大金额 - obj["buy_num"] = data["buy_num"] # 当前求购 - goods_info = data["goods_info"] - info = goods_info["info"] - tags = info["tags"] - obj["weapon_type"] = tags["type"]["localized_name"] # 枪械类型 - if obj["weapon_type"] in ["音乐盒", "印花", "探员"]: - continue - elif obj["weapon_type"] in ["匕首", "手套"]: - obj["color"] = "KNIFE" - obj["name"] = data["short_name"].split("(")[0].strip() # 名称 - elif obj["weapon_type"] in ["武器箱"]: - obj["color"] = "CASE" - obj["name"] = data["short_name"] - else: - obj["color"] = NAME2COLOR[tags["rarity"]["localized_name"]] - obj["name"] = tags["weapon"]["localized_name"] # 名称 - if obj["weapon_type"] not in ["武器箱"]: - obj["abrasion"] = tags["exterior"]["localized_name"] # 磨损 - obj["is_stattrak"] = "StatTrak" in tags["quality"]["localized_name"] # type: ignore # 是否暗金 - if not obj["color"]: - obj["color"] = NAME2COLOR[ - tags["rarity"]["localized_name"] - ] # 品质颜色 - else: - obj["abrasion"] = "CASE" - obj["skin_name"] = ( - data["short_name"].split("|")[-1].strip() - ) # 皮肤名称 - obj["img_url"] = goods_info["original_icon_url"] # 图片url - obj["steam_price"] = goods_info["steam_price_cny"] # steam价格 - obj["sell_min_price"] = data["sell_min_price"] # 售卖最低价格 - obj["sell_num"] = data["sell_num"] # 售卖数量 - obj["sell_reference_price"] = data[ - "sell_reference_price" - ] # 参考价格 - update_data.append(BuffSkin(**obj)) - except Exception as e: - logger.error( - f"更新武器箱: [{s_name}] 皮肤: [{s_name}] 错误", - e=e, - ) - logger.debug( - f"访问武器箱: [{s_name}] 页数: [{page_index}] 成功并收录完成", - "开箱更新", - ) - return update_data, json_data["data"]["total_page"] - else: - logger.warning(f'访问BUFF失败: {json_data["error"]}') - return f'访问失败: {json_data["error"]}', -1 - return f"访问失败, 状态码: {response.status_code}", -1 - - -async def build_case_image(case_name: str | None) -> BuildImage | str: - """构造武器箱图片 - - 参数: - case_name (str): 名称 - - 返回: - BuildImage | str: 图片 - """ - background = random.choice(os.listdir(CASE_BACKGROUND)) - background_img = BuildImage(0, 0, background=CASE_BACKGROUND / background) - if case_name: - log_list = ( - await BuffSkinLog.filter(case_name__contains=case_name) - .annotate(count=Count("id")) - .group_by("skin_name") - .values_list("skin_name", "count") - ) - skin_list_ = await BuffSkin.filter(case_name__contains=case_name).all() - skin2count = {item[0]: item[1] for item in log_list} - case = None - skin_list: list[BuffSkin] = [] - exists_name = [] - for skin in skin_list_: - if skin.color == "CASE": - case = skin - else: - name = skin.name + skin.skin_name - if name not in exists_name: - skin_list.append(skin) - exists_name.append(name) - generate_img = {} - for skin in skin_list: - skin_img = await generate_skin(skin, skin2count.get(skin.skin_name, 0)) - if skin_img: - if not generate_img.get(skin.color): - generate_img[skin.color] = [] - generate_img[skin.color].append(skin_img) - skin_image_list = [] - for color in COLOR2NAME: - if generate_img.get(color): - skin_image_list = skin_image_list + generate_img[color] - img = skin_image_list[0] - img_w, img_h = img.size - total_size = (img_w + 25) * (img_h + 10) * len(skin_image_list) # 总面积 - new_size = get_bk_image_size(total_size, background_img.size, img.size, 250) - A = BuildImage( - new_size[0] + 50, new_size[1], background=CASE_BACKGROUND / background - ) - await A.filter("GaussianBlur", 2) - if case: - case_img = await generate_skin( - case, skin2count.get(f"{case_name}武器箱", 0) - ) - if case_img: - await A.paste(case_img, (25, 25)) - w = 25 - h = 230 - skin_image_list.reverse() - for image in skin_image_list: - await A.paste(image, (w, h)) - w += image.width + 20 - if w + image.width - 25 > A.width: - h += image.height + 10 - w = 25 - if h + img_h + 100 < A.height: - await A.crop((0, 0, A.width, h + img_h + 100)) - return A - else: - log_list = ( - await BuffSkinLog.filter(color="CASE") - .annotate(count=Count("id")) - .group_by("case_name") - .values_list("case_name", "count") - ) - name2count = {item[0]: item[1] for item in log_list} - skin_list = await BuffSkin.filter(color="CASE").all() - image_list: list[BuildImage] = [] - for skin in skin_list: - if img := await generate_skin(skin, name2count[skin.case_name]): - image_list.append(img) - if not image_list: - return "未收录武器箱" - w = 25 - h = 150 - img = image_list[0] - img_w, img_h = img.size - total_size = (img_w + 25) * (img_h + 10) * len(image_list) # 总面积 - - new_size = get_bk_image_size(total_size, background_img.size, img.size, 155) - A = BuildImage( - new_size[0] + 50, new_size[1], background=CASE_BACKGROUND / background - ) - await A.filter("GaussianBlur", 2) - bk_img = BuildImage( - img_w, 120, color=(25, 25, 25, 100), font_size=60, font="CJGaoDeGuo.otf" - ) - await bk_img.text( - (0, 0), - f"已收录 {len(image_list)} 个武器箱", - (255, 255, 255), - center_type="center", - ) - await A.paste(bk_img, (10, 10), "width") - for image in image_list: - await A.paste(image, (w, h)) - w += image.width + 20 - if w + image.width - 25 > A.width: - h += image.height + 10 - w = 25 - if h + img_h + 100 < A.height: - await A.crop((0, 0, A.width, h + img_h + 100)) - return A - - -def get_bk_image_size( - total_size: int, - base_size: tuple[int, int], - img_size: tuple[int, int], - extra_height: int = 0, -) -> tuple[int, int]: - """获取所需背景大小且不改变图片长宽比 - - 参数: - total_size (int): 总面积 - base_size (Tuple[int, int]): 初始背景大小 - img_size (Tuple[int, int]): 贴图大小 - - 返回: - tuple[int, int]: 满足所有贴图大小 - """ - bk_w, bk_h = base_size - img_w, img_h = img_size - is_add_title_size = False - left_dis = 0 - right_dis = 0 - old_size = (0, 0) - new_size = (0, 0) - ratio = 1.1 - while 1: - w_ = int(ratio * bk_w) - h_ = int(ratio * bk_h) - size = w_ * h_ - if size < total_size: - left_dis = size - else: - right_dis = size - r = w_ / (img_w + 25) - if right_dis and r - int(r) < 0.1: - if not is_add_title_size and extra_height: - total_size = int(total_size + w_ * extra_height) - is_add_title_size = True - right_dis = 0 - continue - if total_size - left_dis > right_dis - total_size: - new_size = (w_, h_) - else: - new_size = old_size - break - old_size = (w_, h_) - ratio += 0.1 - return new_size - - -async def get_skin_case(id_: str) -> list[str] | None: - """获取皮肤所在箱子 - - 参数: - id_ (str): 皮肤id - - 返回: - list[str] | None: 武器箱名称 - """ - url = f"{SELL_URL}/{id_}" - proxy = None - if ip := Config.get_config("open_cases", "BUFF_PROXY"): - proxy = {"http://": ip, "https://": ip} - response = await AsyncHttpx.get( - url, - proxy=proxy, - ) - if response.status_code == 200: - text = response.text - if r := re.search('', text): - case_list = [] - for s in r.group(1).split(","): - if "武器箱" in s: - case_list.append( - s.replace("”", "") - .replace("“", "") - .replace('"', "") - .replace("'", "") - .replace("武器箱", "") - .replace(" ", "") - ) - return case_list - else: - logger.debug(f"访问皮肤所属武器箱异常 url: {url} code: {response.status_code}") - return None - - -async def init_skin_trends( - name: str, skin: str, abrasion: str, day: int = 7 -) -> BuildImage | None: - date = datetime.now() - timedelta(days=day) - log_list = ( - await BuffSkinLog.filter( - name__contains=name.upper(), - skin_name=skin, - abrasion__contains=abrasion, - create_time__gt=date, - is_stattrak=False, - ) - .order_by("create_time") - .limit(day * 5) - .all() - ) - if not log_list: - return None - date_list = [] - price_list = [] - for log in log_list: - date = str(log.create_time.date()) - if date not in date_list: - date_list.append(date) - price_list.append(log.sell_min_price) - graph = BuildMat(MatType.LINE) - graph.data = price_list - graph.title = f"{name}({skin})价格趋势({day})" - graph.x_index = date_list - return await graph.build() - - -async def reset_count_daily(): - """ - 重置每日开箱 - """ - try: - await OpenCasesUser.all().update(today_open_total=0) - # await broadcast_group( - # "[[_task|open_case_reset_remind]]今日开箱次数重置成功", - # log_cmd="开箱重置提醒", - # ) - except Exception as e: - logger.error(f"开箱重置错误", e=e) - - -async def download_image(case_name: str | None = None): - """下载皮肤图片 - - 参数: - case_name: 箱子名称. - """ - skin_list = ( - await BuffSkin.filter(case_name=case_name).all() - if case_name - else await BuffSkin.all() - ) - for skin in skin_list: - name_ = skin.name + "-" + skin.skin_name + "-" + skin.abrasion - for c_name_ in skin.case_name.split(","): - try: - pass - # file_path = BASE_PATH / cn2py(c_name_) / f"{cn2py(name_)}.jpg" - # if not file_path.exists(): - # logger.debug( - # f"下载皮肤 {c_name_}/{skin.name} 图片: {skin.img_url}...", - # "开箱图片更新", - # ) - # await AsyncHttpx.download_file(skin.img_url, file_path) - # rand_time = random.randint(1, 5) - # await asyncio.sleep(rand_time) - # logger.debug(f"图片下载随机等待时间: {rand_time}", "开箱图片更新") - # else: - # logger.debug( - # f"皮肤 {c_name_}/{skin.name} 图片已存在...", "开箱图片更新" - # ) - except Exception as e: - logger.error( - f"下载皮肤 {c_name_}/{skin.name} 图片: {skin.img_url}", - "开箱图片更新", - e=e, - ) - - -@driver.on_startup -async def _(): - await CaseManager.reload() diff --git a/zhenxun/plugins/parse_bilibili/__init__.py b/zhenxun/plugins/parse_bilibili/__init__.py deleted file mode 100644 index 791c56f0a..000000000 --- a/zhenxun/plugins/parse_bilibili/__init__.py +++ /dev/null @@ -1,186 +0,0 @@ -import re -import time - -import ujson as json -from nonebot import on_message -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Hyper, Image, UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.configs.utils import PluginExtraData, RegisterConfig, Task -from zhenxun.models.task_info import TaskInfo -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -from .information_container import InformationContainer -from .parse_url import parse_bili_url - -__plugin_meta__ = PluginMetadata( - name="B站内容解析", - description="B站内容解析", - usage=""" - usage: - 被动监听插件,解析B站视频、直播、专栏,支持小程序卡片及文本链接,5分钟内不解析相同内容 - """.strip(), - extra=PluginExtraData( - author="leekooyo", - version="0.1", - plugin_type=PluginType.DEPENDANT, - menu_type="其他", - configs=[ - RegisterConfig( - module="_task", - key="DEFAULT_BILIBILI_PARSE", - value=True, - default_value=True, - help="被动 B站转发解析 进群默认开关状态", - type=bool, - ) - ], - tasks=[Task(module="bilibili_parse", name="b站转发解析")], - ).dict(), -) - - -async def _rule(session: EventSession) -> bool: - return not await TaskInfo.is_block("bilibili_parse", session.id3 or session.id2) - - -_matcher = on_message(priority=1, block=False, rule=_rule) - -_tmp = {} - - -@_matcher.handle() -async def _(session: EventSession, message: UniMsg): - information_container = InformationContainer() - # 判断文本消息内容是否相关 - match = None - # 判断文本消息和小程序的内容是否指向一个b站链接 - get_url = None - # 判断文本消息是否包含视频相关内容 - vd_flag = False - # 设定时间阈值,阈值之下不会解析重复内容 - repet_second = 300 - # 尝试解析小程序消息 - data = message[0] - if isinstance(data, Hyper) and data.raw: - try: - data = json.loads(data.raw) - except (IndexError, KeyError): - data = None - if data: - # 获取相关数据 - meta_data = data.get("meta", {}) - news_value = meta_data.get("news", {}) - detail_1_value = meta_data.get("detail_1", {}) - qqdocurl_value = detail_1_value.get("qqdocurl", {}) - jumpUrl_value = news_value.get("jumpUrl", {}) - get_url = (qqdocurl_value if qqdocurl_value else jumpUrl_value).split("?")[ - 0 - ] - # 解析文本消息 - elif msg := message.extract_plain_text(): - # 消息中含有视频号 - if "bv" in msg.lower() or "av" in msg.lower(): - match = re.search(r"((?=(?:bv|av))([A-Za-z0-9]+))", msg, re.IGNORECASE) - vd_flag = True - - # 消息中含有b23的链接,包括视频、专栏、动态、直播 - elif "https://b23.tv" in msg: - match = re.search(r"https://b23\.tv/[^?\s]+", msg, re.IGNORECASE) - - # 检查消息中是否含有直播、专栏、动态链接 - elif any( - keyword in msg - for keyword in [ - "https://live.bilibili.com/", - "https://www.bilibili.com/read/", - "https://www.bilibili.com/opus/", - "https://t.bilibili.com/", - ] - ): - pattern = r"https://(live|www\.bilibili\.com/read|www\.bilibili\.com/opus|t\.bilibili\.com)/[^?\s]+" - match = re.search(pattern, msg) - - # 匹配成功,则获取链接 - if match: - if vd_flag: - number = match.group(1) - get_url = f"https://www.bilibili.com/video/{number}" - else: - get_url = match.group() - - if get_url: - # 将链接统一发送给处理函数 - data = await parse_bili_url(get_url, information_container) - if data.vd_info: - # 判断一定时间内是否解析重复内容,或者是第一次解析 - if ( - data.vd_url in _tmp.keys() - and time.time() - _tmp[data.vd_url] > repet_second - ) or data.vd_url not in _tmp.keys(): - pic = data.vd_info.get("pic", "") # 封面 - aid = data.vd_info.get("aid", "") # av号 - title = data.vd_info.get("title", "") # 标题 - author = data.vd_info.get("owner", {}).get("name", "") # UP主 - reply = data.vd_info.get("stat", {}).get("reply", "") # 回复 - favorite = data.vd_info.get("stat", {}).get("favorite", "") # 收藏 - coin = data.vd_info.get("stat", {}).get("coin", "") # 投币 - like = data.vd_info.get("stat", {}).get("like", "") # 点赞 - danmuku = data.vd_info.get("stat", {}).get("danmaku", "") # 弹幕 - ctime = data.vd_info["ctime"] - date = time.strftime("%Y-%m-%d", time.localtime(ctime)) - logger.info( - f"解析bilibili转发 {data.vd_url}", "b站解析", session=session - ) - _tmp[data.vd_url] = time.time() - _path = TEMP_PATH / f"{aid}.jpg" - await AsyncHttpx.download_file(pic, _path) - await MessageUtils.build_message( - [ - _path, - f"av{aid}\n标题:{title}\nUP:{author}\n上传日期:{date}\n回复:{reply},收藏:{favorite},投币:{coin}\n点赞:{like},弹幕:{danmuku}\n{data.vd_url}", - ] - ).send() - - elif data.live_info: - if ( - data.live_url in _tmp.keys() - and time.time() - _tmp[data.live_url] > repet_second - ) or data.live_url not in _tmp.keys(): - uid = data.live_info.get("uid", "") # 主播uid - title = data.live_info.get("title", "") # 直播间标题 - description = data.live_info.get( - "description", "" - ) # 简介,可能会出现标签 - user_cover = data.live_info.get("user_cover", "") # 封面 - keyframe = data.live_info.get("keyframe", "") # 关键帧画面 - live_time = data.live_info.get("live_time", "") # 开播时间 - area_name = data.live_info.get("area_name", "") # 分区 - parent_area_name = data.live_info.get("parent_area_name", "") # 父分区 - logger.info( - f"解析bilibili转发 {data.live_url}", "b站解析", session=session - ) - _tmp[data.live_url] = time.time() - await MessageUtils.build_message( - [ - Image(url=user_cover), - f"开播用户:https://space.bilibili.com/{uid}\n开播时间:{live_time}\n直播分区:{parent_area_name}——>{area_name}\n标题:{title}\n简介:{description}\n直播截图:\n", - Image(url=keyframe), - f"{data.live_url}", - ] - ).send() - elif data.image_info: - if ( - data.image_url in _tmp.keys() - and time.time() - _tmp[data.image_url] > repet_second - ) or data.image_url not in _tmp.keys(): - logger.info( - f"解析bilibili转发 {data.image_url}", "b站解析", session=session - ) - _tmp[data.image_url] = time.time() - await data.image_info.send() diff --git a/zhenxun/plugins/parse_bilibili/get_image.py b/zhenxun/plugins/parse_bilibili/get_image.py deleted file mode 100644 index 3b8c70c86..000000000 --- a/zhenxun/plugins/parse_bilibili/get_image.py +++ /dev/null @@ -1,106 +0,0 @@ -import os -import re -from pathlib import Path - -from nonebot_plugin_alconna import UniMessage - -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncPlaywright -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.user_agent import get_user_agent_str - - -async def resize(path: Path): - """调整图像大小的异步函数 - - 参数: - path (str): 图像文件路径 - """ - A = BuildImage.open(path) - await A.resize(0.8) - await A.save(path) - - -async def get_image(url) -> UniMessage | None: - """获取Bilibili链接的截图,并返回base64格式的图片 - - 参数: - url (str): Bilibili链接 - - 返回: - Image: Image - """ - cv_match = None - opus_match = None - t_opus_match = None - - cv_number = None - opus_number = None - t_opus_number = None - - # 提取cv、opus、t_opus的编号 - url = url.split("?")[0] - cv_match = re.search(r"read/cv([A-Za-z0-9]+)", url, re.IGNORECASE) - opus_match = re.search(r"opus/([A-Za-z0-9]+)", url, re.IGNORECASE) - t_opus_match = re.search(r"https://t\.bilibili\.com/(\d+)", url, re.IGNORECASE) - - if cv_match: - cv_number = cv_match.group(1) - elif opus_match: - opus_number = opus_match.group(1) - elif t_opus_match: - t_opus_number = t_opus_match.group(1) - - screenshot_path = None - - # 根据编号构建保存路径 - if cv_number: - screenshot_path = TEMP_PATH / "bilibili_cv_{cv_number}.png" - elif opus_number: - screenshot_path = TEMP_PATH / "bilibili_opus_{opus_number}.png" - elif t_opus_number: - screenshot_path = TEMP_PATH / "bilibili_opus_{t_opus_number}.png" - # t.bilibili.com和https://www.bilibili.com/opus在内容上是一样的,为便于维护,调整url至https://www.bilibili.com/opus/ - url = f"https://www.bilibili.com/opus/{t_opus_number}" - if screenshot_path: - try: - # 如果文件不存在,进行截图 - if not screenshot_path.exists(): - # 创建页面 - try: - async with AsyncPlaywright.new_page() as page: - await page.set_viewport_size({"width": 5120, "height": 2560}) - # 设置请求拦截器 - await page.route( - re.compile(r"(\.png$)|(\.jpg$)"), - lambda route: route.abort(), - ) - # 访问链接 - await page.goto(url, wait_until="networkidle", timeout=10000) - # 根据不同的链接结构,设置对应的CSS选择器 - if cv_number: - css = "#app > div" - elif opus_number or t_opus_number: - css = "#app > div.opus-detail > div.bili-opus-view" - # 点击对应的元素 - await page.click(css) - # 查询目标元素 - div = await page.query_selector(css) - # 对目标元素进行截图 - await div.screenshot( # type: ignore - path=screenshot_path, - timeout=100000, - animations="disabled", - type="png", - ) - # 异步执行调整截图大小的操作 - await resize(screenshot_path) - except Exception as e: - logger.warning(f"尝试解析bilibili转发失败", e=e) - return None - return MessageUtils.build_message(screenshot_path) - except Exception as e: - logger.error(f"尝试解析bilibili转发失败", e=e) - return None diff --git a/zhenxun/plugins/parse_bilibili/information_container.py b/zhenxun/plugins/parse_bilibili/information_container.py deleted file mode 100644 index ddb685f8b..000000000 --- a/zhenxun/plugins/parse_bilibili/information_container.py +++ /dev/null @@ -1,53 +0,0 @@ -class InformationContainer: - def __init__( - self, - vd_info=None, - live_info=None, - vd_url=None, - live_url=None, - image_info=None, - image_url=None, - ): - self._vd_info = vd_info - self._live_info = live_info - self._vd_url = vd_url - self._live_url = live_url - self._image_info = image_info - self._image_url = image_url - - @property - def vd_info(self): - return self._vd_info - - @property - def live_info(self): - return self._live_info - - @property - def vd_url(self): - return self._vd_url - - @property - def live_url(self): - return self._live_url - - @property - def image_info(self): - return self._image_info - - @property - def image_url(self): - return self._image_url - - def update(self, updates): - """ - 更新多个信息的通用方法 - Args: - updates (dict): 包含信息类型和对应新值的字典 - """ - for info_type, new_value in updates.items(): - if hasattr(self, f"_{info_type}"): - setattr(self, f"_{info_type}", new_value) - - def get_information(self): - return self diff --git a/zhenxun/plugins/parse_bilibili/parse_url.py b/zhenxun/plugins/parse_bilibili/parse_url.py deleted file mode 100644 index b4e2a1fe4..000000000 --- a/zhenxun/plugins/parse_bilibili/parse_url.py +++ /dev/null @@ -1,65 +0,0 @@ -import aiohttp -from bilireq import live, video - -from zhenxun.utils.user_agent import get_user_agent - -from .get_image import get_image -from .information_container import InformationContainer - - -async def parse_bili_url(get_url: str, information_container: InformationContainer): - """解析Bilibili链接,获取相关信息 - - 参数: - get_url (str): 待解析的Bilibili链接 - information_container (InformationContainer): 信息容器 - - 返回: - dict: 包含解析得到的信息的字典 - """ - response_url = "" - - # 去除链接末尾的斜杠 - if get_url[-1] == "/": - get_url = get_url[:-1] - - # 发起HTTP请求,获取重定向后的链接 - async with aiohttp.ClientSession(headers=get_user_agent()) as session: - async with session.get( - get_url, - timeout=7, - ) as response: - response_url = str(response.url).split("?")[0] - - # 去除重定向后链接末尾的斜杠 - if response_url[-1] == "/": - response_url = response_url[:-1] - - # 根据不同类型的链接进行处理 - if response_url.startswith( - ("https://www.bilibili.com/video", "https://m.bilibili.com/video/") - ): - vd_url = response_url - vid = vd_url.split("/")[-1] - vd_info = await video.get_video_base_info(vid) - information_container.update({"vd_info": vd_info, "vd_url": vd_url}) - - elif response_url.startswith("https://live.bilibili.com"): - live_url = response_url - liveid = live_url.split("/")[-1] - live_info = await live.get_room_info_by_id(liveid) - information_container.update({"live_info": live_info, "live_url": live_url}) - - elif response_url.startswith("https://www.bilibili.com/read"): - cv_url = response_url - image_info = await get_image(cv_url) - information_container.update({"image_info": image_info, "image_url": cv_url}) - - elif response_url.startswith( - ("https://www.bilibili.com/opus", "https://t.bilibili.com") - ): - opus_url = response_url - image_info = await get_image(opus_url) - information_container.update({"image_info": image_info, "image_url": opus_url}) - - return information_container.get_information() diff --git a/zhenxun/plugins/pid_search.py b/zhenxun/plugins/pid_search.py deleted file mode 100644 index 97fc4d40f..000000000 --- a/zhenxun/plugins/pid_search.py +++ /dev/null @@ -1,125 +0,0 @@ -from asyncio.exceptions import TimeoutError - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import change_pixiv_image_links -from zhenxun.utils.withdraw_manage import WithdrawManager - -__plugin_meta__ = PluginMetadata( - name="pid搜索", - description="通过 pid 搜索图片", - usage=""" - usage: - 通过 pid 搜索图片 - 指令: - p搜 [pid] - """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1").dict(), -) - - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net", -} - -_matcher = on_alconna( - Alconna("p搜", Args["pid", str]), aliases={"P搜"}, priority=5, block=True -) - - -@_matcher.handle() -async def _(pid: Match[int]): - if pid.available: - _matcher.set_path_arg("pid", pid.result) - - -@_matcher.got_path("pid", prompt="需要查询的图片PID是?或发送'取消'结束搜索") -async def _(bot: Bot, session: EventSession, arparma: Arparma, pid: str): - url = Config.get_config("hibiapi", "HIBIAPI") + "/api/pixiv/illust" - if pid in ["取消", "算了"]: - await Text("已取消操作...").finish() - if not pid.isdigit(): - await Text("pid必须为数字...").finish() - for _ in range(3): - try: - data = ( - await AsyncHttpx.get( - url, - params={"id": pid}, - timeout=5, - ) - ).json() - except TimeoutError: - pass - except Exception as e: - logger.error( - f"pixiv pid 搜索发生了一些错误...", - arparma.header_result, - session=session, - e=e, - ) - await MessageUtils.build_message(f"发生了一些错误..{type(e)}:{e}").finish() - else: - if data.get("error"): - await MessageUtils.build_message(data["error"]["user_message"]).finish( - reply_to=True - ) - data = data["illust"] - if not data["width"] and not data["height"]: - await MessageUtils.build_message( - f"没有搜索到 PID:{pid} 的图片" - ).finish(reply_to=True) - pid = data["id"] - title = data["title"] - author = data["user"]["name"] - author_id = data["user"]["id"] - image_list = [] - try: - image_list.append(data["meta_single_page"]["original_image_url"]) - except KeyError: - for image_url in data["meta_pages"]: - image_list.append(image_url["image_urls"]["original"]) - for i, img_url in enumerate(image_list): - img_url = change_pixiv_image_links(img_url) - if not await AsyncHttpx.download_file( - img_url, - TEMP_PATH / f"pid_search_{session.id1}_{i}.png", - headers=headers, - ): - await MessageUtils.build_message("图片下载失败了...").finish( - reply_to=True - ) - tmp = "" - if session.id3 or session.id2: - tmp = "\n【注】将在30后撤回......" - receipt = await MessageUtils.build_message( - [ - f"title:{title}\n" - f"pid:{pid}\n" - f"author:{author}\n" - f"author_id:{author_id}\n", - TEMP_PATH / f"pid_search_{session.id1}_{i}.png", - f"{tmp}", - ] - ).send() - logger.info( - f" 查询图片 PID:{pid}", arparma.header_result, session=session - ) - if session.id3 or session.id2: - await WithdrawManager.withdraw_message( - bot, receipt.msg_ids[0]["message_id"], 30 # type: ignore - ) - break - else: - await Text("图片下载失败了...").send(reply_to=True) diff --git a/zhenxun/plugins/pix_gallery/__init__.py b/zhenxun/plugins/pix_gallery/__init__.py deleted file mode 100644 index d549a249a..000000000 --- a/zhenxun/plugins/pix_gallery/__init__.py +++ /dev/null @@ -1,62 +0,0 @@ -from pathlib import Path -from typing import Tuple - -import nonebot - -from zhenxun.configs.config import Config - -Config.add_plugin_config( - "hibiapi", - "HIBIAPI", - "https://api.obfs.dev", - 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( - "pix", - "PIX_IMAGE_SIZE", - "master", - help="PIX图库下载的画质 可能的值:original:原图,master:缩略图(加快发送速度)", - default_value="master", -) -Config.add_plugin_config( - "pix", - "SEARCH_HIBIAPI_BOOKMARKS", - 5000, - help="最低收藏,PIX使用HIBIAPI搜索图片时达到最低收藏才会添加至图库", - default_value=5000, - type=int, -) -Config.add_plugin_config( - "pix", - "WITHDRAW_PIX_MESSAGE", - (0, 1), - help="自动撤回,参1:延迟撤回色图时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)", - default_value=(0, 1), - type=Tuple[int, int], -) -Config.add_plugin_config( - "pix", - "PIX_OMEGA_PIXIV_RATIO", - (10, 0), - help="PIX图库 与 额外图库OmegaPixivIllusts 混合搜索的比例 参1:PIX图库 参2:OmegaPixivIllusts扩展图库(没有此图库请设置为0)", - default_value=(10, 0), - type=Tuple[int, int], -) -Config.add_plugin_config( - "pix", "TIMEOUT", 10, help="下载图片超时限制(秒)", default_value=10, type=int -) - -Config.add_plugin_config( - "pix", - "SHOW_INFO", - True, - help="是否显示图片的基本信息,如PID等", - default_value=True, - type=bool, -) - -Config.set_name("pix", "PIX图库") - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/pix_gallery/_data_source.py b/zhenxun/plugins/pix_gallery/_data_source.py deleted file mode 100644 index 7e9db2219..000000000 --- a/zhenxun/plugins/pix_gallery/_data_source.py +++ /dev/null @@ -1,426 +0,0 @@ -import asyncio -import math -from asyncio.exceptions import TimeoutError -from asyncio.locks import Semaphore -from copy import deepcopy -from pathlib import Path - -import aiofiles -from httpx import ConnectError - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage -from zhenxun.utils.utils import change_img_md5, change_pixiv_image_links - -from ._model.omega_pixiv_illusts import OmegaPixivIllusts -from ._model.pixiv import Pixiv - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net/", -} - -HIBIAPI = Config.get_config("hibiapi", "HIBIAPI") -if not HIBIAPI: - HIBIAPI = "https://api.obfs.dev" -HIBIAPI = HIBIAPI[:-1] if HIBIAPI[-1] == "/" else HIBIAPI - - -async def start_update_image_url( - current_keyword: list[str], black_pid: list[str], is_pid: bool -) -> tuple[int, int]: - """开始更新图片url - - 参数: - current_keyword: 关键词 - black_pid: 黑名单pid - is_pid: pid强制更新不受限制 - - 返回: - tuple[int, int]: pid数量和图片数量 - """ - global HIBIAPI - pid_count = 0 - pic_count = 0 - tasks = [] - semaphore = asyncio.Semaphore(10) - for keyword in current_keyword: - for page in range(1, 110): - if keyword.startswith("uid:"): - url = f"{HIBIAPI}/api/pixiv/member_illust" - params = {"id": keyword[4:], "page": page} - if page == 30: - break - elif keyword.startswith("pid:"): - url = f"{HIBIAPI}/api/pixiv/illust" - params = {"id": keyword[4:]} - else: - url = f"{HIBIAPI}/api/pixiv/search" - params = {"word": keyword, "page": page} - tasks.append( - asyncio.ensure_future( - search_image( - url, keyword, params, semaphore, page, black_pid, is_pid - ) - ) - ) - if keyword.startswith("pid:"): - break - result = await asyncio.gather(*tasks) - for x in result: - pid_count += x[0] - pic_count += x[1] - return pid_count, pic_count - - -async def search_image( - url: str, - keyword: str, - params: dict, - semaphore: Semaphore, - page: int = 1, - black: list[str] = [], - is_pid: bool = False, -) -> tuple[int, int]: - """搜索图片 - - 参数: - url: 搜索url - keyword: 关键词 - params: params参数 - semaphore: semaphore - page: 页面 - black: pid黑名单 - is_pid: pid强制更新不受限制 - - 返回: - tuple[int, int]: pid数量和图片数量 - """ - tmp_pid = [] - 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"] - 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") - ) - and len(img_urls) < 10 - and _check_black(img_urls, black) - ) or is_pid: - 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 - ): - 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: - 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 - - -async def get_image(img_url: str, user_id: str) -> Path | None: - """下载图片 - - 参数: - img_url: 图片url - user_id: 用户id - - 返回: - Path | None: 图片名称 - """ - if "https://www.pixiv.net/artworks" in img_url: - pid = img_url.rsplit("/", maxsplit=1)[-1] - params = {"id": pid} - for _ in range(3): - try: - response = await AsyncHttpx.get( - f"{HIBIAPI}/api/pixiv/illust", params=params - ) - if response.status_code == 200: - data = response.json() - if data.get("illust"): - if data["illust"]["page_count"] == 1: - img_url = data["illust"]["meta_single_page"][ - "original_image_url" - ] - else: - 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"), - ) - 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"), - ) - if response.status_code == 404: - img_url = old_img_url - continue - async with aiofiles.open( - 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" - ) - return TEMP_PATH / f"pix_{user_id}_{img_url.split('/')[-1][:-4]}.jpg" - except TimeoutError: - logger.warning(f"PIX:{img_url} 图片下载超时...") - except ConnectError: - logger.warning(f"PIX:{img_url} 图片下载连接失败...") - return None - - -async def uid_pid_exists(id_: str) -> bool: - """检测 pid/uid 是否有效 - - 参数: - id_: pid/uid - - 返回: - bool: 是否有效 - """ - if id_.startswith("uid:"): - url = f"{HIBIAPI}/api/pixiv/member" - elif id_.startswith("pid:"): - url = f"{HIBIAPI}/api/pixiv/illust" - else: - return False - params = {"id": int(id_[4:])} - data = (await AsyncHttpx.get(url, params=params)).json() - if data.get("error"): - return False - return True - - -async def get_keyword_num(keyword: str) -> tuple[int, int, int, int, int]: - """查看图片相关 tag 数量 - - 参数: - keyword: 关键词tag - - 返回: - tuple[int, int, int, int, int]: 总数, r18数, Omg图库总数, Omg图库色图数, Omg图库r18数 - """ - count, r18_count = await Pixiv.get_keyword_num(keyword.split()) - count_, setu_count, r18_count_ = await OmegaPixivIllusts.get_keyword_num( - keyword.split() - ) - return count, r18_count, count_, setu_count, r18_count_ - - -async def remove_image(pid: int, img_p: str | None): - """删除置顶图片 - - 参数: - pid: pid - img_p: 图片 p 如 p0,p1 等 - """ - if img_p: - if "p" not in img_p: - img_p = f"p{img_p}" - if img_p: - await Pixiv.filter(pid=pid, img_p=img_p).delete() - else: - await Pixiv.filter(pid=pid).delete() - - -async def gen_keyword_pic( - _pass_keyword: list[str], not_pass_keyword: list[str], is_superuser: bool -) -> BuildImage: - """已通过或未通过的所有关键词/uid/pid - - 参数: - _pass_keyword: 通过列表 - not_pass_keyword: 未通过列表 - is_superuser: 是否超级用户 - - 返回: - BuildImage: 数据图片 - """ - _keyword = [ - x - for x in _pass_keyword - if not x.startswith("uid:") - and not x.startswith("pid:") - and not x.startswith("black:") - ] - _uid = [x for x in _pass_keyword if x.startswith("uid:")] - _pid = [x for x in _pass_keyword if x.startswith("pid:")] - _n_keyword = [ - x - for x in not_pass_keyword - if not x.startswith("uid:") - and not x.startswith("pid:") - and not x.startswith("black:") - ] - _n_uid = [ - x - for x in not_pass_keyword - if x.startswith("uid:") and not x.startswith("black:") - ] - _n_pid = [ - x - for x in not_pass_keyword - if x.startswith("pid:") and not x.startswith("black:") - ] - img_width = 0 - img_data = { - "_keyword": {"width": 0, "data": _keyword}, - "_uid": {"width": 0, "data": _uid}, - "_pid": {"width": 0, "data": _pid}, - "_n_keyword": {"width": 0, "data": _n_keyword}, - "_n_uid": {"width": 0, "data": _n_uid}, - "_n_pid": {"width": 0, "data": _n_pid}, - } - for x in list(img_data.keys()): - img_data[x]["width"] = math.ceil(len(img_data[x]["data"]) / 40) - img_width += img_data[x]["width"] * 200 - if not is_superuser: - img_width = ( - img_width - - ( - img_data["_n_keyword"]["width"] - + img_data["_n_uid"]["width"] - + img_data["_n_pid"]["width"] - ) - * 200 - ) - del img_data["_n_keyword"] - del img_data["_n_pid"] - del img_data["_n_uid"] - current_width = 0 - A = BuildImage(img_width, 1100) - for x in list(img_data.keys()): - if img_data[x]["data"]: - # img = BuildImage(img_data[x]["width"] * 200, 1100, 200, 1100, font_size=40) - img = BuildImage(img_data[x]["width"] * 200, 1100, font_size=40) - start_index = 0 - end_index = 40 - total_index = img_data[x]["width"] * 40 - for _ in range(img_data[x]["width"]): - tmp = BuildImage(198, 1100, font_size=20) - text_img = BuildImage(198, 100, font_size=50) - key_str = "\n".join( - [key for key in img_data[x]["data"][start_index:end_index]] - ) - await tmp.text((10, 100), key_str) - if x.find("_n") == -1: - await text_img.text((24, 24), "已收录") - else: - await text_img.text((24, 24), "待收录") - await tmp.paste(text_img, (0, 0)) - start_index += 40 - end_index = ( - end_index + 40 if end_index + 40 <= total_index else total_index - ) - background_img = BuildImage(200, 1100, color="#FFE4C4") - await background_img.paste(tmp, (1, 1)) - await img.paste(background_img) - await A.paste(img, (current_width, 0)) - current_width += img_data[x]["width"] * 200 - return A - - -def _check_black(img_urls: list[str], black: list[str]) -> bool: - """检测pid是否在黑名单中 - - 参数: - img_urls: 图片img列表 - black: 黑名单 - - 返回: - bool: 是否在黑名单中 - """ - for b in black: - for img_url in img_urls: - if b in img_url: - return False - return True diff --git a/zhenxun/plugins/pix_gallery/_model/__init__.py b/zhenxun/plugins/pix_gallery/_model/__init__.py deleted file mode 100644 index 8b1378917..000000000 --- a/zhenxun/plugins/pix_gallery/_model/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/zhenxun/plugins/pix_gallery/_model/omega_pixiv_illusts.py b/zhenxun/plugins/pix_gallery/_model/omega_pixiv_illusts.py deleted file mode 100644 index 17e2156c4..000000000 --- a/zhenxun/plugins/pix_gallery/_model/omega_pixiv_illusts.py +++ /dev/null @@ -1,89 +0,0 @@ - -from tortoise import fields -from tortoise.contrib.postgres.functions import Random - -from zhenxun.services.db_context import Model - - -class OmegaPixivIllusts(Model): - - 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链接""" - - class Meta: - table = "omega_pixiv_illusts" - table_description = "omega图库数据表" - unique_together = ("pid", "url") - - @classmethod - async def query_images( - cls, - keywords: list[str] | None = None, - uid: int | None = None, - pid: int | None = None, - nsfw_tag: int | None = 0, - num: int = 100, - ) -> list["OmegaPixivIllusts"]: - """查找符合条件的图片 - - 参数: - keywords: 关键词 - uid: 画师uid - pid: 图片pid - nsfw_tag: nsfw标签, 0=safe, 1=setu. 2=r18 - num: 获取图片数量 - """ - if not num: - return [] - query = cls - if nsfw_tag is not None: - query = cls.filter(nsfw_tag=nsfw_tag) - if keywords: - for keyword in keywords: - query = query.filter(tags__contains=keyword) - elif uid: - query = query.filter(uid=uid) - elif pid: - query = query.filter(pid=pid) - query = query.annotate(rand=Random()).limit(num) - return await query.all() # type: ignore - - @classmethod - async def get_keyword_num( - cls, tags: list[str] | None = None - ) -> tuple[int, int, int]: - """获取相关关键词(keyword, tag)在图库中的数量 - - 参数: - tags: 关键词/Tag - """ - query = cls - if tags: - for tag in tags: - 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/zhenxun/plugins/pix_gallery/_model/pixiv.py b/zhenxun/plugins/pix_gallery/_model/pixiv.py deleted file mode 100644 index 3451781df..000000000 --- a/zhenxun/plugins/pix_gallery/_model/pixiv.py +++ /dev/null @@ -1,91 +0,0 @@ -from tortoise import fields -from tortoise.contrib.postgres.functions import Random - -from zhenxun.services.db_context import Model - - -class Pixiv(Model): - - 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() - - class Meta: - table = "pixiv" - table_description = "pix图库数据表" - unique_together = ("pid", "img_url", "img_p") - - # 0:非r18 1:r18 2:混合 - @classmethod - async def query_images( - cls, - keywords: list[str] | None = None, - uid: int | None = None, - pid: int | None = None, - r18: int | None = 0, - num: int = 100, - ) -> list["Pixiv"]: - """查找符合条件的图片 - - 参数: - keywords: 关键词 - uid: 画师uid - pid: 图片pid - r18: 是否r18,0:非r18 1:r18 2:混合 - num: 查找图片的数量 - """ - if not num: - return [] - query = cls - if r18 == 0: - query = query.filter(is_r18=False) - elif r18 == 1: - query = query.filter(is_r18=True) - if keywords: - for keyword in keywords: - query = query.filter(tags__contains=keyword) - elif uid: - query = query.filter(uid=uid) - elif pid: - query = query.filter(pid=pid) - query = query.annotate(rand=Random()).limit(num) - return await query.all() # type: ignore - - @classmethod - async def get_keyword_num(cls, tags: list[str] | None = None) -> tuple[int, int]: - """获取相关关键词(keyword, tag)在图库中的数量 - - 参数: - tags: 关键词/Tag - """ - query = cls - if tags: - for tag in tags: - 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/zhenxun/plugins/pix_gallery/_model/pixiv_keyword_user.py b/zhenxun/plugins/pix_gallery/_model/pixiv_keyword_user.py deleted file mode 100644 index 5de544a5d..000000000 --- a/zhenxun/plugins/pix_gallery/_model/pixiv_keyword_user.py +++ /dev/null @@ -1,52 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class PixivKeywordUser(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊id""" - keyword = fields.CharField(255, unique=True) - """关键词""" - is_pass = fields.BooleanField() - """是否通过""" - - class Meta: - table = "pixiv_keyword_users" - table_description = "pixiv关键词数据表" - - @classmethod - async def get_current_keyword(cls) -> tuple[list[str], list[str]]: - """获取当前通过与未通过的关键词""" - pass_keyword = [] - not_pass_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(data[0]) - return pass_keyword, not_pass_keyword - - @classmethod - async def get_black_pid(cls) -> list[str]: - """获取黑名单PID""" - black_pid = [] - keyword_list = await cls.filter(user_id="114514").values_list( - "keyword", flat=True - ) - for image in keyword_list: - black_pid.append(image[6:]) - return black_pid - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE pixiv_keyword_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE pixiv_keyword_users ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE pixiv_keyword_users ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/pix_gallery/pix.py b/zhenxun/plugins/pix_gallery/pix.py deleted file mode 100644 index 2f8d25c38..000000000 --- a/zhenxun/plugins/pix_gallery/pix.py +++ /dev/null @@ -1,247 +0,0 @@ -import random - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.withdraw_manage import WithdrawManager - -from ._data_source import get_image -from ._model.omega_pixiv_illusts import OmegaPixivIllusts -from ._model.pixiv import Pixiv - -__plugin_meta__ = PluginMetadata( - name="PIX", - description="这里是PIX图库!", - usage=""" - 指令: - pix ?*[tags]: 通过 tag 获取相似图片,不含tag时随机抽取 - pid [uid]: 通过uid获取图片 - pix pid[pid]: 查看图库中指定pid图片 - 示例:pix 萝莉 白丝 - 示例:pix 萝莉 白丝 10 (10为数量) - 示例:pix #02 (当tag只有1个tag且为数字时,使用#标记,否则将被判定为数量) - 示例:pix 34582394 (查询指定uid图片) - 示例:pix pid:12323423 (查询指定pid图片) - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - superuser_help=""" - 指令: - pix -s ?*[tags]: 通过tag获取色图,不含tag时随机 - pix -r ?*[tags]: 通过tag获取r18图,不含tag时随机 - """, - menu_type="来点好康的", - limits=[BaseBlock(result="您有PIX图片正在处理,请稍等...")], - configs=[ - RegisterConfig( - key="MAX_ONCE_NUM2FORWARD", - value=None, - help="单次发送的图片数量达到指定值时转发为合并消息", - default_value=None, - type=int, - ), - RegisterConfig( - key="ALLOW_GROUP_SETU", - value=False, - help="允许非超级用户使用-s参数", - default_value=False, - type=bool, - ), - RegisterConfig( - key="ALLOW_GROUP_R18", - value=False, - help="允许非超级用户使用-r参数", - default_value=False, - type=bool, - ), - ], - ).dict(), -) - -# pix = on_command("pix", aliases={"PIX", "Pix"}, priority=5, block=True) - -_matcher = on_alconna( - Alconna( - "pix", - Args["tags?", str] / "\n", - Option("-s", action=store_true, help_text="色图"), - Option("-r", action=store_true, help_text="r18"), - ), - priority=5, - block=True, -) - -PIX_RATIO = None -OMEGA_RATIO = None - - -@_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, tags: Match[str]): - global PIX_RATIO, OMEGA_RATIO - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if PIX_RATIO is None: - pix_omega_pixiv_ratio = Config.get_config("pix", "PIX_OMEGA_PIXIV_RATIO") - PIX_RATIO = pix_omega_pixiv_ratio[0] / ( - pix_omega_pixiv_ratio[0] + pix_omega_pixiv_ratio[1] - ) - OMEGA_RATIO = 1 - PIX_RATIO - num = 1 - # keyword = arg.extract_plain_text().strip() - keyword = "" - spt = tags.result.split() if tags.available else [] - if arparma.find("s"): - nsfw_tag = 1 - elif arparma.find("r"): - nsfw_tag = 2 - else: - nsfw_tag = 0 - if session.id1 not in bot.config.superusers: - if (nsfw_tag == 1 and not Config.get_config("pix", "ALLOW_GROUP_SETU")) or ( - nsfw_tag == 2 and not Config.get_config("pix", "ALLOW_GROUP_R18") - ): - await MessageUtils.build_message( - "你不能看这些噢,这些都是是留给管理员看的..." - ).finish() - if (n := len(spt)) == 1: - if str(spt[0]).isdigit() and int(spt[0]) < 100: - num = int(spt[0]) - keyword = "" - elif spt[0].startswith("#"): - keyword = spt[0][1:] - elif n > 1: - if str(spt[-1]).isdigit(): - num = int(spt[-1]) - if num > 10: - if session.id1 not in bot.config.superusers or ( - session.id1 in bot.config.superusers and num > 30 - ): - num = random.randint(1, 10) - await MessageUtils.build_message( - f"太贪心了,就给你发 {num}张 好了" - ).send() - spt = spt[:-1] - keyword = " ".join(spt) - pix_num = int(num * PIX_RATIO) + 15 if PIX_RATIO != 0 else 0 - omega_num = num - pix_num + 15 - if str(keyword).isdigit(): - if num == 1: - pix_num = 15 - omega_num = 15 - all_image = await Pixiv.query_images( - uid=int(keyword), num=pix_num, r18=1 if nsfw_tag == 2 else 0 - ) + await OmegaPixivIllusts.query_images( - uid=int(keyword), num=omega_num, nsfw_tag=nsfw_tag - ) - elif keyword.lower().startswith("pid"): - pid = keyword.replace("pid", "").replace(":", "").replace(":", "") - if not str(pid).isdigit(): - await MessageUtils.build_message("PID必须是数字...").finish(reply_to=True) - all_image = await Pixiv.query_images( - pid=int(pid), r18=1 if nsfw_tag == 2 else 0 - ) - if not all_image: - all_image = await OmegaPixivIllusts.query_images( - pid=int(pid), nsfw_tag=nsfw_tag - ) - num = len(all_image) - else: - tmp = await Pixiv.query_images( - spt, r18=1 if nsfw_tag == 2 else 0, num=pix_num - ) + await OmegaPixivIllusts.query_images(spt, nsfw_tag=nsfw_tag, num=omega_num) - tmp_ = [] - all_image = [] - for x in tmp: - if x.pid not in tmp_: - all_image.append(x) - tmp_.append(x.pid) - if not all_image: - await MessageUtils.build_message( - f"未在图库中找到与 {keyword} 相关Tag/UID/PID的图片..." - ).finish(reply_to=True) - msg_list = [] - for _ in range(num): - img_url = None - author = None - if not all_image: - await MessageUtils.build_message("坏了...发完了,没图了...").finish() - img = random.choice(all_image) - all_image.remove(img) # type: ignore - if isinstance(img, OmegaPixivIllusts): - img_url = img.url - author = img.uname - elif isinstance(img, Pixiv): - img_url = img.img_url - author = img.author - pid = img.pid - title = img.title - uid = img.uid - if img_url: - _img = await get_image(img_url, session.id1) - if _img: - if Config.get_config("pix", "SHOW_INFO"): - msg_list.append( - MessageUtils.build_message( - [ - f"title:{title}\n" - f"author:{author}\n" - f"PID:{pid}\nUID:{uid}\n", - _img, - ] - ) - ) - else: - msg_list.append(_img) - logger.info( - f" 查看PIX图库PID: {pid}", arparma.header_result, session=session - ) - else: - msg_list.append(MessageUtils.build_message("这张图似乎下载失败了")) - logger.info( - f" 查看PIX图库PID: {pid},下载图片出错", - arparma.header_result, - session=session, - ) - if ( - Config.get_config("pix", "MAX_ONCE_NUM2FORWARD") - and num >= Config.get_config("pix", "MAX_ONCE_NUM2FORWARD") - and gid - ): - for msg in msg_list: - receipt = await msg.send() - if receipt: - message_id = receipt.msg_ids[0]["message_id"] - await WithdrawManager.withdraw_message( - bot, - str(message_id), - Config.get_config("pix", "WITHDRAW_PIX_MESSAGE"), - session, - ) - else: - for msg in msg_list: - receipt = await msg.send() - if receipt: - message_id = receipt.msg_ids[0]["message_id"] - await WithdrawManager.withdraw_message( - bot, - message_id, - Config.get_config("pix", "WITHDRAW_PIX_MESSAGE"), - session, - ) diff --git a/zhenxun/plugins/pix_gallery/pix_add_keyword.py b/zhenxun/plugins/pix_gallery/pix_add_keyword.py deleted file mode 100644 index 452213e3b..000000000 --- a/zhenxun/plugins/pix_gallery/pix_add_keyword.py +++ /dev/null @@ -1,135 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import uid_pid_exists -from ._model.pixiv import Pixiv -from ._model.pixiv_keyword_user import PixivKeywordUser - -__plugin_meta__ = PluginMetadata( - name="PIX添加", - description="PIX关键词/UID/PID添加管理", - usage=""" - 指令: - 添加pix关键词 [Tag]: 添加一个pix搜索收录Tag - pix添加 uid [uid]: 添加一个pix搜索收录uid - pix添加 pid [pid]: 添加一个pix收录pid - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_add_matcher = on_alconna( - Alconna("添加pix关键词", Args["keyword", str]), priority=5, block=True -) - -_uid_matcher = on_alconna( - Alconna( - "pix添加", - Args["add_type", ["uid", "pid"]]["id", str], - Option("-f", action=store_true, help_text="强制收录不检查是否存在"), - ), - priority=5, - block=True, -) - -_black_matcher = on_alconna( - Alconna("添加pix黑名单", Args["pid", str]), priority=5, block=True -) - - -@_add_matcher.handle() -async def _(bot: Bot, session: EventSession, keyword: str, arparma: Arparma): - group_id = session.id3 or session.id2 or -1 - if not await PixivKeywordUser.exists(keyword=keyword): - await PixivKeywordUser.create( - user_id=str(session.id1), - group_id=str(group_id), - keyword=keyword, - is_pass=str(session.id1) in bot.config.superusers, - ) - text = f"已成功添加pixiv搜图关键词:{keyword}" - if session.id1 not in bot.config.superusers: - text += ",请等待管理员通过该关键词!" - await MessageUtils.build_message(text).send(reply_to=True) - logger.info( - f"添加了pixiv搜图关键词: {keyword}", arparma.header_result, session=session - ) - else: - await MessageUtils.build_message(f"该关键词 {keyword} 已存在...").send() - - -@_uid_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, add_type: str, id: str): - group_id = session.id3 or session.id2 or -1 - exists_flag = True - if arparma.find("f") and session.id1 in bot.config.superusers: - exists_flag = False - word = None - if add_type == "uid": - word = f"uid:{id}" - else: - word = f"pid:{id}" - if await Pixiv.get_or_none(pid=int(id), img_p="p0"): - await MessageUtils.build_message(f"该PID:{id}已存在...").finish( - reply_to=True - ) - if not await uid_pid_exists(word) and exists_flag: - await MessageUtils.build_message( - "画师或作品不存在或搜索正在CD,请稍等..." - ).finish(reply_to=True) - if not await PixivKeywordUser.exists(keyword=word): - await PixivKeywordUser.create( - user_id=session.id1, - group_id=str(group_id), - keyword=word, - is_pass=session.id1 in bot.config.superusers, - ) - text = f"已成功添加pixiv搜图UID/PID:{id}" - if session.id1 not in bot.config.superusers: - text += ",请等待管理员通过该关键词!" - await MessageUtils.build_message(text).send(reply_to=True) - else: - await MessageUtils.build_message(f"该UID/PID:{id} 已存在...").send() - - -@_black_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, pid: str): - img_p = "" - if "p" in pid: - img_p = pid.split("p")[-1] - pid = pid.replace("_", "") - pid = pid[: pid.find("p")] - if not pid.isdigit: - await MessageUtils.build_message("PID必须全部是数字!").finish(reply_to=True) - if not await PixivKeywordUser.exists( - keyword=f"black:{pid}{f'_p{img_p}' if img_p else ''}" - ): - await PixivKeywordUser.create( - user_id=114514, - group_id=114514, - keyword=f"black:{pid}{f'_p{img_p}' if img_p else ''}", - is_pass=session.id1 in bot.config.superusers, - ) - await MessageUtils.build_message(f"已添加PID:{pid} 至黑名单中...").send() - logger.info( - f" 添加了pixiv搜图黑名单 PID:{pid}", arparma.header_result, session=session - ) - else: - await MessageUtils.build_message( - f"PID:{pid} 已添加黑名单中,添加失败..." - ).send() diff --git a/zhenxun/plugins/pix_gallery/pix_pass_del_keyword.py b/zhenxun/plugins/pix_gallery/pix_pass_del_keyword.py deleted file mode 100644 index 9a8f2ea77..000000000 --- a/zhenxun/plugins/pix_gallery/pix_pass_del_keyword.py +++ /dev/null @@ -1,218 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - At, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from ._data_source import remove_image -from ._model.pixiv import Pixiv -from ._model.pixiv_keyword_user import PixivKeywordUser - -__plugin_meta__ = PluginMetadata( - name="PIX删除", - description="PIX关键词/UID/PID添加管理", - usage=""" - 指令: - pix关键词 [y/n] [关键词/pid/uid] - 删除pix关键词 ['pid'/'uid'/'keyword'] [关键词/pid/uid] - 删除pix图片 *[pid] - 示例:pix关键词 y 萝莉 - 示例:pix关键词 y 12312312 uid - 示例:pix关键词 n 12312312 pid - 示例:删除pix关键词 keyword 萝莉 - 示例:删除pix关键词 uid 123123123 - 示例:删除pix关键词 pid 123123 - 示例:删除pix图片 4223442 - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER - ).dict(), -) - - -_pass_matcher = on_alconna( - Alconna( - "pix关键词", Args["status", ["y", "n"]]["keyword", str]["type?", ["uid", "pid"]] - ), - permission=SUPERUSER, - priority=1, - block=True, -) - -_del_matcher = on_alconna( - Alconna("删除pix关键词", Args["type", ["pid", "uid", "keyword"]]["keyword", str]), - permission=SUPERUSER, - priority=1, - block=True, -) - -_del_pic_matcher = on_alconna( - Alconna( - "删除pix图片", - Args["pid", str], - Option("-b|--black", action=store_true, help_text=""), - ), - permission=SUPERUSER, - priority=1, - block=True, -) - - -@_pass_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - status: str, - keyword: str, - type: Match[str], -): - tmp = {"group": {}, "private": {}} - flag = status == "y" - if type.available: - if type.result == "uid": - keyword = f"uid:{keyword}" - else: - keyword = f"pid:{keyword}" - if not keyword[4:].isdigit(): - await MessageUtils.build_message(f"{keyword} 非全数字...").finish( - reply_to=True - ) - data = await PixivKeywordUser.get_or_none(keyword=keyword) - 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_id, data.group_id - if not user_id: - await MessageUtils.build_message( - f"未找到关键词/UID:{keyword},请检查关键词/UID是否存在..." - ).finish(reply_to=True) - if flag: - if group_id == -1: - if not tmp["private"].get(user_id): - tmp["private"][user_id] = {"keyword": [keyword]} - else: - tmp["private"][user_id]["keyword"].append(keyword) - else: - if not tmp["group"].get(group_id): - tmp["group"][group_id] = {} - if not tmp["group"][group_id].get(user_id): - tmp["group"][group_id][user_id] = {"keyword": [keyword]} - else: - tmp["group"][group_id][user_id]["keyword"].append(keyword) - await MessageUtils.build_message( - f"已成功{'通过' if flag else '拒绝'}搜图关键词:{keyword}..." - ).send() - for user in tmp["private"]: - text = ",".join(tmp["private"][user]["keyword"]) - await PlatformUtils.send_message( - bot, - user, - None, - f"你的关键词/UID/PID {text} 已被管理员通过,将在下一次进行更新...", - ) - # await bot.send_private_msg( - # user_id=user, - # message=f"你的关键词/UID/PID {x} 已被管理员通过,将在下一次进行更新...", - # ) - for group in tmp["group"]: - for user in tmp["group"][group]: - text = ",".join(tmp["group"][group][user]["keyword"]) - await PlatformUtils.send_message( - bot, - None, - group_id=group, - message=MessageUtils.build_message( - [ - At(flag="user", target=user), - "你的关键词/UID/PID {x} 已被管理员通过,将在下一次进行更新...", - ] - ), - ) - logger.info( - f" 通过了pixiv搜图关键词/UID: {keyword}", arparma.header_result, session=session - ) - - -@_del_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, type: str, keyword: str): - if type != "keyword": - keyword = f"{type}:{keyword}" - if data := await PixivKeywordUser.get_or_none(keyword=keyword): - await data.delete() - await MessageUtils.build_message( - f"删除搜图关键词/UID:{keyword} 成功..." - ).send() - logger.info( - f" 删除了pixiv搜图关键词: {keyword}", arparma.header_result, session=session - ) - else: - await MessageUtils.build_message( - f"未查询到搜索关键词/UID/PID:{keyword},删除失败!" - ).send() - - -@_del_pic_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, keyword: str): - msg = "" - black_pid = "" - flag = arparma.find("black") - img_p = None - if "p" in keyword: - img_p = keyword.split("p")[-1] - keyword = keyword.replace("_", "") - keyword = keyword[: keyword.find("p")] - elif "ugoira" in keyword: - img_p = keyword.split("ugoira")[-1] - keyword = keyword.replace("_", "") - keyword = keyword[: keyword.find("ugoira")] - if keyword.isdigit(): - if await Pixiv.query_images(pid=int(keyword), r18=2): - if await remove_image(int(keyword), img_p): - msg += f'{keyword}{f"_p{img_p}" if img_p else ""},' - if flag: - if await PixivKeywordUser.exists( - keyword=f"black:{keyword}{f'_p{img_p}' if img_p else ''}" - ): - await PixivKeywordUser.create( - user_id="114514", - group_id="114514", - keyword=f"black:{keyword}{f'_p{img_p}' if img_p else ''}", - is_pass=False, - ) - black_pid += f'{keyword}{f"_p{img_p}" if img_p else ""},' - logger.info( - f" 删除了PIX图片 PID:{keyword}{f'_p{img_p}' if img_p else ''}", - arparma.header_result, - session=session, - ) - else: - await MessageUtils.build_message( - f"PIX:图片pix:{keyword}{f'_p{img_p}' if img_p else ''} 不存在...无法删除.." - ).send() - else: - await MessageUtils.build_message(f"PID必须为数字!pid:{keyword}").send( - reply_to=True - ) - await MessageUtils.build_message(f"PIX:成功删除图片:{msg[:-1]}").send() - if flag: - await MessageUtils.build_message( - f"成功图片PID加入黑名单:{black_pid[:-1]}" - ).send() diff --git a/zhenxun/plugins/pix_gallery/pix_show_info.py b/zhenxun/plugins/pix_gallery/pix_show_info.py deleted file mode 100644 index cb1cbf2a9..000000000 --- a/zhenxun/plugins/pix_gallery/pix_show_info.py +++ /dev/null @@ -1,85 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from ._data_source import gen_keyword_pic, get_keyword_num -from ._model.pixiv_keyword_user import PixivKeywordUser - -__plugin_meta__ = PluginMetadata( - name="查看pix图库", - description="让我看看管理员私藏了多少货", - usage=""" - 指令: - 我的pix关键词 - 显示pix关键词 - 查看pix图库 ?[tag]: 查看指定tag图片数量,为空时查看整个图库 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - ).dict(), -) - -_my_matcher = on_alconna(Alconna("我的pix关键词"), priority=5, block=True) - -_show_matcher = on_alconna(Alconna("显示pix关键词"), priority=5, block=True) - -_pix_matcher = on_alconna( - Alconna("查看pix图库", Args["keyword?", str]), priority=5, block=True -) - - -@_my_matcher.handle() -async def _(arparma: Arparma, session: EventSession): - data = await PixivKeywordUser.filter(user_id=session.id1).values_list( - "keyword", flat=True - ) - if not data: - await MessageUtils.build_message("您目前没有提供任何Pixiv搜图关键字...").finish( - reply_to=True - ) - await MessageUtils.build_message(f"您目前提供的如下关键字:\n\t" + ",".join(data)).send() # type: ignore - logger.info("查看我的pix关键词", arparma.header_result, session=session) - - -@_show_matcher.handle() -async def _(bot: Bot, arparma: Arparma, session: EventSession): - _pass_keyword, not_pass_keyword = await PixivKeywordUser.get_current_keyword() - if _pass_keyword or not_pass_keyword: - image = await gen_keyword_pic( - _pass_keyword, not_pass_keyword, session.id1 in bot.config.superusers - ) - await MessageUtils.build_message(image).send() # type: ignore - else: - if session.id1 in bot.config.superusers: - await MessageUtils.build_message( - f"目前没有已收录或待收录的搜索关键词..." - ).send() - else: - await MessageUtils.build_message(f"目前没有已收录的搜索关键词...").send() - - -@_pix_matcher.handle() -async def _(bot: Bot, arparma: Arparma, session: EventSession, keyword: Match[str]): - _keyword = "" - if keyword.available: - _keyword = keyword.result - count, r18_count, count_, setu_count, r18_count_ = await get_keyword_num(_keyword) - await MessageUtils.build_message( - f"PIX图库:{_keyword}\n" - f"总数:{count + r18_count}\n" - f"美图:{count}\n" - f"R18:{r18_count}\n" - f"---------------\n" - f"Omega图库:{_keyword}\n" - f"总数:{count_ + setu_count + r18_count_}\n" - f"美图:{count_}\n" - f"色图:{setu_count}\n" - f"R18:{r18_count_}" - ).send() - logger.info("查看pix图库", arparma.header_result, session=session) diff --git a/zhenxun/plugins/pix_gallery/pix_update.py b/zhenxun/plugins/pix_gallery/pix_update.py deleted file mode 100644 index b0f209dc0..000000000 --- a/zhenxun/plugins/pix_gallery/pix_update.py +++ /dev/null @@ -1,225 +0,0 @@ -import asyncio -import os -import re -import time -from pathlib import Path - -from nonebot.adapters import Bot -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from ._data_source import start_update_image_url -from ._model.omega_pixiv_illusts import OmegaPixivIllusts -from ._model.pixiv import Pixiv -from ._model.pixiv_keyword_user import PixivKeywordUser - -__plugin_meta__ = PluginMetadata( - name="pix检查更新", - description="pix图库收录数据检查更新", - usage=""" - 指令: - 更新pix关键词 *[keyword/uid/pid] [num=max]: 更新仅keyword/uid/pid或全部 - pix检测更新:检测从未更新过的uid和pid - 示例:更新pix关键词keyword - 示例:更新pix关键词uid 10 - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.SUPERUSER - ).dict(), -) - - -_update_matcher = on_alconna( - Alconna("更新pix关键词", Args["type", ["uid", "pid", "keyword"]]["num?", int]), - permission=SUPERUSER, - priority=1, - block=True, -) - -_check_matcher = on_alconna( - Alconna( - "pix检测更新", Option("-u|--update", action=store_true, help_text="是否更新") - ), - permission=SUPERUSER, - priority=1, - block=True, -) - -_omega_matcher = on_alconna( - Alconna("检测omega图库"), permission=SUPERUSER, priority=1, block=True -) - - -@_update_matcher.handle() -async def _(arparma: Arparma, session: EventSession, type: str, num: Match[int]): - _pass_keyword, _ = await PixivKeywordUser.get_current_keyword() - _pass_keyword.reverse() - black_pid = await PixivKeywordUser.get_black_pid() - _keyword = [ - x - for x in _pass_keyword - if not x.startswith("uid:") - and not x.startswith("pid:") - and not x.startswith("black:") - ] - _uid = [x for x in _pass_keyword if x.startswith("uid:")] - _pid = [x for x in _pass_keyword if x.startswith("pid:")] - _num = num.result if num.available else 9999 - if _num < 10000: - keyword_str = ",".join( - _keyword[: _num if _num < len(_keyword) else len(_keyword)] - ) - uid_str = ",".join(_uid[: _num if _num < len(_uid) else len(_uid)]) - pid_str = ",".join(_pid[: _num if _num < len(_pid) else len(_pid)]) - if type == "pid": - update_lst = _pid - info = f"开始更新Pixiv搜图PID:\n{pid_str}" - elif type == "uid": - update_lst = _uid - info = f"开始更新Pixiv搜图UID:\n{uid_str}" - elif type == "keyword": - update_lst = _keyword - info = f"开始更新Pixiv搜图关键词:\n{keyword_str}" - else: - update_lst = _pass_keyword - info = f"开始更新Pixiv搜图关键词:\n{keyword_str}\n更新UID:{uid_str}\n更新PID:{pid_str}" - _num = _num if _num < len(update_lst) else len(update_lst) - else: - if type == "pid": - update_lst = [f"pid:{_num}"] - info = f"开始更新Pixiv搜图UID:\npid:{_num}" - else: - update_lst = [f"uid:{_num}"] - info = f"开始更新Pixiv搜图UID:\nuid:{_num}" - await MessageUtils.build_message(info).send() - start_time = time.time() - pid_count, pic_count = await start_update_image_url( - update_lst[:_num], black_pid, type == "pid" - ) - await MessageUtils.build_message( - f"Pixiv搜图关键词搜图更新完成...\n" - f"累计更新PID {pid_count} 个\n" - f"累计更新图片 {pic_count} 张" - + "\n耗时:{:.2f}秒".format((time.time() - start_time)) - ).send() - logger.info("更新pix关键词", arparma.header_result, session=session) - - -@_check_matcher.handle() -async def _(bot: Bot, arparma: Arparma, session: EventSession): - _pass_keyword, _ = await PixivKeywordUser.get_current_keyword() - x_uid = [] - x_pid = [] - _uid = [int(x[4:]) for x in _pass_keyword if x.startswith("uid:")] - _pid = [int(x[4:]) for x in _pass_keyword if x.startswith("pid:")] - all_images = await Pixiv.query_images(r18=2) - for img in all_images: - if img.pid not in x_pid: - x_pid.append(img.pid) - if img.uid not in x_uid: - x_uid.append(img.uid) - await MessageUtils.build_message( - "从未更新过的UID:" - + ",".join([f"uid:{x}" for x in _uid if x not in x_uid]) - + "\n" - + "从未更新过的PID:" - + ",".join([f"pid:{x}" for x in _pid if x not in x_pid]) - ).send() - if arparma.find("update"): - await MessageUtils.build_message("开始自动自动更新PID....").send() - update_lst = [f"pid:{x}" for x in _uid if x not in x_uid] - black_pid = await PixivKeywordUser.get_black_pid() - start_time = time.time() - pid_count, pic_count = await start_update_image_url( - update_lst, black_pid, False - ) - await MessageUtils.build_message( - f"Pixiv搜图关键词搜图更新完成...\n" - f"累计更新PID {pid_count} 个\n" - f"累计更新图片 {pic_count} 张" - + "\n耗时:{:.2f}秒".format((time.time() - start_time)) - ).send() - logger.info( - f"pix检测更新, 是否更新: {arparma.find('update')}", - arparma.header_result, - session=session, - ) - - -@_omega_matcher.handle() -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) - pid = int(num_list[1]) - uid = int(num_list[2]) - id_ = 3 - while num_list[id_] not in ["0", "1"]: - id_ += 1 - classified = int(num_list[id_]) - nsfw_tag = int(num_list[id_ + 1]) - width = int(num_list[id_ + 2]) - height = int(num_list[id_ + 3]) - str_list = re.findall(r"'(.*?)',", data) - title = str_list[0] - uname = str_list[1] - tags = str_list[2] - url = str_list[3] - if pid in all_pid: - logger.info(f"添加OmegaPixivIllusts图库数据已存在 ---> pid:{pid}") - return - _, 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: - omega_pixiv_illusts = Path() / file - if omega_pixiv_illusts: - with open(omega_pixiv_illusts, "r", encoding="utf8") as f: - lines = f.readlines() - tasks = [] - length = len([x for x in lines if "INSERT INTO" in x.upper()]) - 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} 加入更新计划") - tasks.append( - asyncio.create_task(_tasks(line, all_pid, length, index)) # type: ignore - ) - await asyncio.gather(*tasks) - omega_pixiv_illusts.unlink() diff --git a/zhenxun/plugins/pixiv_rank_search/__init__.py b/zhenxun/plugins/pixiv_rank_search/__init__.py deleted file mode 100644 index 01945cd85..000000000 --- a/zhenxun/plugins/pixiv_rank_search/__init__.py +++ /dev/null @@ -1,225 +0,0 @@ -from asyncio.exceptions import TimeoutError - -from httpx import NetworkError -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import is_valid_date - -from .data_source import download_pixiv_imgs, get_pixiv_urls, search_pixiv_urls - -__plugin_meta__ = PluginMetadata( - name="P站排行/搜图", - description="P站排行榜直接冲,P站搜图跟着冲", - usage=""" - P站排行: - 可选参数: - 类型: - 1. 日排行 - 2. 周排行 - 3. 月排行 - 4. 原创排行 - 5. 新人排行 - 6. R18日排行 - 7. R18周排行 - 8. R18受男性欢迎排行 - 9. R18重口排行【慎重!】 - 【使用时选择参数序号即可,R18仅可私聊】 - p站排行 ?[参数] ?[数量] ?[日期] - 示例: - p站排行 [无参数默认为日榜] - p站排行 1 - p站排行 1 5 - p站排行 1 5 2018-4-25 - 【注意空格!!】【在线搜索会较慢】 - --------------------------------- - P站搜图: - 搜图 [关键词] ?[数量] ?[页数=1] ?[r18](不屏蔽R-18) - 示例: - 搜图 樱岛麻衣 - 搜图 樱岛麻衣 5 - 搜图 樱岛麻衣 5 r18 - 搜图 樱岛麻衣#1000users 5 - 【多个关键词用#分割】 - 【默认为 热度排序】 - 【注意空格!!】【在线搜索会较慢】【数量可能不符?可能该页数量不够,也可能被R-18屏蔽】 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - aliases={"P站排行", "搜图"}, - menu_type="来点好康的", - limits=[BaseBlock(result="P站排行榜或搜图正在搜索,请不要重复触发命令...")], - configs=[ - RegisterConfig( - key="TIMEOUT", - value=10, - help="图片下载超时限制", - default_value=10, - type=int, - ), - RegisterConfig( - key="MAX_PAGE_LIMIT", - value=20, - help="作品最大页数限制,超过的作品会被略过", - default_value=20, - type=int, - ), - RegisterConfig( - key="ALLOW_GROUP_R18", - value=False, - help="图允许群聊中使用 r18 参数", - default_value=False, - type=bool, - ), - RegisterConfig( - module="hibiapi", - key="HIBIAPI", - value="https://api.obfs.dev", - help="如果没有自建或其他hibiapi请不要修改", - default_value="https://api.obfs.dev", - ), - RegisterConfig( - module="pixiv", - key="PIXIV_NGINX_URL", - value="i.pixiv.re", - help="Pixiv反向代理", - ), - ], - ).dict(), -) - - -rank_dict = { - "1": "day", - "2": "week", - "3": "month", - "4": "week_original", - "5": "week_rookie", - "6": "day_r18", - "7": "week_r18", - "8": "day_male_r18", - "9": "week_r18g", -} - -_rank_matcher = on_alconna( - Alconna("p站排行", Args["rank_type", int, 1]["num", int, 10]["datetime?", str]), - aliases={"p站排行榜"}, - priority=5, - block=True, - rule=to_me(), -) - -_keyword_matcher = on_alconna( - Alconna( - "搜图", - Args["keyword", str]["num", int, 10]["page", int, 1], - Option("-r", action=store_true, help_text="是否屏蔽r18"), - ), - priority=5, - block=True, - rule=to_me(), -) - - -@_rank_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - rank_type: int, - num: int, - datetime: Match[str], -): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - code = 0 - info_list = [] - _datetime = None - if datetime.available: - _datetime = datetime.result - if not is_valid_date(_datetime): - await MessageUtils.build_message("日期不合法,示例: 2018-4-25").finish( - reply_to=True - ) - if rank_type in [6, 7, 8, 9]: - if gid: - await MessageUtils.build_message("羞羞脸!私聊里自己看!").finish( - at_sender=True - ) - info_list, code = await get_pixiv_urls( - rank_dict[str(rank_type)], num, date=_datetime - ) - if code != 200 and info_list: - if isinstance(info_list[0], str): - await MessageUtils.build_message(info_list[0]).finish() - if not info_list: - await MessageUtils.build_message("没有找到啊,等等再试试吧~V").send( - at_sender=True - ) - for title, author, urls in info_list: - try: - images = await download_pixiv_imgs(urls, session.id1) # type: ignore - await MessageUtils.build_message( - [f"title: {title}\nauthor: {author}\n"] + images # type: ignore - ).send() - - except (NetworkError, TimeoutError): - await MessageUtils.build_message("这张图网络直接炸掉了!").send() - logger.info( - f" 查看了P站排行榜 rank_type{rank_type}", arparma.header_result, session=session - ) - - -@_keyword_matcher.handle() -async def _( - bot: Bot, session: EventSession, arparma: Arparma, keyword: str, num: int, page: int -): - gid = session.id3 or session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if gid: - if arparma.find("r") and not Config.get_config( - "pixiv_rank_search", "ALLOW_GROUP_R18" - ): - await MessageUtils.build_message("(脸红#) 你不会害羞的 八嘎!").finish( - at_sender=True - ) - r18 = 0 if arparma.find("r") else 1 - info_list = None - keyword = keyword.replace("#", " ") - info_list, code = await search_pixiv_urls(keyword, num, page, r18) - if code != 200 and isinstance(info_list[0], str): - await MessageUtils.build_message(info_list[0]).finish() - if not info_list: - await MessageUtils.build_message("没有找到啊,等等再试试吧~V").finish( - at_sender=True - ) - for title, author, urls in info_list: - try: - images = await download_pixiv_imgs(urls, session.id1) # type: ignore - await MessageUtils.build_message( - [f"title: {title}\nauthor: {author}\n"] + images # type: ignore - ).send() - - except (NetworkError, TimeoutError): - await MessageUtils.build_message("这张图网络直接炸掉了!").send() - logger.info( - f" 查看了搜索 {keyword} R18:{r18}", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/pixiv_rank_search/data_source.py b/zhenxun/plugins/pixiv_rank_search/data_source.py deleted file mode 100644 index 761a93f20..000000000 --- a/zhenxun/plugins/pixiv_rank_search/data_source.py +++ /dev/null @@ -1,171 +0,0 @@ -from asyncio.exceptions import TimeoutError -from pathlib import Path - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.utils import change_img_md5 - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net/", -} - - -async def get_pixiv_urls( - mode: str, num: int = 10, page: int = 1, date: str | None = None -) -> tuple[list[tuple[str, str, list[str]] | str], int]: - """获取排行榜图片url - - 参数: - mode: 模式类型 - num: 数量. - page: 页数. - date: 日期. - - 返回: - tuple[list[tuple[str, str, list[str]] | str], int]: 图片标题作者url数据,请求状态 - """ - - params = {"mode": mode, "page": page} - if date: - params["date"] = date - hibiapi = Config.get_config("hibiapi", "HIBIAPI") - hibiapi = hibiapi[:-1] if hibiapi[-1] == "/" else hibiapi - rank_url = f"{hibiapi}/api/pixiv/rank" - return await parser_data(rank_url, num, params, "rank") - - -async def search_pixiv_urls( - keyword: str, num: int, page: int, r18: int -) -> tuple[list[tuple[str, str, list[str]] | str], int]: - """搜图图片url - - 参数: - keyword: 关键词 - num: 数量 - page: 页数 - r18: 是否r18 - - 返回: - tuple[list[tuple[str, str, list[str]] | str], int]: 图片标题作者url数据,请求状态 - """ - params = {"word": keyword, "page": page} - hibiapi = Config.get_config("hibiapi", "HIBIAPI") - hibiapi = hibiapi[:-1] if hibiapi[-1] == "/" else hibiapi - search_url = f"{hibiapi}/api/pixiv/search" - return await parser_data(search_url, num, params, "search", r18) - - -async def parser_data( - url: str, num: int, params: dict, type_: str, r18: int = 0 -) -> tuple[list[tuple[str, str, list[str]] | str], int]: - """解析数据搜索 - - 参数: - url: 访问URL - num: 数量 - params: 请求参数 - type_: 类型,rank或search - r18: 是否r18. - - 返回: - tuple[list[tuple[str, str, list[str]] | str], int]: 图片标题作者url数据,请求状态 - """ - info_list = [] - for _ in range(3): - try: - response = await AsyncHttpx.get( - url, - params=params, - timeout=Config.get_config("pixiv_rank_search", "TIMEOUT"), - ) - if response.status_code == 200: - data = response.json() - if data.get("illusts"): - data = data["illusts"] - break - except TimeoutError: - pass - except Exception as e: - logger.error(f"P站排行/搜图解析数据发生错误", e=e) - return ["发生了一些些错误..."], 995 - else: - return ["网络不太好?没有该页数?也许过一会就好了..."], 998 - num = num if num < 30 else 30 - _data = [] - for x in data: - if x["page_count"] < Config.get_config("pixiv_rank_search", "MAX_PAGE_LIMIT"): - if type_ == "search" and r18 == 1: - if "R-18" in str(x["tags"]): - continue - _data.append(x) - if len(_data) == num: - break - for x in _data: - title = x["title"] - author = x["user"]["name"] - urls = [] - if x["page_count"] == 1: - urls.append(x["image_urls"]["large"]) - else: - for j in x["meta_pages"]: - urls.append(j["image_urls"]["large"]) - info_list.append((title, author, urls)) - return info_list, 200 - - -async def download_pixiv_imgs( - urls: list[str], user_id: str, forward_msg_index: int | None = None -) -> list[Path]: - """下载图片 - - 参数: - urls: 图片链接 - user_id: 用户id - forward_msg_index: 转发消息中的图片排序. - - 返回: - MessageFactory: 图片 - """ - result_list = [] - index = 0 - for url in urls: - ws_url = Config.get_config("pixiv", "PIXIV_NGINX_URL") - url = url.replace("_webp", "") - if ws_url: - url = url.replace("i.pximg.net", ws_url).replace("i.pixiv.cat", ws_url) - try: - file = ( - TEMP_PATH / f"{user_id}_{forward_msg_index}_{index}_pixiv.jpg" - if forward_msg_index is not None - else TEMP_PATH / f"{user_id}_{index}_pixiv.jpg" - ) - file = Path(file) - try: - if await AsyncHttpx.download_file( - url, - file, - timeout=Config.get_config("pixiv_rank_search", "TIMEOUT"), - headers=headers, - ): - change_img_md5(file) - image = None - if forward_msg_index is not None: - image = ( - TEMP_PATH - / f"{user_id}_{forward_msg_index}_{index}_pixiv.jpg" - ) - else: - image = TEMP_PATH / f"{user_id}_{index}_pixiv.jpg" - if image: - result_list.append(image) - index += 1 - except OSError: - if file.exists(): - file.unlink() - except Exception as e: - logger.error(f"P站排行/搜图下载图片错误", e=e) - return result_list diff --git a/zhenxun/plugins/poke/__init__.py b/zhenxun/plugins/poke/__init__.py deleted file mode 100644 index 38dafc824..000000000 --- a/zhenxun/plugins/poke/__init__.py +++ /dev/null @@ -1,92 +0,0 @@ -import os -import random - -from nonebot import on_notice -from nonebot.adapters.onebot.v11 import PokeNotifyEvent -from nonebot.adapters.onebot.v11.message import MessageSegment -from nonebot.plugin import PluginMetadata - -from zhenxun.configs.path_config import IMAGE_PATH, RECORD_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.models.ban_console import BanConsole -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import CountLimiter - -__plugin_meta__ = PluginMetadata( - name="戳一戳", - description="戳一戳发送语音美图萝莉图不美哉?", - usage=""" - 戳一戳随机掉落语音或美图萝莉图 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="其他", - plugin_type=PluginType.DEPENDANT, - ).dict(), -) - -REPLY_MESSAGE = [ - "lsp你再戳?", - "连个可爱美少女都要戳的肥宅真恶心啊。", - "你再戳!", - "?再戳试试?", - "别戳了别戳了再戳就坏了555", - "我爪巴爪巴,球球别再戳了", - "你戳你🐎呢?!", - "那...那里...那里不能戳...绝对...", - "(。´・ω・)ん?", - "有事恁叫我,别天天一个劲戳戳戳!", - "欸很烦欸!你戳🔨呢", - "?", - "再戳一下试试?", - "???", - "正在关闭对您的所有服务...关闭成功", - "啊呜,太舒服刚刚竟然睡着了。什么事?", - "正在定位您的真实地址...定位成功。轰炸机已起飞", -] - - -_clmt = CountLimiter(3) - -poke_ = on_notice(priority=5, block=False) - - -@poke_.handle() -async def _(event: PokeNotifyEvent): - uid = str(event.user_id) if event.user_id else None - gid = str(event.group_id) if event.group_id else None - if event.self_id == event.target_id: - _clmt.increase(event.user_id) - if _clmt.check(event.user_id) or random.random() < 0.3: - rst = "" - if random.random() < 0.15: - await BanConsole.ban(uid, gid, 1, 60) - rst = "气死我了!" - await poke_.finish(rst + random.choice(REPLY_MESSAGE), at_sender=True) - 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 - ) - await MessageUtils.build_message( - [ - f"id: {index}", - IMAGE_PATH / "image_management" / path / f"{index}.jpg", - ] - ).send() - logger.info(f"USER {event.user_id} 戳了戳我") - elif 0.3 < rand < 0.6: - voice = random.choice(os.listdir(RECORD_PATH / "dinggong")) - result = MessageSegment.record(RECORD_PATH / "dinggong" / voice) - await poke_.send(result) - await poke_.send(voice.split("_")[1]) - logger.info( - f'USER {event.user_id} 戳了戳我 回复: {result} \n {voice.split("_")[1]}', - "戳一戳", - ) - else: - await poke_.send(MessageSegment("poke", {"qq": event.user_id})) diff --git a/zhenxun/plugins/quotations.py b/zhenxun/plugins/quotations.py deleted file mode 100644 index e213ee04d..000000000 --- a/zhenxun/plugins/quotations.py +++ /dev/null @@ -1,32 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="一言二次元语录", - description="二次元语录给你力量", - usage=""" - usage: - 一言二次元语录 - 指令: - 语录/二次元 - """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1").dict(), -) - -URL = "https://international.v1.hitokoto.cn/?c=a" - -_matcher = on_alconna(Alconna("语录"), aliases={"二次元"}, priority=5, block=True) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - data = (await AsyncHttpx.get(URL, timeout=5)).json() - result = f'{data["hitokoto"]}\t——{data["from"]}' - await MessageUtils.build_message(result).send() - logger.info(f" 发送语录:" + result, arparma.header_result, session=session) diff --git a/zhenxun/plugins/roll.py b/zhenxun/plugins/roll.py deleted file mode 100644 index 1c21421b6..000000000 --- a/zhenxun/plugins/roll.py +++ /dev/null @@ -1,66 +0,0 @@ -import asyncio -import random - -from nonebot import on_command -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import BotConfig -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.depends import UserName -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="roll", - description="犹豫不决吗?那就让我帮你决定吧", - usage=""" - usage: - 随机数字 或 随机选择事件 - 指令: - roll: 随机 0-100 的数字 - roll *[文本]: 随机事件 - 示例:roll 吃饭 睡觉 打游戏 - """.strip(), - extra=PluginExtraData(author="HibiKier", version="0.1").dict(), -) - - -_matcher = on_command("roll", priority=5, block=True) - - -@_matcher.handle() -async def _( - session: EventSession, - message: UniMsg, - user_name: str = UserName(), -): - text = message.extract_plain_text().strip().replace("roll", "", 1).split() - if not text: - await MessageUtils.build_message(f"roll: {random.randint(0, 100)}").finish( - reply_to=True - ) - await MessageUtils.build_message( - random.choice( - [ - "转动命运的齿轮,拨开眼前迷雾...", - f"启动吧,命运的水晶球,为{user_name}指引方向!", - "嗯哼,在此刻转动吧!命运!", - f"在此祈愿,请为{user_name}降下指引...", - ] - ) - ).send() - await asyncio.sleep(1) - random_text = random.choice(text) - await MessageUtils.build_message( - random.choice( - [ - f"让{BotConfig.self_nickname}看看是什么结果!答案是:‘{random_text}’", - f"根据命运的指引,接下来{user_name} ‘{random_text}’ 会比较好", - f"祈愿被回应了!是 ‘{random_text}’!", - f"结束了,{user_name},命运之轮停在了 ‘{random_text}’!", - ] - ) - ).send(reply_to=True) - logger.info(f"发送roll:{text}", "roll", session=session) diff --git a/zhenxun/plugins/russian/__init__.py b/zhenxun/plugins/russian/__init__.py deleted file mode 100644 index d91323c73..000000000 --- a/zhenxun/plugins/russian/__init__.py +++ /dev/null @@ -1,201 +0,0 @@ -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Arparma -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import Match, UniMsg -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.depends import UserName -from zhenxun.utils.message import MessageUtils - -from .command import ( - _accept_matcher, - _rank_matcher, - _record_matcher, - _refuse_matcher, - _russian_matcher, - _settlement_matcher, - _shoot_matcher, -) -from .data_source import Russian, russian_manage -from .model import RussianUser - -__plugin_meta__ = PluginMetadata( - name="俄罗斯轮盘", - description="虽然是运气游戏,但这可是战场啊少年", - usage=""" - 又到了决斗时刻 - 指令: - 装弹 [子弹数] ?[金额] ?[at]: 开启游戏,装填子弹,可选自定义金额,或邀请决斗对象 - 接受对决: 接受当前存在的对决 - 拒绝对决: 拒绝邀请的对决 - 开枪: 开出未知的一枪 - 结算: 强行结束当前比赛 (仅当一方未开枪超过30秒时可使用) - 我的战绩: 对,你的战绩 - 轮盘胜场排行/轮盘败场排行/轮盘欧洲人排行/轮盘慈善家排行/轮盘最高连胜排行/轮盘最高连败排行: 各种排行榜 - 示例:装弹 3 100 @sdd - * 注:同一时间群内只能有一场对决 * - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="群内小游戏", - configs=[ - RegisterConfig( - key="MAX_RUSSIAN_BET_GOLD", - value=1000, - help="俄罗斯轮盘最大赌注金额", - default_value=1000, - type=int, - ) - ], - ).dict(), -) - - -@_russian_matcher.handle() -async def _(num: Match[str], money: Match[int], at_user: Match[alcAt]): - if num.available: - _russian_matcher.set_path_arg("num", num.result) - if money.available: - _russian_matcher.set_path_arg("money", money.result) - if at_user.available: - _russian_matcher.set_path_arg("at_user", at_user.result.target) - - -@_russian_matcher.got_path( - "num", prompt="请输入装填子弹的数量!(最多6颗,输入取消来取消装弹)" -) -async def _( - bot: Bot, - session: EventSession, - message: UniMsg, - arparma: Arparma, - num: str, - money: Match[int], - at_user: Match[alcAt], - uname: str = UserName(), -): - gid = session.id2 - if message.extract_plain_text() == "取消": - await MessageUtils.build_message("已取消装弹...").finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - money = money.result if money.available else 200 - if num in ["取消", "算了"]: - await MessageUtils.build_message("已取消装弹...").finish() - if not num.isdigit(): - await MessageUtils.build_message("输入的子弹数必须是数字!").finish( - reply_to=True - ) - b_num = int(num) - if b_num < 0 or b_num > 6: - await MessageUtils.build_message("子弹数量必须在1-6之间!").finish(reply_to=True) - _at_user = at_user.result.target if at_user.available else None - rus = Russian( - at_user=_at_user, player1=(session.id1, uname), money=money, bullet_num=b_num - ) - result = await russian_manage.add_russian(bot, gid, rus) - await result.send() - logger.info( - f"添加俄罗斯轮盘 装弹: {b_num}, 金额: {money}", - arparma.header_result, - session=session, - ) - - -@_accept_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, uname: str = UserName()): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = await russian_manage.accept(bot, gid, session.id1, uname) - await result.send() - logger.info(f"俄罗斯轮盘接受对决", arparma.header_result, session=session) - - -@_refuse_matcher.handle() -async def _(session: EventSession, arparma: Arparma, uname: str = UserName()): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = russian_manage.refuse(gid, session.id1, uname) - await result.send() - logger.info(f"俄罗斯轮盘拒绝对决", arparma.header_result, session=session) - - -@_settlement_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result = await russian_manage.settlement(gid, session.id1, session.platform) - await result.send() - logger.info(f"俄罗斯轮盘结算", arparma.header_result, session=session) - - -@_shoot_matcher.handle() -async def _(bot: Bot, session: EventSession, arparma: Arparma, uname: str = UserName()): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - result, settle = await russian_manage.shoot( - bot, gid, session.id1, uname, session.platform - ) - await result.send() - if settle: - await settle.send() - logger.info(f"俄罗斯轮盘开枪", arparma.header_result, session=session) - - -@_record_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - user, _ = await RussianUser.get_or_create(user_id=session.id1, group_id=gid) - await MessageUtils.build_message( - f"俄罗斯轮盘\n" - f"总胜利场次:{user.win_count}\n" - f"当前连胜:{user.winning_streak}\n" - f"最高连胜:{user.max_winning_streak}\n" - f"总失败场次:{user.fail_count}\n" - f"当前连败:{user.losing_streak}\n" - f"最高连败:{user.max_losing_streak}\n" - f"赚取金币:{user.make_money}\n" - f"输掉金币:{user.lose_money}", - ).send(reply_to=True) - logger.info(f"俄罗斯轮盘查看战绩", arparma.header_result, session=session) - - -@_rank_matcher.handle() -async def _(session: EventSession, arparma: Arparma, rank_type: str, num: int): - gid = session.id2 - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - if not gid: - await MessageUtils.build_message("群组id为空...").finish() - if 51 < num or num < 10: - num = 10 - result = await russian_manage.rank(session.id1, gid, rank_type, num) - if isinstance(result, str): - await MessageUtils.build_message(result).finish(reply_to=True) - result.show() - await MessageUtils.build_message(result).send(reply_to=True) - logger.info( - f"查看轮盘排行: {rank_type} 数量: {num}", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/russian/command.py b/zhenxun/plugins/russian/command.py deleted file mode 100644 index de9d186cb..000000000 --- a/zhenxun/plugins/russian/command.py +++ /dev/null @@ -1,108 +0,0 @@ -from nonebot_plugin_alconna import Alconna, Args -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import on_alconna - -from zhenxun.utils.rules import ensure_group - -_russian_matcher = on_alconna( - Alconna( - "俄罗斯轮盘", - Args["num?", str]["money?", int]["at_user?", alcAt], - ), - aliases={"装弹", "俄罗斯转盘"}, - rule=ensure_group, - priority=5, - block=True, -) - -_accept_matcher = on_alconna( - Alconna("接受对决"), - aliases={"接受决斗", "接受挑战"}, - rule=ensure_group, - priority=5, - block=True, -) - -_refuse_matcher = on_alconna( - Alconna("拒绝对决"), - aliases={"拒绝决斗", "拒绝挑战"}, - rule=ensure_group, - priority=5, - block=True, -) - -_shoot_matcher = on_alconna( - Alconna("开枪"), - aliases={"咔", "嘭", "嘣"}, - rule=ensure_group, - priority=5, - block=True, -) - -_settlement_matcher = on_alconna( - Alconna("结算"), - rule=ensure_group, - priority=5, - block=True, -) - -_record_matcher = on_alconna( - Alconna("我的战绩"), - rule=ensure_group, - priority=5, - block=True, -) - -_rank_matcher = on_alconna( - Alconna( - "russian-rank", - Args["rank_type", ["win", "lose", "a", "b", "max_win", "max_lose"]][ - "num?", int, 10 - ], - ), - rule=ensure_group, - priority=5, - block=True, -) - -_rank_matcher.shortcut( - r"轮盘胜场排行(?P\d*)", - command="russian-rank", - arguments=["win", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘败场排行(?P\d*)", - command="russian-rank", - arguments=["lose", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘欧洲人排行(?P\d*)", - command="russian-rank", - arguments=["a", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘慈善家排行(?P\d*)", - command="russian-rank", - arguments=["b", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘最高连胜排行(?P\d*)", - command="russian-rank", - arguments=["max_win", "{num}"], - prefix=True, -) - -_rank_matcher.shortcut( - r"轮盘最高连败排行(?P\d*)", - command="russian-rank", - arguments=["max_lose", "{num}"], - prefix=True, -) diff --git a/zhenxun/plugins/russian/data_source.py b/zhenxun/plugins/russian/data_source.py deleted file mode 100644 index 73cdb078b..000000000 --- a/zhenxun/plugins/russian/data_source.py +++ /dev/null @@ -1,535 +0,0 @@ -import random -import time -from datetime import datetime, timedelta - -from apscheduler.jobstores.base import JobLookupError -from nonebot.adapters import Bot -from nonebot_plugin_alconna import At, UniMessage -from nonebot_plugin_apscheduler import scheduler -from pydantic import BaseModel - -from zhenxun.configs.config import BotConfig, Config -from zhenxun.models.group_member_info import GroupInfoUser -from zhenxun.models.user_console import UserConsole -from zhenxun.utils.enum import GoldHandle -from zhenxun.utils.exception import InsufficientGold -from zhenxun.utils.image_utils import BuildImage, BuildMat, MatType, text2image -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from .model import RussianUser - -base_config = Config.get("russian") - - -class Russian(BaseModel): - - at_user: str | None - """指定决斗对象""" - player1: tuple[str, str] - """玩家1id, 昵称""" - player2: tuple[str, str] | None = None - """玩家2id, 昵称""" - money: int - """金额""" - bullet_num: int - """子弹数""" - bullet_arr: list[int] = [] - """子弹排列""" - bullet_index: int = 0 - """当前子弹下标""" - next_user: str = "" - """下一个开枪用户""" - time: float = time.time() - """创建时间""" - win_user: str | None = None - """胜利者""" - - -class RussianManage: - - def __init__(self) -> None: - self._data: dict[str, Russian] = {} - - def __check_is_timeout(self, group_id: str) -> bool: - """检查决斗是否超时 - - 参数: - group_id: 群组id - - 返回: - bool: 是否超时 - """ - if russian := self._data.get(group_id): - if russian.time + 30 < time.time(): - return True - return False - - def __random_bullet(self, num: int) -> list[int]: - """随机排列子弹 - - 参数: - num: 子弹数量 - - 返回: - list[int]: 子弹排列数组 - """ - bullet_list = [0, 0, 0, 0, 0, 0, 0] - for i in random.sample([0, 1, 2, 3, 4, 5, 6], num): - bullet_list[i] = 1 - return bullet_list - - def __remove_job(self, group_id: str): - """移除定时任务 - - 参数: - group_id: 群组id - """ - try: - scheduler.remove_job(f"russian_job_{group_id}") - except JobLookupError: - pass - - def __build_job( - self, bot: Bot, group_id: str, is_add: bool = False, platform: str | None = None - ): - """移除定时任务和构建新定时任务 - - 参数: - bot: Bot - group_id: 群组id - is_add: 是否添加新定时任务. - platform: 平台 - """ - self.__remove_job(group_id) - if is_add: - date = datetime.now() + timedelta(seconds=31) - scheduler.add_job( - self.__auto_end_game, - "date", - run_date=date.replace(microsecond=0), - id=f"russian_job_{group_id}", - args=[bot, group_id, platform], - ) - - async def __auto_end_game(self, bot: Bot, group_id: str, platform: str): - """自动结束对决 - - 参数: - bot: Bot - group_id: 群组id - platform: 平台 - """ - result = await self.settlement(group_id, None, platform) - if result: - await PlatformUtils.send_message(bot, None, group_id, result) - - async def add_russian(self, bot: Bot, group_id: str, rus: Russian) -> UniMessage: - """添加决斗 - - 参数: - bot: Bot - group_id: 群组id - rus: Russian - - 返回: - UniMessage: 返回消息 - """ - russian = self._data.get(group_id) - if russian: - if russian.time + 30 < time.time(): - if not russian.player2: - return MessageUtils.build_message( - f"现在是 {russian.player1[1]} 发起的对决, 请接受对决或等待决斗超时..." - ) - else: - return MessageUtils.build_message( - f"{russian.player1[1]} 和 {russian.player2[1]}的对决还未结束!" - ) - return MessageUtils.build_message( - f"现在是 {russian.player1[1]} 发起的对决\n请等待比赛结束后再开始下一轮..." - ) - max_money = base_config.get("MAX_RUSSIAN_BET_GOLD") - if rus.money > max_money: - return MessageUtils.build_message(f"太多了!单次金额不能超过{max_money}!") - user = await UserConsole.get_user(rus.player1[0]) - if user.gold < rus.money: - return MessageUtils.build_message("你没有足够的钱支撑起这场挑战") - rus.bullet_arr = self.__random_bullet(rus.bullet_num) - self._data[group_id] = rus - message_list: list[str | At] = [] - if rus.at_user: - user = await GroupInfoUser.get_or_none( - user_id=rus.at_user, group_id=group_id - ) - message_list = [ - f"{rus.player1[1]} 向", - At(flag="user", target=rus.at_user), - f"发起了决斗!请 {user.user_name if user else rus.at_user} 在30秒内回复‘接受对决’ or ‘拒绝对决’,超时此次决斗作废!", - ] - else: - message_list = [ - "若30秒内无人接受挑战则此次对决作废【首次游玩请at我发送 ’帮助俄罗斯轮盘‘ 来查看命令】" - ] - result = ( - "咔 " * rus.bullet_num - + f"装填完毕\n挑战金额:{rus.money}\n第一枪的概率为:{float(rus.bullet_num) / 7.0 * 100:.2f}%\n" - ) - - message_list.insert(0, result) - self.__build_job(bot, group_id, True) - return MessageUtils.build_message(message_list) # type: ignore - - async def accept( - self, bot: Bot, group_id: str, user_id: str, uname: str - ) -> UniMessage: - """接受对决 - - 参数: - bot: Bot - group_id: 群组id - user_id: 用户id - uname: 用户名称 - - 返回: - Text | MessageFactory: 返回消息 - """ - if russian := self._data.get(group_id): - if russian.at_user and russian.at_user != user_id: - return MessageUtils.build_message("又不是找你决斗,你接受什么啊!气!") - if russian.player2: - return MessageUtils.build_message( - "当前决斗已被其他玩家接受!请等待下局对决!" - ) - if russian.player1[0] == user_id: - return MessageUtils.build_message("你发起的对决,你接受什么啊!气!") - user = await UserConsole.get_user(user_id) - if user.gold < russian.money: - return MessageUtils.build_message("你没有足够的钱来接受这场挑战...") - russian.player2 = (user_id, uname) - russian.next_user = russian.player1[0] - self.__build_job(bot, group_id, True) - return MessageUtils.build_message( - [ - "决斗已经开始!请", - At(flag="user", target=russian.player1[0]), - "先开枪!", - ] - ) - return MessageUtils.build_message( - "目前没有进行的决斗,请发送 装弹 开启决斗吧!" - ) - - def refuse(self, group_id: str, user_id: str, uname: str) -> UniMessage: - """拒绝决斗 - - 参数: - group_id: 群组id - user_id: 用户id - uname: 用户名称 - - 返回: - Text | MessageFactory: 返回消息 - """ - if russian := self._data.get(group_id): - if russian.at_user: - if russian.at_user != user_id: - return MessageUtils.build_message( - "又不是找你决斗,你拒绝什么啊!气!" - ) - del self._data[group_id] - self.__remove_job(group_id) - return MessageUtils.build_message( - [ - At(flag="user", target=russian.player1[0]), - f"{uname}拒绝了你的对决!", - ] - ) - return MessageUtils.build_message("当前决斗并没有指定对手,无法拒绝哦!") - return MessageUtils.build_message( - "目前没有进行的决斗,请发送 装弹 开启决斗吧!" - ) - - async def shoot( - self, bot: Bot, group_id: str, user_id: str, uname: str, platform: str - ) -> tuple[UniMessage, UniMessage | None]: - """开枪 - - 参数: - bot: Bot - group_id: 群组id - user_id: 用户id - uname: 用户名称 - platform: 平台 - - 返回: - Text | MessageFactory: 返回消息 - """ - if russian := self._data.get(group_id): - if not russian.player2: - return ( - MessageUtils.build_message("当前还没有玩家接受对决,无法开枪..."), - None, - ) - if user_id not in [russian.player1[0], russian.player2[0]]: - """非玩家1和玩家2发送开枪""" - return ( - MessageUtils.build_message( - random.choice( - [ - f"不要打扰 {russian.player1[1]} 和 {russian.player2[1]} 的决斗啊!", - f"给我好好做好一个观众!不然{BotConfig.self_nickname}就要生气了", - f"不要捣乱啊baka{uname}!", - ] - ) - ), - None, - ) - if user_id != russian.next_user: - """相同玩家连续开枪""" - return ( - MessageUtils.build_message( - f"你的左轮不是连发的!该 {russian.player2[1]} 开枪了!" - ), - None, - ) - if russian.bullet_arr[russian.bullet_index] == 1: - """去世""" - result = MessageUtils.build_message( - random.choice( - [ - '"嘭!",你直接去世了', - "眼前一黑,你直接穿越到了异世界...(死亡)", - "终究还是你先走一步...", - ] - ) - ) - settle = await self.settlement(group_id, user_id, platform) - return result, settle - else: - """存活""" - p = ( - (russian.bullet_index + russian.bullet_num + 1) - / len(russian.bullet_arr) - * 100 - ) - result = ( - random.choice( - [ - "呼呼,没有爆裂的声响,你活了下来", - "虽然黑洞洞的枪口很恐怖,但好在没有子弹射出来,你活下来了", - '"咔",你没死,看来运气不错', - ] - ) - + f"\n下一枪中弹的概率: {p:.2f}%, 轮到 " - ) - next_user = ( - russian.player2[0] - if russian.next_user == russian.player1[0] - else russian.player1[0] - ) - russian.next_user = next_user - russian.bullet_index += 1 - self.__build_job(bot, group_id, True) - return ( - MessageUtils.build_message( - [result, At(flag="user", target=next_user), " 了!"] - ), - None, - ) - return ( - MessageUtils.build_message("目前没有进行的决斗,请发送 装弹 开启决斗吧!"), - None, - ) - - async def settlement( - self, group_id: str, user_id: str | None, platform: str | None = None - ) -> UniMessage: - """结算 - - 参数: - group_id: 群组id - user_id: 用户id - platform: 平台 - - 返回: - Text | MessageFactory: 返回消息 - """ - if russian := self._data.get(group_id): - if not russian.player2: - if self.__check_is_timeout(group_id): - del self._data[group_id] - return MessageUtils.build_message( - "规定时间内还未有人接受决斗,当前决斗过期..." - ) - return MessageUtils.build_message("决斗还未开始,,无法结算哦...") - if user_id and user_id not in [russian.player1[0], russian.player2[0]]: - return MessageUtils.build_message(f"吃瓜群众不要捣乱!黄牌警告!") - if not self.__check_is_timeout(group_id): - return MessageUtils.build_message( - f"{russian.player1[1]} 和 {russian.player2[1]} 比赛并未超时,请继续比赛..." - ) - win_user = None - lose_user = None - if win_user: - russian.next_user = ( - russian.player1[0] - if win_user == russian.player2[0] - else russian.player2[0] - ) - if russian.next_user != russian.player1[0]: - win_user = russian.player1 - lose_user = russian.player2 - else: - win_user = russian.player2 - lose_user = russian.player1 - if win_user and lose_user: - rand = 0 - if russian.money > 10: - rand = random.randint(0, 5) - fee = int(russian.money * float(rand) / 100) - fee = 1 if fee < 1 and rand != 0 else fee - else: - fee = 0 - winner = await RussianUser.add_count(win_user[0], group_id, "win") - loser = await RussianUser.add_count(lose_user[0], group_id, "lose") - await RussianUser.money( - win_user[0], group_id, "win", russian.money - fee - ) - await RussianUser.money(lose_user[0], group_id, "lose", russian.money) - await UserConsole.add_gold( - win_user[0], russian.money - fee, "russian", platform - ) - try: - await UserConsole.reduce_gold( - lose_user[0], - russian.money, - GoldHandle.PLUGIN, - "russian", - platform, - ) - except InsufficientGold: - if u := await UserConsole.get_user(lose_user[0]): - u.gold = 0 - await u.save(update_fields=["gold"]) - result = [ - "这场决斗是 ", - At(flag="user", target=win_user[0]), - " 胜利了!", - ] - image = await text2image( - f"结算:\n" - f"\t胜者:{win_user[1]}\n" - f"\t赢取金币:{russian.money - fee}\n" - f"\t累计胜场:{winner.win_count}\n" - f"\t累计赚取金币:{winner.make_money}\n" - f"-------------------\n" - f"\t败者:{lose_user[1]}\n" - f"\t输掉金币:{russian.money}\n" - f"\t累计败场:{loser.fail_count}\n" - f"\t累计输掉金币:{loser.lose_money}\n" - f"-------------------\n" - f"哼哼,{BotConfig.self_nickname}从中收取了 {float(rand)}%({fee}金币) 作为手续费!\n" - f"子弹排列:{russian.bullet_arr}", - padding=10, - color="#f9f6f2", - ) - self.__remove_job(group_id) - result.append(image) - del self._data[group_id] - return MessageUtils.build_message(result) - return MessageUtils.build_message("赢家和输家获取错误...") - return MessageUtils.build_message("比赛并没有开始...无法结算...") - - async def __get_x_index(self, users: list[RussianUser], group_id: str): - uid_list = [u.user_id for u in users] - group_user_list = await GroupInfoUser.filter( - user_id__in=uid_list, group_id=group_id - ).all() - group_user = {gu.user_id: gu.user_name for gu in group_user_list} - data = [] - for uid in uid_list: - if uid in group_user: - data.append(group_user[uid]) - else: - data.append(uid) - return data - - async def rank( - self, user_id: str, group_id: str, rank_type: str, num: int - ) -> BuildImage | str: - x_index = [] - data = [] - title = "" - x_name = "" - if rank_type == "win": - users = ( - await RussianUser.filter(group_id=group_id, win_count__not=0) - .order_by("win_count") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.win_count for u in users] - title = "胜场排行" - x_name = "场次" - if rank_type == "lose": - users = ( - await RussianUser.filter(group_id=group_id, fail_count__not=0) - .order_by("fail_count") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.fail_count for u in users] - title = "败场排行" - x_name = "场次" - if rank_type == "a": - users = ( - await RussianUser.filter(group_id=group_id, make_money__not=0) - .order_by("make_money") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.make_money for u in users] - title = "欧洲人排行" - x_name = "金币" - if rank_type == "b": - users = ( - await RussianUser.filter(group_id=group_id, lose_money__not=0) - .order_by("lose_money") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.lose_money for u in users] - title = "慈善家排行" - x_name = "金币" - if rank_type == "max_win": - users = ( - await RussianUser.filter(group_id=group_id, max_winning_streak__not=0) - .order_by("max_winning_streak") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.max_winning_streak for u in users] - title = "最高连胜排行" - x_name = "场次" - if rank_type == "max_lose": - users = ( - await RussianUser.filter(group_id=group_id, max_losing_streak__not=0) - .order_by("max_losing_streak") - .limit(num) - ) - x_index = await self.__get_x_index(users, group_id) - data = [u.max_losing_streak for u in users] - title = "最高连败排行" - x_name = "场次" - if not data: - return "当前数据为空..." - mat = BuildMat(MatType.BARH) - mat.x_index = x_index - mat.data = data # type: ignore - mat.title = title - mat.x_name = x_name - return await mat.build() - - -russian_manage = RussianManage() diff --git a/zhenxun/plugins/russian/model.py b/zhenxun/plugins/russian/model.py deleted file mode 100644 index 0fab9298c..000000000 --- a/zhenxun/plugins/russian/model.py +++ /dev/null @@ -1,107 +0,0 @@ -from tortoise import fields - -from zhenxun.services.db_context import Model - - -class RussianUser(Model): - - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255) - """群聊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) - """最大连败""" - - class Meta: - table = "russian_users" - table_description = "俄罗斯轮盘数据表" - unique_together = ("user_id", "group_id") - - @classmethod - async def add_count(cls, user_id: str, group_id: str, itype: str): - """添加用户输赢次数 - - 说明: - user_id: 用户id - group_id: 群号 - itype: 输或赢 'win' or 'lose' - """ - user, _ = await cls.get_or_create(user_id=user_id, 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", - ] - ) - return user - - @classmethod - async def money(cls, user_id: str, group_id: str, itype: str, count: int): - """添加用户输赢金钱 - - 参数: - user_id: 用户id - group_id: 群号 - itype: 输或赢 'win' or 'lose' - count: 金钱数量 - """ - user, _ = await cls.get_or_create(user_id=str(user_id), 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"]) - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE russian_users RENAME COLUMN user_qq TO user_id;", # 将user_qq改为user_id - "ALTER TABLE russian_users ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE russian_users ALTER COLUMN group_id TYPE character varying(255);", - ] diff --git a/zhenxun/plugins/search_anime/__init__.py b/zhenxun/plugins/search_anime/__init__.py deleted file mode 100644 index d12ad03ec..000000000 --- a/zhenxun/plugins/search_anime/__init__.py +++ /dev/null @@ -1,68 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from .data_source import from_anime_get_info - -__plugin_meta__ = PluginMetadata( - name="搜番", - description="找不到想看的动漫吗?", - usage=""" - 搜索动漫资源 - 指令: - 搜番 [番剧名称或者关键词] - 示例:搜番 刀剑神域 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="一些工具", - limits=[BaseBlock(result="搜索还未完成,不要重复触发!")], - configs=[ - RegisterConfig( - key="SEARCH_ANIME_MAX_INFO", - value=20, - help="搜索动漫返回的最大数量", - default_value=20, - type=int, - ) - ], - ).dict(), -) - -_matcher = on_alconna(Alconna("搜番", Args["name?", str]), priority=5, block=True) - - -@_matcher.handle() -async def _(name: Match[str]): - if name.available: - _matcher.set_path_arg("name", name.result) - - -@_matcher.got_path("name", prompt="是不是少了番名?") -async def _(session: EventSession, arparma: Arparma, name: str): - gid = session.id3 or session.id2 - await MessageUtils.build_message(f"开始搜番 {name}...").send() - anime_report = await from_anime_get_info( - name, - Config.get_config("search_anime", "SEARCH_ANIME_MAX_INFO"), - ) - if anime_report: - if isinstance(anime_report, str): - await MessageUtils.build_message(anime_report).finish() - await MessageUtils.build_message("\n\n".join(anime_report)).send() - logger.info( - f"搜索番剧 {name} 成功: {anime_report}", - arparma.header_result, - session=session, - ) - else: - logger.info(f"未找到番剧 {name}...") - await MessageUtils.build_message( - f"未找到番剧 {name}(也有可能是超时,再尝试一下?)" - ).send() diff --git a/zhenxun/plugins/search_anime/data_source.py b/zhenxun/plugins/search_anime/data_source.py deleted file mode 100644 index 59d0ac615..000000000 --- a/zhenxun/plugins/search_anime/data_source.py +++ /dev/null @@ -1,53 +0,0 @@ -import time -from urllib import parse - -import feedparser -from lxml import etree - -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx - - -async def from_anime_get_info(key_word: str, max_: int) -> str | list[str]: - s_time = time.time() - url = "https://share.dmhy.org/topics/rss/rss.xml?keyword=" + parse.quote(key_word) - try: - repass = await get_repass(url, max_) - except Exception as e: - logger.error(f"发生了一些错误 {type(e)}", e=e) - return "发生了一些错误!" - repass.insert(0, f"搜索 {key_word} 结果(耗时 {int(time.time() - s_time)} 秒):\n") - return repass - - -async def get_repass(url: str, max_: int) -> list[str]: - put_line = [] - text = (await AsyncHttpx.get(url)).text - d = feedparser.parse(text) - max_ = ( - max_ - if max_ < len([e.link for e in d.entries]) - else len([e.link for e in d.entries]) - ) - url_list = [e.link for e in d.entries][:max_] - for u in url_list: - try: - text = (await AsyncHttpx.get(u)).text - html = etree.HTML(text) # type: ignore - magent = html.xpath('.//a[@id="a_magnet"]/text()')[0] - title = html.xpath(".//h3/text()")[0] - item = html.xpath('//div[@class="info resource-info right"]/ul/li') - class_a = ( - item[0] - .xpath("string(.)")[5:] - .strip() - .replace("\xa0", "") - .replace("\t", "") - ) - size = item[3].xpath("string(.)")[5:].strip() - put_line.append( - "【{}】| {}\n【{}】| {}".format(class_a, title, size, magent) - ) - except Exception as e: - logger.error(f"搜番发生错误", e=e) - return put_line diff --git a/zhenxun/plugins/search_buff_skin_price/__init__.py b/zhenxun/plugins/search_buff_skin_price/__init__.py deleted file mode 100644 index ca2a320fa..000000000 --- a/zhenxun/plugins/search_buff_skin_price/__init__.py +++ /dev/null @@ -1,104 +0,0 @@ -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import BotConfig -from zhenxun.configs.utils import BaseBlock, PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from .data_source import get_price, update_buff_cookie - -__plugin_meta__ = PluginMetadata( - name="BUFF查询皮肤", - description="BUFF皮肤底价查询", - usage=""" - 在线实时获取BUFF指定皮肤所有磨损底价 - 指令: - 查询皮肤 [枪械名] [皮肤名称] - 示例:查询皮肤 ak47 二西莫夫 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="一些工具", - limits=[BaseBlock(result="您有皮肤正在搜索,请稍等...")], - configs=[ - RegisterConfig( - key="BUFF_PROXY", - value=None, - help="BUFF代理,有些厂ip可能被屏蔽", - ), - RegisterConfig( - key="COOKIE", - value=None, - help="BUFF的账号cookie", - ), - ], - ).dict(), -) - - -_matcher = on_alconna( - Alconna("查询皮肤", Args["name", str]["skin", str]), - aliases={"皮肤查询"}, - priority=5, - block=True, -) - -_cookie_matcher = on_alconna( - Alconna("设置cookie", Args["cookie", str]), - rule=to_me(), - permission=SUPERUSER, - priority=1, -) - - -@_matcher.handle() -async def _(name: Match[str], skin: Match[str]): - if name.available: - _matcher.set_path_arg("name", name.result) - if skin.available: - _matcher.set_path_arg("skin", skin.result) - - -@_matcher.got_path("name", prompt="要查询什么武器呢?") -@_matcher.got_path("skin", prompt="要查询该武器的什么皮肤呢?") -async def arg_handle( - session: EventSession, - arparma: Arparma, - name: str, - skin: str, -): - if name in ["算了", "取消"] or skin in ["算了", "取消"]: - await MessageUtils.build_message("已取消操作...").finish() - result = "" - if name in ["ak", "ak47"]: - name = "ak-47" - name = name + " | " + skin - status_code = -1 - try: - result, status_code = await get_price(name) - except FileNotFoundError: - await MessageUtils.build_message( - f'请先对{BotConfig.self_nickname}说"设置cookie"来设置cookie!' - ).send(at_sender=True) - if status_code in [996, 997, 998]: - await MessageUtils.build_message(result).finish() - if result: - logger.info(f"查询皮肤: {name}", arparma.header_result, session=session) - await MessageUtils.build_message(result).finish() - else: - logger.info( - f" 查询皮肤:{name} 没有查询到", arparma.header_result, session=session - ) - await MessageUtils.build_message("没有查询到哦,请检查格式吧").send() - - -@_cookie_matcher.handle() -async def _(session: EventSession, arparma: Arparma, cookie: str): - result = update_buff_cookie(cookie) - await MessageUtils.build_message(result).send(at_sender=True) - logger.info("更新BUFF COOKIE", arparma.header_result, session=session) diff --git a/zhenxun/plugins/search_buff_skin_price/data_source.py b/zhenxun/plugins/search_buff_skin_price/data_source.py deleted file mode 100644 index 8dbe6a596..000000000 --- a/zhenxun/plugins/search_buff_skin_price/data_source.py +++ /dev/null @@ -1,62 +0,0 @@ -from asyncio.exceptions import TimeoutError - -from zhenxun.configs.config import Config -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx - -url = "https://buff.163.com/api/market/goods" - - -async def get_price(d_name: str) -> tuple[str, int]: - """查看皮肤价格 - - 参数: - d_name: 武器皮肤,如:awp 二西莫夫 - - 返回: - tuple[str, int]: 查询数据和状态 - """ - cookie = {"session": Config.get_config("search_buff_skin_price", "COOKIE")} - name_list = [] - price_list = [] - parameter = {"game": "csgo", "page_num": "1", "search": d_name} - try: - response = await AsyncHttpx.get( - url, - proxy=Config.get_config("search_buff_skin_price", "BUFF_PROXY"), - params=parameter, - cookies=cookie, - ) - if response.status_code == 200: - try: - if response.text.find("Login Required") != -1: - return "BUFF登录被重置,请联系管理员重新登入", 996 - data = response.json()["data"] - total_page = data["total_page"] - data = data["items"] - 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: - logger.error(f"BUFF查询皮肤发生错误 {type(e)}:{e}") - return "没有查询到...", 998 - else: - return "访问失败!", response.status_code - except TimeoutError: - return "访问超时! 请重试或稍后再试!", 997 - result = f"皮肤: {d_name}({len(name_list)})\n" - for i in range(len(name_list)): - result += name_list[i] + ": " + price_list[i] + "\n" - return result[:-1], 999 - - -def update_buff_cookie(cookie: str) -> str: - Config.set_config("search_buff_skin_price", "COOKIE", cookie) - return "更新cookie成功" - - -if __name__ == "__main__": - print(get_price("awp 二西莫夫")) diff --git a/zhenxun/plugins/search_image/__init__.py b/zhenxun/plugins/search_image/__init__.py deleted file mode 100644 index 38e86de0c..000000000 --- a/zhenxun/plugins/search_image/__init__.py +++ /dev/null @@ -1,94 +0,0 @@ -from pathlib import Path - -from nonebot.adapters import Bot -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma -from nonebot_plugin_alconna import Image as alcImg -from nonebot_plugin_alconna import Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils - -from .saucenao import get_saucenao_image - -__plugin_meta__ = PluginMetadata( - name="识图", - description="以图搜图,看破本源", - usage=""" - 识别图片 [二次元图片] - 指令: - 识图 [图片] - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="一些工具", - configs=[ - RegisterConfig( - key="MAX_FIND_IMAGE_COUNT", - value=3, - help="搜索动漫返回的最大数量", - default_value=3, - type=int, - ), - RegisterConfig( - key="API_KEY", - value=None, - help="Saucenao的API_KEY,通过 https://saucenao.com/user.php?page=search-api 注册获取", - ), - ], - ).dict(), -) - - -_matcher = on_alconna( - Alconna("识图", Args["mode?", str]["image?", alcImg]), block=True, priority=5 -) - - -async def get_image_info(mod: str, url: str) -> str | list[str | Path] | None: - if mod == "saucenao": - return await get_saucenao_image(url) - - -@_matcher.handle() -async def _(mode: Match[str], image: Match[alcImg]): - if mode.available: - _matcher.set_path_arg("mode", mode.result) - else: - _matcher.set_path_arg("mode", "saucenao") - if image.available: - _matcher.set_path_arg("image", image.result) - - -@_matcher.got_path("image", prompt="图来!") -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - mode: str, - image: alcImg, -): - gid = session.id3 or session.id2 - if not image.url: - await MessageUtils.build_message("图片url为空...").finish() - await MessageUtils.build_message("开始处理图片...").send() - info_list = await get_image_info(mode, image.url) - if isinstance(info_list, str): - await MessageUtils.build_message(info_list).finish(at_sender=True) - if not info_list: - await MessageUtils.build_message("未查询到...").finish() - platform = PlatformUtils.get_platform(bot) - if "qq" == platform and gid: - forward = MessageUtils.template2forward(info_list[1:], bot.self_id) # type: ignore - await bot.send_group_forward_msg( - group_id=int(gid), - messages=forward, # type: ignore - ) - else: - for info in info_list[1:]: - await MessageUtils.build_message(info).send() - logger.info(f" 识图: {image.url}", arparma.header_result, session=session) diff --git a/zhenxun/plugins/search_image/saucenao.py b/zhenxun/plugins/search_image/saucenao.py deleted file mode 100644 index eab44fab4..000000000 --- a/zhenxun/plugins/search_image/saucenao.py +++ /dev/null @@ -1,62 +0,0 @@ -import random -from pathlib import Path - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import TEMP_PATH -from zhenxun.services import logger -from zhenxun.utils.http_utils import AsyncHttpx - -API_URL_SAUCENAO = "https://saucenao.com/search.php" -API_URL_ASCII2D = "https://ascii2d.net/search/url/" -API_URL_IQDB = "https://iqdb.org/" - - -async def get_saucenao_image(url: str) -> str | list[str | Path]: - """获取图片源 - - 参数: - url: 图片url - - 返回: - str | list[Image | Text]: 识图数据 - """ - api_key = Config.get_config("search_image", "API_KEY") - if not api_key: - return "Saucenao 缺失API_KEY!" - params = { - "output_type": 2, - "api_key": api_key, - "testmode": 1, - "numres": 6, - "db": 999, - "url": url, - } - data = (await AsyncHttpx.post(API_URL_SAUCENAO, params=params)).json() - if data["header"]["status"] != 0: - return f"Saucenao识图失败..status:{data['header']['status']}" - data = data["results"] - data = ( - data - if len(data) < Config.get_config("search_image", "MAX_FIND_IMAGE_COUNT") - else data[: Config.get_config("search_image", "MAX_FIND_IMAGE_COUNT")] - ) - msg_list = [] - index = random.randint(0, 10000) - if await AsyncHttpx.download_file(url, TEMP_PATH / f"saucenao_search_{index}.jpg"): - msg_list.append(TEMP_PATH / f"saucenao_search_{index}.jpg") - for info in data: - try: - similarity = info["header"]["similarity"] - tmp = f"相似度:{similarity}%\n" - for x in info["data"].keys(): - if x != "ext_urls": - tmp += f"{x}:{info['data'][x]}\n" - try: - if "source" not in info["data"].keys(): - tmp += f'source:{info["data"]["ext_urls"][0]}\n' - except KeyError: - tmp += f'source:{info["header"]["thumbnail"]}\n' - msg_list.append(tmp[:-1]) - except Exception as e: - logger.warning(f"识图获取图片信息发生错误", e=e) - return msg_list diff --git a/zhenxun/plugins/send_setu_/__init__.py b/zhenxun/plugins/send_setu_/__init__.py deleted file mode 100644 index eb35e275d..000000000 --- a/zhenxun/plugins/send_setu_/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from pathlib import Path - -import nonebot - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/send_setu_/_model.py b/zhenxun/plugins/send_setu_/_model.py deleted file mode 100644 index 865af7d13..000000000 --- a/zhenxun/plugins/send_setu_/_model.py +++ /dev/null @@ -1,87 +0,0 @@ -from tortoise import fields -from tortoise.contrib.postgres.functions import Random -from tortoise.expressions import Q -from typing_extensions import Self - -from zhenxun.services.db_context import Model - - -class Setu(Model): - - 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: int | None = None, - tags: list[str] | None = None, - r18: bool = False, - limit: int = 50, - ) -> list[Self] | Self | None: - """通过tag查找色图 - - 参数: - local_id: 本地色图 id - tags: tags - r18: 是否 r18,0:非r18 1:r18 2:混合 - limit: 获取数量 - - 返回: - list[Self] | Self | None: 色图数据 - """ - if local_id: - 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.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, img_url: str) -> int: - """删除图片并替换 - - 参数: - pid: 图片pid - - 返回: - int: 删除返回的本地id - """ - print(pid) - return_id = -1 - if query := await cls.get_or_none(pid=pid, img_url=img_url): - 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/zhenxun/plugins/send_setu_/send_setu/__init__.py b/zhenxun/plugins/send_setu_/send_setu/__init__.py deleted file mode 100644 index fea1ad41e..000000000 --- a/zhenxun/plugins/send_setu_/send_setu/__init__.py +++ /dev/null @@ -1,243 +0,0 @@ -import random -from typing import Tuple - -from nonebot.adapters import Bot -from nonebot.matcher import Matcher -from nonebot.message import run_postprocessor -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import ( - Alconna, - Args, - Arparma, - Match, - Option, - on_alconna, - store_true, -) -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import BotConfig -from zhenxun.configs.utils import PluginCdBlock, PluginExtraData, RegisterConfig -from zhenxun.models.sign_user import SignUser -from zhenxun.models.user_console import UserConsole -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.platform import PlatformUtils -from zhenxun.utils.withdraw_manage import WithdrawManager - -from ._data_source import SetuManage, base_config - -__plugin_meta__ = PluginMetadata( - name="色图", - description="不要小看涩图啊混蛋!", - usage=""" - 搜索 lolicon 图库,每日色图time... - 多个tag使用#连接 - 指令: - 色图: 随机色图 - 色图 -r: 随机在线r18涩图 - 色图 -id [id]: 本地指定id色图 - 色图 *[tags]: 在线搜索指定tag色图 - 色图 *[tags] -r: 同上, r18色图 - [1-9]张涩图: 本地随机色图连发 - [1-9]张[tags]的涩图: 在线搜索指定tag色图连发 - 示例:色图 萝莉|少女#白丝|黑丝 - 示例:色图 萝莉#猫娘 - 注: - tag至多取前20项,| 为或,萝莉|少女=萝莉或者少女 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="来点好康的", - limits=[PluginCdBlock(result="您冲的太快了,请稍后再冲.")], - configs=[ - RegisterConfig( - key="WITHDRAW_SETU_MESSAGE", - value=(0, 1), - help="自动撤回,参1:延迟撤回色图时间(秒),0 为关闭 | 参2:监控聊天类型,0(私聊) 1(群聊) 2(群聊+私聊)", - default_value=(0, 1), - type=Tuple[int, int], - ), - RegisterConfig( - key="ONLY_USE_LOCAL_SETU", - value=False, - help="仅仅使用本地色图,不在线搜索", - default_value=False, - type=bool, - ), - RegisterConfig( - key="INITIAL_SETU_PROBABILITY", - value=0.7, - help="初始色图概率,总概率 = 初始色图概率 + 好感度", - default_value=0.7, - type=float, - ), - RegisterConfig( - key="DOWNLOAD_SETU", - value=True, - help="是否存储下载的色图,使用本地色图可以加快图片发送速度", - default_value=True, - type=float, - ), - RegisterConfig( - key="TIMEOUT", - value=10, - help="色图下载超时限制(秒)", - default_value=10, - type=int, - ), - RegisterConfig( - key="SHOW_INFO", - value=True, - help="是否显示色图的基本信息,如PID等", - default_value=True, - type=bool, - ), - RegisterConfig( - key="ALLOW_GROUP_R18", - value=False, - help="在群聊中启用R18权限", - default_value=False, - type=bool, - ), - RegisterConfig( - key="MAX_ONCE_NUM2FORWARD", - value=None, - help="单次发送的图片数量达到指定值时转发为合并消息", - default_value=None, - type=int, - ), - RegisterConfig( - key="MAX_ONCE_NUM", - value=10, - help="单次发送图片数量限制", - default_value=10, - type=int, - ), - RegisterConfig( - module="pixiv", - key="PIXIV_NGINX_URL", - value="i.pixiv.re", - help="Pixiv反向代理", - default_value="i.pixiv.re", - ), - ], - ).dict(), -) - - -@run_postprocessor -async def _( - matcher: Matcher, - exception: Exception | None, - session: EventSession, -): - if matcher.plugin_name == "send_setu": - # 添加数据至数据库 - try: - await SetuManage.save_to_database() - logger.info("色图数据自动存储数据库成功...") - except Exception: - pass - - -_matcher = on_alconna( - Alconna( - "色图", - Args["tags?", str], - Option("-n", Args["num", int, 1], help_text="数量"), - Option("-id", Args["local_id", int], help_text="本地id"), - Option("-r", action=store_true, help_text="r18"), - ), - aliases={"涩图", "不够色", "来一发", "再来点"}, - priority=5, - block=True, -) - -_matcher.shortcut( - r".*?(?P\d*)[份|发|张|个|次|点](?P.*)[瑟|色|涩]图.*?", - command="色图", - arguments=["{tags}", "-n", "{num}"], - prefix=True, -) - - -@_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - arparma: Arparma, - num: Match[int], - tags: Match[str], - local_id: Match[int], -): - _tags = tags.result.split("#") if tags.available else None - if _tags and BotConfig.self_nickname in _tags: - await MessageUtils.build_message( - "咳咳咳,虽然我很可爱,但是我木有自己的色图~~~有的话记得发我一份呀" - ).finish() - if not session.id1: - await MessageUtils.build_message("用户id为空...").finish() - gid = session.id3 or session.id2 - user_console = await UserConsole.get_user(session.id1, session.platform) - user, _ = await SignUser.get_or_create( - user_id=session.id1, - defaults={"user_console": user_console, "platform": session.platform}, - ) - if session.id1 not in bot.config.superusers: - """超级用户跳过罗翔""" - if result := SetuManage.get_luo(float(user.impression)): - await result.finish() - is_r18 = arparma.find("r") - _num = num.result if num.available else 1 - if is_r18 and gid: - """群聊中禁止查看r18""" - if not base_config.get("ALLOW_GROUP_R18"): - await MessageUtils.build_message( - random.choice( - [ - "这种不好意思的东西怎么可能给这么多人看啦", - "羞羞脸!给我滚出克私聊!", - "变态变态变态变态大变态!", - ] - ) - ).finish() - if local_id.available: - """指定id""" - result = await SetuManage.get_setu(local_id=local_id.result) - if isinstance(result, str): - await MessageUtils.build_message(result).finish(reply_to=True) - await result[0].finish() - result_list = await SetuManage.get_setu(tags=_tags, num=_num, is_r18=is_r18) - if isinstance(result_list, str): - await MessageUtils.build_message(result_list).finish(reply_to=True) - max_once_num2forward = base_config.get("MAX_ONCE_NUM2FORWARD") - platform = PlatformUtils.get_platform(bot) - if ( - "qq" == platform - and gid - and max_once_num2forward - and len(result_list) >= max_once_num2forward - ): - logger.debug("使用合并转发转发色图数据", arparma.header_result, session=session) - forward = MessageUtils.template2forward(result_list, bot.self_id) # type: ignore - await bot.send_group_forward_msg( - group_id=int(gid), - messages=forward, # type: ignore - ) - else: - for result in result_list: - logger.info(f"发送色图 {result}", arparma.header_result, session=session) - receipt = await result.send() - if receipt: - message_id = receipt.msg_ids[0]["message_id"] - await WithdrawManager.withdraw_message( - bot, - message_id, - base_config.get("WITHDRAW_SETU_MESSAGE"), - session, - ) - logger.info( - f"调用发送 {num}张 色图 tags: {_tags}", arparma.header_result, session=session - ) diff --git a/zhenxun/plugins/send_setu_/send_setu/_data_source.py b/zhenxun/plugins/send_setu_/send_setu/_data_source.py deleted file mode 100644 index f7a15d328..000000000 --- a/zhenxun/plugins/send_setu_/send_setu/_data_source.py +++ /dev/null @@ -1,360 +0,0 @@ -import os -import random -from pathlib import Path - -from asyncpg import UniqueViolationError -from nonebot_plugin_alconna import UniMessage - -from zhenxun.configs.config import BotConfig, Config -from zhenxun.configs.path_config import IMAGE_PATH, TEMP_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import compressed_image -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.utils import change_img_md5, change_pixiv_image_links - -from .._model import Setu - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net", -} - -base_config = Config.get("send_setu") - - -class SetuManage: - - URL = "https://api.lolicon.app/setu/v2" - save_data = [] - - @classmethod - async def get_setu( - cls, - *, - local_id: int | None = None, - num: int = 10, - tags: list[str] | None = None, - is_r18: bool = False, - ) -> list[UniMessage] | str: - """获取色图 - - 参数: - local_id: 指定图片id - num: 数量 - tags: 标签 - is_r18: 是否r18 - - 返回: - list[MessageFactory] | str: 色图数据列表或消息 - - """ - result_list = [] - if local_id: - """本地id""" - data_list = await cls.get_setu_list(local_id=local_id) - if isinstance(data_list, str): - return data_list - file = await cls.get_image(data_list[0]) - if isinstance(file, str): - return file - return [cls.init_image_message(file, data_list[0])] - if base_config.get("ONLY_USE_LOCAL_SETU"): - """仅使用本地色图""" - flag = False - data_list = await cls.get_setu_list(tags=tags, is_r18=is_r18) - if isinstance(data_list, str): - return data_list - cls.save_data = data_list - if num > len(data_list): - num = len(data_list) - flag = True - setu_list = random.sample(data_list, num) - for setu in setu_list: - base_path = None - if setu.is_r18: - base_path = IMAGE_PATH / "_r18" - else: - base_path = IMAGE_PATH / "_setu" - file_path = base_path / f"{setu.local_id}.jpg" - if not file_path.exists(): - return f"本地色图Id: {setu.local_id} 不存在..." - result_list.append(cls.init_image_message(file_path, setu)) - if flag: - result_list.append( - MessageUtils.build_message("坏了,已经没图了,被榨干了!") - ) - return result_list - data_list = await cls.search_lolicon(tags, num, is_r18) - if isinstance(data_list, str): - """搜索失败, 从本地数据库中搜索""" - data_list = await cls.get_setu_list(tags=tags, is_r18=is_r18) - if isinstance(data_list, str): - return data_list - if not data_list: - return "没找到符合条件的色图..." - cls.save_data = data_list - flag = False - if num > len(data_list): - num = len(data_list) - flag = True - for setu in data_list: - file = await cls.get_image(setu) - if isinstance(file, str): - result_list.append(MessageUtils.build_message(file)) - continue - result_list.append(cls.init_image_message(file, setu)) - if not result_list: - return "没找到符合条件的色图..." - if flag: - result_list.append( - MessageUtils.build_message("坏了,已经没图了,被榨干了!") - ) - return result_list - - @classmethod - def init_image_message(cls, file: Path, setu: Setu) -> UniMessage: - """初始化图片发送消息 - - 参数: - file: 图片路径 - setu: Setu - - 返回: - UniMessage: 发送消息内容 - """ - data_list = [] - if base_config.get("SHOW_INFO"): - data_list.append( - f"id:{setu.local_id or ''}\n" - f"title:{setu.title}\n" - f"author:{setu.author}\n" - f"PID:{setu.pid}\n" - ) - data_list.append(file) - return MessageUtils.build_message(data_list) - - @classmethod - async def get_setu_list( - cls, - *, - local_id: int | None = None, - tags: list[str] | None = None, - is_r18: bool = False, - ) -> list[Setu] | str: - """获取数据库中的色图数据 - - 参数: - local_id: 色图本地id. - tags: 标签. - is_r18: 是否r18. - - 返回: - list[Setu] | str: 色图数据列表或消息 - """ - image_list: list[Setu] = [] - if local_id: - image_count = await Setu.filter(is_r18=is_r18).count() - 1 - if local_id < 0 or local_id > image_count: - return f"超过当前上下限!({image_count})" - image_list = [await Setu.query_image(local_id, r18=is_r18)] # type: ignore - elif tags: - image_list = await Setu.query_image(tags=tags, r18=is_r18) # type: ignore - else: - image_list = await Setu.query_image(r18=is_r18) # type: ignore - if not image_list: - return "没找到符合条件的色图..." - return image_list - - @classmethod - def get_luo(cls, impression: float) -> UniMessage | None: - """罗翔 - - 参数: - impression: 好感度 - - 返回: - MessageFactory | None: 返回数据 - """ - if initial_setu_probability := base_config.get("INITIAL_SETU_PROBABILITY"): - probability = float(impression) + initial_setu_probability * 100 - if probability < random.randint(1, 101): - return MessageUtils.build_message( - [ - "我为什么要给你发这个?", - IMAGE_PATH - / "luoxiang" - / random.choice(os.listdir(IMAGE_PATH / "luoxiang")), - f"\n(快向{BotConfig.self_nickname}签到提升好感度吧!)", - ] - ) - return None - - @classmethod - async def get_image(cls, setu: Setu) -> str | Path: - """下载图片 - - 参数: - setu: Setu - - 返回: - str | Path: 图片路径或返回消息 - """ - url = change_pixiv_image_links(setu.img_url) - index = setu.local_id if setu.local_id else random.randint(1, 100000) - file_name = f"{index}_temp_setu.jpg" - base_path = TEMP_PATH - if setu.local_id: - """本地图片存在直接返回""" - file_name = f"{index}.jpg" - if setu.is_r18: - base_path = IMAGE_PATH / "_r18" - else: - base_path = IMAGE_PATH / "_setu" - local_file = base_path / file_name - if local_file.exists(): - return local_file - file = base_path / file_name - download_success = False - for i in range(3): - logger.debug(f"尝试在线下载第 {i+1} 次", "色图") - try: - if await AsyncHttpx.download_file( - url, - file, - timeout=base_config.get("TIMEOUT"), - ): - download_success = True - if setu.local_id is not None: - if ( - os.path.getsize(base_path / f"{index}.jpg") - > 1024 * 1024 * 1.5 - ): - compressed_image( - base_path / f"{index}.jpg", - ) - change_img_md5(file) - logger.info(f"下载 lolicon 图片 {url} 成功, id:{index}") - break - except TimeoutError as e: - logger.error(f"下载图片超时", "色图", e=e) - except Exception as e: - logger.error(f"下载图片错误", "色图", e=e) - return file if download_success else "图片被小怪兽恰掉啦..!QAQ" - - @classmethod - async def search_lolicon( - cls, tags: list[str] | None, num: int, is_r18: bool - ) -> list[Setu] | str: - """搜索lolicon色图 - - 参数: - tags: 标签 - num: 数量 - is_r18: 是否r18 - - 返回: - list[Setu] | str: 色图数据或返回消息 - """ - params = { - "r18": 1 if is_r18 else 0, # 添加r18参数 0为否,1为是,2为混合 - "tag": tags, # 若指定tag - "num": 20, # 一次返回的结果数量 - "size": ["original"], - } - for count in range(3): - logger.debug(f"尝试获取图片URL第 {count+1} 次", "色图") - try: - response = await AsyncHttpx.get( - cls.URL, timeout=base_config.get("TIMEOUT"), params=params - ) - if response.status_code == 200: - data = response.json() - if not data["error"]: - data = data["data"] - result_list = cls.__handle_data(data) - num = num if num < len(data) else len(data) - random_list = random.sample(result_list, num) - if not random_list: - return "没找到符合条件的色图..." - return random_list - else: - return "没找到符合条件的色图..." - except TimeoutError as e: - logger.error(f"获取图片URL超时", "色图", e=e) - except Exception as e: - logger.error(f"访问页面错误", "色图", e=e) - return "我网线被人拔了..QAQ" - - @classmethod - def __handle_data(cls, data: dict) -> list[Setu]: - """lolicon数据处理 - - 参数: - data: lolicon数据 - - 返回: - list[Setu]: 整理的数据 - """ - result_list = [] - for i in range(len(data)): - img_url = data[i]["urls"]["original"] - img_url = change_pixiv_image_links(img_url) - title = data[i]["title"] - author = data[i]["author"] - pid = data[i]["pid"] - tags = [] - for j in range(len(data[i]["tags"])): - tags.append(data[i]["tags"][j]) - # if command != "色图r": - # if "R-18" in tags: - # tags.remove("R-18") - setu = Setu( - title=title, - author=author, - pid=pid, - img_url=img_url, - tags=",".join(tags), - is_r18="R-18" in tags, - ) - result_list.append(setu) - return result_list - - @classmethod - async def save_to_database(cls): - """存储色图数据到数据库 - - 参数: - data_list: 色图数据列表 - """ - set_list = [] - exists_list = [] - for data in cls.save_data: - if f"{data.pid}:{data.img_url}" not in exists_list: - exists_list.append(f"{data.pid}:{data.img_url}") - set_list.append(data) - if set_list: - create_list = [] - _cnt = 0 - _r18_cnt = 0 - for setu in set_list: - try: - if not await Setu.exists(pid=setu.pid, img_url=setu.img_url): - idx = await Setu.filter(is_r18=setu.is_r18).count() - setu.local_id = idx + (_r18_cnt if setu.is_r18 else _cnt) - setu.img_hash = "" - if setu.is_r18: - _r18_cnt += 1 - else: - _cnt += 1 - create_list.append(setu) - except UniqueViolationError: - pass - cls.save_data = [] - if create_list: - try: - await Setu.bulk_create(create_list, 10) - logger.debug(f"成功保存 {len(create_list)} 条色图数据") - except Exception as e: - logger.error("存储色图数据错误...", e=e) diff --git a/zhenxun/plugins/send_setu_/update_setu/__init__.py b/zhenxun/plugins/send_setu_/update_setu/__init__.py deleted file mode 100644 index 2b5b6ae93..000000000 --- a/zhenxun/plugins/send_setu_/update_setu/__init__.py +++ /dev/null @@ -1,59 +0,0 @@ -from nonebot.permission import SUPERUSER -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Arparma, on_alconna -from nonebot_plugin_apscheduler import scheduler -from nonebot_plugin_session import EventSession - -from zhenxun.configs.config import Config -from zhenxun.configs.utils import BaseBlock, PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.enum import PluginType -from zhenxun.utils.message import MessageUtils - -from .data_source import update_setu_img - -__plugin_meta__ = PluginMetadata( - name="更新色图", - description="更新数据库内存在的色图", - usage=""" - 更新数据库内存在的色图 - 指令: - 更新色图 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - plugin_type=PluginType.SUPERUSER, - limits=[BaseBlock(result="色图正在更新...")], - ).dict(), -) - -_matcher = on_alconna( - Alconna("更新色图"), rule=to_me(), permission=SUPERUSER, priority=1, block=True -) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - if Config.get_config("send_setu", "DOWNLOAD_SETU"): - await MessageUtils.build_message("开始更新色图...").send(reply_to=True) - result = await update_setu_img(True) - if result: - await MessageUtils.build_message(result).send() - logger.info("更新色图", arparma.header_result, session=session) - else: - await MessageUtils.build_message("更新色图配置未开启...").send() - - -# 更新色图 -@scheduler.scheduled_job( - "cron", - hour=4, - minute=30, -) -async def _(): - if Config.get_config("send_setu", "DOWNLOAD_SETU"): - result = await update_setu_img() - if result: - logger.info(result, "自动更新色图") diff --git a/zhenxun/plugins/send_setu_/update_setu/data_source.py b/zhenxun/plugins/send_setu_/update_setu/data_source.py deleted file mode 100644 index 07d217d6e..000000000 --- a/zhenxun/plugins/send_setu_/update_setu/data_source.py +++ /dev/null @@ -1,187 +0,0 @@ -import os -import shutil -from datetime import datetime - -import nonebot -import ujson as json -from asyncpg.exceptions import UniqueViolationError -from nonebot.drivers import Driver -from PIL import UnidentifiedImageError - -from zhenxun.configs.config import Config -from zhenxun.configs.path_config import IMAGE_PATH, TEMP_PATH, TEXT_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import compressed_image -from zhenxun.utils.utils import change_pixiv_image_links - -from .._model import Setu - -driver: Driver = nonebot.get_driver() - -_path = IMAGE_PATH - - -# 替换旧色图数据,修复local_id一直是50的问题 -@driver.on_startup -async def update_old_setu_data(): - path = TEXT_PATH - setu_data_file = path / "setu_data.json" - r18_data_file = path / "r18_setu_data.json" - if setu_data_file.exists() or r18_data_file.exists(): - index = 0 - r18_index = 0 - count = 0 - fail_count = 0 - for file in [setu_data_file, r18_data_file]: - if file.exists(): - data = json.load(open(file, "r", encoding="utf8")) - for x in data: - if file == setu_data_file: - idx = index - if "R-18" in data[x]["tags"]: - data[x]["tags"].remove("R-18") - else: - idx = r18_index - img_url = ( - data[x]["img_url"].replace("i.pixiv.cat", "i.pximg.net") - if "i.pixiv.cat" in data[x]["img_url"] - else data[x]["img_url"] - ) - # idx = r18_index if 'R-18' in data[x]["tags"] else index - try: - 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 - else: - index += 1 - logger.info( - f'添加旧色图数据成功 PID:{data[x]["pid"]} index:{idx}....' - ) - except UniqueViolationError: - fail_count += 1 - logger.info( - f'添加旧色图数据失败,色图重复 PID:{data[x]["pid"]} index:{idx}....' - ) - file.unlink() - setu_url_path = path / "setu_url.json" - setu_r18_url_path = path / "setu_r18_url.json" - if setu_url_path.exists(): - setu_url_path.unlink() - if setu_r18_url_path.exists(): - setu_r18_url_path.unlink() - logger.info( - f"更新旧色图数据完成,成功更新数据:{count} 条,累计失败:{fail_count} 条" - ) - - -# 删除色图rar文件夹 -shutil.rmtree(IMAGE_PATH / "setu_rar", ignore_errors=True) -shutil.rmtree(IMAGE_PATH / "r18_rar", ignore_errors=True) -shutil.rmtree(IMAGE_PATH / "rar", ignore_errors=True) - -headers = { - "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6;" - " rv:2.0.1) Gecko/20100101 Firefox/4.0.1", - "Referer": "https://www.pixiv.net", -} - - -async def update_setu_img(flag: bool = False) -> str | None: - """更新色图 - - 参数: - flag: 是否手动更新. - - 返回: - str | None: 更新信息 - """ - image_list = await Setu.all().order_by("local_id") - image_list.reverse() - _success = 0 - error_info = [] - error_type = [] - count = 0 - for image in image_list: - count += 1 - path = _path / "_r18" if image.is_r18 else _path / "_setu" - local_image = path / f"{image.local_id}.jpg" - path.mkdir(exist_ok=True, parents=True) - TEMP_PATH.mkdir(exist_ok=True, parents=True) - if not local_image.exists() or not image.img_hash: - temp_file = TEMP_PATH / f"{image.local_id}.jpg" - if temp_file.exists(): - temp_file.unlink() - url_ = change_pixiv_image_links(image.img_url) - try: - if not await AsyncHttpx.download_file( - url_, TEMP_PATH / f"{image.local_id}.jpg" - ): - continue - _success += 1 - try: - if ( - os.path.getsize( - TEMP_PATH / f"{image.local_id}.jpg", - ) - > 1024 * 1024 * 1.5 - ): - compressed_image( - TEMP_PATH / f"{image.local_id}.jpg", - path / f"{image.local_id}.jpg", - ) - else: - logger.info( - f"不需要压缩,移动图片{TEMP_PATH}/{image.local_id}.jpg " - f"--> /{path}/{image.local_id}.jpg" - ) - os.rename( - TEMP_PATH / f"{image.local_id}.jpg", - path / f"{image.local_id}.jpg", - ) - except FileNotFoundError: - logger.warning(f"文件 {image.local_id}.jpg 不存在,跳过...") - continue - # img_hash = str(get_img_hash(f"{path}/{image.local_id}.jpg")) - image.img_hash = "" - await image.save(update_fields=["img_hash"]) - # await Setu.update_setu_data(image.pid, img_hash=img_hash) - except UnidentifiedImageError: - # 图片已删除 - 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, image.img_url) - if (path / f"{max_num}.jpg").exists(): - 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}") - if type(e) not in error_type: - error_type.append(type(e)) - error_info.append( - f"更新色图 {image.local_id}.jpg 错误 {type(e)}: {e}" - ) - else: - logger.info(f"更新色图 {image.local_id}.jpg 已存在") - if _success or error_info or flag: - return ( - f'{str(datetime.now()).split(".")[0]} 更新 色图 完成,本地存在 {count} 张,实际更新 {_success} 张,以下为更新时未知错误:\n' - + "\n".join(error_info), - ) - return None diff --git a/zhenxun/plugins/send_voice/__init__.py b/zhenxun/plugins/send_voice/__init__.py deleted file mode 100644 index eb35e275d..000000000 --- a/zhenxun/plugins/send_voice/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from pathlib import Path - -import nonebot - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/send_voice/dinggong.py b/zhenxun/plugins/send_voice/dinggong.py deleted file mode 100644 index a01129ca1..000000000 --- a/zhenxun/plugins/send_voice/dinggong.py +++ /dev/null @@ -1,51 +0,0 @@ -import os -import random - -from nonebot.plugin import PluginMetadata -from nonebot.rule import to_me -from nonebot_plugin_alconna import Alconna, Arparma, UniMessage, Voice, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import RECORD_PATH -from zhenxun.configs.utils import PluginCdBlock, PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -__plugin_meta__ = PluginMetadata( - name="钉宫骂我", - description="请狠狠的骂我一次!", - usage=""" - 多骂我一点,球球了 - 指令: - 骂老子 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - limits=[PluginCdBlock(cd=3, result="就...就算求我骂你也得慢慢来...")], - ).dict(), -) - -_matcher = on_alconna(Alconna("ma-wo"), rule=to_me(), priority=5, block=True) - -_matcher.shortcut( - r".*?骂.*?我.*?", - command="ma-wo", - arguments=[], - prefix=True, -) - -path = RECORD_PATH / "dinggong" - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - if not path.exists(): - await MessageUtils.build_message("钉宫语音文件夹不存在...").finish() - files = os.listdir(path) - if not files: - await MessageUtils.build_message("钉宫语音文件夹为空...").finish() - voice = random.choice(files) - await UniMessage([Voice(path=path / voice)]).send() - await MessageUtils.build_message(voice.split("_")[1]).send() - logger.info(f"发送钉宫骂人: {voice}", arparma.header_result, session=session) diff --git a/zhenxun/plugins/translate/__init__.py b/zhenxun/plugins/translate/__init__.py deleted file mode 100644 index 372a57740..000000000 --- a/zhenxun/plugins/translate/__init__.py +++ /dev/null @@ -1,91 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, Option, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData, RegisterConfig -from zhenxun.services.log import logger -from zhenxun.utils.depends import CheckConfig -from zhenxun.utils.image_utils import ImageTemplate -from zhenxun.utils.message import MessageUtils - -from .data_source import language, translate_message - -__plugin_meta__ = PluginMetadata( - name="翻译", - description="出国旅游好助手", - usage=""" - 指令: - 翻译语种: (查看soruce与to可用值,代码与中文都可) - 示例: - 翻译 你好: 将中文翻译为英文 - 翻译 Hello: 将英文翻译为中文 - - 翻译 你好 -to 希腊语: 将"你好"翻译为希腊语 - 翻译 你好: 允许form和to使用中文 - 翻译 你好 -form:中文 to:日语 你好: 指定原语种并将"你好"翻译为日文 - """.strip(), - extra=PluginExtraData( - author="HibiKier", - version="0.1", - menu_type="一些工具", - configs=[ - RegisterConfig(key="APPID", value=None, help="百度翻译APPID"), - RegisterConfig(key="SECRET_KEY", value=None, help="百度翻译秘钥"), - ], - ).dict(), -) - -_matcher = on_alconna( - Alconna( - "翻译", - Args["text", str], - Option("-s|--source", Args["source_text", str, "auto"]), - Option("-t|--to", Args["to_text", str, "auto"]), - ), - priority=5, - block=True, -) - -_language_matcher = on_alconna(Alconna("翻译语种"), priority=5, block=True) - - -@_language_matcher.handle() -async def _(session: EventSession, arparma: Arparma): - s = "" - column_list = ["语种", "代码"] - data_list = [] - for key, value in language.items(): - data_list.append([key, value]) - image = await ImageTemplate.table_page("翻译语种", "", column_list, data_list) - await MessageUtils.build_message(image).send() - logger.info(f"查看翻译语种", arparma.header_result, session=session) - - -@_matcher.handle( - parameterless=[ - CheckConfig(config="APPID"), - CheckConfig(config="SECRET_KEY"), - ] -) -async def _( - session: EventSession, - arparma: Arparma, - text: str, - source_text: Match[str], - to_text: Match[str], -): - source = source_text.result if source_text.available else "auto" - to = to_text.result if to_text.available else "auto" - values = language.values() - keys = language.keys() - if source not in values and source not in keys: - await MessageUtils.build_message("源语种不支持...").finish() - if to not in values and to not in keys: - await MessageUtils.build_message("目标语种不支持...").finish() - result = await translate_message(text, source, to) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info( - f"source: {source}, to: {to}, 翻译: {text}", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/plugins/translate/data_source.py b/zhenxun/plugins/translate/data_source.py deleted file mode 100644 index a7a3018d0..000000000 --- a/zhenxun/plugins/translate/data_source.py +++ /dev/null @@ -1,83 +0,0 @@ -import time -from hashlib import md5 - -from zhenxun.configs.config import Config -from zhenxun.utils.http_utils import AsyncHttpx - -URL = "http://api.fanyi.baidu.com/api/trans/vip/translate" - - -language = { - "自动": "auto", - "粤语": "yue", - "韩语": "kor", - "泰语": "th", - "葡萄牙语": "pt", - "希腊语": "el", - "保加利亚语": "bul", - "芬兰语": "fin", - "斯洛文尼亚语": "slo", - "繁体中文": "cht", - "中文": "zh", - "文言文": "wyw", - "法语": "fra", - "阿拉伯语": "ara", - "德语": "de", - "荷兰语": "nl", - "爱沙尼亚语": "est", - "捷克语": "cs", - "瑞典语": "swe", - "越南语": "vie", - "英语": "en", - "日语": "jp", - "西班牙语": "spa", - "俄语": "ru", - "意大利语": "it", - "波兰语": "pl", - "丹麦语": "dan", - "罗马尼亚语": "rom", - "匈牙利语": "hu", -} - - -async def translate_message(word: str, form: str, to: str) -> str: - """翻译 - - 参数: - word (str): 翻译文字 - form (str): 源语言 - to (str): 目标语言 - - 返回: - str: 翻译后的文字 - """ - if form in language: - form = language[form] - if to in language: - to = language[to] - salt = str(time.time()) - app_id = Config.get_config("translate", "APPID") - secret_key = Config.get_config("translate", "SECRET_KEY") - sign = app_id + word + salt + secret_key # type: ignore - md5_ = md5() - md5_.update(sign.encode("utf-8")) - sign = md5_.hexdigest() - params = { - "q": word, - "from": form, - "to": to, - "appid": app_id, - "salt": salt, - "sign": sign, - } - url = URL + "?" - for key, value in params.items(): - url += f"{key}={value}&" - url = url[:-1] - resp = await AsyncHttpx.get(url) - data = resp.json() - if data.get("error_code"): - return data.get("error_msg") - if trans_result := data.get("trans_result"): - return trans_result[0]["dst"] - return "没有找到翻译捏..." diff --git a/zhenxun/plugins/wbtop/__init__.py b/zhenxun/plugins/wbtop/__init__.py deleted file mode 100644 index fce1b9954..000000000 --- a/zhenxun/plugins/wbtop/__init__.py +++ /dev/null @@ -1,56 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma, Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncPlaywright -from zhenxun.utils.message import MessageUtils - -from .data_source import get_hot_image - -__plugin_meta__ = PluginMetadata( - name="微博热搜", - description="刚买完瓜,在吃瓜现场", - usage=""" - 指令: - 微博热搜:发送实时热搜 - 微博热搜 [id]:截图该热搜页面 - 示例:微博热搜 5 - """.strip(), - extra=PluginExtraData( - author="HibiKier & yajiwa", - version="0.1", - ).dict(), -) - - -_matcher = on_alconna(Alconna("微博热搜", Args["idx?", int]), priority=5, block=True) - - -@_matcher.handle() -async def _(session: EventSession, arparma: Arparma, idx: Match[int]): - result, data_list = await get_hot_image() - if isinstance(result, str): - await MessageUtils.build_message(result).finish(reply_to=True) - if idx.available: - _idx = idx.result - url = data_list[_idx - 1]["url"] - file = IMAGE_PATH / "temp" / f"wbtop_{session.id1}.png" - img = await AsyncPlaywright.screenshot( - url, - file, - "#pl_feed_main", - wait_time=12, - ) - if img: - await MessageUtils.build_message(file).send() - logger.info( - f"查询微博热搜 Id: {_idx}", arparma.header_result, session=session - ) - else: - await MessageUtils.build_message("获取图片失败...").send() - else: - await MessageUtils.build_message(result).send() - logger.info(f"查询微博热搜", arparma.header_result, session=session) diff --git a/zhenxun/plugins/wbtop/data_source.py b/zhenxun/plugins/wbtop/data_source.py deleted file mode 100644 index e9c206273..000000000 --- a/zhenxun/plugins/wbtop/data_source.py +++ /dev/null @@ -1,63 +0,0 @@ -from zhenxun.configs.path_config import IMAGE_PATH -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.utils.image_utils import BuildImage - -URL = "https://weibo.com/ajax/side/hotSearch" - - -async def get_data() -> list | str: - """获取数据 - - 返回: - list | str: 数据或消息 - """ - data_list = [] - for _ in range(3): - try: - response = await AsyncHttpx.get(URL, timeout=20) - if response.status_code == 200: - data_json = response.json()["data"]["realtime"] - for item in data_json: - if "is_ad" in item: - """广告跳过""" - continue - data = { - "hot_word": item["note"], - "hot_word_num": str(item["num"]), - "url": "https://s.weibo.com/weibo?q=%23" + item["word"] + "%23", - } - data_list.append(data) - if not data: - return "没有搜索到..." - return data_list - except Exception as e: - logger.error("获取微博热搜错误", e=e) - return "获取失败,请十分钟后再试..." - - -async def get_hot_image() -> tuple[BuildImage | str, list]: - """构造图片 - - 返回: - BuildImage | str: 热搜图片 - """ - data = await get_data() - if isinstance(data, str): - return data, [] - bk = BuildImage(700, 32 * 50 + 280, color="#797979") - wbtop_bk = BuildImage(700, 280, background=f"{IMAGE_PATH}/other/webtop.png") - await bk.paste(wbtop_bk) - text_bk = BuildImage(700, 32 * 50, color="#797979") - image_list = [] - for i, _data in enumerate(data): - title = f"{i + 1}. {_data['hot_word']}" - hot = str(_data["hot_word_num"]) - img = BuildImage(700, 30, font_size=20) - _, h = img.getsize(title) - await img.text((10, int((30 - h) / 2)), title) - await img.text((580, int((30 - h) / 2)), hot) - image_list.append(img) - text_bk = await text_bk.auto_paste(image_list, 1, 2, 0) - await bk.paste(text_bk, (0, 280)) - return bk, data diff --git a/zhenxun/plugins/what_anime/__init__.py b/zhenxun/plugins/what_anime/__init__.py deleted file mode 100644 index d3b918760..000000000 --- a/zhenxun/plugins/what_anime/__init__.py +++ /dev/null @@ -1,60 +0,0 @@ -from nonebot.plugin import PluginMetadata -from nonebot_plugin_alconna import Alconna, Args, Arparma -from nonebot_plugin_alconna import Image as alcImg -from nonebot_plugin_alconna import Match, on_alconna -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services.log import logger -from zhenxun.utils.message import MessageUtils - -from .data_source import get_anime - -__plugin_meta__ = PluginMetadata( - name="识番", - description="以图识番", - usage=""" - usage: - api.trace.moe 以图识番 - 指令: - 识番 [图片] - """.strip(), - extra=PluginExtraData( - author="HibiKier", version="0.1", menu_type="一些工具" - ).dict(), -) - - -_matcher = on_alconna(Alconna("识番", Args["image?", alcImg]), block=True, priority=5) - - -@_matcher.handle() -async def _(image: Match[alcImg]): - if image.available: - _matcher.set_path_arg("image", image.result) - - -@_matcher.got_path("image", prompt="图来!") -async def _( - session: EventSession, - arparma: Arparma, - image: alcImg, -): - if not image.url: - await MessageUtils.build_message("图片url为空...").finish() - await MessageUtils.build_message("开始识别...").send() - anime_data_report = await get_anime(image.url) - if anime_data_report: - await MessageUtils.build_message(anime_data_report).send(reply_to=True) - logger.info( - f" 识番 {image.url} --> {anime_data_report}", - arparma.header_result, - session=session, - ) - else: - logger.info( - f"识番 {image.url} 未找到...", arparma.header_result, session=session - ) - await MessageUtils.build_message(f"没有寻找到该番剧,果咩..").send( - reply_to=True - ) diff --git a/zhenxun/plugins/what_anime/data_source.py b/zhenxun/plugins/what_anime/data_source.py deleted file mode 100644 index 15801f624..000000000 --- a/zhenxun/plugins/what_anime/data_source.py +++ /dev/null @@ -1,47 +0,0 @@ -import time - -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx - - -async def get_anime(anime: str) -> str: - s_time = time.time() - url = "https://api.trace.moe/search?anilistInfo&url={}".format(anime) - logger.debug("[info]Now starting get the {}".format(url)) - try: - anime_json = (await AsyncHttpx.get(url)).json() - if not anime_json["error"]: - if anime_json == "Error reading imagenull": - return "图像源错误,注意必须是静态图片哦" - repass = "" - # 拿到动漫 中文名 - for anime in anime_json["result"][:5]: - synonyms = anime["anilist"]["synonyms"] - for x in synonyms: - _count_ch = 0 - for word in x: - if "\u4e00" <= word <= "\u9fff": - _count_ch += 1 - if _count_ch > 3: - anime_name = x - break - else: - anime_name = anime["anilist"]["title"]["native"] - episode = anime["episode"] - from_ = int(anime["from"]) - m, s = divmod(from_, 60) - similarity = anime["similarity"] - putline = "[ {} ][{}][{}:{}] 相似度:{:.2%}".format( - anime_name, - episode if episode else "?", - m, - s, - similarity, - ) - repass += putline + "\n" - return f"耗时 {int(time.time() - s_time)} 秒\n" + repass[:-1] - else: - return f'访问错误 error:{anime_json["error"]}' - except Exception as e: - logger.error(f"识番发生错误", e=e) - return "发生了奇怪的错误,那就没办法了,再试一次?" diff --git a/zhenxun/plugins/word_bank/__init__.py b/zhenxun/plugins/word_bank/__init__.py deleted file mode 100644 index c3ef65460..000000000 --- a/zhenxun/plugins/word_bank/__init__.py +++ /dev/null @@ -1,18 +0,0 @@ -from pathlib import Path - -import nonebot - -from zhenxun.configs.config import Config - -Config.add_plugin_config( - "word_bank", - "WORD_BANK_LEVEL", - 5, - help="设置增删词库的权限等级", - default_value=5, - type=int, -) -Config.set_name("word_bank", "词库问答") - - -nonebot.load_plugins(str(Path(__file__).parent.resolve())) diff --git a/zhenxun/plugins/word_bank/_config.py b/zhenxun/plugins/word_bank/_config.py deleted file mode 100644 index 72c4c584f..000000000 --- a/zhenxun/plugins/word_bank/_config.py +++ /dev/null @@ -1,49 +0,0 @@ -from enum import Enum - -from zhenxun.configs.path_config import DATA_PATH - -data_dir = DATA_PATH / "word_bank" -data_dir.mkdir(parents=True, exist_ok=True) - - -class ScopeType(Enum): - """ - 全局、群聊、私聊 - """ - - GLOBAL = 0 - GROUP = 1 - PRIVATE = 2 - - -scope2int = { - "全局": ScopeType.GLOBAL, - "群聊": ScopeType.GROUP, - "私聊": ScopeType.PRIVATE, -} - - -class WordType(Enum): - """ - 精准、模糊、正则、图片 - """ - - EXACT = 0 - FUZZY = 1 - REGEX = 2 - IMAGE = 3 - - -type2int = { - "精准": WordType.EXACT, - "模糊": WordType.FUZZY, - "正则": WordType.REGEX, - "图片": WordType.IMAGE, -} - -int2type = { - "EXACT": "精准", - "FUZZY": "模糊", - "REGEX": "正则", - "IMAGE": "图片", -} diff --git a/zhenxun/plugins/word_bank/_data_source.py b/zhenxun/plugins/word_bank/_data_source.py deleted file mode 100644 index dded404c0..000000000 --- a/zhenxun/plugins/word_bank/_data_source.py +++ /dev/null @@ -1,295 +0,0 @@ -from nonebot_plugin_alconna import At -from nonebot_plugin_alconna import Image -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import Text as alcText -from nonebot_plugin_alconna import Image as alcImage -from nonebot_plugin_alconna import UniMsg, UniMessage - -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.image_utils import ImageTemplate -from zhenxun.plugins.word_bank._config import ScopeType - -from ._model import WordBank - - -def get_img_and_at_list(message: UniMsg) -> tuple[list[str], list[str]]: - """获取图片和at数据 - - 参数: - message: UniMsg - - 返回: - tuple[list[str], list[str]]: 图片列表,at列表 - """ - img_list, at_list = [], [] - for msg in message: - if isinstance(msg, alcImage): - img_list.append(msg.url) - elif isinstance(msg, alcAt): - at_list.append(msg.target) - return img_list, at_list - - -def get_problem(message: UniMsg) -> str: - """获取问题内容 - - 参数: - message: UniMsg - - 返回: - str: 问题文本 - """ - problem = "" - a, b = True, True - for msg in message: - if isinstance(msg, alcText | str): - msg = str(msg) - if "问" in msg and a: - a = False - split_text = msg.split("问") - if len(split_text) > 1: - problem += "问".join(split_text[1:]) - if b: - if "答" in problem: - b = False - problem = problem.split("答")[0] - elif "答" in msg: - b = False - # problem += "答".join(msg.split("答")[:-1]) - problem += msg.split("答")[0] - if not a and not b: - break - if isinstance(msg, alcAt): - problem += f"[at:{msg.target}]" - return problem - - -def get_answer(message: UniMsg) -> UniMessage | None: - """获取at时回答 - - 参数: - message: UniMsg - - 返回: - str: 回答内容 - """ - temp_message = None - answer = "" - index = 0 - for msg in message: - index += 1 - if isinstance(msg, alcText | str): - msg = str(msg) - if "答" in msg: - answer += "答".join(msg.split("答")[1:]) - break - if answer: - temp_message = message[index:] - temp_message.insert(0, alcText(answer)) - return temp_message - - -class WordBankManage: - @classmethod - async def update_word( - cls, - replace: str, - problem: str = "", - index: int | None = None, - group_id: str | None = None, - word_scope: ScopeType = ScopeType.GROUP, - ) -> tuple[str, str]: - """修改群词条 - - 参数: - params: 参数 - group_id: 群号 - word_scope: 词条范围 - - 返回: - tuple[str, str]: 处理消息,替换的旧词条 - """ - return await cls.__word_handle( - problem, group_id, "update", index, None, word_scope, replace - ) - - @classmethod - async def delete_word( - cls, - problem: str, - index: int | None = None, - aid: int | None = None, - group_id: str | None = None, - word_scope: ScopeType = ScopeType.GROUP, - ) -> tuple[str, str]: - """删除群词条 - - 参数: - params: 参数 - index: 指定下标 - aid: 指定回答下标 - group_id: 群号 - word_scope: 词条范围 - - 返回: - tuple[str, str]: 处理消息,空 - """ - return await cls.__word_handle( - problem, group_id, "delete", index, aid, word_scope - ) - - @classmethod - async def __word_handle( - cls, - problem: str, - group_id: str | None, - handle_type: str, - index: int | None = None, - aid: int | None = None, - word_scope: ScopeType = ScopeType.GLOBAL, - replace_problem: str = "", - ) -> tuple[str, str]: - """词条操作 - - 参数: - problem: 参数 - group_id: 群号 - handle_type: 类型 - index: 指定回答下标 - aid: 指定回答下标 - word_scope: 词条范围 - replace_problem: 替换问题内容 - - 返回: - tuple[str, str]: 处理消息,替换的旧词条 - """ - if index is not None: - problem, code = await cls.__get_problem_str(index, group_id, word_scope) - if code != 200: - return problem, "" - if handle_type == "delete": - if index: - problem, _problem_list = await WordBank.get_problem_all_answer( - problem, None, group_id, word_scope - ) - if not _problem_list: - return problem, "" - return ( - ("删除词条成功!", "") - if await WordBank.delete_group_problem( - problem, group_id, aid, word_scope - ) - else ("词条不存在", "") - ) - elif handle_type == "update": - old_problem = await WordBank.update_group_problem( - problem, replace_problem, group_id, word_scope=word_scope - ) - return f"修改词条成功!\n{old_problem} -> {replace_problem}", old_problem - return "类型错误", "" - - @classmethod - async def __get_problem_str( - cls, - idx: int, - group_id: str | None = None, - word_scope: ScopeType = ScopeType.GROUP, - ) -> tuple[str, int]: - """通过id获取问题字符串 - - 参数: - idx: 下标 - group_id: 群号 - word_scope: 获取类型 - """ - if word_scope in [ScopeType.GLOBAL, ScopeType.PRIVATE]: - all_problem = await WordBank.get_problem_by_scope(word_scope) - elif group_id: - all_problem = await WordBank.get_group_all_problem(group_id) - else: - raise Exception("词条类型与群组id不能为空") - if idx < 0 or idx >= len(all_problem): - return "问题下标id必须在范围内", 999 - return all_problem[idx][0], 200 - - @classmethod - async def show_word( - cls, - problem: str | None, - index: int | None = None, - group_id: str | None = None, - word_scope: ScopeType | None = ScopeType.GROUP, - ) -> UniMessage: - """获取群词条 - - 参数: - problem: 问题 - group_id: 群组id - word_scope: 词条范围 - index: 指定回答下标 - """ - if problem or index is not None: - msg_list = [] - problem, _problem_list = await WordBank.get_problem_all_answer( - problem, # type: ignore - index, - group_id if group_id is not None else None, - word_scope, - ) - if not _problem_list: - return MessageUtils.build_message(problem) - for msg in _problem_list: - _text = str(msg) - if isinstance(msg, At): - _text = f"[at:{msg.target}]" - elif isinstance(msg, Image): - _text = msg.url or msg.path - elif isinstance(msg, list): - _text = [] - for m in msg: - __text = str(m) - if isinstance(m, At): - __text = f"[at:{m.target}]" - elif isinstance(m, Image): - # TODO: 显示词条回答图片 - # __text = (m.data["image"], 30, 30) - __text = "[图片]" - _text.append(__text) - _text = "".join(_text) - msg_list.append(_text) - column_name = ["序号", "回答内容"] - data_list = [] - for index, msg in enumerate(msg_list): - data_list.append([index, msg]) - template_image = await ImageTemplate.table_page( - f"词条 {problem} 的回答", None, column_name, data_list - ) - return MessageUtils.build_message(template_image) - else: - result = [] - if group_id: - _problem_list = await WordBank.get_group_all_problem(group_id) - elif word_scope is not None: - _problem_list = await WordBank.get_problem_by_scope(word_scope) - else: - raise Exception("群组id和词条范围不能都为空") - global_problem_list = await WordBank.get_problem_by_scope(ScopeType.GLOBAL) - if not _problem_list and not global_problem_list: - return MessageUtils.build_message("未收录任何词条...") - column_name = ["序号", "关键词", "匹配类型", "收录用户"] - data_list = [list(s) for s in _problem_list] - for i in range(len(data_list)): - data_list[i].insert(0, i) - group_image = await ImageTemplate.table_page( - "群组内词条" if group_id else "私聊词条", None, column_name, data_list - ) - result.append(group_image) - if global_problem_list: - data_list = [list(s) for s in global_problem_list] - for i in range(len(data_list)): - data_list[i].insert(0, i) - global_image = await ImageTemplate.table_page( - "全局词条", None, column_name, data_list - ) - result.append(global_image) - return MessageUtils.build_message(result) diff --git a/zhenxun/plugins/word_bank/_model.py b/zhenxun/plugins/word_bank/_model.py deleted file mode 100644 index 62d1b8608..000000000 --- a/zhenxun/plugins/word_bank/_model.py +++ /dev/null @@ -1,591 +0,0 @@ -import re -import uuid -import random -from typing import Any -from datetime import datetime -from typing_extensions import Self - -from tortoise.expressions import Q -from tortoise import Tortoise, fields -from nonebot_plugin_alconna import UniMessage -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_alconna import Text as alcText -from nonebot_plugin_alconna import Image as alcImage - -from zhenxun.services.db_context import Model -from zhenxun.utils.message import MessageUtils -from zhenxun.utils.http_utils import AsyncHttpx -from zhenxun.configs.path_config import DATA_PATH -from zhenxun.utils.image_utils import get_img_hash - -from ._config import WordType, ScopeType, int2type - -path = DATA_PATH / "word_bank" - - -class WordBank(Model): - id = fields.IntField(pk=True, generated=True, auto_increment=True) - """自增id""" - user_id = fields.CharField(255) - """用户id""" - group_id = fields.CharField(255, null=True) - """群聊id""" - word_scope = fields.IntField(default=ScopeType.GLOBAL.value) - """生效范围 0: 全局 1: 群聊 2: 私聊""" - word_type = fields.IntField(default=WordType.EXACT.value) - """词条类型 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) - """更新时间""" - platform = fields.CharField(255, default="qq") - """平台""" - author = fields.CharField(255, null=True, default="") - """收录人""" - - class Meta: # type: ignore - table = "word_bank2" - table_description = "词条数据库" - - @classmethod - async def exists( # type: ignore - cls, - user_id: str | None, - group_id: str | None, - problem: str, - answer: str | None, - word_scope: ScopeType | None = None, - word_type: WordType | None = None, - ) -> bool: - """检测问题是否存在 - - 参数: - user_id: 用户id - group_id: 群号 - problem: 问题 - answer: 回答 - word_scope: 词条范围 - word_type: 词条类型 - """ - query = cls.filter(problem=problem) - if user_id: - query = query.filter(user_id=user_id) - if group_id: - query = query.filter(group_id=group_id) - if answer: - query = query.filter(answer=answer) - if word_type is not None: - query = query.filter(word_type=word_type.value) - if word_scope is not None: - query = query.filter(word_scope=word_scope.value) - return await query.exists() - - @classmethod - async def add_problem_answer( - cls, - user_id: str, - group_id: str | None, - word_scope: ScopeType, - word_type: WordType, - problem: str, - answer: list[str | alcText | alcAt | alcImage], - to_me_nickname: str | None = None, - platform: str = "", - author: str = "", - ): - """添加或新增一个问答 - - 参数: - user_id: 用户id - group_id: 群号 - word_scope: 词条范围, - word_type: 词条类型, - problem: 问题, 为图片时是URl - answer: 回答 - to_me_nickname: at真寻名称 - platform: 所属平台 - author: 收录人id - """ - # 对图片做额外处理 - image_path = None - if word_type == WordType.IMAGE: - _uuid = uuid.uuid1() - _file = path / "problem" / f"{group_id}" / f"{user_id}_{_uuid}.jpg" - _file.parent.mkdir(exist_ok=True, parents=True) - await AsyncHttpx.download_file(problem, _file) - problem = get_img_hash(_file) - image_path = f"problem/{group_id}/{user_id}_{_uuid}.jpg" - new_answer, placeholder_list = await cls._answer2format( - answer, user_id, group_id - ) - if not await cls.exists( - user_id, group_id, problem, new_answer, word_scope, word_type - ): - await cls.create( - user_id=user_id, - group_id=group_id, - word_scope=word_scope.value, - word_type=word_type.value, - status=True, - problem=str(problem).strip(), - answer=new_answer, - image_path=image_path, - placeholder=",".join(placeholder_list), - create_time=datetime.now().replace(microsecond=0), - update_time=datetime.now().replace(microsecond=0), - to_me=to_me_nickname, - platform=platform, - author=author, - ) - - @classmethod - async def _answer2format( - cls, - answer: list[str | alcText | alcAt | alcImage], - user_id: str, - group_id: str | None, - ) -> tuple[str, list[Any]]: - """将特殊字段转化为占位符,图片,at等 - - 参数: - answer: 回答内容 - user_id: 用户id - group_id: 群号 - - 返回: - tuple[str, list[Any]]: 替换后的文本回答内容,占位符 - """ - placeholder_list = [] - text = "" - index = 0 - for seg in answer: - placeholder = uuid.uuid1() - if isinstance(seg, str): - text += seg - elif isinstance(seg, alcText): - text += seg.text - elif seg.type == "face": # TODO: face貌似无用... - text += f"[face:placeholder_{placeholder}]" - placeholder_list.append(seg.data["id"]) - elif isinstance(seg, alcAt): - text += f"[at:placeholder_{placeholder}]" - placeholder_list.append(seg.target) - elif isinstance(seg, alcImage) and seg.url: - text += f"[image:placeholder_{placeholder}]" - index += 1 - _file = ( - path - / "answer" - / f"{group_id or user_id}" - / f"{user_id}_{placeholder}.jpg" - ) - _file.parent.mkdir(exist_ok=True, parents=True) - await AsyncHttpx.download_file(seg.url, _file) - placeholder_list.append( - f"answer/{group_id or user_id}/{user_id}_{placeholder}.jpg" - ) - return text, placeholder_list - - @classmethod - async def _format2answer( - cls, - problem: str, - answer: str, - user_id: int, - group_id: int, - query: Self | None = None, - ) -> UniMessage: - """将占位符转换为实际内容 - - 参数: - problem: 问题内容 - answer: 回答内容 - user_id: 用户id - group_id: 群组id - """ - if not query: - query = await cls.get_or_none( - problem=problem, - user_id=user_id, - group_id=group_id, - answer=answer, - ) - if not answer: - answer = str(query.answer) # type: ignore - if query and query.placeholder: - type_list = re.findall(r"\[(.*?):placeholder_.*?]", answer) - answer_split = re.split(r"\[.*:placeholder_.*?]", answer) - placeholder_split = query.placeholder.split(",") - result_list = [] - for index, ans in enumerate(answer_split): - result_list.append(ans) - if index < len(type_list): - t = type_list[index] - p = placeholder_split[index] - if t == "at": - result_list.append(alcAt(flag="user", target=p)) - elif t == "image": - result_list.append(path / p) - return MessageUtils.build_message(result_list) - return MessageUtils.build_message(answer) - - @classmethod - async def check_problem( - cls, - group_id: str | None, - problem: str, - word_scope: ScopeType | None = None, - word_type: WordType | None = None, - ) -> Any: - """检测是否包含该问题并获取所有回答 - - 参数: - group_id: 群组id - problem: 问题内容 - word_scope: 词条范围 - word_type: 词条类型 - """ - query = cls - if group_id: - if word_scope: - query = query.filter(word_scope=word_scope.value) - else: - query = query.filter( - Q(group_id=group_id) | Q(word_scope=WordType.EXACT.value) - ) - else: - query = query.filter( - Q(word_scope=ScopeType.PRIVATE.value) - | Q(word_scope=ScopeType.GLOBAL.value) - ) - if word_type: - query = query.filter(word_scope=word_type.value) - # 完全匹配 - if data_list := await query.filter( - Q(Q(word_type=WordType.EXACT.value) | Q(word_type=WordType.IMAGE.value)), - Q(problem=problem), - ).all(): - return data_list - db = Tortoise.get_connection("default") - # 模糊匹配 - sql = ( - query.filter(word_type=WordType.FUZZY.value).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=WordType.REGEX.value, word_scope__not=999).sql() - + " and $1 ~ problem;" - ) - data_list = await db.execute_query_dict(sql, [problem]) - return [cls(**data) for data in data_list] if data_list else None - - @classmethod - async def get_answer( - cls, - group_id: str | None, - problem: str, - word_scope: ScopeType | None = None, - word_type: WordType | None = None, - ) -> UniMessage | None: - """根据问题内容获取随机回答 - - 参数: - user_id: 用户id - group_id: 群组id - problem: 问题内容 - word_scope: 词条范围 - word_type: 词条类型 - """ - data_list = await cls.check_problem(group_id, problem, word_scope, word_type) - if data_list: - random_answer = random.choice(data_list) - if random_answer.word_type == WordType.REGEX: - r = re.search(random_answer.problem, problem) - has_placeholder = re.search(r"\$(\d)", random_answer.answer) - if r and r.groups() and has_placeholder: - pats = re.sub(r"\$(\d)", r"\\\1", random_answer.answer) - random_answer.answer = re.sub(random_answer.problem, pats, problem) - return ( - await cls._format2answer( - random_answer.problem, - random_answer.answer, - random_answer.user_id, - random_answer.group_id, - random_answer, - ) - if random_answer.placeholder - else MessageUtils.build_message(random_answer.answer) - ) - - @classmethod - async def get_problem_all_answer( - cls, - problem: str, - index: int | None = None, - group_id: str | None = None, - word_scope: ScopeType | None = ScopeType.GLOBAL, - ) -> tuple[str, list[UniMessage]]: - """获取指定问题所有回答 - - 参数: - problem: 问题 - index: 下标 - group_id: 群号 - word_scope: 词条范围 - - 返回: - tuple[str, list[UniMessage]]: 问题和所有回答 - """ - if index is not None: - # TODO: group_by和order_by不能同时使用 - if group_id and word_scope != ScopeType.GLOBAL: - _problem = ( - await cls.filter(group_id=group_id).order_by("create_time") - # .group_by("problem") - .values_list("problem", flat=True) - ) - else: - _problem = ( - await cls.filter( - word_scope=(word_scope or ScopeType.GLOBAL).value - ).order_by("create_time") - # .group_by("problem") - .values_list("problem", flat=True) - ) - # if index is None and problem not in _problem: - # return "词条不存在...", [] - sort_problem = [] - for p in _problem: - if p not in sort_problem: - sort_problem.append(p) - if index > len(sort_problem) - 1: - return "下标错误,必须小于问题数量...", [] - problem = sort_problem[index] # type: ignore - f = cls.filter( - problem=problem, word_scope=(word_scope or ScopeType.GLOBAL).value - ) - if group_id: - f = f.filter(group_id=group_id) - answer_list = await f.all() - if not answer_list: - return "词条不存在...", [] - return problem, [await cls._format2answer("", "", 0, 0, a) for a in answer_list] - - @classmethod - async def delete_group_problem( - cls, - problem: str, - group_id: str | None, - index: int | None = None, - word_scope: ScopeType = ScopeType.GROUP, - ): - """删除指定问题全部或指定回答 - - 参数: - problem: 问题文本 - group_id: 群号 - index: 回答下标 - word_scope: 词条范围 - """ - if await cls.exists(None, group_id, problem, None, word_scope): - if index is not None: - if group_id: - query = await cls.filter( - group_id=group_id, problem=problem, word_scope=word_scope.value - ).all() - else: - query = await cls.filter( - word_scope=word_scope.value, problem=problem - ).all() - await query[index].delete() - else: - if group_id: - await WordBank.filter( - group_id=group_id, problem=problem, word_scope=word_scope.value - ).delete() - else: - await WordBank.filter( - word_scope=word_scope.value, problem=problem - ).delete() - return True - return False - - @classmethod - async def update_group_problem( - cls, - problem: str, - replace_str: str, - group_id: str | None, - index: int | None = None, - word_scope: ScopeType = ScopeType.GROUP, - ) -> str: - """修改词条问题 - - 参数: - problem: 问题 - replace_str: 替换问题 - group_id: 群号 - index: 问题下标 - word_scope: 词条范围 - - 返回: - str: 修改前的问题 - """ - if index is not None: - if group_id: - query = await cls.filter(group_id=group_id, problem=problem).all() - else: - query = await cls.filter( - word_scope=word_scope.value, problem=problem - ).all() - tmp = query[index].problem - query[index].problem = replace_str - await query[index].save(update_fields=["problem"]) - return tmp - else: - if group_id: - await cls.filter(group_id=group_id, problem=problem).update( - problem=replace_str - ) - else: - await cls.filter(word_scope=word_scope.value, problem=problem).update( - problem=replace_str - ) - return problem - - @classmethod - async def get_group_all_problem(cls, group_id: str) -> list[tuple[Any | str]]: - """获取群聊所有词条 - - 参数: - group_id: 群号 - """ - return cls._handle_problem( - await cls.filter(group_id=group_id).order_by("create_time").all() # type: ignore - ) - - @classmethod - async def get_problem_by_scope(cls, word_scope: ScopeType): - """通过词条范围获取词条 - - 参数: - word_scope: 词条范围 - """ - return cls._handle_problem( - await cls.filter(word_scope=word_scope.value).order_by("create_time").all() # type: ignore - ) - - @classmethod - async def get_problem_by_type(cls, word_type: int): - """通过词条类型获取词条 - - 参数: - word_type: 词条类型 - """ - return cls._handle_problem( - await cls.filter(word_type=word_type).order_by("create_time").all() # type: ignore - ) - - @classmethod - def __type2int(cls, value: int) -> str: - for key, member in WordType.__members__.items(): - if member.value == value: - return key - return "" - - @classmethod - def _handle_problem(cls, problem_list: list["WordBank"]): - """格式化处理问题 - - 参数: - msg_list: 消息列表 - """ - _tmp = [] - result_list = [] - for q in problem_list: - if q.problem not in _tmp: - word_type = cls.__type2int(q.word_type) - # TODO: 获取收录人名称 - problem = ( - (path / q.image_path, 30, 30) if q.image_path else q.problem, - int2type[word_type], - # q.author, - "-", - ) - result_list.append(problem) - _tmp.append(q.problem) - return result_list - - @classmethod - async def _move( - cls, - user_id: str, - group_id: str | None, - problem: str, - answer: str, - placeholder: str, - ): - """旧词条图片移动方法 - - 参数: - user_id: 用户id - group_id: 群号 - problem: 问题 - answer: 回答 - placeholder: 占位符 - """ - word_scope = ScopeType.GLOBAL - word_type = WordType.EXACT - # 对图片做额外处理 - if not await cls.exists( - user_id, group_id, problem, answer, word_scope, word_type - ): - await cls.create( - user_id=user_id, - group_id=group_id, - word_scope=word_scope.value, - word_type=word_type.value, - status=True, - problem=problem, - answer=answer, - image_path=None, - placeholder=placeholder, - create_time=datetime.now().replace(microsecond=0), - update_time=datetime.now().replace(microsecond=0), - ) - - @classmethod - async def _run_script(cls): - return [ - "ALTER TABLE word_bank2 ADD to_me varchar(255);", # 添加 to_me 字段 - ( - "ALTER TABLE word_bank2 ALTER COLUMN create_time TYPE timestamp" - " with time zone USING create_time::timestamp with time zone;" - ), - ( - "ALTER TABLE word_bank2 ALTER COLUMN update_time TYPE timestamp" - " with time zone USING update_time::timestamp with time zone;" - ), - "ALTER TABLE word_bank2 RENAME COLUMN user_qq TO user_id;", - # 将user_qq改为user_id - "ALTER TABLE word_bank2 ALTER COLUMN user_id TYPE character varying(255);", - "ALTER TABLE word_bank2 ALTER COLUMN group_id TYPE character varying(255);", - "ALTER TABLE word_bank2 ADD platform varchar(255) DEFAULT 'qq';", - "ALTER TABLE word_bank2 ADD author varchar(255) DEFAULT '';", - ] diff --git a/zhenxun/plugins/word_bank/_rule.py b/zhenxun/plugins/word_bank/_rule.py deleted file mode 100644 index f64daa75f..000000000 --- a/zhenxun/plugins/word_bank/_rule.py +++ /dev/null @@ -1,58 +0,0 @@ -from io import BytesIO - -import imagehash -from PIL import Image -from nonebot.typing import T_State -from nonebot.adapters import Bot, Event -from nonebot_plugin_alconna import UniMsg -from nonebot_plugin_alconna import At as alcAt -from nonebot_plugin_session import EventSession -from nonebot_plugin_alconna import Text as alcText - -from zhenxun.services.log import logger -from zhenxun.utils.http_utils import AsyncHttpx - -from ._model import WordBank -from ._data_source import get_img_and_at_list - - -async def check( - bot: Bot, - event: Event, - message: UniMsg, - session: EventSession, - state: T_State, -) -> bool: - text = message.extract_plain_text().strip() - img_list, at_list = get_img_and_at_list(message) - problem = text - if not text and len(img_list) == 1: - try: - r = await AsyncHttpx.get(img_list[0]) - problem = str(imagehash.average_hash(Image.open(BytesIO(r.content)))) - except Exception as e: - logger.warning("获取图片失败", "词条检测", session=session, e=e) - if at_list: - temp = "" - # TODO: 支持更多消息类型 - for msg in message: - if isinstance(msg, alcAt): - temp += f"[at:{msg.target}]" - elif isinstance(msg, alcText): - temp += msg.text - problem = temp - if event.is_tome() and bot.config.nickname: - if isinstance(message[0], alcAt) and message[0].target == bot.self_id: - problem = f"[at:{bot.self_id}]" + problem - else: - if problem and bot.config.nickname: - nickname = [ - nk for nk in bot.config.nickname if str(message).startswith(nk) - ] - problem = nickname[0] + problem if nickname else problem - if problem and ( - await WordBank.check_problem(session.id3 or session.id2, problem) is not None - ): - state["problem"] = problem # type: ignore - return True - return False diff --git a/zhenxun/plugins/word_bank/command.py b/zhenxun/plugins/word_bank/command.py deleted file mode 100644 index 64049d30d..000000000 --- a/zhenxun/plugins/word_bank/command.py +++ /dev/null @@ -1,47 +0,0 @@ -from nonebot import on_regex -from nonebot_plugin_alconna import Alconna, Args, Option, on_alconna, store_true - -from zhenxun.utils.rules import admin_check, ensure_group - -_add_matcher = on_regex( - r"^(全局|私聊)?添加词条\s*?(模糊|正则|图片)?问\s*?(\S*\s?\S*)\s*?答\s?(\S*)", - priority=5, - block=True, - rule=admin_check("word_bank", "WORD_BANK_LEVEL"), -) - - -_del_matcher = on_alconna( - Alconna( - "删除词条", - Args["problem?", str], - Option("--all", action=store_true, help_text="所有词条"), - Option("--id", Args["index", int], help_text="下标id"), - Option("--aid", Args["answer_id", int], help_text="回答下标id"), - ), - priority=5, - block=True, -) - - -_update_matcher = on_alconna( - Alconna( - "修改词条", - Args["replace", str]["problem?", str], - Option("--id", Args["index", int], help_text="词条id"), - Option("--all", action=store_true, help_text="全局词条"), - ) -) - -_show_matcher = on_alconna( - Alconna( - "显示词条", - Args["problem?", str], - Option("-g|--group", Args["gid", str], help_text="群组id"), - Option("--id", Args["index", int], help_text="词条id"), - Option("--all", action=store_true, help_text="全局词条"), - ), - aliases={"查看词条"}, - priority=5, - block=True, -) diff --git a/zhenxun/plugins/word_bank/message_handle.py b/zhenxun/plugins/word_bank/message_handle.py deleted file mode 100644 index 07a4b518f..000000000 --- a/zhenxun/plugins/word_bank/message_handle.py +++ /dev/null @@ -1,31 +0,0 @@ -from nonebot import on_message -from nonebot.plugin import PluginMetadata -from nonebot.typing import T_State -from nonebot_plugin_session import EventSession - -from zhenxun.configs.utils import PluginExtraData -from zhenxun.services import logger -from zhenxun.utils.enum import PluginType - -from ._model import WordBank -from ._rule import check - -__plugin_meta__ = PluginMetadata( - name="词库问答回复操作", - description="", - usage="""""", - extra=PluginExtraData( - author="HibiKier", version="0.1", plugin_type=PluginType.DEPENDANT - ).dict(), -) - -_matcher = on_message(priority=6, block=True, rule=check) - - -@_matcher.handle() -async def _(session: EventSession, state: T_State): - if problem := state.get("problem"): - gid = session.id3 or session.id2 - if result := await WordBank.get_answer(gid, problem): - await result.send() - logger.info(f"触发词条 {problem}", "词条检测", session=session) diff --git a/zhenxun/plugins/word_bank/word_handle.py b/zhenxun/plugins/word_bank/word_handle.py deleted file mode 100644 index 0bda50c28..000000000 --- a/zhenxun/plugins/word_bank/word_handle.py +++ /dev/null @@ -1,328 +0,0 @@ -import re -from typing import Any - -from nonebot.adapters import Bot -from nonebot.typing import T_State -from nonebot.params import RegexGroup -from nonebot_plugin_alconna import Image -from nonebot.plugin import PluginMetadata -from nonebot.exception import FinishedException -from nonebot.internal.params import Arg, ArgStr -from nonebot_plugin_session import EventSession -from nonebot.adapters.onebot.v11 import unescape -from nonebot_plugin_alconna import Image as alcImage -from nonebot_plugin_alconna import Match, Query, UniMsg, Arparma, AlconnaQuery - -from zhenxun.services.log import logger -from zhenxun.configs.config import Config -from zhenxun.utils.message import MessageUtils -from zhenxun.configs.utils import PluginExtraData - -from ._model import WordBank -from ._config import WordType, ScopeType, type2int, scope2int -from .command import _add_matcher, _del_matcher, _show_matcher, _update_matcher -from ._data_source import WordBankManage, get_answer, get_problem, get_img_and_at_list - -base_config = Config.get("word_bank") - - -__plugin_meta__ = PluginMetadata( - name="词库问答", - description="自定义词条内容随机回复", - usage=r""" - usage: - 对指定问题的随机回答,对相同问题可以设置多个不同回答 - 删除词条后每个词条的id可能会变化,请查看后再删除 - 更推荐使用id方式删除 - 问题回答支持的类型:at, image - 查看词条命令:群聊时为 群词条+全局词条,私聊时为 私聊词条+全局词条 - 添加词条正则:添加词条(模糊|正则|图片)?问\s*?(\S*\s?\S*)\s*?答\s?(\S*) - 正则问可以通过$1类推()捕获的组 - 指令: - 添加词条 ?[模糊|正则|图片]问...答...:添加问答词条,可重复添加相同问题的不同回答 - 示例: - 添加词条问你好答你也好 - 添加词条图片问答看看涩图 - 删除词条 ?[问题] ?[序号] ?[回答序号]:删除指定词条指定或全部回答 - 示例: - 删除词条 谁是萝莉 : 删除文字是 谁是萝莉 的词条 - 删除词条 --id 2 : 删除序号为2的词条 - 删除词条 谁是萝莉 --aid 2 : 删除 谁是萝莉 词条的第2个回答 - 删除词条 --id 2 --aid 2 : 删除序号为2词条的第2个回答 - 修改词条 [替换文字] ?[旧词条文字] ?[序号]:修改词条问题 - 示例: - 修改词条 谁是萝莉 谁是萝莉啊? : 将词条 谁是萝莉 修改为 谁是萝莉啊? - 修改词条 谁是萝莉 --id 2 : 将序号为2的词条修改为 谁是萝莉 - 查看词条 ?[问题] ?[序号]:查看全部词条或对应词条回答 - 示例: - 查看词条: - (在群组中使用时): 查看当前群组词条和全局词条 - (在私聊中使用时): 查看当前私聊词条和全局词条 - 查看词条 谁是萝莉 : 查看词条 谁是萝莉 的全部回答 - 查看词条 --id 2 : 查看词条序号为2的全部回答 - 查看词条 谁是萝莉 --all: 查看全局词条 谁是萝莉 的全部回答 - 查看词条 --id 2 --all: 查看全局词条序号为2的全部回答 - """.strip(), # noqa: E501 - extra=PluginExtraData( - author="HibiKier & yajiwa", - version="0.1", - superuser_help=r""" - 在私聊中超级用户额外设置 - 指令: - (全局|私聊)?添加词条\s*?(模糊|正则|图片)?问\s*?(\S*\s?\S*)\s*?答\s?(\S*):添加问答词条,可重复添加相同问题的不同回答 - 全局添加词条 - 私聊添加词条 - (私聊情况下)删除词条: 删除私聊词条 - (私聊情况下)修改词条: 修改私聊词条 - 通过添加参数 --all才指定全局词条 - 示例: - 删除词条 --id 2 --all: 删除全局词条中序号为2的词条 - 用法与普通用法相同 - """, - admin_level=base_config.get("WORD_BANK_LEVEL"), - ).dict(), -) - - -@_add_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - state: T_State, - message: UniMsg, - reg_group: tuple[Any, ...] = RegexGroup(), -): - img_list, at_list = get_img_and_at_list(message) - user_id = session.id1 - group_id = session.id3 or session.id2 - if not group_id and user_id not in bot.config.superusers: - await MessageUtils.build_message("权限不足捏...").finish(reply_to=True) - word_scope, word_type, problem, answer = reg_group - if not word_scope and not group_id: - word_scope = "私聊" - if ( - word_scope - and word_scope in ["全局", "私聊"] - and user_id not in bot.config.superusers - ): - await MessageUtils.build_message("权限不足,无法添加该范围词条...").finish( - reply_to=True - ) - if (not problem or not problem.strip()) and word_type != "图片": - await MessageUtils.build_message("词条问题不能为空!").finish(reply_to=True) - if (not answer or not answer.strip()) and not len(img_list) and not len(at_list): - await MessageUtils.build_message("词条回答不能为空!").finish(reply_to=True) - if word_type != "图片": - state["problem_image"] = "YES" - temp_problem = message.copy() - # answer = message.copy() - # 对at问题对额外处理 - # if at_list: - answer = get_answer(message.copy()) - # text = str(message.pop(0)).split("答", maxsplit=1)[-1].strip() - # temp_problem.insert(0, alcMessageUtils.build_message(text)) - state["word_scope"] = word_scope - state["word_type"] = word_type - state["problem"] = get_problem(temp_problem) - state["answer"] = answer - logger.info( - f"添加词条 范围: {word_scope} 类型: {word_type} 问题: {problem} 回答: {answer}", - "添加词条", - session=session, - ) - - -@_add_matcher.got("problem_image", prompt="请发送该回答设置的问题图片") -async def _( - bot: Bot, - session: EventSession, - message: UniMsg, - word_scope: str | None = ArgStr("word_scope"), - word_type: str | None = ArgStr("word_type"), - problem: str | None = ArgStr("problem"), - answer: Any = Arg("answer"), -): - if not session.id1: - await MessageUtils.build_message("用户id不存在...").finish() - user_id = session.id1 - group_id = session.id3 or session.id2 - try: - if word_type == "图片": - problem = next(m for m in message if isinstance(m, alcImage)).url - elif word_type == "正则" and problem: - problem = unescape(problem) - try: - re.compile(problem) - except re.error: - await MessageUtils.build_message( - f"添加词条失败,正则表达式 {problem} 非法!" - ).finish(reply_to=True) - # if str(event.user_id) in bot.config.superusers and isinstance( - # event, PrivateMessageEvent - # ): - # word_scope = "私聊" - nickname = None - if problem and bot.config.nickname: - nickname = [nk for nk in bot.config.nickname if problem.startswith(nk)] - if not problem: - await MessageUtils.build_message("获取问题失败...").finish(reply_to=True) - await WordBank.add_problem_answer( - user_id, - ( - group_id - if group_id and (not word_scope or word_scope == "私聊") - else "0" - ), - scope2int[word_scope] if word_scope else ScopeType.GROUP, - type2int[word_type] if word_type else WordType.EXACT, - problem, - answer, - nickname[0] if nickname else None, - session.platform, - session.id1, - ) - except Exception as e: - if isinstance(e, FinishedException): - await _add_matcher.finish() - logger.error( - f"添加词条 {problem} 错误...", - "添加词条", - session=session, - e=e, - ) - await MessageUtils.build_message( - f"添加词条 {problem if word_type != '图片' else '图片'} 发生错误!" - ).finish(reply_to=True) - if word_type == "图片": - result = MessageUtils.build_message( - ["添加词条 ", Image(url=problem), " 成功!"] - ) - else: - result = MessageUtils.build_message(f"添加词条 {problem} 成功!") - await result.send() - logger.info( - f"添加词条 {problem} 成功!", - "添加词条", - session=session, - ) - - -@_del_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - problem: Match[str], - index: Match[int], - answer_id: Match[int], - arparma: Arparma, - all: Query[bool] = AlconnaQuery("all.value", False), -): - if not problem.available and not index.available: - await MessageUtils.build_message( - "此命令之后需要跟随指定词条或id,通过“显示词条“查看" - ).finish(reply_to=True) - word_scope = ScopeType.GROUP if session.id3 or session.id2 else ScopeType.PRIVATE - if all.result: - word_scope = ScopeType.GLOBAL - if gid := session.id3 or session.id2: - result, _ = await WordBankManage.delete_word( - problem.result, - index.result if index.available else None, - answer_id.result if answer_id.available else None, - gid, - word_scope, - ) - else: - if session.id1 not in bot.config.superusers: - await MessageUtils.build_message("权限不足捏...").finish(reply_to=True) - result, _ = await WordBankManage.delete_word( - problem.result, - index.result if index.available else None, - answer_id.result if answer_id.available else None, - None, - word_scope, - ) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info(f"删除词条: {problem.result}", arparma.header_result, session=session) - - -@_update_matcher.handle() -async def _( - bot: Bot, - session: EventSession, - replace: str, - problem: Match[str], - index: Match[int], - arparma: Arparma, - all: Query[bool] = AlconnaQuery("all.value", False), -): - if not problem.available and not index.available: - await MessageUtils.build_message( - "此命令之后需要跟随指定词条或id,通过“显示词条“查看" - ).finish(reply_to=True) - word_scope = ScopeType.GROUP if session.id3 or session.id2 else ScopeType.PRIVATE - if all.result: - word_scope = ScopeType.GLOBAL - if gid := session.id3 or session.id2: - result, old_problem = await WordBankManage.update_word( - replace, - problem.result if problem.available else "", - index.result if index.available else None, - gid, - word_scope, - ) - else: - if session.id1 not in bot.config.superusers: - await MessageUtils.build_message("权限不足捏...").finish(reply_to=True) - result, old_problem = await WordBankManage.update_word( - replace, - problem.result if problem.available else "", - index.result if index.available else None, - session.id3 or session.id2, - word_scope, - ) - await MessageUtils.build_message(result).send(reply_to=True) - logger.info( - f"更新词条词条: {old_problem} -> {replace}", - arparma.header_result, - session=session, - ) - - -@_show_matcher.handle() -async def _( - session: EventSession, - problem: Match[str], - index: Match[int], - gid: Match[str], - arparma: Arparma, - all: Query[bool] = AlconnaQuery("all.value", False), -): - word_scope = ScopeType.GROUP if session.id3 or session.id2 else ScopeType.PRIVATE - group_id = session.id3 or session.id2 - if all.result: - word_scope = ScopeType.GLOBAL - if gid.available: - group_id = gid.result - if problem.available: - if index.available and ( - index.result < 0 - or index.result > len(await WordBank.get_problem_by_scope(word_scope)) - ): - await MessageUtils.build_message("id必须在范围内...").finish(reply_to=True) - result = await WordBankManage.show_word( - problem.result, - index.result if index.available else None, - group_id, - word_scope, - ) - else: - result = await WordBankManage.show_word( - None, index.result if index.available else None, group_id, word_scope - ) - await result.send() - logger.info( - f"查看词条回答: {problem.result if problem.available else index.result}", - arparma.header_result, - session=session, - ) diff --git a/zhenxun/utils/common_utils.py b/zhenxun/utils/common_utils.py new file mode 100644 index 000000000..0c0d0821d --- /dev/null +++ b/zhenxun/utils/common_utils.py @@ -0,0 +1,36 @@ +from zhenxun.models.task_info import TaskInfo +from zhenxun.models.ban_console import BanConsole +from zhenxun.models.group_console import GroupConsole + + +class CommonUtils: + + @classmethod + async def is_block(cls, module: str, group_id: str | None) -> bool: + """判断被动技能是否可以发送 + + 参数: + module: 被动技能模块名 + group_id: 群组id + + 返回: + bool: 是否可以发送 + """ + if task := await TaskInfo.get_or_none(module=module): + """被动全局状态""" + if not task.status: + return True + if group_id: + if await GroupConsole.is_block_task(group_id, module): + """群组是否禁用被动""" + return True + if g := await GroupConsole.get_or_none( + group_id=group_id, channel_id__isnull=True + ): + """群组权限是否小于0""" + if g.level < 0: + return True + if await BanConsole.is_ban(None, group_id): + """群组是否被ban""" + return True + return False diff --git a/zhenxun/utils/decorator/shop.py b/zhenxun/utils/decorator/shop.py index 3e46db6a9..a48b6c2bb 100644 --- a/zhenxun/utils/decorator/shop.py +++ b/zhenxun/utils/decorator/shop.py @@ -1,8 +1,8 @@ -from typing import Any, Callable, Dict +from collections.abc import Callable -from nonebot.adapters.onebot.v11 import Message, MessageSegment -from nonebot.plugin import require from pydantic import BaseModel +from nonebot.plugin import require +from nonebot.adapters.onebot.v11 import Message, MessageSegment from zhenxun.models.goods_info import GoodsInfo @@ -19,7 +19,7 @@ class Goods(BaseModel): icon: str | None = None is_passive: bool func: Callable - kwargs: Dict[str, str] = {} + kwargs: dict[str, str] = {} send_success_msg: bool max_num_limit: int @@ -27,8 +27,8 @@ class Goods(BaseModel): class ShopRegister(dict): def __init__(self, *args, **kwargs): - super(ShopRegister, self).__init__(*args, **kwargs) - self._data: Dict[str, Goods] = {} + super().__init__(*args, **kwargs) + self._data: dict[str, Goods] = {} self._flag = True def before_handle(self, name: str | tuple[str, ...], load_status: bool = True): @@ -42,7 +42,7 @@ def before_handle(self, name: str | tuple[str, ...], load_status: bool = True): def register_before_handle(name_list: tuple[str, ...], func: Callable): if load_status: for name_ in name_list: - if goods := self._data.get(name_): + if self._data.get(name_): self._data[name_].before_handle.append(func) _name = (name,) if isinstance(name, str) else name @@ -59,7 +59,7 @@ def after_handle(self, name: str | tuple[str, ...], load_status: bool = True): def register_after_handle(name_list: tuple[str, ...], func: Callable): if load_status: for name_ in name_list: - if goods := self._data.get(name_): + if self._data.get(name_): self._data[name_].after_handle.append(func) _name = (name,) if isinstance(name, str) else name @@ -99,7 +99,7 @@ def register( def add_register_item(func: Callable): if name in self._data.keys(): raise ValueError("该商品已注册,请替换其他名称!") - for n, p, d, dd, l, s, dl, pa, i, ssm, mnl in zip( + for n, p, d, dd, lmt, s, dl, pa, i, ssm, mnl in zip( name, price, des, @@ -123,7 +123,7 @@ def add_register_item(func: Callable): price=p, des=d, discount=dd, - limit_time=l, + limit_time=lmt, daily_limit=dl, is_passive=pa, func=func, @@ -133,7 +133,7 @@ def add_register_item(func: Callable): goods.price = p goods.des = d goods.discount = dd - goods.limit_time = l + goods.limit_time = lmt goods.daily_limit = dl goods.icon = i goods.is_passive = pa @@ -216,7 +216,8 @@ def __call__( _current_len = len(x) if _current_len != len(x): raise ValueError( - f"注册商品 {name} 中 name,price,des,discount,limit_time,load_status,daily_limit 数量不符!" + f"注册商品 {name} 中 name,price,des,discount,limit_time," + "load_status,daily_limit 数量不符!" ) _current_len = _current_len if _current_len > -1 else 1 _name = self.__get(name, _current_len) @@ -249,7 +250,7 @@ def __get(self, value, _current_len): return ( value if isinstance(value, tuple) - else tuple([value for _ in range(_current_len)]) + else tuple(value for _ in range(_current_len)) ) def __setitem__(self, key, value): diff --git a/zhenxun/utils/platform.py b/zhenxun/utils/platform.py index cc4811048..3e3b4afc9 100644 --- a/zhenxun/utils/platform.py +++ b/zhenxun/utils/platform.py @@ -1,23 +1,25 @@ import random -from typing import Awaitable, Callable, Literal, Set +from typing import Literal +from collections.abc import Callable, Awaitable import httpx import nonebot +from pydantic import BaseModel from nonebot.adapters import Bot -from nonebot.adapters.discord import Bot as DiscordBot +from nonebot.utils import is_coroutine_callable from nonebot.adapters.dodo import Bot as DodoBot -from nonebot.adapters.kaiheila import Bot as KaiheilaBot from nonebot.adapters.onebot.v11 import Bot as v11Bot from nonebot.adapters.onebot.v12 import Bot as v12Bot -from nonebot.utils import is_coroutine_callable -from nonebot_plugin_alconna.uniseg import Receipt, Target, UniMessage -from pydantic import BaseModel +from nonebot.adapters.discord import Bot as DiscordBot +from nonebot.adapters.kaiheila import Bot as KaiheilaBot +from nonebot_plugin_alconna.uniseg import Target, Receipt, UniMessage -from zhenxun.models.friend_user import FriendUser -from zhenxun.models.group_console import GroupConsole from zhenxun.services.log import logger -from zhenxun.utils.exception import NotFindSuperuser +from zhenxun.configs.config import BotConfig from zhenxun.utils.message import MessageUtils +from zhenxun.models.friend_user import FriendUser +from zhenxun.utils.exception import NotFindSuperuser +from zhenxun.models.group_console import GroupConsole class UserData(BaseModel): @@ -78,11 +80,11 @@ async def send_superuser( Receipt | None: Receipt """ if not superuser_id: - platform = cls.get_platform(bot) - platform_superusers = bot.config.PLATFORM_SUPERUSERS.get(platform) or [] - if not platform_superusers: - raise NotFindSuperuser() - superuser_id = random.choice(platform_superusers) + if platform := cls.get_platform(bot): + if platform_superusers := BotConfig.get_superuser(platform): + superuser_id = random.choice(platform_superusers) + else: + raise NotFindSuperuser() if isinstance(message, str): message = MessageUtils.build_message(message) return await cls.send_message(bot, superuser_id, None, message) @@ -128,25 +130,23 @@ async def get_group_member_list(cls, bot: Bot, group_id: str) -> list[UserData]: ): max_id = result_data.max_id result_list = result_data.list - data_list = [] while max_id == 100: result_data = await bot.get_member_list( island_source_id=group_id, page_size=100, max_id=0 ) result_list += result_data.list max_id = result_data.max_id - for user in result_list: - data_list.append( - UserData( - name=user.nick_name, - card=user.personal_nick_name, - avatar_url=user.avatar_url, - user_id=user.dodo_source_id, - group_id=user.island_source_id, - join_time=int(user.join_time.timestamp()), - ) + return [ + UserData( + name=user.nick_name, + card=user.personal_nick_name, + avatar_url=user.avatar_url, + user_id=user.dodo_source_id, + group_id=user.island_source_id, + join_time=int(user.join_time.timestamp()), ) - return data_list + for user in result_list + ] if isinstance(bot, KaiheilaBot): if result_data := await bot.guild_userList(guild_id=group_id): if result_data.users: @@ -165,9 +165,6 @@ async def get_group_member_list(cls, bot: Bot, group_id: str) -> list[UserData]: ) ) return data_list - if isinstance(bot, DiscordBot): - # TODO: discord获取用户 - pass return [] @classmethod @@ -197,15 +194,14 @@ async def get_user( role=user["role"], join_time=user["join_time"], ) - else: - if friend_list := await bot.get_friend_list(): - for f in friend_list: - if f["user_id"] == int(user_id): - return UserData( - name=f["nickname"], - card=f["remark"], - user_id=f["user_id"], - ) + elif friend_list := await bot.get_friend_list(): + for f in friend_list: + if f["user_id"] == int(user_id): + return UserData( + name=f["nickname"], + card=f["remark"], + user_id=f["user_id"], + ) if isinstance(bot, v12Bot): if group_id: if user := await bot.get_group_member_info( @@ -217,50 +213,36 @@ async def get_user( user_id=user["user_id"], group_id=group_id, ) - else: - if friend_list := await bot.get_friend_list(): - for f in friend_list: - if f["user_id"] == int(user_id): - return UserData( - name=f["user_name"], - card=f["user_remark"], - user_id=f["user_id"], - ) - if isinstance(bot, DodoBot): - if group_id: - if user := await bot.get_member_info( - island_source_id=group_id, dodo_source_id=user_id - ): - return UserData( - name=user.nick_name, - card=user.personal_nick_name, - avatar_url=user.avatar_url, - user_id=user.dodo_source_id, - group_id=user.island_source_id, - join_time=int(user.join_time.timestamp()), - ) - else: - # TODO: DoDo个人数据 - pass - if isinstance(bot, KaiheilaBot): - if group_id: - if user := await bot.user_view(guild_id=group_id, user_id=user_id): - second = None - if user.joined_at: - second = int(user.joined_at / 1000) - return UserData( - name=user.nickname or "", - avatar_url=user.avatar, - user_id=user_id, - group_id=group_id, - join_time=second, - ) - else: - # TODO: kaiheila用户详情 - pass - if isinstance(bot, DiscordBot): - # TODO: discord获取用户 - pass + elif friend_list := await bot.get_friend_list(): + for f in friend_list: + if f["user_id"] == int(user_id): + return UserData( + name=f["user_name"], + card=f["user_remark"], + user_id=f["user_id"], + ) + if isinstance(bot, DodoBot) and group_id: + if user := await bot.get_member_info( + island_source_id=group_id, dodo_source_id=user_id + ): + return UserData( + name=user.nick_name, + card=user.personal_nick_name, + avatar_url=user.avatar_url, + user_id=user.dodo_source_id, + group_id=user.island_source_id, + join_time=int(user.join_time.timestamp()), + ) + if isinstance(bot, KaiheilaBot) and group_id: + if user := await bot.user_view(guild_id=group_id, user_id=user_id): + second = int(user.joined_at / 1000) if user.joined_at else None + return UserData( + name=user.nickname or "", + avatar_url=user.avatar, + user_id=user_id, + group_id=group_id, + join_time=second, + ) return None @classmethod @@ -277,15 +259,13 @@ async def get_user_avatar(cls, user_id: str, platform: str) -> bytes | None: for _ in range(3): try: return (await client.get(url)).content - except Exception as e: + except Exception: logger.error( "获取用户头像错误", "Util", target=user_id, platform=platform, ) - else: - pass return None @classmethod @@ -302,12 +282,10 @@ async def get_group_avatar(cls, gid: str, platform: str) -> bytes | None: for _ in range(3): try: return (await client.get(url)).content - except Exception as e: + except Exception: logger.error( "获取群头像错误", "Util", target=gid, platform=platform ) - else: - pass return None @classmethod @@ -364,12 +342,12 @@ async def update_group(cls, bot: Bot) -> int: target=f"{group.group_id}:{group.channel_id}", ) else: - _group = [ + _group = next( g for g in db_group if g.group_id == group.group_id and g.channel_id == group.channel_id - ][0] + ) _group.group_name = group.group_name _group.max_member_count = group.max_member_count _group.member_count = group.member_count @@ -392,15 +370,13 @@ def get_platform(cls, bot: Bot) -> str | None: 返回: str | None: 平台 """ - if isinstance(bot, (v11Bot, v12Bot)): + if isinstance(bot, v11Bot | v12Bot): return "qq" - # if isinstance(bot, DodoBot): - # return "dodo" - # if isinstance(bot, KaiheilaBot): - # return "kaiheila" - # if isinstance(bot, DiscordBot): - # return "discord" - return None + if isinstance(bot, DodoBot): + return "dodo" + if isinstance(bot, KaiheilaBot): + return "kaiheila" + return "discord" if isinstance(bot, DiscordBot) else None @classmethod async def get_group_list(cls, bot: Bot) -> tuple[list[GroupConsole], str]: @@ -466,9 +442,6 @@ async def get_group_list(cls, bot: Bot) -> tuple[list[GroupConsole], str]: if c.type != 0 ] return group_list, "kaiheila" - if isinstance(bot, DiscordBot): - # TODO: discord群组列表 - pass return [], "" @classmethod @@ -518,15 +491,15 @@ async def get_friend_list(cls, bot: Bot) -> tuple[list[FriendUser], str]: ) for f in friend_list ], "qq" - if isinstance(bot, DodoBot): - # TODO: dodo好友列表 - pass - if isinstance(bot, KaiheilaBot): - # TODO: kaiheila好友列表 - pass - if isinstance(bot, DiscordBot): - # TODO: discord好友列表 - pass + # if isinstance(bot, DodoBot): + # # TODO: dodo好友列表 + # pass + # if isinstance(bot, KaiheilaBot): + # # TODO: kaiheila好友列表 + # pass + # if isinstance(bot, DiscordBot): + # # TODO: discord好友列表 + # pass return [], "" @classmethod @@ -549,12 +522,12 @@ def get_target( target: 对应平台Target """ target = None - if isinstance(bot, (v11Bot, v12Bot)): + if isinstance(bot, v11Bot | v12Bot): if group_id: target = Target(group_id) elif user_id: target = Target(user_id, private=True) - elif isinstance(bot, (DodoBot, KaiheilaBot)): + elif isinstance(bot, DodoBot | KaiheilaBot): if group_id and channel_id: target = Target(channel_id, parent_id=group_id, channel=True) elif user_id: @@ -565,8 +538,8 @@ def get_target( async def broadcast_group( message: str | UniMessage, bot: Bot | list[Bot] | None = None, - bot_id: str | Set[str] | None = None, - ignore_group: Set[int] | None = None, + bot_id: str | set[str] | None = None, + ignore_group: set[int] | None = None, check_func: Callable[[str], Awaitable] | None = None, log_cmd: str | None = None, platform: Literal["qq", "dodo", "kaiheila"] | None = None, diff --git a/zhenxun/utils/utils.py b/zhenxun/utils/utils.py index 7ea698a9d..e05896ce6 100644 --- a/zhenxun/utils/utils.py +++ b/zhenxun/utils/utils.py @@ -1,16 +1,16 @@ import os import time -from collections import defaultdict -from datetime import datetime -from pathlib import Path from typing import Any +from pathlib import Path +from datetime import datetime +from collections import defaultdict +import pytz import httpx import pypinyin -import pytz -from zhenxun.configs.config import Config from zhenxun.services.log import logger +from zhenxun.configs.config import Config class ResourceDirManager: @@ -18,7 +18,7 @@ class ResourceDirManager: 临时文件管理器 """ - temp_path = [] + temp_path = [] # noqa: RUF012 @classmethod def __tree_append(cls, path: Path): @@ -69,7 +69,7 @@ def check(self, key) -> bool: if day != self.today: self.today = day self.count.clear() - return bool(self.count[key] < self.max) + return self.count[key] < self.max def get_num(self, key): return self.count[key] @@ -130,10 +130,7 @@ def cn2py(word: str) -> str: 参数: word: 文本 """ - temp = "" - for i in pypinyin.pinyin(word, style=pypinyin.NORMAL): - temp += "".join(i) - return temp + return "".join("".join(i) for i in pypinyin.pinyin(word, style=pypinyin.NORMAL)) async def get_user_avatar(uid: int | str) -> bytes | None: @@ -147,7 +144,7 @@ async def get_user_avatar(uid: int | str) -> bytes | None: for _ in range(3): try: return (await client.get(url)).content - except Exception as e: + except Exception: logger.error("获取用户头像错误", "Util", target=uid) return None @@ -163,7 +160,7 @@ async def get_group_avatar(gid: int | str) -> bytes | None: for _ in range(3): try: return (await client.get(url)).content - except Exception as e: + except Exception: logger.error("获取群头像错误", "Util", target=gid) return None diff --git a/zhenxun/utils/withdraw_manage.py b/zhenxun/utils/withdraw_manage.py index d88b394f8..f6ab4e0e3 100644 --- a/zhenxun/utils/withdraw_manage.py +++ b/zhenxun/utils/withdraw_manage.py @@ -1,21 +1,21 @@ import asyncio from nonebot.adapters import Bot +from ruamel.yaml.comments import CommentedSeq +from nonebot_plugin_session import EventSession # from nonebot.adapters.discord import Bot as DiscordBot # from nonebot.adapters.dodo import Bot as DodoBot # from nonebot.adapters.kaiheila import Bot as KaiheilaBot from nonebot.adapters.onebot.v11 import Bot as v11Bot from nonebot.adapters.onebot.v12 import Bot as v12Bot -from nonebot_plugin_session import EventSession -from ruamel.yaml.comments import CommentedSeq from zhenxun.services.log import logger class WithdrawManager: - _data = {} + _data = {} # noqa: RUF012 _index = 0 @classmethod @@ -34,7 +34,7 @@ def check(cls, session: EventSession, withdraw_time: tuple[int, int]) -> bool: return True if withdraw_time[1] == 1 and (session.id2 or session.id3): return True - if withdraw_time[1] == 0 and not (session.id2 or session.id3): + if withdraw_time[1] == 0 and not session.id2 and not session.id3: return True return False @@ -82,7 +82,7 @@ async def withdraw_message( if time: gid = None _time = 1 - if isinstance(time, (tuple, CommentedSeq)): + if isinstance(time, tuple | CommentedSeq): if time[0] == 0: return if session: