新增gs个人日历

This commit is contained in:
KimigaiiWuyi 2024-11-07 05:33:25 +08:00
parent 106d014d76
commit f0d1f0abcd
17 changed files with 387 additions and 23 deletions

View File

@ -0,0 +1,23 @@
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.utils.error_reply import UID_HINT
from ..utils.convert import get_uid
from .draw_cale_pic import draw_cale_img
sv_cale = SV('个人日历')
@sv_cale.on_command(
('个人日历', '日历', '查询个人日历', '查询日历'), block=True
)
async def send_cale_pic(bot: Bot, ev: Event):
uid = await get_uid(bot, ev)
if uid is None:
return await bot.send(UID_HINT)
logger.info(f'[个人日历] uid: {uid}')
im = await draw_cale_img(ev, uid)
await bot.send_option(im)

View File

@ -0,0 +1,201 @@
from pathlib import Path
from typing import 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.convert import convert_img
from ..utils.api.mys.models import Act, FixedAct
from ..utils.mys_api import mys_api, get_base_data
from ..utils.resource.download_url import download
from ..genshinuid_xkdata.draw_teyvat_img import gen_char
from ..utils.fonts.genshin_fonts import gs_font_26, gs_font_30, gs_font_38
from ..utils.resource.RESOURCE_PATH import CHAR_PATH, TEMP_PATH, WEAPON_PATH
from ..utils.image.image_tools import (
get_v4_bg,
add_footer,
get_avatar,
get_v4_title,
)
GREY = (189, 189, 189)
TEXT_PATH = Path(__file__).parent / 'texture2d'
yes = Image.open(TEXT_PATH / 'yes.png')
no = Image.open(TEXT_PATH / 'no.png')
def convert_timestamp_to_string(timestamp):
days = timestamp // (24 * 3600)
hours = (timestamp % (24 * 3600)) // 3600
minutes = (timestamp % 3600) // 60
result = f"{days}{hours}{minutes}"
return result
async def draw_act(act: Union[Act, FixedAct]):
act_bg = Image.open(TEXT_PATH / 'act_bg.png')
fg = yes if act['is_finished'] else no
t = '已完成' if act['is_finished'] else '未完成'
act_bg.paste(fg, (0, 0), fg)
act_draw = ImageDraw.Draw(act_bg)
act_name = act['name']
limit_time = '剩余时间:' + convert_timestamp_to_string(
act['countdown_seconds']
)
act_draw.text((94, 60), act_name, 'white', gs_font_38, 'lm')
act_draw.text((130, 102), limit_time, GREY, gs_font_26, 'lm')
act_draw.text((840, 81), t, 'white', gs_font_30, 'mm')
for j, reward in enumerate(act['reward_list'][:2]):
icon_bg = Image.open(TEXT_PATH / 'icon_bg.png')
icon_fg = Image.open(TEXT_PATH / 'icon_fg.png')
ipath = TEMP_PATH / f'{reward["name"]}.png'
num = reward['num']
if not ipath.exists():
await download(reward['icon'], 16, f'{reward["name"]}.png')
icon = Image.open(ipath)
icon = icon.resize((100, 100))
icon_bg.paste(icon, (0, 0), icon)
icon_bg.paste(icon_fg, (0, 0), icon_fg)
icon_draw = ImageDraw.Draw(icon_bg)
icon_draw.text((50, 81), str(num), 'white', gs_font_26, 'mm')
act_bg.paste(icon_bg, (539 + j * 105, 31), icon_bg)
return act_bg
async def draw_cale_img(ev: Event, uid: str):
data = await mys_api.get_calendar_data(uid)
if isinstance(data, int):
return get_error(data)
raw_data = await get_base_data(uid)
if isinstance(raw_data, (str, bytes)):
return raw_data
elif isinstance(raw_data, (bytearray, memoryview)):
return bytes(raw_data)
char_pic = await get_avatar(ev, 377, False)
title_img = get_v4_title(char_pic, uid, raw_data)
title_img = title_img.resize((1058, 441))
w, h = 1000, title_img.size[1] + 385
if data['avatar_card_pool_list']:
h += 270
if data['weapon_card_pool_list']:
h += 270
h += len(data['act_list']) * 160
h += len(data['fixed_act_list']) * 160
img = get_v4_bg(w, h)
img.paste(title_img, (-30, 34), title_img)
p = title_img.size[1] + 60
bar1 = Image.open(TEXT_PATH / 'bar1.png')
img.paste(bar1, (0, p), bar1)
p += 60
if data['avatar_card_pool_list']:
pool_bg = Image.open(TEXT_PATH / 'pool_bg.png')
pool_draw = ImageDraw.Draw(pool_bg)
fd = data['avatar_card_pool_list'][0]
pool_draw.text((159, 61), fd['pool_name'], 'white', gs_font_38, 'lm')
pool_draw.text(
(110, 61), fd['version_name'], 'white', gs_font_30, 'mm'
)
limit_time = '剩余时间:' + convert_timestamp_to_string(
fd['countdown_seconds']
)
pool_draw.text((110, 106), limit_time, GREY, gs_font_26, 'lm')
for index, ap in enumerate(data['avatar_card_pool_list']):
avatars = ap['avatars']
for aindex, avatar in enumerate(avatars):
cpath = CHAR_PATH / f"{avatar['id']}.png"
if not cpath.exists():
await download(avatar['icon'], 1, f"{avatar['id']}.png")
char = Image.open(cpath)
char_bg = gen_char(char, avatar['rarity'])
char_bg = char_bg.resize((105, 105))
pool_bg.paste(
char_bg,
(64 + aindex * 105 + index * 444, 136),
char_bg,
)
img.paste(pool_bg, (0, p), pool_bg)
p += 270
if data['weapon_card_pool_list']:
pool_bg = Image.open(TEXT_PATH / 'pool_bg.png')
pool_draw = ImageDraw.Draw(pool_bg)
fd = data['weapon_card_pool_list'][0]
pool_draw.text((159, 61), fd['pool_name'], 'white', gs_font_38, 'lm')
pool_draw.text(
(110, 61), fd['version_name'], 'white', gs_font_30, 'mm'
)
limit_time = '剩余时间:' + convert_timestamp_to_string(
fd['countdown_seconds']
)
pool_draw.text((110, 106), limit_time, GREY, gs_font_26, 'lm')
for index, wp in enumerate(data['weapon_card_pool_list']):
weapon = wp['weapon']
for windex, weapon in enumerate(weapon):
cpath = WEAPON_PATH / f"{weapon['name']}.png"
if not cpath.exists():
await download(weapon['icon'], 5, f"{weapon['name']}.png")
char = Image.open(cpath)
char_bg = gen_char(char, weapon['rarity'])
char_bg = char_bg.resize((105, 105))
pool_bg.paste(
char_bg,
(64 + windex * 105 + index * 444, 136),
char_bg,
)
img.paste(pool_bg, (0, p), pool_bg)
p += 270
p += 30
bar2 = Image.open(TEXT_PATH / 'bar2.png')
img.paste(bar2, (0, p), bar2)
p += 60
for act in data['act_list']:
act_bg = await draw_act(act)
img.paste(act_bg, (0, p), act_bg)
p += 160
p += 30
bar3 = Image.open(TEXT_PATH / 'bar3.png')
img.paste(bar3, (0, p), bar3)
p += 60
for fixed_act in data['fixed_act_list']:
fixed_act_bg = await draw_act(fixed_act)
img.paste(fixed_act_bg, (0, p), fixed_act_bg)
p += 160
img = add_footer(img, 1000)
return await convert_img(img)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -1,6 +1,7 @@
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.utils.error_reply import UID_HINT
@ -16,7 +17,7 @@ async def send_gcg_pic(bot: Bot, ev: Event):
uid = await get_uid(bot, ev)
if uid is None:
return await bot.send(UID_HINT)
await bot.logger.info('[七圣召唤]uid: {}'.format(uid))
logger.info(f'[七圣召唤] uid: {uid}')
im = await draw_gcg_info(uid)
await bot.send_option(im, [Button('✅我的卡组', '我的卡组')])
@ -27,7 +28,7 @@ async def send_deck_pic(bot: Bot, ev: Event):
uid = await get_uid(bot, ev)
if uid is None:
return await bot.send(UID_HINT)
await bot.logger.info('[我的卡组]uid: {}'.format(uid))
logger.info(f'[我的卡组] uid: {uid}')
if not ev.text:
deck_id = 1
elif ev.text.strip().isdigit():

