✨ 新增幻想真境剧诗
31
GenshinUID/genshinuid_poetry_abyss/__init__.py
Normal file
@ -0,0 +1,31 @@
|
||||
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 .draw_poetry_abyss import draw_poetry_abyss_img
|
||||
|
||||
sv_poetry_abyss = SV('查询幻想真境剧诗')
|
||||
|
||||
|
||||
@sv_poetry_abyss.on_command(
|
||||
('查询幻想真境剧诗', '幻想真境剧诗', '新深渊', '查询新深渊', '真剧诗'),
|
||||
block=True,
|
||||
)
|
||||
async def send_poetry_abyss_info(bot: Bot, ev: Event):
|
||||
name = ''.join(re.findall('[\u4e00-\u9fa5]', ev.text))
|
||||
if name:
|
||||
return
|
||||
|
||||
await bot.logger.info('开始执行[幻想真境剧诗]')
|
||||
uid, user_id = await get_uid(bot, ev, True)
|
||||
if uid is None:
|
||||
return await bot.send(UID_HINT)
|
||||
await bot.logger.info('[幻想真境剧诗]uid: {}'.format(uid))
|
||||
|
||||
im = await draw_poetry_abyss_img(uid, ev)
|
||||
|
||||
await bot.send(im)
|
212
GenshinUID/genshinuid_poetry_abyss/draw_poetry_abyss.py
Normal file
@ -0,0 +1,212 @@
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
from typing import List, Union
|
||||
|
||||
from PIL import Image, ImageDraw
|
||||
from gsuid_core.models import Event
|
||||
from gsuid_core.utils.error_reply import get_error
|
||||
from gsuid_core.utils.image.image_tools import get_avatar_with_ring
|
||||
|
||||
from ..utils.mys_api import mys_api
|
||||
from ..utils.colors import first_color
|
||||
from ..utils.image.convert import convert_img
|
||||
from ..utils.image.image_tools import add_footer
|
||||
from ..utils.resource.download_url import download_file
|
||||
from ..utils.resource.RESOURCE_PATH import ICON_PATH, CHAR_CARD_PATH
|
||||
from ..utils.resource.generate_char_card import create_single_char_card
|
||||
from ..utils.fonts.genshin_fonts import (
|
||||
gs_font_20,
|
||||
gs_font_28,
|
||||
gs_font_36,
|
||||
gs_font_40,
|
||||
gs_font_50,
|
||||
)
|
||||
|
||||
TEXT_PATH = Path(__file__).parent / 'texture2d'
|
||||
DIFFICULTY_MAP = {
|
||||
1: '简单模式',
|
||||
2: '普通模式',
|
||||
3: '困难模式',
|
||||
}
|
||||
|
||||
|
||||
def timestamp_to_str(timestamp: float):
|
||||
dt = datetime.fromtimestamp(timestamp)
|
||||
return dt.strftime('%Y.%m.%d %H:%M:%S')
|
||||
|
||||
|
||||
async def draw_buff(
|
||||
data: List,
|
||||
startb: int,
|
||||
stage: Image.Image,
|
||||
stage_draw: ImageDraw.ImageDraw,
|
||||
_type: str,
|
||||
):
|
||||
if _type == '神秘收获':
|
||||
y = 206
|
||||
else:
|
||||
y = 253
|
||||
|
||||
for index_choice, choice in enumerate(data):
|
||||
name = f'{choice["name"]}.png'
|
||||
path = ICON_PATH / name
|
||||
if not path.exists():
|
||||
await download_file(choice['icon'], 8, name)
|
||||
icon = Image.open(path).resize((45, 45)).convert('RGBA')
|
||||
buff = Image.open(TEXT_PATH / 'buff.png')
|
||||
buff.paste(icon, (-3, -4), icon)
|
||||
stage.paste(buff, (startb + index_choice * 44, y), buff)
|
||||
|
||||
if not data:
|
||||
stage_draw.text(
|
||||
(startb, y + 21), f'暂无{_type}', (68, 59, 45), gs_font_20, 'lm'
|
||||
)
|
||||
|
||||
|
||||
async def draw_poetry_abyss_img(uid: str, ev: Event) -> Union[str, bytes]:
|
||||
data = await mys_api.get_poetry_abyss_data(uid)
|
||||
if isinstance(data, int):
|
||||
return get_error(data)
|
||||
|
||||
if not data['is_unlock'] or not data['data']:
|
||||
return '[幻想真境剧诗] 你还没有解锁该模式!'
|
||||
|
||||
data = data['data'][0]
|
||||
round_data = data['detail']['rounds_data']
|
||||
stat_data = data['stat']
|
||||
|
||||
start_time = timestamp_to_str(float(data['schedule']['start_time']))
|
||||
end_time = timestamp_to_str(float(data['schedule']['end_time']))
|
||||
|
||||
w, h = 1000, 1040 + 50 + (len(round_data) // 2) * 330
|
||||
|
||||
img = Image.new('RGBA', (w, h), (22, 18, 20))
|
||||
|
||||
title = Image.open(TEXT_PATH / 'title.png').convert('RGBA')
|
||||
status = Image.open(TEXT_PATH / 'status.png').convert('RGBA')
|
||||
title_draw = ImageDraw.Draw(title)
|
||||
status_draw = ImageDraw.Draw(status)
|
||||
|
||||
avatar = await get_avatar_with_ring(ev, 278)
|
||||
title.paste(avatar, (361, 115), avatar)
|
||||
title_draw.text((500, 473), f'UID {uid}', 'white', gs_font_36, 'mm')
|
||||
|
||||
difficulty = DIFFICULTY_MAP.get(stat_data['difficulty_id'], '困难模式')
|
||||
max_round_id = stat_data['max_round_id']
|
||||
is_gold = (
|
||||
True
|
||||
if stat_data['difficulty_id'] == 3 and max_round_id == 8
|
||||
else False
|
||||
)
|
||||
time = f'{start_time} ~ {end_time}'
|
||||
avatar_bonus = stat_data['avatar_bonus_num']
|
||||
rent_cnt = stat_data['rent_cnt']
|
||||
coin_num = stat_data['coin_num']
|
||||
|
||||
if is_gold:
|
||||
status_icon = Image.open(TEXT_PATH / 'yes.png')
|
||||
else:
|
||||
status_icon = Image.open(TEXT_PATH / 'no.png')
|
||||
|
||||
status_icon = status_icon.convert('RGBA')
|
||||
status.paste(status_icon, (560, 111), status_icon)
|
||||
status_draw.text((462, 132), difficulty, (236, 233, 229), gs_font_36, 'mm')
|
||||
status_draw.text((500, 189), time, (139, 137, 133), gs_font_20, 'mm')
|
||||
|
||||
status_draw.text(
|
||||
(220, 398), f'第{max_round_id}幕', (68, 59, 45), gs_font_50, 'mm'
|
||||
)
|
||||
status_draw.text(
|
||||
(416, 398), f'{avatar_bonus}', (68, 59, 45), gs_font_50, 'mm'
|
||||
)
|
||||
status_draw.text((604, 398), f'{rent_cnt}', (68, 59, 45), gs_font_50, 'mm')
|
||||
status_draw.text((793, 398), f'{coin_num}', (68, 59, 45), gs_font_50, 'mm')
|
||||
|
||||
flower_yes = Image.open(TEXT_PATH / 'flower_yes.png').convert('RGBA')
|
||||
flower_no = Image.open(TEXT_PATH / 'flower_no.png').convert('RGBA')
|
||||
medals = stat_data['get_medal_round_list']
|
||||
|
||||
while len(medals) < 8:
|
||||
medals.append(0)
|
||||
|
||||
for index, medal in enumerate(medals):
|
||||
flower = flower_yes if medal else flower_no
|
||||
status.paste(flower, (449 + 42 * index, 270), flower)
|
||||
|
||||
img.paste(title, (0, 0), title)
|
||||
img.paste(status, (0, 474), status)
|
||||
|
||||
for i, r in enumerate(round_data):
|
||||
is_get_medal = r['is_get_medal']
|
||||
round_id = r['round_id']
|
||||
_medal = flower_yes if is_get_medal else flower_no
|
||||
|
||||
if i % 2 == 0:
|
||||
stage = Image.open(TEXT_PATH / 'stage.png')
|
||||
stage_draw = ImageDraw.Draw(stage)
|
||||
stage_draw.text(
|
||||
(160, 54), f'第{round_id}幕', (68, 59, 45), gs_font_28, 'lm'
|
||||
)
|
||||
stage.paste(_medal, (97, 32), _medal)
|
||||
startx = 92
|
||||
startb = 116
|
||||
else:
|
||||
stage_draw.text(
|
||||
(830, 54), f'第{round_id}幕', (68, 59, 45), gs_font_28, 'rm'
|
||||
)
|
||||
stage.paste(_medal, (866, 32), _medal)
|
||||
startx = 520
|
||||
startb = 552
|
||||
|
||||
for index_char, char in enumerate(r['avatars']):
|
||||
char_id = char['avatar_id']
|
||||
char_type = char['avatar_type']
|
||||
char_pic_path = CHAR_CARD_PATH / f'{char_id}.png'
|
||||
# 不存在自动下载
|
||||
if not char_pic_path.exists():
|
||||
await create_single_char_card(char_id)
|
||||
char_card = Image.open(char_pic_path).convert('RGBA')
|
||||
char_card_draw = ImageDraw.Draw(char_card)
|
||||
char_card_draw.text(
|
||||
(128, 280),
|
||||
f'Lv.{char["level"]}',
|
||||
font=gs_font_40,
|
||||
fill=first_color,
|
||||
anchor='mm',
|
||||
)
|
||||
char_card = char_card.resize((89, 108), Image.Resampling.LANCZOS)
|
||||
stage.paste(
|
||||
char_card,
|
||||
(startx + 97 * index_char, 88),
|
||||
char_card,
|
||||
)
|
||||
if char_type != 1:
|
||||
char_tag = Image.open(TEXT_PATH / f'{char_type}.png')
|
||||
stage.paste(
|
||||
char_tag,
|
||||
(startx + 16 + 97 * index_char, 75),
|
||||
char_tag,
|
||||
)
|
||||
|
||||
await draw_buff(
|
||||
r['choice_cards'],
|
||||
startb,
|
||||
stage,
|
||||
stage_draw,
|
||||
'神秘收获',
|
||||
)
|
||||
await draw_buff(
|
||||
r['buffs'],
|
||||
startb,
|
||||
stage,
|
||||
stage_draw,
|
||||
'奇妙助益',
|
||||
)
|
||||
|
||||
if i % 2 == 1:
|
||||
img.paste(stage, (0, 1040 + 330 * (i // 2)), stage)
|
||||
|
||||
img = add_footer(img, 1000)
|
||||
|
||||
res = await convert_img(img)
|
||||
return res
|
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/2.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/3.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/buff.png
Normal file
After Width: | Height: | Size: 776 B |
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/flower_no.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/flower_yes.png
Normal file
After Width: | Height: | Size: 2.4 KiB |
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/no.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/stage.png
Normal file
After Width: | Height: | Size: 180 KiB |
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/status.png
Normal file
After Width: | Height: | Size: 327 KiB |
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/title.png
Normal file
After Width: | Height: | Size: 480 KiB |
BIN
GenshinUID/genshinuid_poetry_abyss/texture2d/yes.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
@ -1,2 +1,3 @@
|
||||
base_url = 'https://api-takumi-record.mihoyo.com/'
|
||||
widget_url = f'{base_url}game_record/genshin/aapi/widget/v2'
|
||||
base_url = 'https://api-takumi-record.mihoyo.com'
|
||||
widget_url = f'{base_url}/game_record/genshin/aapi/widget/v2'
|
||||
new_abyss_url = f'{base_url}/game_record/app/genshin/api/role_combat'
|
||||
|
@ -73,3 +73,97 @@ class FakeResin(WidgetResin):
|
||||
transformer: Transformer
|
||||
daily_task: DayilyTask
|
||||
archon_quest_progress: ArchonProgress
|
||||
|
||||
|
||||
class PoetryAbyssLinks(TypedDict):
|
||||
lineup_link: str
|
||||
lineup_link_pc: str
|
||||
strategy_link: str
|
||||
lineup_publish_link: str
|
||||
lineup_publish_link_pc: str
|
||||
|
||||
|
||||
class PoetryAbyssAvatar(TypedDict):
|
||||
avatar_id: int
|
||||
avatar_type: int
|
||||
name: str
|
||||
element: str
|
||||
image: str
|
||||
level: int
|
||||
rarity: int
|
||||
|
||||
|
||||
class PoetryAbyssChoiceCard(TypedDict):
|
||||
icon: str
|
||||
name: str
|
||||
desc: str
|
||||
is_enhanced: bool
|
||||
id: int
|
||||
|
||||
|
||||
class PoetryAbyssBuff(TypedDict):
|
||||
icon: str
|
||||
name: str
|
||||
desc: str
|
||||
is_enhanced: bool
|
||||
id: int
|
||||
|
||||
|
||||
class PoetryAbyssDateTime(TypedDict):
|
||||
year: int
|
||||
month: int
|
||||
day: int
|
||||
hour: int
|
||||
minute: int
|
||||
second: int
|
||||
|
||||
|
||||
class PoetryAbyssSchedule(TypedDict):
|
||||
start_time: int
|
||||
end_time: int
|
||||
schedule_type: int
|
||||
schedule_id: int
|
||||
start_date_time: PoetryAbyssDateTime
|
||||
end_date_time: PoetryAbyssDateTime
|
||||
|
||||
|
||||
class PoetryAbyssDetailStat(TypedDict):
|
||||
difficulty_id: int
|
||||
max_round_id: int
|
||||
heraldry: int
|
||||
get_medal_round_list: List[int]
|
||||
medal_num: int
|
||||
coin_num: int
|
||||
avatar_bonus_num: int
|
||||
rent_cnt: int
|
||||
|
||||
|
||||
class RoundData(TypedDict):
|
||||
avatars: List[PoetryAbyssAvatar]
|
||||
choice_cards: List[PoetryAbyssChoiceCard]
|
||||
buffs: List[PoetryAbyssBuff]
|
||||
is_get_medal: bool
|
||||
round_id: int
|
||||
finish_time: int
|
||||
finish_date_time: PoetryAbyssDateTime
|
||||
detail_stat: PoetryAbyssDetailStat
|
||||
|
||||
|
||||
class PoetryAbyssDetail(TypedDict):
|
||||
rounds_data: List[RoundData]
|
||||
detail_stat: PoetryAbyssDetailStat
|
||||
backup_avatars: List[PoetryAbyssAvatar]
|
||||
|
||||
|
||||
class PoetryAbyssData(TypedDict):
|
||||
detail: PoetryAbyssDetail
|
||||
stat: PoetryAbyssDetailStat
|
||||
schedule: PoetryAbyssSchedule
|
||||
has_data: bool
|
||||
has_detail_data: bool
|
||||
|
||||
|
||||
class PoetryAbyssDatas(TypedDict):
|
||||
data: List[PoetryAbyssData]
|
||||
is_unlock: bool
|
||||
links: PoetryAbyssLinks
|
||||
|
@ -2,10 +2,10 @@ from copy import deepcopy
|
||||
from typing import Dict, Union, cast
|
||||
|
||||
from gsuid_core.utils.api.mys_api import _MysApi
|
||||
from gsuid_core.utils.api.mys.tools import get_web_ds_token
|
||||
from gsuid_core.utils.api.mys.tools import get_ds_token, get_web_ds_token
|
||||
|
||||
from .api import widget_url
|
||||
from .models import WidgetResin
|
||||
from .api import widget_url, new_abyss_url
|
||||
from .models import WidgetResin, PoetryAbyssDatas
|
||||
|
||||
|
||||
class GsMysAPI(_MysApi):
|
||||
@ -28,3 +28,30 @@ class GsMysAPI(_MysApi):
|
||||
if isinstance(data, Dict):
|
||||
data = cast(WidgetResin, data['data'])
|
||||
return data
|
||||
|
||||
async def get_poetry_abyss_data(
|
||||
self, uid: str
|
||||
) -> Union[PoetryAbyssDatas, int]:
|
||||
server_id = self.RECOGNIZE_SERVER.get(uid[0])
|
||||
HEADER = deepcopy(self._HEADER)
|
||||
ck = await self.get_ck(uid)
|
||||
if ck is None:
|
||||
return -51
|
||||
HEADER['Cookie'] = ck
|
||||
params = {
|
||||
'server': server_id,
|
||||
'role_id': uid,
|
||||
'need_detail': True,
|
||||
}
|
||||
HEADER['DS'] = get_ds_token(
|
||||
'&'.join([f'{k}={v}' for k, v in params.items()])
|
||||
)
|
||||
data = await self._mys_request(
|
||||
new_abyss_url,
|
||||
'GET',
|
||||
HEADER,
|
||||
params,
|
||||
)
|
||||
if isinstance(data, Dict):
|
||||
data = cast(PoetryAbyssDatas, data['data'])
|
||||
return data
|
||||
|
@ -1,2 +1,2 @@
|
||||
GenshinUID_version = '4.7.1'
|
||||
GenshinUID_version = '4.7.2'
|
||||
Genshin_version = '4.7.0'
|
||||
|