diff --git a/LittlePaimon/plugins/Paimon_Abyss/__init__.py b/LittlePaimon/plugins/Paimon_Abyss/__init__.py new file mode 100644 index 0000000..115f111 --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Abyss/__init__.py @@ -0,0 +1,83 @@ +from nonebot import on_command +from nonebot.adapters.onebot.v11 import Message, MessageEvent, GroupMessageEvent +from nonebot.params import Arg +from nonebot.plugin import PluginMetadata + +from LittlePaimon.utils import logger +from LittlePaimon.utils.genshin import GenshinInfoManager +from LittlePaimon.utils.message import CommandPlayer +from .abyss_statistics import get_statistics +from .draw_abyss import draw_abyss_card +from .youchuang import draw_team + +__plugin_meta__ = PluginMetadata( + name='原神深渊查询', + description='原神深渊查询', + usage='...', + extra={ + 'author': '惜月', + 'version': '3.0', + 'priority': 2, + } +) + +sy = on_command('sy', aliases={'深渊战报', '深渊信息'}, priority=10, block=True, state={ + 'pm_name': 'sy', + 'pm_description': '查看本期|上期的深渊战报', + 'pm_usage': 'sy(uid)(本期|上期)', + 'pm_priority': 1 +}) +abyss_stat = on_command('深渊统计', aliases={'深渊群数据', '深渊群排行'}, priority=10, block=True, state={ + 'pm_name': '深渊统计', + 'pm_description': '查看本群深渊统计,仅群可用', + 'pm_usage': '深渊统计', + 'pm_priority': 2 +}) +abyss_team = on_command('深渊配队', aliases={'配队推荐', '深渊阵容'}, priority=10, block=True, state={ + 'pm_name': '深渊配队', + 'pm_description': '查看深渊配队推荐,数据来源于游创工坊', + 'pm_usage': '深渊配队', + 'pm_priority': 3 +}) + + +@sy.handle() +async def _(event: MessageEvent, players=CommandPlayer(), msg: str = Arg('msg')): + logger.info('原神深渊战报', '开始执行') + abyss_index = 2 if any(i in msg for i in ['上', 'last']) else 1 + msg = Message() + for player in players: + logger.info('原神深渊战报', '➤ ', {'用户': players[0].user_id, 'UID': players[0].uid}) + gim = GenshinInfoManager(player.user_id, player.uid) + abyss_info = await gim.get_abyss_info(abyss_index) + if isinstance(abyss_info, str): + logger.info('原神深渊战报', '➤➤', {}, abyss_info, False) + msg += f'UID{player.uid}{abyss_info}\n' + else: + logger.info('原神深渊战报', '➤➤', {}, '数据获取成功', True) + try: + img = await draw_abyss_card(abyss_info) + logger.info('原神深渊战报', '➤➤➤', {}, '制图完成', True) + msg += img + except Exception as e: + logger.info('原神深渊战报', '➤➤➤', {}, f'制图出错:{e}', False) + msg += F'UID{player.uid}制图时出错:{e}\n' + await sy.finish(msg, at_sender=True) + + +@abyss_stat.handle() +async def _(event: GroupMessageEvent): + try: + result = await get_statistics(event.group_id) + except Exception as e: + result = f'制作深渊统计时出错:{e}' + await abyss_stat.finish(result) + + +@abyss_team.handle() +async def _(event: MessageEvent): + try: + result = await draw_team(str(event.user_id)) + except Exception as e: + result = f'制作深渊配队时出错:{e}' + await abyss_team.finish(result) diff --git a/LittlePaimon/plugins/Paimon_Info/abyss_statistics.py b/LittlePaimon/plugins/Paimon_Abyss/abyss_statistics.py similarity index 100% rename from LittlePaimon/plugins/Paimon_Info/abyss_statistics.py rename to LittlePaimon/plugins/Paimon_Abyss/abyss_statistics.py diff --git a/LittlePaimon/plugins/Paimon_Info/draw_abyss.py b/LittlePaimon/plugins/Paimon_Abyss/draw_abyss.py similarity index 100% rename from LittlePaimon/plugins/Paimon_Info/draw_abyss.py rename to LittlePaimon/plugins/Paimon_Abyss/draw_abyss.py diff --git a/LittlePaimon/plugins/Paimon_Abyss/youchuang/__init__.py b/LittlePaimon/plugins/Paimon_Abyss/youchuang/__init__.py new file mode 100644 index 0000000..c4ee714 --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Abyss/youchuang/__init__.py @@ -0,0 +1 @@ +from .draw import draw_team diff --git a/LittlePaimon/plugins/Paimon_Abyss/youchuang/api.py b/LittlePaimon/plugins/Paimon_Abyss/youchuang/api.py new file mode 100644 index 0000000..a975543 --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Abyss/youchuang/api.py @@ -0,0 +1,39 @@ +from typing import List, Tuple + +from pydantic import parse_obj_as +from LittlePaimon.utils.requests import aiorequests +from .models import TeamRateResult, TeamRate + +# API均来自微信公众号`原神创意工坊`,感谢坊主提供的API +BASE_API = 'https://www.youchuang.fun' +TEAM_RATE_API = f'{BASE_API}/gamerole/formationRate' + +VERSION_API = 'https://api-cloudgame-static.mihoyo.com/hk4e_cg_cn/gamer/api/getFunctionShieldNew?client_type=1' +HEADERS = { + 'Host': 'www.youchuang.fun', + 'Referer': 'https://servicewechat.com/wxce4dbe0cb0f764b3/91/page-frame.html', + 'User-Agent': 'Mozilla/5.0 (iPad; CPU OS 15_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) ' + 'Mobile/15E148 MicroMessenger/8.0.20(0x1800142f) NetType/WIFI Language/zh_CN', + 'content-type': 'application/json' +} + + +async def get_game_version() -> float: + data = await aiorequests.get(VERSION_API) + data = data.json() + return float(data['data']['config']['cg.key_function_queue_news']['versions'][0].replace('.0', '')) + + +async def get_team_rate() -> Tuple[TeamRateResult, float]: + version = await get_game_version() + data_up = (await aiorequests.post(TEAM_RATE_API, json={ + 'version': version, + 'layer': 1 + }, headers=HEADERS)).json()['result'] + data_down = (await aiorequests.post(TEAM_RATE_API, json={ + 'version': version, + 'layer': 2 + }, headers=HEADERS)).json()['result'] + return TeamRateResult(rateListUp=parse_obj_as(List[TeamRate], data_up['rateList']), + rateListDown=parse_obj_as(List[TeamRate], data_down['rateList']), + userCount=data_up['userCount']), version diff --git a/LittlePaimon/plugins/Paimon_Abyss/youchuang/draw.py b/LittlePaimon/plugins/Paimon_Abyss/youchuang/draw.py new file mode 100644 index 0000000..edada7a --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Abyss/youchuang/draw.py @@ -0,0 +1,60 @@ +import asyncio +from typing import List + +from LittlePaimon.database.models import Character, LastQuery +from LittlePaimon.config import RESOURCE_BASE_PATH +from LittlePaimon.utils.files import load_image +from LittlePaimon.utils.alias import get_chara_icon +from LittlePaimon.utils.image import PMImage, font_manager as fm +from LittlePaimon.utils.message import MessageBuild +from .api import get_team_rate +from .models import TeamRate + + +async def draw_team_line(up: TeamRate, down: TeamRate, characters: List[str]) -> PMImage: + img = PMImage(size=(1013, 127), mode='RGBA', color=(0, 0, 0, 0)) + for i, member in enumerate(up.formation): + owned = member.name in characters if characters else True + await img.paste(await load_image(RESOURCE_BASE_PATH / 'icon' / f'{member.star}starbox.png'), (110 * i, 0)) + await img.paste( + await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{get_chara_icon(name=member.name)}.png', size=(100, 100)), + (110 * i, 1)) + await img.text(member.name if owned else '未拥有', (110 * i, 110 * i + 100), 103, fm.get('hywh', 18), '#33231a', + 'center') + if not owned: + await img.paste(await load_image(RESOURCE_BASE_PATH / 'icon' / 'grey_box.png'), (110 * i, 0)) + await img.text(f'{round(up.rate * 100, 2)}%', 439, 30, fm.get('bahnschrift_bold', 30), '#33231a') + for i, member in enumerate(down.formation): + owned = member.name in characters if characters else True + await img.paste(await load_image(RESOURCE_BASE_PATH / 'icon' / f'{member.star}starbox.png'), (583 + 110 * i, 0)) + await img.paste( + await load_image(RESOURCE_BASE_PATH / 'avatar' / f'{get_chara_icon(name=member.name)}.png', size=(100, 100)), + (583 + 110 * i, 1)) + await img.text(member.name if owned else '未拥有', (583 + 110 * i, 110 * i + 683), 103, fm.get('hywh', 18), '#33231a', + 'center') + if not owned: + await img.paste(await load_image(RESOURCE_BASE_PATH / 'icon' / 'grey_box.png'), (583 + 110 * i, 0)) + await img.text(f'{round(down.rate * 100, 2)}%', 574, 59, fm.get('bahnschrift_bold', 30), '#33231a', 'right') + return img + + +async def draw_team(user_id: str): + if last_query := await LastQuery.get_or_none(user_id=user_id): + charas = await Character.filter(user_id=user_id, uid=last_query.uid) + else: + charas = await Character.filter(user_id=user_id) + characters = [chara.name for chara in charas if chara is not None] + team_data, version = await get_team_rate() + if characters: + team_data.sort_by_own(characters) + img = PMImage(RESOURCE_BASE_PATH / 'abyss' / 'team_bg.png') + await img.text('深境螺旋十二层配队', 36, 29, fm.get('优设标题黑', 72), '#40342d') + await img.paste(await load_image(RESOURCE_BASE_PATH / 'general' / 'bubble.png'), (625, 35)) + await img.text(f'{version}版本', (625, 625 + 147), 38, fm.get('SourceHanSansCN-Bold.otf', 30), 'white', 'center') + await img.text('CREATED BY LITTLEPAIMON | DATA FROM YOU-CHUANG', + 1025, 158, fm.get('bahnschrift_regular.ttf', 30), '#8c4c2e', 'right') + + await asyncio.gather(*[img.paste(await draw_team_line(team_data.rateListUp[i], team_data.rateListDown[i], characters), (33, 222 + 153 * i)) for i in range(15)]) + return MessageBuild.Image(img, mode='RGB', quality=80) + + diff --git a/LittlePaimon/plugins/Paimon_Abyss/youchuang/models.py b/LittlePaimon/plugins/Paimon_Abyss/youchuang/models.py new file mode 100644 index 0000000..cf1a07c --- /dev/null +++ b/LittlePaimon/plugins/Paimon_Abyss/youchuang/models.py @@ -0,0 +1,33 @@ +from typing import List, Optional + +from pydantic import BaseModel, validator + + +class Member(BaseModel): + star: int + attr: str + name: str + + +class TeamRate(BaseModel): + rate: float + formation: List[Member] + ownerNum: Optional[int] + + @validator('rate', pre=True) + def str2float(cls, v): + return float(v.replace('%', '')) / 100.0 if isinstance(v, str) else v + + +class TeamRateResult(BaseModel): + rateListUp: List[TeamRate] + rateListDown: List[TeamRate] + userCount: int + + def sort_by_own(self, characters: List[str]): + for team in self.rateListUp: + team.ownerNum = len(set(characters) & {member.name for member in team.formation}) + for team in self.rateListDown: + team.ownerNum = len(set(characters) & {member.name for member in team.formation}) + self.rateListUp.sort(key=lambda x: (x.ownerNum / 4 * x.rate), reverse=True) + self.rateListDown.sort(key=lambda x: (x.ownerNum / 4 * x.rate), reverse=True) diff --git a/LittlePaimon/plugins/Paimon_Info/__init__.py b/LittlePaimon/plugins/Paimon_Info/__init__.py index 4090edd..541b010 100644 --- a/LittlePaimon/plugins/Paimon_Info/__init__.py +++ b/LittlePaimon/plugins/Paimon_Info/__init__.py @@ -1,7 +1,8 @@ -from nonebot import on_command, on_regex -from nonebot.adapters.onebot.v11 import Message, MessageEvent, MessageSegment, GroupMessageEvent -from nonebot.params import Arg, RegexDict, CommandArg +from nonebot import on_command +from nonebot.adapters.onebot.v11 import Message, MessageEvent, MessageSegment +from nonebot.params import Arg, ArgPlainText, CommandArg from nonebot.plugin import PluginMetadata +from nonebot.typing import T_State from LittlePaimon import NICKNAME from LittlePaimon.database.models import PlayerAlias @@ -9,14 +10,12 @@ from LittlePaimon.utils import logger from LittlePaimon.utils.message import CommandPlayer, CommandCharacter, CommandUID from LittlePaimon.utils.genshin import GenshinInfoManager from LittlePaimon.utils.tool import freq_limiter -from LittlePaimon.utils.typing import CHARA_RE +from LittlePaimon.utils.typing import CHARACTERS from .draw_player_card import draw_player_card from .draw_character_bag import draw_chara_bag from .draw_character_detail import draw_chara_detail from .draw_character_card import draw_chara_card -from .draw_abyss import draw_abyss_card -from .abyss_statistics import get_statistics __plugin_meta__ = PluginMetadata( name='原神信息查询', @@ -41,12 +40,6 @@ ysa = on_command('ysa', aliases={'角色背包', '练度统计'}, priority=10, b 'pm_usage': 'ysa(uid)', 'pm_priority': 2 }) -sy = on_command('sy', aliases={'深渊战报', '深渊信息'}, priority=10, block=True, state={ - 'pm_name': 'sy', - 'pm_description': '查看本期|上期的深渊战报', - 'pm_usage': 'sy(uid)(本期|上期)', - 'pm_priority': 3 -}) ysc = on_command('ysc', aliases={'角色图', '角色卡片'}, priority=10, block=True, state={ 'pm_name': 'ysc', 'pm_description': '随机角色同人图+角色信息卡片', @@ -65,14 +58,12 @@ update_info = on_command('udi', aliases={'更新角色信息', '更新面板', ' 'pm_usage': '更新角色信息[天赋](uid)', 'pm_priority': 6 }) -add_alias = on_regex( - rf'((?P{CHARA_RE})是[我俺咱]的?(?P\w+))|([我俺咱]的?(?P\w+)[是叫名](?P{CHARA_RE}))', priority=10, - block=True, state={ - 'pm_name': '角色别名设置', - 'pm_description': '设置专属于你的角色别名', - 'pm_usage': '<角色名>是我<别名>', - 'pm_priority': 7 - }) +add_alias = on_command('设置别名', priority=10, block=True, state={ + 'pm_name': '角色别名设置', + 'pm_description': '设置专属于你的角色别名,例如【设置别名钟离 老公】', + 'pm_usage': '设置别名<角色> <别名>', + 'pm_priority': 7 +}) delete_alias = on_command('删除别名', priority=10, block=True, state={ 'pm_name': '角色别名删除', 'pm_description': '删除你已设置的角色别名', @@ -85,12 +76,6 @@ show_alias = on_command('查看别名', priority=10, block=True, state={ 'pm_usage': '查看别名', 'pm_priority': 9 }) -show_abyss = on_command('深渊统计', priority=10, block=True, state={ - 'pm_name': '深渊统计', - 'pm_description': '查看本群深渊统计,仅群可用', - 'pm_usage': '深渊统计', - 'pm_priority': 10 -}) @ys.handle() @@ -149,30 +134,6 @@ async def _(event: MessageEvent, players=CommandPlayer(2)): await ysa.finish(msg, at_sender=True) -@sy.handle() -async def _(event: MessageEvent, players=CommandPlayer(), msg: str = Arg('msg')): - logger.info('原神深渊战报', '开始执行') - abyss_index = 2 if any(i in msg for i in ['上', 'last']) else 1 - msg = Message() - for player in players: - logger.info('原神深渊战报', '➤ ', {'用户': players[0].user_id, 'UID': players[0].uid}) - gim = GenshinInfoManager(player.user_id, player.uid) - abyss_info = await gim.get_abyss_info(abyss_index) - if isinstance(abyss_info, str): - logger.info('原神深渊战报', '➤➤', {}, abyss_info, False) - msg += f'UID{player.uid}{abyss_info}\n' - else: - logger.info('原神深渊战报', '➤➤', {}, '数据获取成功', True) - try: - img = await draw_abyss_card(abyss_info) - logger.info('原神深渊战报', '➤➤➤', {}, '制图完成', True) - msg += img - except Exception as e: - logger.info('原神深渊战报', '➤➤➤', {}, f'制图出错:{e}', False) - msg += F'UID{player.uid}制图时出错:{e}\n' - await ysa.finish(msg, at_sender=True) - - @ysc.handle() async def _(event: MessageEvent, players=CommandPlayer(only_cn=False), characters=CommandCharacter()): logger.info('原神角色卡片', '开始执行') @@ -267,21 +228,42 @@ async def _(event: MessageEvent, uid=CommandUID(), msg: str = Arg('msg')): @add_alias.handle() -async def _(event: MessageEvent, regex_dict: dict = RegexDict()): - chara = regex_dict['chara'] or regex_dict['chara2'] - alias = regex_dict['alias'] or regex_dict['alias2'] +async def _(event: MessageEvent, state: T_State, msg: Message = CommandArg()): + msg = msg.extract_plain_text().strip().split(' ') + if msg[0] in CHARACTERS: + state['chara'] = Message(msg[0]) + else: + await add_alias.finish(f'{NICKNAME}不认识{msg[0]}哦,请使用角色全称', at_sender=True) + if len(msg) > 1: + state['alias'] = Message(msg[1]) + + +@add_alias.got('alias', prompt=Message.template('你想把{chara}设置为你的谁呢?')) +async def _(event: MessageEvent, chara: str = ArgPlainText('chara'), alias: str = ArgPlainText('alias')): await PlayerAlias.update_or_create(user_id=str(event.user_id), alias=alias, defaults={'character': chara}) - await add_alias.finish(f'{NICKNAME}知道{chara}是你的{alias}啦..') + await add_alias.finish(f'设置成功,{NICKNAME}知道{chara}是你的{alias}啦..') @delete_alias.handle() -async def _(event: MessageEvent, msg: Message = CommandArg()): - msg = msg.extract_plain_text().strip() - if alias := await PlayerAlias.get_or_none(user_id=str(event.user_id), alias=msg): +async def _(event: MessageEvent, state: T_State, msg: Message = CommandArg()): + if msg: + state['alias'] = msg + elif aliases := await PlayerAlias.filter(user_id=str(event.user_id)).all(): + state['msg'] = '你已设置以下别名:\n' + '\n'.join([f'{i.alias} -> {i.character}' for i in aliases]) + '\n请输入你想删除的别名或发送"全部"删除全部别名' + else: + await delete_alias.finish('你还没有设置任何别名哦') + + +@delete_alias.got('alias', prompt=Message.template('{msg}')) +async def _(event: MessageEvent, msg: str = ArgPlainText('alias')): + if msg == '全部': + await PlayerAlias.filter(user_id=str(event.user_id)).delete() + await delete_alias.finish('已删除你设置的全部别名') + elif alias := await PlayerAlias.get_or_none(user_id=str(event.user_id), alias=msg): await alias.delete() await delete_alias.finish(f'别名{msg}删除成功!', at_sender=True) else: - await delete_alias.finish(f'你并没有将{msg}设置为某个角色的别名', at_sender=True) + await delete_alias.reject(f'你并没有将{msg}设置为某个角色的别名', at_sender=True) @show_alias.handle() @@ -291,9 +273,3 @@ async def _(event: MessageEvent): at_sender=True) else: await show_alias.finish('你还没有设置过角色别名哦', at_sender=True) - - -@show_abyss.handle() -async def _(event: GroupMessageEvent): - result = await get_statistics(event.group_id) - await show_abyss.finish(result)