View File

@ -42,7 +42,8 @@ async def send_abyss_pic(bot: Bot, ev: Event):
@sv_get_abyss_database.on_fullmatch(
('深渊队伍', '深渊队伍统计', '深渊队伍推荐', '深渊组队'), block=True
('深渊队伍', '深渊队伍统计', '深渊队伍推荐', '深渊组队', '深渊配队'),
block=True,
)
async def send_abyss_team_pic(bot: Bot, ev: Event):
# await draw_xk_abyss_img()

View File

@ -46,6 +46,14 @@ async def get_teyvat_title(data: TeyvatAbyssRank):
return title
def gen_char(char: Image.Image, char_star: int):
char = char.resize((128, 128))
char = char.convert('RGBA')
char_bg = Image.open(TEXT_PATH / f'char{char_star}_bg.png')
char_bg.paste(char, (11, 11), char)
return char_bg
async def draw_teyvat_team_img():
data = await teyvat_api.get_abyss_rank()
if isinstance(data, int):
@ -92,9 +100,7 @@ async def draw_teyvat_team_img():
await download(icon_url, 16, char_name)
char = Image.open(char_path).convert('RGBA')
char = char.resize((128, 128))
char_bg = Image.open(TEXT_PATH / f'char{role["star"]}_bg.png')
char_bg.paste(char, (11, 11), char)
char_bg = gen_char(char, role["star"])
bar.paste(char_bg, (55 + rindex * 150, 20), char_bg)
bar_draw = ImageDraw.Draw(bar)

