新增虚构叙事和末日幻影查询 (#28)
* 新增虚构叙事和末日幻影查询 * 去除楼层相关参数,去除快速通关图片渲染 --------- Co-authored-by: 梦落 <mengluo@shuangsheng0513.cn>
@ -49,8 +49,10 @@ STAR_RAIL_AVATAR_INFO_URL_OS = (
|
|||||||
STAR_RAIL_AVATAR_LIST_URL = f'{OLD_URL}/event/rpgcalc/avatar/list'
|
STAR_RAIL_AVATAR_LIST_URL = f'{OLD_URL}/event/rpgcalc/avatar/list'
|
||||||
STAR_RAIL_AVATAR_DETAIL_URL = f'{OLD_URL}/event/rpgcalc/avatar/detail'
|
STAR_RAIL_AVATAR_DETAIL_URL = f'{OLD_URL}/event/rpgcalc/avatar/detail'
|
||||||
|
|
||||||
CHALLENGE_INFO_URL = f'{NEW_URL}/game_record/app/hkrpg/api/challenge'
|
CHALLENGE_INFO_URL = f'{NEW_URL}/game_record/app/hkrpg/api/challenge' # 忘却之庭
|
||||||
CHALLENGE_INFO_URL_OS = f'{OS_INFO_URL}/game_record/hkrpg/api/challenge'
|
CHALLENGE_INFO_URL_OS = f'{OS_INFO_URL}/game_record/hkrpg/api/challenge' # OS忘却之庭
|
||||||
|
CHALLENGE_STORY_INFO_URL = f'{NEW_URL}/game_record/app/hkrpg/api/challenge_story' # 虚构叙事
|
||||||
|
CHALLENGE_BOSS_INFO_URL = f'{NEW_URL}/game_record/app/hkrpg/api/challenge_boss' # 末日幻影
|
||||||
|
|
||||||
ROGUE_INFO_URL = (
|
ROGUE_INFO_URL = (
|
||||||
f'{NEW_URL}/game_record/app/hkrpg/api/rogue' # 角色模拟宇宙信息接口
|
f'{NEW_URL}/game_record/app/hkrpg/api/rogue' # 角色模拟宇宙信息接口
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Dict, List, Union
|
from typing import Any, Dict, List, Union, Optional
|
||||||
|
|
||||||
from msgspec import Struct
|
from msgspec import Struct
|
||||||
|
|
||||||
@ -254,16 +254,17 @@ class AbyssAvatar(Struct):
|
|||||||
|
|
||||||
|
|
||||||
class AbyssNodeDetail(Struct):
|
class AbyssNodeDetail(Struct):
|
||||||
challenge_time: AbyssTime
|
challenge_time: Union[AbyssTime, None]
|
||||||
avatars: List[AbyssAvatar]
|
avatars: List[AbyssAvatar]
|
||||||
|
|
||||||
|
|
||||||
class AbyssFloorDetail(Struct):
|
class AbyssFloorDetail(Struct):
|
||||||
name: str
|
name: str
|
||||||
round_num: int
|
star_num: Union[int, str]
|
||||||
star_num: int
|
|
||||||
node_1: AbyssNodeDetail
|
node_1: AbyssNodeDetail
|
||||||
node_2: AbyssNodeDetail
|
node_2: AbyssNodeDetail
|
||||||
|
round_num: Optional[int] = None
|
||||||
|
is_fast: Optional[bool] = False
|
||||||
|
|
||||||
|
|
||||||
class AbyssData(Struct):
|
class AbyssData(Struct):
|
||||||
@ -278,6 +279,26 @@ class AbyssData(Struct):
|
|||||||
max_floor_detail: Union[bool, None] = None
|
max_floor_detail: Union[bool, None] = None
|
||||||
|
|
||||||
|
|
||||||
|
class AbyssStoryData(Struct):
|
||||||
|
groups: Any
|
||||||
|
star_num: int
|
||||||
|
max_floor: str
|
||||||
|
battle_num: int
|
||||||
|
has_data: bool
|
||||||
|
all_floor_detail: List[AbyssFloorDetail]
|
||||||
|
max_floor_id: int
|
||||||
|
|
||||||
|
|
||||||
|
class AbyssBossData(Struct):
|
||||||
|
groups: Any
|
||||||
|
star_num: int
|
||||||
|
max_floor: str
|
||||||
|
battle_num: int
|
||||||
|
has_data: bool
|
||||||
|
all_floor_detail: List[AbyssFloorDetail]
|
||||||
|
max_floor_id: int
|
||||||
|
|
||||||
|
|
||||||
################
|
################
|
||||||
# 每月札记相关 #
|
# 每月札记相关 #
|
||||||
################
|
################
|
||||||
|
@ -1,68 +1,46 @@
|
|||||||
import re
|
import re
|
||||||
|
|
||||||
from gsuid_core.sv import SV
|
from gsuid_core.sv import SV
|
||||||
from gsuid_core.bot import Bot
|
from gsuid_core.bot import Bot
|
||||||
from gsuid_core.models import Event
|
from gsuid_core.models import Event
|
||||||
from gsuid_core.utils.error_reply import UID_HINT
|
from gsuid_core.utils.error_reply import UID_HINT
|
||||||
|
|
||||||
from ..utils.convert import get_uid
|
from ..utils.convert import get_uid
|
||||||
from ..utils.sr_prefix import PREFIX
|
from ..utils.sr_prefix import PREFIX
|
||||||
from .draw_abyss_card import draw_abyss_img
|
from .draw_abyss_card import draw_abyss_img
|
||||||
|
|
||||||
sv_srabyss = SV('sr查询深渊')
|
sv_srabyss = SV('sr查询深渊')
|
||||||
|
|
||||||
|
|
||||||
@sv_srabyss.on_command(
|
@sv_srabyss.on_command(
|
||||||
(
|
(
|
||||||
f'{PREFIX}查询深渊',
|
f'{PREFIX}查询深渊',
|
||||||
f'{PREFIX}sy',
|
f'{PREFIX}查询上期深渊',
|
||||||
f'{PREFIX}查询上期深渊',
|
f'{PREFIX}上期深渊',
|
||||||
f'{PREFIX}sqsy',
|
f'{PREFIX}深渊',
|
||||||
f'{PREFIX}上期深渊',
|
),
|
||||||
f'{PREFIX}深渊',
|
block=True,
|
||||||
),
|
)
|
||||||
block=True,
|
async def send_srabyss_info(bot: Bot, ev: Event):
|
||||||
)
|
name = ''.join(re.findall('[\u4e00-\u9fa5]', ev.text))
|
||||||
async def send_srabyss_info(bot: Bot, ev: Event):
|
if name:
|
||||||
name = ''.join(re.findall('[\u4e00-\u9fa5]', ev.text))
|
return None
|
||||||
if name:
|
|
||||||
return None
|
await bot.logger.info('开始执行[sr查询深渊信息]')
|
||||||
|
get_uid_ = await get_uid(bot, ev, True)
|
||||||
await bot.logger.info('开始执行[sr查询深渊信息]')
|
if get_uid_ is None:
|
||||||
get_uid_ = await get_uid(bot, ev, True)
|
return await bot.send(UID_HINT)
|
||||||
if get_uid_ is None:
|
uid, user_id = get_uid_
|
||||||
return await bot.send(UID_HINT)
|
if uid is None:
|
||||||
uid, user_id = get_uid_
|
return await bot.send(UID_HINT)
|
||||||
if uid is None:
|
await bot.logger.info(f'[sr查询深渊信息]uid: {uid}')
|
||||||
return await bot.send(UID_HINT)
|
|
||||||
await bot.logger.info(f'[sr查询深渊信息]uid: {uid}')
|
if '上期' in ev.command:
|
||||||
|
schedule_type = '2'
|
||||||
if 'sq' in ev.command or '上期' in ev.command:
|
else:
|
||||||
schedule_type = '2'
|
schedule_type = '1'
|
||||||
else:
|
await bot.logger.info(f'[sr查询深渊信息]深渊期数: {schedule_type}')
|
||||||
schedule_type = '1'
|
|
||||||
await bot.logger.info(f'[sr查询深渊信息]深渊期数: {schedule_type}')
|
im = await draw_abyss_img(user_id, uid, ev.sender, schedule_type)
|
||||||
|
await bot.send(im)
|
||||||
if ev.text in ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十']:
|
return None
|
||||||
floor = (
|
|
||||||
ev.text.replace('一', '1')
|
|
||||||
.replace('二', '2')
|
|
||||||
.replace('三', '3')
|
|
||||||
.replace('四', '4')
|
|
||||||
.replace('五', '5')
|
|
||||||
.replace('六', '6')
|
|
||||||
.replace('七', '7')
|
|
||||||
.replace('八', '8')
|
|
||||||
.replace('九', '9')
|
|
||||||
.replace('十', '10')
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
floor = ev.text
|
|
||||||
if floor and floor.isdigit():
|
|
||||||
floor = int(floor)
|
|
||||||
else:
|
|
||||||
floor = None
|
|
||||||
await bot.logger.info(f'[sr查询深渊信息]深渊层数: {floor}')
|
|
||||||
im = await draw_abyss_img(user_id, uid, ev.sender, floor, schedule_type)
|
|
||||||
await bot.send(im)
|
|
||||||
return None
|
|
||||||
|
@ -21,19 +21,6 @@ from ..utils.fonts.starrail_fonts import (
|
|||||||
sr_font_42,
|
sr_font_42,
|
||||||
)
|
)
|
||||||
|
|
||||||
abyss_list = {
|
|
||||||
'1': '一',
|
|
||||||
'2': '二',
|
|
||||||
'3': '三',
|
|
||||||
'4': '四',
|
|
||||||
'5': '五',
|
|
||||||
'6': '六',
|
|
||||||
'7': '七',
|
|
||||||
'8': '八',
|
|
||||||
'9': '九',
|
|
||||||
'10': '十',
|
|
||||||
}
|
|
||||||
|
|
||||||
TEXT_PATH = Path(__file__).parent / 'texture2D'
|
TEXT_PATH = Path(__file__).parent / 'texture2D'
|
||||||
white_color = (255, 255, 255)
|
white_color = (255, 255, 255)
|
||||||
gray_color = (175, 175, 175)
|
gray_color = (175, 175, 175)
|
||||||
@ -70,14 +57,10 @@ async def get_abyss_star_pic(star: int) -> Image.Image:
|
|||||||
|
|
||||||
async def _draw_abyss_card(
|
async def _draw_abyss_card(
|
||||||
char: AbyssAvatar,
|
char: AbyssAvatar,
|
||||||
talent_num: str,
|
|
||||||
floor_pic: Image.Image,
|
floor_pic: Image.Image,
|
||||||
index_char: int,
|
index_char: int,
|
||||||
index_part: int,
|
index_part: int,
|
||||||
):
|
):
|
||||||
# char_id = char['id']
|
|
||||||
# # 确认角色头像路径
|
|
||||||
# char_pic_path = CHAR_ICON_PATH / f'{char_id}.png'
|
|
||||||
char_bg = (char_bg_4 if char.rarity == 4 else char_bg_5).copy()
|
char_bg = (char_bg_4 if char.rarity == 4 else char_bg_5).copy()
|
||||||
char_icon = (await get_icon(char.icon)).resize((150, 170))
|
char_icon = (await get_icon(char.icon)).resize((150, 170))
|
||||||
element_icon = elements[char.element]
|
element_icon = elements[char.element]
|
||||||
@ -94,12 +77,6 @@ async def _draw_abyss_card(
|
|||||||
fill=white_color,
|
fill=white_color,
|
||||||
anchor='mm',
|
anchor='mm',
|
||||||
)
|
)
|
||||||
# 不存在自动下载
|
|
||||||
# if not char_pic_path.exists():
|
|
||||||
# await create_single_char_card(char_id)
|
|
||||||
# talent_pic = await get_talent_pic(int(talent_num))
|
|
||||||
# talent_pic = talent_pic.resize((90, 45))
|
|
||||||
# char_card.paste(talent_pic, (137, 260), talent_pic)
|
|
||||||
char_card_draw.text(
|
char_card_draw.text(
|
||||||
(100, 165),
|
(100, 165),
|
||||||
f'等级 {char.level}',
|
f'等级 {char.level}',
|
||||||
@ -151,32 +128,23 @@ async def draw_abyss_img(
|
|||||||
qid: Union[str, int],
|
qid: Union[str, int],
|
||||||
uid: str,
|
uid: str,
|
||||||
sender: Union[str, str],
|
sender: Union[str, str],
|
||||||
floor: Optional[int] = None,
|
|
||||||
schedule_type: str = '1',
|
schedule_type: str = '1',
|
||||||
) -> Union[bytes, str]:
|
) -> Union[bytes, str]:
|
||||||
raw_abyss_data = await mys_api.get_srspiral_abyss_info(uid, schedule_type)
|
raw_abyss_data = await mys_api.get_abyss_info(uid, schedule_type)
|
||||||
|
|
||||||
if isinstance(raw_abyss_data, int):
|
if isinstance(raw_abyss_data, int):
|
||||||
return get_error(raw_abyss_data)
|
return get_error(raw_abyss_data)
|
||||||
|
|
||||||
# 获取查询者数据
|
# 获取查询者数据
|
||||||
if floor:
|
if raw_abyss_data.max_floor == '':
|
||||||
floor_num = 1
|
return '你还没有挑战本期深渊!\n可以使用[sr上期深渊]命令查询上期~'
|
||||||
if floor > 12:
|
# 过滤掉 is_fast(快速通关) 为 True 的项
|
||||||
return '楼层不能大于12层!'
|
floor_detail = [detail for detail in raw_abyss_data.all_floor_detail if not detail.is_fast]
|
||||||
if len(raw_abyss_data.all_floor_detail) < floor:
|
floor_num = len(floor_detail)
|
||||||
return '你还没有挑战该层!'
|
|
||||||
else:
|
|
||||||
if raw_abyss_data.max_floor == '':
|
|
||||||
return '你还没有挑战本期深渊!\n可以使用[sr上期深渊]命令查询上期~'
|
|
||||||
floor_num = len(raw_abyss_data.all_floor_detail)
|
|
||||||
|
|
||||||
# 获取背景图片各项参数
|
# 获取背景图片各项参数
|
||||||
based_w = 900
|
based_w = 900
|
||||||
if floor_num >= 3:
|
based_h = 657 + 570 * floor_num
|
||||||
based_h = 2367
|
|
||||||
else:
|
|
||||||
based_h = 657 + 570 * floor_num
|
|
||||||
img = img_bg.copy()
|
img = img_bg.copy()
|
||||||
img = img.crop((0, 0, based_w, based_h))
|
img = img.crop((0, 0, based_w, based_h))
|
||||||
abyss_title = Image.open(TEXT_PATH / 'head.png')
|
abyss_title = Image.open(TEXT_PATH / 'head.png')
|
||||||
@ -231,13 +199,6 @@ async def draw_abyss_img(
|
|||||||
)
|
)
|
||||||
|
|
||||||
for index_floor, level in enumerate(raw_abyss_data.all_floor_detail):
|
for index_floor, level in enumerate(raw_abyss_data.all_floor_detail):
|
||||||
if floor:
|
|
||||||
if abyss_list[str(floor)] == level.name.split('其')[1]:
|
|
||||||
index_floor = 0 # noqa: PLW2901
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
elif index_floor >= 3:
|
|
||||||
break
|
|
||||||
floor_pic = Image.open(TEXT_PATH / 'floor_bg.png')
|
floor_pic = Image.open(TEXT_PATH / 'floor_bg.png')
|
||||||
level_star = level.star_num
|
level_star = level.star_num
|
||||||
floor_name = level.name
|
floor_name = level.name
|
||||||
@ -276,7 +237,6 @@ async def draw_abyss_img(
|
|||||||
for index_char, char in enumerate(avatars_array.avatars):
|
for index_char, char in enumerate(avatars_array.avatars):
|
||||||
await _draw_abyss_card(
|
await _draw_abyss_card(
|
||||||
char,
|
char,
|
||||||
0, # type: ignore
|
|
||||||
floor_pic,
|
floor_pic,
|
||||||
index_char,
|
index_char,
|
||||||
index_part,
|
index_part,
|
||||||
@ -290,114 +250,6 @@ async def draw_abyss_img(
|
|||||||
round_num,
|
round_num,
|
||||||
)
|
)
|
||||||
|
|
||||||
# title_data = {
|
|
||||||
# '最强一击!': damage_rank[0],
|
|
||||||
# '最多击破!': defeat_rank[0],
|
|
||||||
# '承受伤害': take_damage_rank[0],
|
|
||||||
# '元素战技': energy_skill_rank[0],
|
|
||||||
# }
|
|
||||||
# for _index, _name in enumerate(title_data):
|
|
||||||
# _char = title_data[_name]
|
|
||||||
# _char_id = _char['avatar_id']
|
|
||||||
# char_side_path = TEXT_PATH / f'{_char_id}.png'
|
|
||||||
# # if not char_side_path.exists():
|
|
||||||
# # await download_file(_char['avatar_icon'], 3, f'{_char_id}.png')
|
|
||||||
# char_side = Image.open(char_side_path)
|
|
||||||
# char_side = char_side.resize((75, 75))
|
|
||||||
# intent = _index * 224
|
|
||||||
# title_xy = (115 + intent, 523)
|
|
||||||
# val_xy = (115 + intent, 545)
|
|
||||||
# _val = str(_char['value'])
|
|
||||||
# img.paste(char_side, (43 + intent, 484), char_side)
|
|
||||||
# img_draw.text(title_xy, _name, white_color, gs_font_20, 'lm')
|
|
||||||
# img_draw.text(val_xy, _val, white_color, gs_font_26, 'lm')
|
|
||||||
|
|
||||||
# 过滤数据
|
|
||||||
# raw_abyss_data['floors'] = [
|
|
||||||
# i for i in raw_abyss_data['floors'] if i['index'] >= 9
|
|
||||||
# ]
|
|
||||||
|
|
||||||
# 绘制缩略信息
|
|
||||||
# for num in range(4):
|
|
||||||
# omit_bg = Image.open(TEXT_PATH / 'abyss_omit.png')
|
|
||||||
# omit_draw = ImageDraw.Draw(omit_bg)
|
|
||||||
# omit_draw.text((56, 34), f'第{num+9}层', white_color, gs_font_32, 'lm')
|
|
||||||
# omit_draw.rounded_rectangle((165, 19, 225, 49), 20, red_color)
|
|
||||||
# if len(raw_abyss_data['floors']) - 1 >= num:
|
|
||||||
# _floor = raw_abyss_data['floors'][num]
|
|
||||||
# if _floor['star'] == _floor['max_star']:
|
|
||||||
# _color = red_color
|
|
||||||
# _text = '全满星'
|
|
||||||
# else:
|
|
||||||
# _gap = _floor['max_star'] - _floor['star']
|
|
||||||
# _color = blue_color
|
|
||||||
# _text = f'差{_gap}颗'
|
|
||||||
# if not is_unfull:
|
|
||||||
# _timestamp = int(
|
|
||||||
# _floor['levels'][-1]['battles'][-1]['timestamp']
|
|
||||||
# )
|
|
||||||
# _time_array = time.localtime(_timestamp)
|
|
||||||
# _time_str = time.strftime('%Y-%m-%d %H:%M:%S', _time_array)
|
|
||||||
# else:
|
|
||||||
# _time_str = '请挑战后查看时间数据!'
|
|
||||||
# else:
|
|
||||||
# _color = gray_color
|
|
||||||
# _text = '未解锁'
|
|
||||||
# _time_str = '请挑战后查看时间数据!'
|
|
||||||
# omit_draw.rounded_rectangle((165, 19, 255, 49), 20, _color)
|
|
||||||
# omit_draw.text((210, 34), _text, white_color, gs_font_26, 'mm')
|
|
||||||
# omit_draw.text((54, 65), _time_str, sec_color, gs_font_22, 'lm')
|
|
||||||
# pos = (20 + 459 * (num % 2), 613 + 106 * (num // 2))
|
|
||||||
# img.paste(omit_bg, pos, omit_bg)
|
|
||||||
|
|
||||||
# if is_unfull:
|
|
||||||
# hint = Image.open(TEXT_PATH / 'hint.png')
|
|
||||||
# img.paste(hint, (0, 830), hint)
|
|
||||||
# else:
|
|
||||||
# task = []
|
|
||||||
# floor_num = floors_data['index']
|
|
||||||
# for index_floor, level in enumerate(floors_data['levels']):
|
|
||||||
# floor_pic = Image.open(TEXT_PATH / 'abyss_floor.png')
|
|
||||||
# level_star = level['star']
|
|
||||||
# timestamp = int(level['battles'][0]['timestamp'])
|
|
||||||
# time_array = time.localtime(timestamp)
|
|
||||||
# time_str = time.strftime('%Y-%m-%d %H:%M:%S', time_array)
|
|
||||||
# for index_part, battle in enumerate(level['battles']):
|
|
||||||
# for index_char, char in enumerate(battle['avatars']):
|
|
||||||
# # 获取命座
|
|
||||||
# if char["id"] in char_temp:
|
|
||||||
# talent_num = char_temp[char["id"]]
|
|
||||||
# else:
|
|
||||||
# for i in char_data:
|
|
||||||
# if i["id"] == char["id"]:
|
|
||||||
# talent_num = str(
|
|
||||||
# i["actived_constellation_num"]
|
|
||||||
# )
|
|
||||||
# char_temp[char["id"]] = talent_num
|
|
||||||
# break
|
|
||||||
# task.append(
|
|
||||||
# _draw_abyss_card(
|
|
||||||
# char,
|
|
||||||
# talent_num, # type: ignore
|
|
||||||
# floor_pic,
|
|
||||||
# index_char,
|
|
||||||
# index_part,
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
# await asyncio.gather(*task)
|
|
||||||
# task.clear()
|
|
||||||
# task.append(
|
|
||||||
# _draw_floor_card(
|
|
||||||
# level_star,
|
|
||||||
# floor_pic,
|
|
||||||
# img,
|
|
||||||
# time_str,
|
|
||||||
# index_floor,
|
|
||||||
# floor_num,
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
# await asyncio.gather(*task)
|
|
||||||
|
|
||||||
res = await convert_img(img)
|
res = await convert_img(img)
|
||||||
logger.info('[查询深渊信息]绘图已完成,等待发送!')
|
logger.info('[查询深渊信息]绘图已完成,等待发送!')
|
||||||
return res
|
return res
|
||||||
|
46
StarRailUID/starrailuid_abyss_boss/__init__.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from gsuid_core.sv import SV
|
||||||
|
from gsuid_core.bot import Bot
|
||||||
|
from gsuid_core.models import Event
|
||||||
|
from gsuid_core.utils.error_reply import UID_HINT
|
||||||
|
|
||||||
|
from ..utils.convert import get_uid
|
||||||
|
from ..utils.sr_prefix import PREFIX
|
||||||
|
from .draw_abyss_card import draw_abyss_img
|
||||||
|
|
||||||
|
sv_abyss_boss = SV('sr查询末日幻影')
|
||||||
|
|
||||||
|
|
||||||
|
@sv_abyss_boss.on_command(
|
||||||
|
(
|
||||||
|
f'{PREFIX}查询末日幻影',
|
||||||
|
f'{PREFIX}查询上期末日幻影',
|
||||||
|
f'{PREFIX}上期末日',
|
||||||
|
f'{PREFIX}末日',
|
||||||
|
),
|
||||||
|
block=True,
|
||||||
|
)
|
||||||
|
async def send_srabyss_info(bot: Bot, ev: Event):
|
||||||
|
name = ''.join(re.findall('[\u4e00-\u9fa5]', ev.text))
|
||||||
|
if name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
await bot.logger.info('开始执行[sr查询末日幻影信息]')
|
||||||
|
get_uid_ = await get_uid(bot, ev, True)
|
||||||
|
if get_uid_ is None:
|
||||||
|
return await bot.send(UID_HINT)
|
||||||
|
uid, user_id = get_uid_
|
||||||
|
if uid is None:
|
||||||
|
return await bot.send(UID_HINT)
|
||||||
|
await bot.logger.info(f'[sr查询末日幻影信息]uid: {uid}')
|
||||||
|
|
||||||
|
if '上期' in ev.command:
|
||||||
|
schedule_type = '2'
|
||||||
|
else:
|
||||||
|
schedule_type = '1'
|
||||||
|
await bot.logger.info(f'[sr查询末日幻影信息]末日幻影期数: {schedule_type}')
|
||||||
|
|
||||||
|
im = await draw_abyss_img(user_id, uid, ev.sender, schedule_type)
|
||||||
|
await bot.send(im)
|
||||||
|
return None
|
248
StarRailUID/starrailuid_abyss_boss/draw_abyss_card.py
Normal file
@ -0,0 +1,248 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Union, Optional
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
from gsuid_core.logger import logger
|
||||||
|
from gsuid_core.utils.error_reply import get_error
|
||||||
|
from gsuid_core.utils.image.image_tools import (
|
||||||
|
get_qq_avatar,
|
||||||
|
draw_pic_with_ring,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .utils import get_icon
|
||||||
|
from ..utils.mys_api import mys_api
|
||||||
|
from ..utils.image.convert import convert_img
|
||||||
|
from ..sruid_utils.api.mys.models import AbyssAvatar
|
||||||
|
from ..utils.fonts.starrail_fonts import (
|
||||||
|
sr_font_22,
|
||||||
|
sr_font_28,
|
||||||
|
sr_font_30,
|
||||||
|
sr_font_34,
|
||||||
|
sr_font_42,
|
||||||
|
)
|
||||||
|
|
||||||
|
TEXT_PATH = Path(__file__).parent / 'texture2D'
|
||||||
|
white_color = (255, 255, 255)
|
||||||
|
gray_color = (175, 175, 175)
|
||||||
|
img_bg = Image.open(TEXT_PATH / 'bg.jpg')
|
||||||
|
level_cover = Image.open(TEXT_PATH / 'level_cover.png').convert('RGBA')
|
||||||
|
char_bg_4 = Image.open(TEXT_PATH / 'char4_bg.png').convert('RGBA')
|
||||||
|
char_bg_5 = Image.open(TEXT_PATH / 'char5_bg.png').convert('RGBA')
|
||||||
|
rank_bg = Image.open(TEXT_PATH / 'rank_bg.png').convert('RGBA')
|
||||||
|
star_yes = Image.open(TEXT_PATH / 'star.png').convert('RGBA')
|
||||||
|
star_gray = Image.open(TEXT_PATH / 'star_gray.png').convert('RGBA')
|
||||||
|
|
||||||
|
elements = {
|
||||||
|
'ice': Image.open(TEXT_PATH / 'IconNatureColorIce.png').convert('RGBA'),
|
||||||
|
'fire': Image.open(TEXT_PATH / 'IconNatureColorFire.png').convert('RGBA'),
|
||||||
|
'imaginary': Image.open(
|
||||||
|
TEXT_PATH / 'IconNatureColorImaginary.png'
|
||||||
|
).convert('RGBA'),
|
||||||
|
'quantum': Image.open(TEXT_PATH / 'IconNatureColorQuantum.png').convert(
|
||||||
|
'RGBA'
|
||||||
|
),
|
||||||
|
'lightning': Image.open(TEXT_PATH / 'IconNatureColorThunder.png').convert(
|
||||||
|
'RGBA'
|
||||||
|
),
|
||||||
|
'wind': Image.open(TEXT_PATH / 'IconNatureColorWind.png').convert('RGBA'),
|
||||||
|
'physical': Image.open(TEXT_PATH / 'IconNaturePhysical.png').convert(
|
||||||
|
'RGBA'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def get_abyss_star_pic(star: int) -> Image.Image:
|
||||||
|
return Image.open(TEXT_PATH / f'star{star}.png')
|
||||||
|
|
||||||
|
|
||||||
|
async def _draw_abyss_card(
|
||||||
|
char: AbyssAvatar,
|
||||||
|
floor_pic: Image.Image,
|
||||||
|
index_char: int,
|
||||||
|
index_part: int,
|
||||||
|
):
|
||||||
|
# char_id = char['id']
|
||||||
|
# # 确认角色头像路径
|
||||||
|
# char_pic_path = CHAR_ICON_PATH / f'{char_id}.png'
|
||||||
|
char_bg = (char_bg_4 if char.rarity == 4 else char_bg_5).copy()
|
||||||
|
char_icon = (await get_icon(char.icon)).resize((150, 170))
|
||||||
|
element_icon = elements[char.element]
|
||||||
|
char_bg.paste(char_icon, (24, 16), mask=char_icon)
|
||||||
|
char_bg.paste(level_cover, (0, 0), mask=level_cover)
|
||||||
|
char_bg.paste(element_icon, (35, 25), mask=element_icon)
|
||||||
|
char_card_draw = ImageDraw.Draw(char_bg)
|
||||||
|
if char.rank > 0:
|
||||||
|
char_bg.paste(rank_bg, (150, 16), mask=rank_bg)
|
||||||
|
char_card_draw.text(
|
||||||
|
(162, 31),
|
||||||
|
f'{char.rank}',
|
||||||
|
font=sr_font_22,
|
||||||
|
fill=white_color,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
char_card_draw.text(
|
||||||
|
(100, 165),
|
||||||
|
f'等级 {char.level}',
|
||||||
|
font=sr_font_22,
|
||||||
|
fill=white_color,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
floor_pic.paste(
|
||||||
|
char_bg,
|
||||||
|
(75 + 185 * index_char, 130 + index_part * 219),
|
||||||
|
char_bg,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _draw_floor_card(
|
||||||
|
level_star: int,
|
||||||
|
floor_pic: Image.Image,
|
||||||
|
img: Image.Image,
|
||||||
|
index_floor: int,
|
||||||
|
floor_name: str,
|
||||||
|
):
|
||||||
|
for index_num in [0, 1, 2]:
|
||||||
|
star_num = index_num + 1
|
||||||
|
if star_num <= level_star:
|
||||||
|
star_pic = star_yes.copy()
|
||||||
|
else:
|
||||||
|
star_pic = star_gray.copy()
|
||||||
|
floor_pic.paste(star_pic, (103 + index_num * 50, 25), star_pic)
|
||||||
|
floor_pic_draw = ImageDraw.Draw(floor_pic)
|
||||||
|
floor_pic_draw.text(
|
||||||
|
(450, 60),
|
||||||
|
floor_name,
|
||||||
|
font=sr_font_42,
|
||||||
|
fill=white_color,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
img.paste(floor_pic, (0, 657 + index_floor * 570), floor_pic)
|
||||||
|
|
||||||
|
|
||||||
|
async def draw_abyss_img(
|
||||||
|
qid: Union[str, int],
|
||||||
|
uid: str,
|
||||||
|
sender: Union[str, str],
|
||||||
|
schedule_type: str = '1',
|
||||||
|
) -> Union[bytes, str]:
|
||||||
|
raw_abyss_data = await mys_api.get_abyss_boss_info(uid, schedule_type)
|
||||||
|
if isinstance(raw_abyss_data, int):
|
||||||
|
return get_error(raw_abyss_data)
|
||||||
|
|
||||||
|
# 获取查询者数据
|
||||||
|
if raw_abyss_data.max_floor == '':
|
||||||
|
return '你还没有挑战本期末日幻影!\n可以使用[sr上期末日幻影]命令查询上期~'
|
||||||
|
# 过滤掉 is_fast(快速通关) 为 True 的项
|
||||||
|
floor_detail = [detail for detail in raw_abyss_data.all_floor_detail if not detail.is_fast]
|
||||||
|
floor_num = len(floor_detail)
|
||||||
|
|
||||||
|
# 获取背景图片各项参数
|
||||||
|
based_w = 900
|
||||||
|
based_h = 657 + 570 * floor_num
|
||||||
|
img = img_bg.copy()
|
||||||
|
img = img.crop((0, 0, based_w, based_h))
|
||||||
|
abyss_title = Image.open(TEXT_PATH / 'head.png')
|
||||||
|
img.paste(abyss_title, (0, 0), abyss_title)
|
||||||
|
|
||||||
|
# 获取头像
|
||||||
|
_id = str(qid)
|
||||||
|
if _id.startswith('http'):
|
||||||
|
char_pic = await get_qq_avatar(avatar_url=_id)
|
||||||
|
elif sender.get('avatar') is not None:
|
||||||
|
char_pic = await get_qq_avatar(avatar_url=sender['avatar'])
|
||||||
|
else:
|
||||||
|
char_pic = await get_qq_avatar(qid=qid)
|
||||||
|
char_pic = await draw_pic_with_ring(char_pic, 250, None, False)
|
||||||
|
|
||||||
|
img.paste(char_pic, (325, 132), char_pic)
|
||||||
|
|
||||||
|
# 绘制抬头
|
||||||
|
img_draw = ImageDraw.Draw(img)
|
||||||
|
img_draw.text((450, 442), f'UID {uid}', white_color, sr_font_28, 'mm')
|
||||||
|
|
||||||
|
# 总体数据
|
||||||
|
abyss_data = Image.open(TEXT_PATH / 'data.png')
|
||||||
|
img.paste(abyss_data, (0, 500), abyss_data)
|
||||||
|
|
||||||
|
# 最深抵达
|
||||||
|
img_draw.text(
|
||||||
|
(220, 565),
|
||||||
|
f'{raw_abyss_data.max_floor}',
|
||||||
|
white_color,
|
||||||
|
sr_font_34,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
# 挑战次数
|
||||||
|
img_draw.text(
|
||||||
|
(220, 612),
|
||||||
|
f'{raw_abyss_data.battle_num}',
|
||||||
|
white_color,
|
||||||
|
sr_font_34,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
|
||||||
|
star_num_pic = Image.open(TEXT_PATH / 'star.png')
|
||||||
|
img.paste(star_num_pic, (615, 557), star_num_pic)
|
||||||
|
|
||||||
|
img_draw.text(
|
||||||
|
(695, 590),
|
||||||
|
f'{raw_abyss_data.star_num}/12',
|
||||||
|
white_color,
|
||||||
|
sr_font_42,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
|
||||||
|
for index_floor, level in enumerate(floor_detail):
|
||||||
|
floor_pic = Image.open(TEXT_PATH / 'floor_bg.png')
|
||||||
|
level_star = int(level.star_num)
|
||||||
|
floor_name = level.name
|
||||||
|
node_1 = level.node_1
|
||||||
|
node_2 = level.node_2
|
||||||
|
for index_part in [0, 1]:
|
||||||
|
node_num = index_part + 1
|
||||||
|
if node_num == 1:
|
||||||
|
time_array = node_1.challenge_time
|
||||||
|
else:
|
||||||
|
time_array = node_2.challenge_time
|
||||||
|
time_str = f'{time_array.year}-{time_array.month}'
|
||||||
|
time_str = f'{time_str}-{time_array.day}'
|
||||||
|
time_str = f'{time_str} {time_array.hour}:{time_array.minute}:00'
|
||||||
|
floor_pic_draw = ImageDraw.Draw(floor_pic)
|
||||||
|
floor_pic_draw.text(
|
||||||
|
(112, 120 + index_part * 219),
|
||||||
|
f'节点{node_num}',
|
||||||
|
white_color,
|
||||||
|
sr_font_30,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
floor_pic_draw.text(
|
||||||
|
(201, 120 + index_part * 219),
|
||||||
|
f'{time_str}',
|
||||||
|
gray_color,
|
||||||
|
sr_font_22,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
if node_num == 1:
|
||||||
|
avatars_array = node_1
|
||||||
|
else:
|
||||||
|
avatars_array = node_2
|
||||||
|
|
||||||
|
for index_char, char in enumerate(avatars_array.avatars):
|
||||||
|
await _draw_abyss_card(
|
||||||
|
char,
|
||||||
|
floor_pic,
|
||||||
|
index_char,
|
||||||
|
index_part,
|
||||||
|
)
|
||||||
|
await _draw_floor_card(
|
||||||
|
level_star,
|
||||||
|
floor_pic,
|
||||||
|
img,
|
||||||
|
index_floor,
|
||||||
|
floor_name,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
||||||
|
res = await convert_img(img)
|
||||||
|
logger.info('[查询末日幻影信息]绘图已完成,等待发送!')
|
||||||
|
return res
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/bg.jpg
Normal file
After Width: | Height: | Size: 396 KiB |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/char4_bg.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/char5_bg.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/data.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/floor_bg.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/head.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/level_cover.png
Normal file
After Width: | Height: | Size: 381 B |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/rank_bg.png
Normal file
After Width: | Height: | Size: 953 B |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/star.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
StarRailUID/starrailuid_abyss_boss/texture2D/star_gray.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
26
StarRailUID/starrailuid_abyss_boss/utils.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
from gsuid_core.data_store import get_res_path
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
ROLEINFO_PATH = get_res_path() / 'StarRailUID' / 'roleinfo'
|
||||||
|
ROLEINFO_PATH.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_icon(url: str) -> Image.Image:
|
||||||
|
name = url.split('/')[-1]
|
||||||
|
path = ROLEINFO_PATH / name
|
||||||
|
if (path).exists():
|
||||||
|
content = path.read_bytes()
|
||||||
|
else:
|
||||||
|
async with ClientSession() as client:
|
||||||
|
async with client.get(url) as resp:
|
||||||
|
content = await resp.read()
|
||||||
|
with Path.open(path, 'wb') as f:
|
||||||
|
f.write(content)
|
||||||
|
return Image.open(BytesIO(content)).convert('RGBA')
|
48
StarRailUID/starrailuid_abyss_story/__init__.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
from gsuid_core.sv import SV
|
||||||
|
from gsuid_core.bot import Bot
|
||||||
|
from gsuid_core.models import Event
|
||||||
|
from gsuid_core.utils.error_reply import UID_HINT
|
||||||
|
|
||||||
|
from ..utils.convert import get_uid
|
||||||
|
from ..utils.sr_prefix import PREFIX
|
||||||
|
from .draw_abyss_card import draw_abyss_img
|
||||||
|
|
||||||
|
sv_abyss_story = SV('sr查询虚构叙事')
|
||||||
|
|
||||||
|
|
||||||
|
@sv_abyss_story.on_command(
|
||||||
|
(
|
||||||
|
f'{PREFIX}查询虚构叙事',
|
||||||
|
f'{PREFIX}xg',
|
||||||
|
f'{PREFIX}查询上期虚构叙事',
|
||||||
|
f'{PREFIX}sqxg',
|
||||||
|
f'{PREFIX}上期虚构',
|
||||||
|
f'{PREFIX}虚构',
|
||||||
|
),
|
||||||
|
block=True,
|
||||||
|
)
|
||||||
|
async def send_srabyss_info(bot: Bot, ev: Event):
|
||||||
|
name = ''.join(re.findall('[\u4e00-\u9fa5]', ev.text))
|
||||||
|
if name:
|
||||||
|
return None
|
||||||
|
|
||||||
|
await bot.logger.info('开始执行[sr查询虚构叙事信息]')
|
||||||
|
get_uid_ = await get_uid(bot, ev, True)
|
||||||
|
if get_uid_ is None:
|
||||||
|
return await bot.send(UID_HINT)
|
||||||
|
uid, user_id = get_uid_
|
||||||
|
if uid is None:
|
||||||
|
return await bot.send(UID_HINT)
|
||||||
|
await bot.logger.info(f'[sr查询虚构叙事信息]uid: {uid}')
|
||||||
|
|
||||||
|
if 'sq' in ev.command or '上期' in ev.command:
|
||||||
|
schedule_type = '2'
|
||||||
|
else:
|
||||||
|
schedule_type = '1'
|
||||||
|
await bot.logger.info(f'[sr查询虚构叙事信息]虚构叙事期数: {schedule_type}')
|
||||||
|
|
||||||
|
im = await draw_abyss_img(user_id, uid, ev.sender, schedule_type)
|
||||||
|
await bot.send(im)
|
||||||
|
return None
|
265
StarRailUID/starrailuid_abyss_story/draw_abyss_card.py
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from typing import Union, Optional
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
from gsuid_core.logger import logger
|
||||||
|
from gsuid_core.utils.error_reply import get_error
|
||||||
|
from gsuid_core.utils.image.image_tools import (
|
||||||
|
get_qq_avatar,
|
||||||
|
draw_pic_with_ring,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .utils import get_icon
|
||||||
|
from ..utils.mys_api import mys_api
|
||||||
|
from ..utils.image.convert import convert_img
|
||||||
|
from ..sruid_utils.api.mys.models import AbyssAvatar
|
||||||
|
from ..utils.fonts.starrail_fonts import (
|
||||||
|
sr_font_22,
|
||||||
|
sr_font_28,
|
||||||
|
sr_font_30,
|
||||||
|
sr_font_34,
|
||||||
|
sr_font_42,
|
||||||
|
)
|
||||||
|
|
||||||
|
TEXT_PATH = Path(__file__).parent / 'texture2D'
|
||||||
|
white_color = (255, 255, 255)
|
||||||
|
gray_color = (175, 175, 175)
|
||||||
|
img_bg = Image.open(TEXT_PATH / 'bg.jpg')
|
||||||
|
level_cover = Image.open(TEXT_PATH / 'level_cover.png').convert('RGBA')
|
||||||
|
char_bg_4 = Image.open(TEXT_PATH / 'char4_bg.png').convert('RGBA')
|
||||||
|
char_bg_5 = Image.open(TEXT_PATH / 'char5_bg.png').convert('RGBA')
|
||||||
|
rank_bg = Image.open(TEXT_PATH / 'rank_bg.png').convert('RGBA')
|
||||||
|
star_yes = Image.open(TEXT_PATH / 'star.png').convert('RGBA')
|
||||||
|
star_gray = Image.open(TEXT_PATH / 'star_gray.png').convert('RGBA')
|
||||||
|
|
||||||
|
elements = {
|
||||||
|
'ice': Image.open(TEXT_PATH / 'IconNatureColorIce.png').convert('RGBA'),
|
||||||
|
'fire': Image.open(TEXT_PATH / 'IconNatureColorFire.png').convert('RGBA'),
|
||||||
|
'imaginary': Image.open(
|
||||||
|
TEXT_PATH / 'IconNatureColorImaginary.png'
|
||||||
|
).convert('RGBA'),
|
||||||
|
'quantum': Image.open(TEXT_PATH / 'IconNatureColorQuantum.png').convert(
|
||||||
|
'RGBA'
|
||||||
|
),
|
||||||
|
'lightning': Image.open(TEXT_PATH / 'IconNatureColorThunder.png').convert(
|
||||||
|
'RGBA'
|
||||||
|
),
|
||||||
|
'wind': Image.open(TEXT_PATH / 'IconNatureColorWind.png').convert('RGBA'),
|
||||||
|
'physical': Image.open(TEXT_PATH / 'IconNaturePhysical.png').convert(
|
||||||
|
'RGBA'
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def get_abyss_star_pic(star: int) -> Image.Image:
|
||||||
|
return Image.open(TEXT_PATH / f'star{star}.png')
|
||||||
|
|
||||||
|
|
||||||
|
async def _draw_abyss_card(
|
||||||
|
char: AbyssAvatar,
|
||||||
|
talent_num: str,
|
||||||
|
floor_pic: Image.Image,
|
||||||
|
index_char: int,
|
||||||
|
index_part: int,
|
||||||
|
):
|
||||||
|
# char_id = char['id']
|
||||||
|
# # 确认角色头像路径
|
||||||
|
# char_pic_path = CHAR_ICON_PATH / f'{char_id}.png'
|
||||||
|
char_bg = (char_bg_4 if char.rarity == 4 else char_bg_5).copy()
|
||||||
|
char_icon = (await get_icon(char.icon)).resize((150, 170))
|
||||||
|
element_icon = elements[char.element]
|
||||||
|
char_bg.paste(char_icon, (24, 16), mask=char_icon)
|
||||||
|
char_bg.paste(level_cover, (0, 0), mask=level_cover)
|
||||||
|
char_bg.paste(element_icon, (35, 25), mask=element_icon)
|
||||||
|
char_card_draw = ImageDraw.Draw(char_bg)
|
||||||
|
if char.rank > 0:
|
||||||
|
char_bg.paste(rank_bg, (150, 16), mask=rank_bg)
|
||||||
|
char_card_draw.text(
|
||||||
|
(162, 31),
|
||||||
|
f'{char.rank}',
|
||||||
|
font=sr_font_22,
|
||||||
|
fill=white_color,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
# 不存在自动下载
|
||||||
|
# if not char_pic_path.exists():
|
||||||
|
# await create_single_char_card(char_id)
|
||||||
|
# talent_pic = await get_talent_pic(int(talent_num))
|
||||||
|
# talent_pic = talent_pic.resize((90, 45))
|
||||||
|
# char_card.paste(talent_pic, (137, 260), talent_pic)
|
||||||
|
char_card_draw.text(
|
||||||
|
(100, 165),
|
||||||
|
f'等级 {char.level}',
|
||||||
|
font=sr_font_22,
|
||||||
|
fill=white_color,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
floor_pic.paste(
|
||||||
|
char_bg,
|
||||||
|
(75 + 185 * index_char, 130 + index_part * 219),
|
||||||
|
char_bg,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def _draw_floor_card(
|
||||||
|
level_star: int,
|
||||||
|
floor_pic: Image.Image,
|
||||||
|
img: Image.Image,
|
||||||
|
index_floor: int,
|
||||||
|
floor_name: str,
|
||||||
|
round_num: int,
|
||||||
|
):
|
||||||
|
for index_num in [0, 1, 2]:
|
||||||
|
star_num = index_num + 1
|
||||||
|
if star_num <= level_star:
|
||||||
|
star_pic = star_yes.copy()
|
||||||
|
else:
|
||||||
|
star_pic = star_gray.copy()
|
||||||
|
floor_pic.paste(star_pic, (103 + index_num * 50, 25), star_pic)
|
||||||
|
floor_pic_draw = ImageDraw.Draw(floor_pic)
|
||||||
|
floor_pic_draw.text(
|
||||||
|
(450, 60),
|
||||||
|
floor_name,
|
||||||
|
font=sr_font_42,
|
||||||
|
fill=white_color,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
floor_pic_draw.text(
|
||||||
|
(802, 60),
|
||||||
|
f'使用轮: {round_num}',
|
||||||
|
font=sr_font_28,
|
||||||
|
fill=gray_color,
|
||||||
|
anchor='rm',
|
||||||
|
)
|
||||||
|
img.paste(floor_pic, (0, 657 + index_floor * 570), floor_pic)
|
||||||
|
|
||||||
|
|
||||||
|
async def draw_abyss_img(
|
||||||
|
qid: Union[str, int],
|
||||||
|
uid: str,
|
||||||
|
sender: Union[str, str],
|
||||||
|
schedule_type: str = '1',
|
||||||
|
) -> Union[bytes, str]:
|
||||||
|
raw_abyss_data = await mys_api.get_abyss_story_info(uid, schedule_type)
|
||||||
|
if isinstance(raw_abyss_data, int):
|
||||||
|
return get_error(raw_abyss_data)
|
||||||
|
|
||||||
|
# 获取查询者数据
|
||||||
|
if raw_abyss_data.max_floor == '':
|
||||||
|
return '你还没有挑战本期虚构叙事!\n可以使用[sr上期虚构叙事]命令查询上期~'
|
||||||
|
# 过滤掉 is_fast(快速通关) 为 True 的项
|
||||||
|
floor_detail = [detail for detail in raw_abyss_data.all_floor_detail if not detail.is_fast]
|
||||||
|
floor_num = len(floor_detail)
|
||||||
|
|
||||||
|
# 获取背景图片各项参数
|
||||||
|
based_w = 900
|
||||||
|
based_h = 657 + 570 * floor_num
|
||||||
|
img = img_bg.copy()
|
||||||
|
img = img.crop((0, 0, based_w, based_h))
|
||||||
|
abyss_title = Image.open(TEXT_PATH / 'head.png')
|
||||||
|
img.paste(abyss_title, (0, 0), abyss_title)
|
||||||
|
|
||||||
|
# 获取头像
|
||||||
|
_id = str(qid)
|
||||||
|
if _id.startswith('http'):
|
||||||
|
char_pic = await get_qq_avatar(avatar_url=_id)
|
||||||
|
elif sender.get('avatar') is not None:
|
||||||
|
char_pic = await get_qq_avatar(avatar_url=sender['avatar'])
|
||||||
|
else:
|
||||||
|
char_pic = await get_qq_avatar(qid=qid)
|
||||||
|
char_pic = await draw_pic_with_ring(char_pic, 250, None, False)
|
||||||
|
|
||||||
|
img.paste(char_pic, (325, 132), char_pic)
|
||||||
|
|
||||||
|
# 绘制抬头
|
||||||
|
img_draw = ImageDraw.Draw(img)
|
||||||
|
img_draw.text((450, 442), f'UID {uid}', white_color, sr_font_28, 'mm')
|
||||||
|
|
||||||
|
# 总体数据
|
||||||
|
abyss_data = Image.open(TEXT_PATH / 'data.png')
|
||||||
|
img.paste(abyss_data, (0, 500), abyss_data)
|
||||||
|
|
||||||
|
# 最深抵达
|
||||||
|
img_draw.text(
|
||||||
|
(220, 565),
|
||||||
|
f'{raw_abyss_data.max_floor}',
|
||||||
|
white_color,
|
||||||
|
sr_font_34,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
# 挑战次数
|
||||||
|
img_draw.text(
|
||||||
|
(220, 612),
|
||||||
|
f'{raw_abyss_data.battle_num}',
|
||||||
|
white_color,
|
||||||
|
sr_font_34,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
|
||||||
|
star_num_pic = Image.open(TEXT_PATH / 'star.png')
|
||||||
|
img.paste(star_num_pic, (615, 557), star_num_pic)
|
||||||
|
|
||||||
|
img_draw.text(
|
||||||
|
(695, 590),
|
||||||
|
f'{raw_abyss_data.star_num}/12',
|
||||||
|
white_color,
|
||||||
|
sr_font_42,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
|
||||||
|
for index_floor, level in enumerate(raw_abyss_data.all_floor_detail):
|
||||||
|
floor_pic = Image.open(TEXT_PATH / 'floor_bg.png')
|
||||||
|
level_star = level.star_num
|
||||||
|
floor_name = level.name
|
||||||
|
round_num = level.round_num
|
||||||
|
node_1 = level.node_1
|
||||||
|
node_2 = level.node_2
|
||||||
|
for index_part in [0, 1]:
|
||||||
|
node_num = index_part + 1
|
||||||
|
if node_num == 1:
|
||||||
|
time_array = node_1.challenge_time
|
||||||
|
else:
|
||||||
|
time_array = node_2.challenge_time
|
||||||
|
time_str = f'{time_array.year}-{time_array.month}'
|
||||||
|
time_str = f'{time_str}-{time_array.day}'
|
||||||
|
time_str = f'{time_str} {time_array.hour}:{time_array.minute}:00'
|
||||||
|
floor_pic_draw = ImageDraw.Draw(floor_pic)
|
||||||
|
floor_pic_draw.text(
|
||||||
|
(112, 120 + index_part * 219),
|
||||||
|
f'节点{node_num}',
|
||||||
|
white_color,
|
||||||
|
sr_font_30,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
floor_pic_draw.text(
|
||||||
|
(201, 120 + index_part * 219),
|
||||||
|
f'{time_str}',
|
||||||
|
gray_color,
|
||||||
|
sr_font_22,
|
||||||
|
'lm',
|
||||||
|
)
|
||||||
|
if node_num == 1:
|
||||||
|
avatars_array = node_1
|
||||||
|
else:
|
||||||
|
avatars_array = node_2
|
||||||
|
|
||||||
|
for index_char, char in enumerate(avatars_array.avatars):
|
||||||
|
await _draw_abyss_card(
|
||||||
|
char,
|
||||||
|
0, # type: ignore
|
||||||
|
floor_pic,
|
||||||
|
index_char,
|
||||||
|
index_part,
|
||||||
|
)
|
||||||
|
await _draw_floor_card(
|
||||||
|
level_star,
|
||||||
|
floor_pic,
|
||||||
|
img,
|
||||||
|
index_floor,
|
||||||
|
floor_name,
|
||||||
|
round_num,
|
||||||
|
)
|
||||||
|
|
||||||
|
res = await convert_img(img)
|
||||||
|
logger.info('[查询虚构叙事信息]绘图已完成,等待发送!')
|
||||||
|
return res
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/bg.jpg
Normal file
After Width: | Height: | Size: 396 KiB |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/char4_bg.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/char5_bg.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/data.png
Normal file
After Width: | Height: | Size: 55 KiB |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/floor_bg.png
Normal file
After Width: | Height: | Size: 102 KiB |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/head.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/level_cover.png
Normal file
After Width: | Height: | Size: 381 B |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/rank_bg.png
Normal file
After Width: | Height: | Size: 953 B |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/star.png
Normal file
After Width: | Height: | Size: 5.9 KiB |
BIN
StarRailUID/starrailuid_abyss_story/texture2D/star_gray.png
Normal file
After Width: | Height: | Size: 4.7 KiB |
26
StarRailUID/starrailuid_abyss_story/utils.py
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
from io import BytesIO
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from aiohttp import ClientSession
|
||||||
|
from gsuid_core.data_store import get_res_path
|
||||||
|
|
||||||
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
ROLEINFO_PATH = get_res_path() / 'StarRailUID' / 'roleinfo'
|
||||||
|
ROLEINFO_PATH.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_icon(url: str) -> Image.Image:
|
||||||
|
name = url.split('/')[-1]
|
||||||
|
path = ROLEINFO_PATH / name
|
||||||
|
if (path).exists():
|
||||||
|
content = path.read_bytes()
|
||||||
|
else:
|
||||||
|
async with ClientSession() as client:
|
||||||
|
async with client.get(url) as resp:
|
||||||
|
content = await resp.read()
|
||||||
|
with Path.open(path, 'wb') as f:
|
||||||
|
f.write(content)
|
||||||
|
return Image.open(BytesIO(content)).convert('RGBA')
|
@ -4,7 +4,7 @@ from gsuid_core.utils.plugins_config.models import (
|
|||||||
GSC,
|
GSC,
|
||||||
GsStrConfig,
|
GsStrConfig,
|
||||||
GsBoolConfig,
|
GsBoolConfig,
|
||||||
GsListStrConfig,
|
GsListStrConfig, GsIntConfig,
|
||||||
)
|
)
|
||||||
|
|
||||||
CONIFG_DEFAULT: Dict[str, GSC] = {
|
CONIFG_DEFAULT: Dict[str, GSC] = {
|
||||||
@ -21,6 +21,17 @@ CONIFG_DEFAULT: Dict[str, GSC] = {
|
|||||||
'开启后每晚00:30将开始自动签到任务',
|
'开启后每晚00:30将开始自动签到任务',
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
|
'SchedStaminaPush': GsBoolConfig(
|
||||||
|
'定时检查开拓力',
|
||||||
|
'开启后每隔半小时检查一次开拓力',
|
||||||
|
True,
|
||||||
|
),
|
||||||
|
'push_max_value': GsIntConfig(
|
||||||
|
'提醒阈值',
|
||||||
|
'发送提醒的阈值',
|
||||||
|
200,
|
||||||
|
240
|
||||||
|
),
|
||||||
'CrazyNotice': GsBoolConfig(
|
'CrazyNotice': GsBoolConfig(
|
||||||
'催命模式',
|
'催命模式',
|
||||||
'开启后当达到推送阈值将会一直推送',
|
'开启后当达到推送阈值将会一直推送',
|
||||||
|
@ -28,7 +28,7 @@ from ..sruid_utils.api.mys.models import (
|
|||||||
DailyNoteData,
|
DailyNoteData,
|
||||||
RoleBasicInfo,
|
RoleBasicInfo,
|
||||||
WidgetStamina,
|
WidgetStamina,
|
||||||
RogueLocustData,
|
RogueLocustData, AbyssStoryData, AbyssBossData,
|
||||||
)
|
)
|
||||||
|
|
||||||
RECOGNIZE_SERVER = {
|
RECOGNIZE_SERVER = {
|
||||||
@ -340,7 +340,7 @@ class MysApi(_MysApi):
|
|||||||
# data = cast(SignInfo, data['data'])
|
# data = cast(SignInfo, data['data'])
|
||||||
return data
|
return data
|
||||||
|
|
||||||
async def get_srspiral_abyss_info(
|
async def get_abyss_info(
|
||||||
self,
|
self,
|
||||||
uid: str,
|
uid: str,
|
||||||
schedule_type='1',
|
schedule_type='1',
|
||||||
@ -386,6 +386,98 @@ class MysApi(_MysApi):
|
|||||||
# data = cast(AbyssData, data['data'])
|
# data = cast(AbyssData, data['data'])
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
async def get_abyss_story_info(
|
||||||
|
self,
|
||||||
|
uid: str,
|
||||||
|
schedule_type='1',
|
||||||
|
ck: Optional[str] = None,
|
||||||
|
) -> Union[AbyssData, int]:
|
||||||
|
server_id = self.RECOGNIZE_SERVER.get(uid[0])
|
||||||
|
is_os = self.check_os(uid)
|
||||||
|
if is_os:
|
||||||
|
HEADER = copy.deepcopy(self._HEADER_OS)
|
||||||
|
ck = await self.get_sr_ck(uid, 'OWNER')
|
||||||
|
if ck is None:
|
||||||
|
return -51
|
||||||
|
HEADER['Cookie'] = ck
|
||||||
|
HEADER['DS'] = generate_os_ds()
|
||||||
|
header = HEADER
|
||||||
|
data = await self.simple_sr_req(
|
||||||
|
'CHALLENGE_STORY_INFO_URL',
|
||||||
|
uid,
|
||||||
|
params={
|
||||||
|
'need_all': 'true',
|
||||||
|
'role_id': uid,
|
||||||
|
'schedule_type': schedule_type,
|
||||||
|
'server': server_id,
|
||||||
|
},
|
||||||
|
header=header,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
data = await self.simple_sr_req(
|
||||||
|
'CHALLENGE_STORY_INFO_URL',
|
||||||
|
uid,
|
||||||
|
params={
|
||||||
|
'isPrev': 'true',
|
||||||
|
'need_all': 'true',
|
||||||
|
'role_id': uid,
|
||||||
|
'schedule_type': schedule_type,
|
||||||
|
'server': server_id,
|
||||||
|
},
|
||||||
|
cookie=ck,
|
||||||
|
header=self._HEADER,
|
||||||
|
)
|
||||||
|
if isinstance(data, Dict):
|
||||||
|
data = msgspec.convert(data['data'], type=AbyssStoryData)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
async def get_abyss_boss_info(
|
||||||
|
self,
|
||||||
|
uid: str,
|
||||||
|
schedule_type='1',
|
||||||
|
ck: Optional[str] = None,
|
||||||
|
) -> Union[AbyssBossData, int]:
|
||||||
|
server_id = self.RECOGNIZE_SERVER.get(uid[0])
|
||||||
|
is_os = self.check_os(uid)
|
||||||
|
if is_os:
|
||||||
|
HEADER = copy.deepcopy(self._HEADER_OS)
|
||||||
|
ck = await self.get_sr_ck(uid, 'OWNER')
|
||||||
|
if ck is None:
|
||||||
|
return -51
|
||||||
|
HEADER['Cookie'] = ck
|
||||||
|
HEADER['DS'] = generate_os_ds()
|
||||||
|
header = HEADER
|
||||||
|
data = await self.simple_sr_req(
|
||||||
|
'CHALLENGE_BOSS_INFO_URL',
|
||||||
|
uid,
|
||||||
|
params={
|
||||||
|
'need_all': 'true',
|
||||||
|
'role_id': uid,
|
||||||
|
'schedule_type': schedule_type,
|
||||||
|
'server': server_id,
|
||||||
|
},
|
||||||
|
header=header,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
data = await self.simple_sr_req(
|
||||||
|
'CHALLENGE_BOSS_INFO_URL',
|
||||||
|
uid,
|
||||||
|
params={
|
||||||
|
'isPrev': 'true',
|
||||||
|
'need_all': 'true',
|
||||||
|
'role_id': uid,
|
||||||
|
'schedule_type': schedule_type,
|
||||||
|
'server': server_id,
|
||||||
|
},
|
||||||
|
cookie=ck,
|
||||||
|
header=self._HEADER,
|
||||||
|
)
|
||||||
|
if isinstance(data, Dict):
|
||||||
|
data = msgspec.convert(data['data'], type=AbyssBossData)
|
||||||
|
# data = cast(AbyssData, data['data'])
|
||||||
|
return data
|
||||||
|
|
||||||
async def get_rogue_info(
|
async def get_rogue_info(
|
||||||
self,
|
self,
|
||||||
uid: str,
|
uid: str,
|
||||||
|