diff --git a/BlueArchiveUID/bauid_info/draw_user_info_pic.py b/BlueArchiveUID/bauid_info/draw_user_info_pic.py index 2a08487..50a738c 100644 --- a/BlueArchiveUID/bauid_info/draw_user_info_pic.py +++ b/BlueArchiveUID/bauid_info/draw_user_info_pic.py @@ -13,6 +13,7 @@ from ..utils.ba_api import xtzx_api from ..utils.error_reply import get_error +from ..utils.api.models import AssistInfo, RankAssistInfo from ..utils.resource_path import ( SKILL_ICON_PATH, WEAPON_ICON_PATH, @@ -44,6 +45,118 @@ } +async def draw_assist_card(assist: Union[AssistInfo, RankAssistInfo]): + assist_card = Image.open(TEXT_PATH / 'assist_bg.png') + if assist: + assist_draw = ImageDraw.Draw(assist_card) + + student_id = assist['uniqueId'] + student_star = assist['starGrade'] + student_level = assist['level'] + student_star_pic = Image.open(TEXT_PATH / f'star{student_star}.png') + student_star_pic = student_star_pic.convert('RGBA') + student_name = studentId2Name[str(student_id)] + student_type = studentId2Type[str(student_id)] + + student_pic = Image.open( + STUDENT_COLLECTION_PATH / f'{student_id}.webp' + ) + student_color = COLOR_MAP[student_type] + student_color_pic = Image.new('RGBA', student_pic.size, student_color) + + favor_rank = assist['favorRank'] + ex = assist['exSkillLevel'] + nm = assist['publicSkillLevel'] + ps = assist['passiveSkillLevel'] + sub = assist['extraPassiveSkillLevel'] + + skill_data = { + 'ex': ex, + 'nm': nm, + 'ps': ps, + 'sub': sub, + } + for sindex, s in enumerate(skill_data): + skill_bg = Image.open(TEXT_PATH / f'{student_type}_skill_bg.png') + skill_draw = ImageDraw.Draw(skill_bg) + + skill_icon = studentSkill2Icon[str(student_id)][s] + skill_path = SKILL_ICON_PATH / f'{skill_icon}.webp' + skill_pic = Image.open(skill_path).resize((36, 38)) + + skill = skill_data[s] if skill_data[s] != 10 else 'M' + skill_color = GREY if skill != 'M' else student_color + + skill_bg.paste(skill_pic, (25, 21), skill_pic) + skill_draw.text( + (70, 40), f'等级{skill}', skill_color, cf(30), 'lm' + ) + assist_card.paste(skill_bg, (312 + 172 * sindex, 96), skill_bg) + + assist_card.paste(student_color_pic, (66, 108), student_pic) + assist_card.paste(student_pic, (68, 96), student_pic) + assist_card.paste(student_star_pic, (228, 390), student_star_pic) + assist_draw.text((146, 364), student_name, GREY, cf(32), 'mm') + assist_draw.text((251, 361), str(favor_rank), 'white', cf(25), 'mm') + assist_draw.text( + (151, 414), f'等级{student_level}', BLACK, cf(25), 'mm' + ) + + weapon_bg = Image.open(TEXT_PATH / 'weapon_bar.png') + if assist['weapon']: + weapon_draw = ImageDraw.Draw(weapon_bg) + + weapon_star = assist['weaponStartGrade'] + weapon_name = weaponId2Nmae[str(student_id)] + weapon_level = assist['weaponLevel'] + weapon_icon_id = studentId2weaponIcon[str(student_id)] + weapon_path = WEAPON_ICON_PATH / f'{weapon_icon_id}.webp' + weapon_icon = Image.open(weapon_path).resize((400, 102)) + + weapon_bg.paste(weapon_icon, (11, 42), weapon_icon) + weapon_draw.text( + (470, 70), f'等级{weapon_level}', BLACK, cf(25), 'mm' + ) + weapon_draw.text((486, 115), weapon_name, BLACK, cf(38), 'lm') + + for i in range(5): + if i < weapon_star: + star_pic = weapon_star_full + else: + star_pic = weapon_star_empty + weapon_bg.paste(star_pic, (227 + 29 * i, 110), star_pic) + + assist_card.paste(weapon_bg, (275, 147), weapon_bg) + + equip_fg = Image.open(TEXT_PATH / 'equip_fg.png') + for eindex, equip in enumerate(assist['equipment']): + equip_bg = Image.open(TEXT_PATH / 'equip_bg.png') + equip_id = equip['UniqueId'] + equip_icon = equipId2Icon[str(equip_id)] + equip_pic = Image.open(EQUIPMENT_ICON_PATH / f'{equip_icon}.webp') + equip_level = equip['Level'] + # equip_tier = equip['Tier'] + + equip_bg.paste(equip_pic, (2, 17), equip_pic) + equip_bg.paste(equip_fg, (0, 0), equip_fg) + equip_bg_draw = ImageDraw.Draw(equip_bg) + equip_bg_draw.text( + (75, 124), + f'等级{equip_level}', + 'white', + cf(24), + 'mm', + ) + assist_card.paste(equip_bg, (304 + eindex * 131, 306), equip_bg) + return assist_card + + +def get_bg(w: int, h: int): + img = crop_center_img(Image.open(TEXT_PATH / 'bg.jpg'), w, h) + img = img.convert('RGBA') + return img + + async def draw_user_info_img( _fcode: str, ev: Event, user_id: str ) -> Union[str, bytes]: @@ -54,8 +167,7 @@ async def draw_user_info_img( return get_error(data) w, h = 1100, 2880 - img = crop_center_img(Image.open(TEXT_PATH / 'bg.jpg'), w, h) - img = img.convert('RGBA') + img = get_bg(w, h) title = Image.open(TEXT_PATH / 'title.png') title_draw = ImageDraw.Draw(title) @@ -129,120 +241,7 @@ async def draw_user_info_img( _assist_list.append({}) for index, assist in enumerate(_assist_list): - assist_card = Image.open(TEXT_PATH / 'assist_bg.png') - if assist: - assist_draw = ImageDraw.Draw(assist_card) - - student_id = assist['uniqueId'] - student_star = assist['starGrade'] - student_level = assist['level'] - student_star_pic = Image.open( - TEXT_PATH / f'star{student_star}.png' - ) - student_star_pic = student_star_pic.convert('RGBA') - student_name = studentId2Name[str(student_id)] - student_type = studentId2Type[str(student_id)] - - student_pic = Image.open( - STUDENT_COLLECTION_PATH / f'{student_id}.webp' - ) - student_color = COLOR_MAP[student_type] - student_color_pic = Image.new( - 'RGBA', student_pic.size, student_color - ) - - favor_rank = assist['favorRank'] - ex = assist['exSkillLevel'] - nm = assist['publicSkillLevel'] - ps = assist['passiveSkillLevel'] - sub = assist['extraPassiveSkillLevel'] - - skill_data = { - 'ex': ex, - 'nm': nm, - 'ps': ps, - 'sub': sub, - } - for sindex, s in enumerate(skill_data): - skill_bg = Image.open( - TEXT_PATH / f'{student_type}_skill_bg.png' - ) - skill_draw = ImageDraw.Draw(skill_bg) - - skill_icon = studentSkill2Icon[str(student_id)][s] - skill_path = SKILL_ICON_PATH / f'{skill_icon}.webp' - skill_pic = Image.open(skill_path).resize((36, 38)) - - skill = skill_data[s] if skill_data[s] != 10 else 'M' - skill_color = GREY if skill != 'M' else student_color - - skill_bg.paste(skill_pic, (25, 21), skill_pic) - skill_draw.text( - (70, 40), f'等级{skill}', skill_color, cf(30), 'lm' - ) - assist_card.paste(skill_bg, (312 + 172 * sindex, 96), skill_bg) - - assist_card.paste(student_color_pic, (66, 108), student_pic) - assist_card.paste(student_pic, (68, 96), student_pic) - assist_card.paste(student_star_pic, (228, 390), student_star_pic) - assist_draw.text((146, 364), student_name, GREY, cf(32), 'mm') - assist_draw.text( - (251, 361), str(favor_rank), 'white', cf(25), 'mm' - ) - assist_draw.text( - (151, 414), f'等级{student_level}', BLACK, cf(25), 'mm' - ) - - weapon_bg = Image.open(TEXT_PATH / 'weapon_bar.png') - if assist['weapon']: - weapon_draw = ImageDraw.Draw(weapon_bg) - - weapon_star = assist['weaponStartGrade'] - weapon_name = weaponId2Nmae[str(student_id)] - weapon_level = assist['weaponLevel'] - weapon_icon_id = studentId2weaponIcon[str(student_id)] - weapon_path = WEAPON_ICON_PATH / f'{weapon_icon_id}.webp' - weapon_icon = Image.open(weapon_path).resize((400, 102)) - - weapon_bg.paste(weapon_icon, (11, 42), weapon_icon) - weapon_draw.text( - (470, 70), f'等级{weapon_level}', BLACK, cf(25), 'mm' - ) - weapon_draw.text((486, 115), weapon_name, BLACK, cf(38), 'lm') - - for i in range(5): - if i < weapon_star: - star_pic = weapon_star_full - else: - star_pic = weapon_star_empty - weapon_bg.paste(star_pic, (227 + 29 * i, 110), star_pic) - - assist_card.paste(weapon_bg, (275, 147), weapon_bg) - - equip_fg = Image.open(TEXT_PATH / 'equip_fg.png') - for eindex, equip in enumerate(assist['equipment']): - equip_bg = Image.open(TEXT_PATH / 'equip_bg.png') - equip_id = equip['UniqueId'] - equip_icon = equipId2Icon[str(equip_id)] - equip_pic = Image.open( - EQUIPMENT_ICON_PATH / f'{equip_icon}.webp' - ) - equip_level = equip['Level'] - # equip_tier = equip['Tier'] - - equip_bg.paste(equip_pic, (2, 17), equip_pic) - equip_bg.paste(equip_fg, (0, 0), equip_fg) - equip_bg_draw = ImageDraw.Draw(equip_bg) - equip_bg_draw.text( - (75, 124), - f'等级{equip_level}', - 'white', - cf(24), - 'mm', - ) - assist_card.paste( - equip_bg, (304 + eindex * 131, 306), equip_bg - ) + assist_card = await draw_assist_card(assist) x = 80 if index >= 2 else 0 img.paste(assist_card, (0, 884 + 450 * index + x), assist_card) diff --git a/BlueArchiveUID/bauid_ranklist/__init__.py b/BlueArchiveUID/bauid_ranklist/__init__.py new file mode 100644 index 0000000..c598630 --- /dev/null +++ b/BlueArchiveUID/bauid_ranklist/__init__.py @@ -0,0 +1,12 @@ +from gsuid_core.sv import SV +from gsuid_core.bot import Bot +from gsuid_core.models import Event + +from .draw_rank_pic import draw_rank_pic + +sv_ba_xtzx_rank = SV('BA什亭之匣学生排行榜') + + +@sv_ba_xtzx_rank.on_command(('ba学生排行')) +async def send_rank_msg(bot: Bot, ev: Event): + await bot.send(await draw_rank_pic(ev.text.strip())) diff --git a/BlueArchiveUID/bauid_ranklist/draw_rank_pic.py b/BlueArchiveUID/bauid_ranklist/draw_rank_pic.py new file mode 100644 index 0000000..e98417f --- /dev/null +++ b/BlueArchiveUID/bauid_ranklist/draw_rank_pic.py @@ -0,0 +1,100 @@ +from pathlib import Path +from typing import Union + +from PIL import Image, ImageDraw +from gsuid_core.utils.image.convert import convert_img +from gsuid_core.utils.fonts.fonts import core_font as cf + +from ..utils.ba_api import xtzx_api +from ..utils.error_reply import get_error +from ..utils.ba_map import student_name_to_id +from ..bauid_info.draw_user_info_pic import BLACK, get_bg, draw_assist_card + +TEXT_PATH = Path(__file__).parent / 'texture2d' + + +def get_color(rank_key: int): + if rank_key < 10: + rank_color = (235, 126, 163) + elif rank_key < 80: + rank_color = (235, 177, 129) + elif rank_key < 200: + rank_color = (151, 129, 235) + else: + rank_color = (202, 235, 129) + return rank_color + + +async def draw_rank_pic(student: str) -> Union[bytes, str]: + student_id = student_name_to_id(student) + if student_id == '9999': + return '要查询的角色不存在或别名未收录, 请尝试使用完整名字。' + data = await xtzx_api.get_xtzx_friend_ranking(1, student_id) + if isinstance(data, int): + return get_error(data) + teacher_data = data['records'] + + img = get_bg(1100, 2800) + for index, teacher in enumerate(teacher_data): + info = teacher['assistInfoList'][0] + assist_card = await draw_assist_card(info) + rank_key = info['baRank']['key'] + rank_value = info['baRank']['value'] + rank_str = f'{rank_key} / {rank_value}' + + global_rank_key = info['baGlobalRank']['key'] + global_rank_value = info['baGlobalRank']['value'] + global_rank_str = f'{global_rank_key} / {global_rank_value}' + + rank_color = get_color(rank_key) + global_rank_color = get_color(global_rank_key) + + card = Image.open(TEXT_PATH / 'card.png') + card_draw = ImageDraw.Draw(card) + + card_draw.text( + (230, 52), + f'{teacher["nickname"]}', + BLACK, + cf(40), + 'lm', + ) + + if teacher['server'] == 1: + s_f = (136, 205, 242) + s_t = '官服' + else: + s_f = (243, 143, 225) + s_t = 'B服' + + card_draw.rounded_rectangle((50, 36, 124, 68), 30, s_f) + card_draw.text( + (87, 52), + s_t, + BLACK, + cf(24), + 'mm', + ) + + card_draw.rounded_rectangle((632, 36, 780, 68), 30, rank_color) + card_draw.rounded_rectangle((904, 36, 1052, 68), 30, global_rank_color) + card_draw.text( + (706, 52), + rank_str, + BLACK, + cf(24), + 'mm', + ) + card_draw.text( + (978, 52), + global_rank_str, + BLACK, + cf(24), + 'mm', + ) + card.paste(assist_card, (0, 40), assist_card) + + img.paste(card, (0, 28 + index * 550), card) + + img = await convert_img(img) + return img diff --git a/BlueArchiveUID/bauid_ranklist/texture2d/card.png b/BlueArchiveUID/bauid_ranklist/texture2d/card.png new file mode 100644 index 0000000..5820e93 Binary files /dev/null and b/BlueArchiveUID/bauid_ranklist/texture2d/card.png differ diff --git a/BlueArchiveUID/utils/api/api.py b/BlueArchiveUID/utils/api/api.py index 882b301..f552c25 100644 --- a/BlueArchiveUID/utils/api/api.py +++ b/BlueArchiveUID/utils/api/api.py @@ -16,4 +16,5 @@ XTZX_RAID_CHART = XTZX_API + '/raid/new/charts/{}?s={}' XTZX_FRIEND_DATA = XTZX_API + '/api/friends/find' XTZX_FRIEND_REFRESH = XTZX_API + '/api/friends/refresh' -XTZX_ASSIST = XTZX_API + 'api/friends/assist_query' +XTZX_ASSIST = XTZX_API + '/api/friends/assist_query' +XTZX_FRIEND_RANK = XTZX_API + '/api/friends/rank' diff --git a/BlueArchiveUID/utils/api/models.py b/BlueArchiveUID/utils/api/models.py index affc7b2..342953d 100644 --- a/BlueArchiveUID/utils/api/models.py +++ b/BlueArchiveUID/utils/api/models.py @@ -1,4 +1,4 @@ -from typing import List, TypedDict +from typing import Dict, List, TypedDict class FriendData(TypedDict): @@ -53,3 +53,54 @@ class Equipment(TypedDict): BoundCharacterServerId: int isNew: bool IsLocked: bool + + +class RankAssistInfo(TypedDict): + baRank: Dict[str, int] + baGlobalRank: Dict[str, int] + type: int + uniqueId: int + bulletType: str + tacticRole: str + echelonType: int + level: int + slotIndex: int + starGrade: int + favorRank: int + publicSkillLevel: int + exSkillLevel: int + passiveSkillLevel: int + extraPassiveSkillLevel: int + equipment: List[Equipment] + weapon: bool + weaponUniqueId: int + weaponType: int + weaponLevel: int + weaponStartGrade: int + + +class Record(TypedDict): + server: int + friendCode: str + friendCount: int + nickname: str + representCharacterUniqueId: int + clanName: str + comment: str + level: int + db: bool + lastHardCampaignClearStageId: int + lastNormalCampaignClearStageId: int + updateTime: int + maxFavorRank: int + echelonType: int + assistInfoList: List[RankAssistInfo] + + +class RankResp(TypedDict): + page: int + size: int + totalPages: int + totalData: int + records: List[Record] + lastPage: bool diff --git a/BlueArchiveUID/utils/api/request.py b/BlueArchiveUID/utils/api/request.py index 72025d9..40ec83e 100644 --- a/BlueArchiveUID/utils/api/request.py +++ b/BlueArchiveUID/utils/api/request.py @@ -3,8 +3,8 @@ from gsuid_core.logger import logger from aiohttp import FormData, TCPConnector, ClientSession, ContentTypeError -from .models import FriendData from ..ba_config import ba_config +from .models import RankResp, FriendData from .api import ( ARONA_URL, BATTLE_URL, @@ -13,6 +13,7 @@ XTZX_RAID_RANK, XTZX_RAID_CHART, XTZX_FRIEND_DATA, + XTZX_FRIEND_RANK, XTZX_RAID_CHART_PERSON, ) @@ -41,7 +42,7 @@ async def get_raid_ranking( async def _ba_request( self, url: str, - method: Literal["GET", "POST"] = "GET", + method: Literal['GET', 'POST'] = 'GET', header: Dict[str, str] = _HEADER, params: Optional[Dict[str, Any]] = None, json: Optional[Dict[str, Any]] = None, @@ -63,7 +64,7 @@ async def _ba_request( raw_data = await resp.json() except ContentTypeError: _raw_data = await resp.text() - raw_data = {"retcode": -999, "data": _raw_data} + raw_data = {'retcode': -999, 'data': _raw_data} logger.debug(raw_data) return raw_data @@ -97,8 +98,8 @@ async def get_xtzx_raid_chart_person( XTZX_RAID_CHART_PERSON, 'POST', json={ - "server": int(server_id), - "season": int(season), + 'server': int(server_id), + 'season': int(season), }, ) if ( @@ -186,8 +187,8 @@ async def get_xtzx_friend_data( XTZX_FRIEND_DATA, 'POST', json={ - "server": int(server_id), - "friend": friend_code, + 'server': int(server_id), + 'friend': friend_code, }, ) if isinstance(data, Dict) and 'code' in data: @@ -198,10 +199,31 @@ async def get_xtzx_friend_data( else: return -500 + async def get_xtzx_friend_ranking( + self, + page: int, + student_id: Union[str, int] = 10000, + ) -> Union[int, RankResp]: + data = await self._ba_request( + XTZX_FRIEND_RANK, + 'POST', + json={ + 'page': page, + 'studentId': student_id, + }, + ) + if isinstance(data, Dict) and 'code' in data: + if data['code'] == 200: + return cast(RankResp, data['data']) + else: + return data['code'] + else: + return -500 + async def _ba_request( self, url: str, - method: Literal["GET", "POST"] = "GET", + method: Literal['GET', 'POST'] = 'GET', header: Dict[str, str] = _HEADER, params: Optional[Dict[str, Any]] = None, json: Optional[Dict[str, Any]] = None, @@ -226,6 +248,6 @@ async def _ba_request( raw_data = await resp.json() except ContentTypeError: _raw_data = await resp.text() - raw_data = {"retcode": -999, "data": _raw_data} + raw_data = {'retcode': -999, 'data': _raw_data} logger.debug(raw_data) return raw_data diff --git a/BlueArchiveUID/utils/ba_map.py b/BlueArchiveUID/utils/ba_map.py index 18bd1f6..a160e78 100644 --- a/BlueArchiveUID/utils/ba_map.py +++ b/BlueArchiveUID/utils/ba_map.py @@ -2,6 +2,7 @@ from msgspec import json as msgjson +from .alias.name_convert import alias_to_char_name from ..tools.make_map import ( equipId2Icon_path, weaponId2Nmae_path, @@ -53,3 +54,12 @@ f.read(), type=Dict[str, Dict[str, str]], ) + + +def student_name_to_id(name: str) -> str: + name = alias_to_char_name(name) + for _id in studentId2Name: + if name in studentId2Name[_id]: + return _id + else: + return '9999'