View File

@ -1,4 +1,5 @@
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'
char_detail_url = f'{base_url}/game_record/app/genshin/api/character/detail'
widget_url = '/game_record/genshin/aapi/widget/v2'
new_abyss_url = '/game_record/app/genshin/api/role_combat'
char_detail_url = '/game_record/app/genshin/api/character/detail'
calendar_url = '/game_record/app/genshin/api/act_calendar'

View File

@ -1,4 +1,114 @@
from typing import List, Literal, TypedDict
from typing import List, Literal, Optional, TypedDict
class PoetryAbyssDateTime(TypedDict):
year: int
month: int
day: int
hour: int
minute: int
second: int
class AvatarPool(TypedDict):
id: int
icon: str
name: str
element: str
rarity: int
is_invisible: bool
class WeaponPool(TypedDict):
id: int
icon: str
rarity: int
name: str
wiki_url: str
class RewardItem(TypedDict):
item_id: int
name: str
icon: str
wiki_url: str
num: int
rarity: str
homepage_show: bool
class Act(TypedDict):
id: int
name: str
type: str
start_timestamp: int
start_time: PoetryAbyssDateTime
end_timestamp: int
end_time: PoetryAbyssDateTime
desc: str
strategy: str
countdown_seconds: int
status: int
reward_list: List[RewardItem]
is_finished: bool
class RoleCombatDetail(TypedDict):
is_unlock: bool
max_round_id: int
has_data: bool
class TowerDetail(TypedDict):
is_unlock: bool
max_star: int
total_star: int
has_data: bool
class FixedAct(TypedDict):
id: int
name: str
type: str
start_timestamp: int
start_time: PoetryAbyssDateTime
end_timestamp: int
end_time: PoetryAbyssDateTime
desc: str
strategy: str
countdown_seconds: int
status: int
reward_list: List[RewardItem]
is_finished: bool
role_combat_detail: Optional[RoleCombatDetail]
tower_detail: Optional[TowerDetail]
class Pool(TypedDict):
pool_id: int
version_name: str
pool_name: str
pool_type: int
avatars: List[AvatarPool]
weapon: List[WeaponPool]
start_timestamp: int
start_time: PoetryAbyssDateTime
end_timestamp: int
end_time: PoetryAbyssDateTime
jump_url: str
pool_status: int
countdown_seconds: int
class CalendarData(TypedDict):
avatar_card_pool_list: List[Pool]
weapon_card_pool_list: List[Pool]
mixed_card_pool_list: List[Pool]
selected_avatar_card_pool_list: List[Pool]
selected_mixed_card_pool_list: List[Pool]
act_list: List[Act]
fixed_act_list: List[FixedAct] # Updated to include FixedAct
selected_act_list: List[Act]
class Expedition(TypedDict):
@ -109,15 +219,6 @@ class PoetryAbyssBuff(TypedDict):
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

