mirror of
https://github.com/KimigaiiWuyi/GenshinUID.git
synced 2025-05-06 11:43:45 +08:00
✨ 新增gs剧诗版本深渊
This commit is contained in:
parent
d09bd873a7
commit
86a4169f68
@ -4,6 +4,7 @@ from typing import List
|
|||||||
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.logger import logger
|
||||||
from gsuid_core.message_models import Button
|
from gsuid_core.message_models import Button
|
||||||
from gsuid_core.segment import MessageSegment
|
from gsuid_core.segment import MessageSegment
|
||||||
|
|
||||||
@ -16,9 +17,11 @@ from .get_new_abyss_data import get_review_data
|
|||||||
from ..utils.resource.RESOURCE_PATH import REF_PATH
|
from ..utils.resource.RESOURCE_PATH import REF_PATH
|
||||||
from .get_bbs_post_guide import get_material_way_post
|
from .get_bbs_post_guide import get_material_way_post
|
||||||
from ..utils.map.name_covert import alias_to_char_name
|
from ..utils.map.name_covert import alias_to_char_name
|
||||||
|
from .draw_poetry_abyss_pic import draw_poetry_abyss_image
|
||||||
|
|
||||||
sv_char_guide = SV('查询角色攻略')
|
sv_char_guide = SV('查询角色攻略')
|
||||||
sv_abyss_review = SV('查询深渊阵容')
|
sv_abyss_review = SV('查询深渊阵容')
|
||||||
|
sv_poetry_abyss_review = SV('查询剧诗深渊阵容')
|
||||||
sv_bbs_post_guide = SV('查询BBS攻略')
|
sv_bbs_post_guide = SV('查询BBS攻略')
|
||||||
|
|
||||||
|
|
||||||
@ -57,6 +60,13 @@ async def send_bluekun_pic(bot: Bot, ev: Event):
|
|||||||
await bot.logger.warning('未找到{}参考面板图片'.format(name))
|
await bot.logger.warning('未找到{}参考面板图片'.format(name))
|
||||||
|
|
||||||
|
|
||||||
|
@sv_poetry_abyss_review.on_command(('剧诗版本深渊', '剧诗深渊阵容'))
|
||||||
|
async def send_poetry_abyss_review(bot: Bot, ev: Event):
|
||||||
|
im = await draw_poetry_abyss_image()
|
||||||
|
logger.info('[剧诗版本深渊] 获得深渊信息成功!')
|
||||||
|
await bot.send(im)
|
||||||
|
|
||||||
|
|
||||||
@sv_abyss_review.on_command(('版本深渊', '深渊阵容', '深渊怪物'))
|
@sv_abyss_review.on_command(('版本深渊', '深渊阵容', '深渊怪物'))
|
||||||
async def send_abyss_review(bot: Bot, ev: Event):
|
async def send_abyss_review(bot: Bot, ev: Event):
|
||||||
floor = '12'
|
floor = '12'
|
||||||
@ -88,7 +98,7 @@ async def send_abyss_review(bot: Bot, ev: Event):
|
|||||||
d = Button(f'♾️版本深渊{adv_version}', f'深渊概览{adv_version}')
|
d = Button(f'♾️版本深渊{adv_version}', f'深渊概览{adv_version}')
|
||||||
await bot.send_option(im, [c, d])
|
await bot.send_option(im, [c, d])
|
||||||
elif isinstance(im, List):
|
elif isinstance(im, List):
|
||||||
mes = [MessageSegment.text(str(msg)) for msg in im]
|
mes = [MessageSegment.text(str(msg)) for msg in im] # type: ignore
|
||||||
await bot.send(MessageSegment.node(mes))
|
await bot.send(MessageSegment.node(mes))
|
||||||
elif isinstance(im, str):
|
elif isinstance(im, str):
|
||||||
await bot.send(im)
|
await bot.send(im)
|
||||||
|
222
GenshinUID/genshinuid_guide/draw_poetry_abyss_pic.py
Normal file
222
GenshinUID/genshinuid_guide/draw_poetry_abyss_pic.py
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from PIL import Image, ImageDraw
|
||||||
|
from gsuid_core.utils.error_reply import get_error
|
||||||
|
from gsuid_core.utils.image.convert import convert_img
|
||||||
|
|
||||||
|
from ..utils.image.image_tools import add_footer
|
||||||
|
from ..utils.api.hakush.request import hakush_api
|
||||||
|
from ..utils.map.name_covert import avatar_id_to_name
|
||||||
|
from ..utils.resource.RESOURCE_PATH import CHAR_CARD_PATH, MONSTER_ICON_PATH
|
||||||
|
from ..utils.fonts.genshin_fonts import (
|
||||||
|
gs_font_20,
|
||||||
|
gs_font_22,
|
||||||
|
gs_font_26,
|
||||||
|
gs_font_28,
|
||||||
|
gs_font_30,
|
||||||
|
gs_font_38,
|
||||||
|
)
|
||||||
|
|
||||||
|
TEXT_PATH = Path(__file__).parent / 'texture2d2'
|
||||||
|
|
||||||
|
|
||||||
|
def remove_angle_brackets(text: str) -> str:
|
||||||
|
cleaned_text = re.sub(r'<.*?>', '', text)
|
||||||
|
return cleaned_text
|
||||||
|
|
||||||
|
|
||||||
|
def is_current_time_in_range(start_time: str, end_time: str) -> bool:
|
||||||
|
start_datetime = datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S")
|
||||||
|
end_datetime = datetime.strptime(end_time, "%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
current_datetime = datetime.now()
|
||||||
|
|
||||||
|
return start_datetime <= current_datetime <= end_datetime
|
||||||
|
|
||||||
|
|
||||||
|
async def draw_poetry_abyss_image():
|
||||||
|
data = await hakush_api.get_hakush_rolecombats()
|
||||||
|
if isinstance(data, int):
|
||||||
|
return get_error(data)
|
||||||
|
|
||||||
|
poetry_id = 0
|
||||||
|
for _id in data:
|
||||||
|
item = data[_id]
|
||||||
|
begin = item['begin']
|
||||||
|
end = item['end']
|
||||||
|
if is_current_time_in_range(begin, end):
|
||||||
|
poetry_id = _id
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return '没有找到当前的活动!'
|
||||||
|
|
||||||
|
pdata = await hakush_api.get_hakush_rolecombat(poetry_id)
|
||||||
|
if isinstance(pdata, int):
|
||||||
|
return get_error(pdata)
|
||||||
|
|
||||||
|
w, h = 1000, 1270 + 50
|
||||||
|
|
||||||
|
monster_list = list(pdata['DifficultyConfig'].values())[-1]
|
||||||
|
monster_room = monster_list['Room']
|
||||||
|
|
||||||
|
for room_id in monster_room:
|
||||||
|
monster = monster_room[room_id]
|
||||||
|
if 'Title' in monster:
|
||||||
|
h += 301
|
||||||
|
else:
|
||||||
|
h += 145
|
||||||
|
|
||||||
|
time = f"{pdata['BeginTime']} ~ {pdata['EndTime']}"
|
||||||
|
img = Image.new('RGBA', (w, h), (22, 18, 20, 255))
|
||||||
|
|
||||||
|
title = Image.open(TEXT_PATH / 'title.png')
|
||||||
|
title_draw = ImageDraw.Draw(title)
|
||||||
|
title_draw.text(
|
||||||
|
(500, 473),
|
||||||
|
f'剧诗ID:{poetry_id} 详细信息',
|
||||||
|
(255, 255, 255),
|
||||||
|
font=gs_font_38,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
img.paste(title, (0, 0), title)
|
||||||
|
|
||||||
|
avatar_bg = Image.open(TEXT_PATH / 'avatar_bg.png')
|
||||||
|
avatar_draw = ImageDraw.Draw(avatar_bg)
|
||||||
|
|
||||||
|
avatar_draw.text(
|
||||||
|
(500, 184),
|
||||||
|
time,
|
||||||
|
(139, 137, 133),
|
||||||
|
font=gs_font_22,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
|
||||||
|
avatar_draw.text(
|
||||||
|
(500, 474),
|
||||||
|
pdata['AvatarConfig']['BuffAvatarList'][0]['Desc'][16:-9],
|
||||||
|
(139, 137, 133),
|
||||||
|
font=gs_font_26,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
|
||||||
|
for cindex, char in enumerate(pdata['AvatarConfig']['BuffAvatarList']):
|
||||||
|
char_id = str(char['Id'])
|
||||||
|
char_img = Image.open(CHAR_CARD_PATH / f'{char_id}.png')
|
||||||
|
char_img = char_img.resize((102, 124))
|
||||||
|
char_name = await avatar_id_to_name(char_id)
|
||||||
|
char_draw = ImageDraw.Draw(char_img)
|
||||||
|
char_draw.text(
|
||||||
|
(51, 111),
|
||||||
|
char_name,
|
||||||
|
(10, 10, 10),
|
||||||
|
font=gs_font_20,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
avatar_bg.paste(char_img, (121 + cindex * 130, 302), char_img)
|
||||||
|
|
||||||
|
for iindex, char_id in enumerate(
|
||||||
|
pdata['AvatarConfig']['InviteAvatarList']
|
||||||
|
):
|
||||||
|
char_id = str(char_id)
|
||||||
|
char_img = Image.open(CHAR_CARD_PATH / f'{char_id}.png')
|
||||||
|
char_img = char_img.resize((102, 124))
|
||||||
|
char_name = await avatar_id_to_name(char_id)
|
||||||
|
char_draw = ImageDraw.Draw(char_img)
|
||||||
|
char_draw.text(
|
||||||
|
(51, 111),
|
||||||
|
char_name,
|
||||||
|
(10, 10, 10),
|
||||||
|
font=gs_font_20,
|
||||||
|
anchor='mm',
|
||||||
|
)
|
||||||
|
avatar_bg.paste(char_img, (252 + iindex * 130, 576), char_img)
|
||||||
|
|
||||||
|
img.paste(avatar_bg, (0, 482), avatar_bg)
|
||||||
|
|
||||||
|
point = 1270
|
||||||
|
for room_id in monster_room:
|
||||||
|
monster = monster_room[room_id]
|
||||||
|
if 'Title' in monster:
|
||||||
|
monster_bg = Image.open(TEXT_PATH / 'long_monster_bg.png')
|
||||||
|
monster_draw = ImageDraw.Draw(monster_bg)
|
||||||
|
desc = remove_angle_brackets(monster['Desc'])
|
||||||
|
desc = desc[:26] + '...'
|
||||||
|
monster_draw.text(
|
||||||
|
(245, 69),
|
||||||
|
f'{monster["Title"]}',
|
||||||
|
'black',
|
||||||
|
font=gs_font_30,
|
||||||
|
anchor='lm',
|
||||||
|
)
|
||||||
|
for mindex, m in enumerate(monster['MonsterPreviewList']):
|
||||||
|
monster_icon = m['Icon']
|
||||||
|
monster_img = await hakush_api.get_hakush_ui(
|
||||||
|
monster_icon, MONSTER_ICON_PATH
|
||||||
|
)
|
||||||
|
monster_img = monster_img.resize((90, 90))
|
||||||
|
monster_name = m['Name']
|
||||||
|
if '·' in monster_name:
|
||||||
|
monster_name = monster_name.split('·')[-1]
|
||||||
|
|
||||||
|
monster_name = monster_name[:6]
|
||||||
|
|
||||||
|
monster_hp = int(m['Hp'])
|
||||||
|
mimg = Image.new(
|
||||||
|
'RGBA',
|
||||||
|
(250, 90),
|
||||||
|
(219, 209, 203),
|
||||||
|
)
|
||||||
|
ming_draw = ImageDraw.Draw(mimg)
|
||||||
|
|
||||||
|
mimg.paste(monster_img, (0, 0), monster_img)
|
||||||
|
ming_draw.text(
|
||||||
|
(100, 30),
|
||||||
|
f'{monster_name}',
|
||||||
|
(36, 36, 36),
|
||||||
|
font=gs_font_22,
|
||||||
|
anchor='lm',
|
||||||
|
)
|
||||||
|
|
||||||
|
ming_draw.text(
|
||||||
|
(100, 57),
|
||||||
|
f'HP {monster_hp}',
|
||||||
|
(90, 90, 90),
|
||||||
|
font=gs_font_22,
|
||||||
|
anchor='lm',
|
||||||
|
)
|
||||||
|
monster_bg.paste(mimg, (126 + mindex * 257, 158), mimg)
|
||||||
|
else:
|
||||||
|
monster_bg = Image.open(TEXT_PATH / 'short_monster_bg.png')
|
||||||
|
monster_draw = ImageDraw.Draw(monster_bg)
|
||||||
|
desc = f'怪物等级 Lv{monster["MonsterLevel"]}'
|
||||||
|
|
||||||
|
monster_draw.text(
|
||||||
|
(131, 69),
|
||||||
|
f'第{room_id}幕',
|
||||||
|
'black',
|
||||||
|
font=gs_font_30,
|
||||||
|
anchor='lm',
|
||||||
|
)
|
||||||
|
|
||||||
|
monster_draw.text(
|
||||||
|
(131, 114),
|
||||||
|
desc,
|
||||||
|
(99, 99, 99),
|
||||||
|
font=gs_font_28,
|
||||||
|
anchor='lm',
|
||||||
|
)
|
||||||
|
|
||||||
|
if 'Title' not in monster:
|
||||||
|
point -= 12
|
||||||
|
|
||||||
|
img.paste(monster_bg, (0, point), monster_bg)
|
||||||
|
if 'Title' not in monster:
|
||||||
|
point += 145
|
||||||
|
else:
|
||||||
|
point += 301
|
||||||
|
|
||||||
|
img = add_footer(img, 1000)
|
||||||
|
res = await convert_img(img)
|
||||||
|
return res
|
BIN
GenshinUID/genshinuid_guide/texture2d2/avatar_bg.png
Normal file
BIN
GenshinUID/genshinuid_guide/texture2d2/avatar_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 522 KiB |
BIN
GenshinUID/genshinuid_guide/texture2d2/long_monster_bg.png
Normal file
BIN
GenshinUID/genshinuid_guide/texture2d2/long_monster_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 247 KiB |
BIN
GenshinUID/genshinuid_guide/texture2d2/short_monster_bg.png
Normal file
BIN
GenshinUID/genshinuid_guide/texture2d2/short_monster_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 130 KiB |
BIN
GenshinUID/genshinuid_guide/texture2d2/title.png
Normal file
BIN
GenshinUID/genshinuid_guide/texture2d2/title.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 504 KiB |
@ -507,6 +507,14 @@
|
|||||||
"need_sk": false,
|
"need_sk": false,
|
||||||
"need_admin": false
|
"need_admin": false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "剧诗版本深渊",
|
||||||
|
"desc": "获取当前版本剧诗深渊阵容",
|
||||||
|
"eg": "剧诗版本深渊",
|
||||||
|
"need_ck": false,
|
||||||
|
"need_sk": false,
|
||||||
|
"need_admin": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "查成就",
|
"name": "查成就",
|
||||||
"desc": "查询这个成就的攻略",
|
"desc": "查询这个成就的攻略",
|
||||||
|
5
GenshinUID/utils/api/hakush/api.py
Normal file
5
GenshinUID/utils/api/hakush/api.py
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
HAKUSH_D_API = 'https://api.hakush.in/gi/data'
|
||||||
|
HAKUSH_U_API = 'https://api.hakush.in/gi/UI'
|
||||||
|
|
||||||
|
HAKUSH_ROLECOMBATS_API = f'{HAKUSH_D_API}/rolecombat.json'
|
||||||
|
HAKUSH_ROLECOMBAT_API = f'{HAKUSH_D_API}/zh/rolecombat/' + '{}.json'
|
58
GenshinUID/utils/api/hakush/models.py
Normal file
58
GenshinUID/utils/api/hakush/models.py
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
from typing import Dict, List, TypedDict
|
||||||
|
|
||||||
|
|
||||||
|
class RoleCombatEvent(TypedDict):
|
||||||
|
begin: str
|
||||||
|
end: str
|
||||||
|
element: List[int]
|
||||||
|
invite: List[int]
|
||||||
|
buff: List[int]
|
||||||
|
live_begin: str
|
||||||
|
live_end: str
|
||||||
|
|
||||||
|
|
||||||
|
class BuffAvatar(TypedDict):
|
||||||
|
Id: int
|
||||||
|
Desc: str
|
||||||
|
|
||||||
|
|
||||||
|
class MonsterPreview(TypedDict):
|
||||||
|
Id: int
|
||||||
|
Hp: float
|
||||||
|
Name: str
|
||||||
|
Icon: str
|
||||||
|
|
||||||
|
|
||||||
|
class RoomConfig(TypedDict):
|
||||||
|
MonsterLevel: int
|
||||||
|
Title: str
|
||||||
|
Desc: str
|
||||||
|
MonsterPreviewList: List[MonsterPreview]
|
||||||
|
|
||||||
|
|
||||||
|
class DifficultyLevel(TypedDict):
|
||||||
|
CanInvitePool: int
|
||||||
|
BossMaxRoomNumber: int
|
||||||
|
MinimumAvatarLevel: int
|
||||||
|
Room: Dict[str, RoomConfig]
|
||||||
|
CanInvitePoolAdd: int
|
||||||
|
|
||||||
|
|
||||||
|
class AvatarConfig(TypedDict):
|
||||||
|
ElementList: List[int]
|
||||||
|
InviteAvatarList: List[int]
|
||||||
|
BuffAvatarList: List[BuffAvatar]
|
||||||
|
|
||||||
|
|
||||||
|
class ShopItem(TypedDict):
|
||||||
|
Id: int
|
||||||
|
Name: str
|
||||||
|
Desc: str
|
||||||
|
|
||||||
|
|
||||||
|
class RoleCombatData(TypedDict):
|
||||||
|
BeginTime: str
|
||||||
|
EndTime: str
|
||||||
|
AvatarConfig: AvatarConfig
|
||||||
|
DifficultyConfig: Dict[str, DifficultyLevel]
|
||||||
|
ShopConfig: List[ShopItem]
|
87
GenshinUID/utils/api/hakush/request.py
Normal file
87
GenshinUID/utils/api/hakush/request.py
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import io
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, Dict, Union, Literal, Optional, cast
|
||||||
|
|
||||||
|
from PIL import Image
|
||||||
|
from httpx import AsyncClient
|
||||||
|
from gsuid_core.logger import logger
|
||||||
|
|
||||||
|
from .models import RoleCombatData, RoleCombatEvent
|
||||||
|
from .api import HAKUSH_U_API, HAKUSH_ROLECOMBAT_API, HAKUSH_ROLECOMBATS_API
|
||||||
|
|
||||||
|
|
||||||
|
class _HakushAPI:
|
||||||
|
ssl_verify = True
|
||||||
|
_HEADER = {'User-Agent': 'GenshinUID & GsCore'}
|
||||||
|
|
||||||
|
async def get_hakush_rolecombats(
|
||||||
|
self,
|
||||||
|
) -> Union[Dict[str, RoleCombatEvent], int]:
|
||||||
|
data = await self._hakush_request(HAKUSH_ROLECOMBATS_API)
|
||||||
|
if isinstance(data, Dict):
|
||||||
|
return cast(Dict[str, RoleCombatEvent], data)
|
||||||
|
else:
|
||||||
|
return -500
|
||||||
|
|
||||||
|
async def get_hakush_rolecombat(
|
||||||
|
self, id: str
|
||||||
|
) -> Union[RoleCombatData, int]:
|
||||||
|
data = await self._hakush_request(HAKUSH_ROLECOMBAT_API.format(id))
|
||||||
|
if isinstance(data, Dict):
|
||||||
|
return cast(RoleCombatData, data)
|
||||||
|
else:
|
||||||
|
return -500
|
||||||
|
|
||||||
|
async def get_hakush_ui(
|
||||||
|
self, ui_name: str, save_path: Path
|
||||||
|
) -> Image.Image:
|
||||||
|
png_file_path = save_path / f'{ui_name}.png'
|
||||||
|
if png_file_path.exists():
|
||||||
|
return Image.open(png_file_path)
|
||||||
|
url = f'{HAKUSH_U_API}/{ui_name}.webp'
|
||||||
|
data = await self._hakush_request(url)
|
||||||
|
if isinstance(data, bytes):
|
||||||
|
webp_stream = io.BytesIO(data)
|
||||||
|
with Image.open(webp_stream) as img:
|
||||||
|
img.save(str(png_file_path), 'PNG')
|
||||||
|
return img
|
||||||
|
else:
|
||||||
|
return Image.new('RGBA', (256, 256))
|
||||||
|
|
||||||
|
async def _hakush_request(
|
||||||
|
self,
|
||||||
|
url: str,
|
||||||
|
method: Literal['GET', 'POST'] = 'GET',
|
||||||
|
header: Dict[str, Any] = _HEADER,
|
||||||
|
params: Optional[Dict[str, Any]] = None,
|
||||||
|
_json: Optional[Dict[str, Any]] = None,
|
||||||
|
) -> Union[Dict, int, bytes]:
|
||||||
|
async with AsyncClient(timeout=None) as client:
|
||||||
|
logger.debug(f'[HAKUSH] 正在请求{url}')
|
||||||
|
resp = await client.request(
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
headers=header,
|
||||||
|
params=params,
|
||||||
|
json=_json,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
raw_data = await resp.json()
|
||||||
|
except: # noqa
|
||||||
|
try:
|
||||||
|
raw_data = json.loads(resp.text)
|
||||||
|
except: # noqa
|
||||||
|
try:
|
||||||
|
raw_data = resp.content
|
||||||
|
except: # noqa
|
||||||
|
raw_data = {
|
||||||
|
'retcode': -999,
|
||||||
|
'data': resp.text,
|
||||||
|
}
|
||||||
|
if not isinstance(raw_data, bytes):
|
||||||
|
logger.debug(raw_data)
|
||||||
|
return raw_data
|
||||||
|
|
||||||
|
|
||||||
|
hakush_api = _HakushAPI()
|
Loading…
x
Reference in New Issue
Block a user