mirror of
https://github.com/KimigaiiWuyi/GenshinUID.git
synced 2025-05-03 18:27:32 +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.bot import Bot
|
||||
from gsuid_core.models import Event
|
||||
from gsuid_core.logger import logger
|
||||
from gsuid_core.message_models import Button
|
||||
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 .get_bbs_post_guide import get_material_way_post
|
||||
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_abyss_review = SV('查询深渊阵容')
|
||||
sv_poetry_abyss_review = SV('查询剧诗深渊阵容')
|
||||
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))
|
||||
|
||||
|
||||
@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(('版本深渊', '深渊阵容', '深渊怪物'))
|
||||
async def send_abyss_review(bot: Bot, ev: Event):
|
||||
floor = '12'
|
||||
@ -88,7 +98,7 @@ async def send_abyss_review(bot: Bot, ev: Event):
|
||||
d = Button(f'♾️版本深渊{adv_version}', f'深渊概览{adv_version}')
|
||||
await bot.send_option(im, [c, d])
|
||||
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))
|
||||
elif isinstance(im, str):
|
||||
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_admin": false
|
||||
},
|
||||
{
|
||||
"name": "剧诗版本深渊",
|
||||
"desc": "获取当前版本剧诗深渊阵容",
|
||||
"eg": "剧诗版本深渊",
|
||||
"need_ck": false,
|
||||
"need_sk": false,
|
||||
"need_admin": false
|
||||
},
|
||||
{
|
||||
"name": "查成就",
|
||||
"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