View File

@ -2,10 +2,11 @@ from copy import deepcopy
from typing import Dict, List, Union, cast
from gsuid_core.utils.api.mys_api import _MysApi
from gsuid_core.utils.api.mys.api import RECORD_BASE, RECORD_BASE_OS
from gsuid_core.utils.api.mys.tools import get_ds_token, get_web_ds_token
from .api import widget_url, new_abyss_url, char_detail_url
from .models import Character, WidgetResin, PoetryAbyssDatas
from .api import widget_url, calendar_url, new_abyss_url, char_detail_url
from .models import Character, WidgetResin, CalendarData, PoetryAbyssDatas
class GsMysAPI(_MysApi):
@ -14,6 +15,26 @@ class GsMysAPI(_MysApi):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
async def get_calendar_data(self, uid: str) -> Union[CalendarData, int]:
server_id = self.RECOGNIZE_SERVER.get(uid[0])
header = deepcopy(self._HEADER)
ck = await self.get_ck(uid, 'OWNER')
if ck is None:
return -51
header['Cookie'] = ck
base = RECORD_BASE_OS if self.check_os(uid) else RECORD_BASE
data = await self._mys_request(
calendar_url,
'POST',
header,
data={"role_id": uid, "server": server_id},
base_url=base,
)
if isinstance(data, Dict):
data = cast(CalendarData, data['data'])
return data
async def get_widget_resin_data(self, uid: str) -> Union[WidgetResin, int]:
header = deepcopy(self._HEADER)
sk = await self.get_stoken(uid)
@ -22,8 +43,13 @@ class GsMysAPI(_MysApi):
header['Cookie'] = sk
header['DS'] = get_web_ds_token(True)
header['x-rpc-channel'] = 'miyousheluodi'
base = RECORD_BASE_OS if self.check_os(uid) else RECORD_BASE
data = await self._mys_request(
widget_url, 'GET', header, {'game_id': 2}
widget_url,
'GET',
header,
{'game_id': 2},
base_url=base,
)
if isinstance(data, Dict):
data = cast(WidgetResin, data['data'])
@ -33,6 +59,7 @@ class GsMysAPI(_MysApi):
self, uid: str
) -> Union[PoetryAbyssDatas, int]:
server_id = self.RECOGNIZE_SERVER.get(uid[0])
base = RECORD_BASE_OS if self.check_os(uid) else RECORD_BASE
HEADER = deepcopy(self._HEADER)
ck = await self.get_ck(uid, 'OWNER')
if ck is None:
@ -51,6 +78,7 @@ class GsMysAPI(_MysApi):
'GET',
HEADER,
params,
base_url=base,
)
if isinstance(data, Dict):
data = cast(PoetryAbyssDatas, data['data'])
@ -66,6 +94,7 @@ class GsMysAPI(_MysApi):
return -51
HEADER['Cookie'] = ck
base = RECORD_BASE_OS if self.check_os(uid) else RECORD_BASE
data = await self._mys_request(
char_detail_url,
'POST',
@ -75,6 +104,7 @@ class GsMysAPI(_MysApi):
"server": server_id,
"character_ids": char_id_list,
},
base_url=base,
)
if isinstance(data, Dict):