新增尘歌壶摹本材料查询,优化部分提示

This commit is contained in:
CMHopeSunshine 2022-11-16 17:41:50 +08:00
parent aa6262fad1
commit d8cc490c32
9 changed files with 250 additions and 31 deletions

View File

@ -260,7 +260,7 @@ class MihoyoBBSCoin:
num_cancel += 1 num_cancel += 1
logger.info('米游币自动获取', '➤➤点赞任务<g>完成</g>') logger.info('米游币自动获取', '➤➤点赞任务<g>完成</g>')
await asyncio.sleep(random.randint(5, 10)) await asyncio.sleep(random.randint(5, 10))
return f'点赞帖子:完成{str(num_ok)}' return f'点赞帖子:完成{str(num_ok)}{",遇验证码" if num_ok == 0 else ""}'
async def share_post(self): async def share_post(self):
""" """

View File

@ -104,9 +104,12 @@ async def check_note():
except Exception as e: except Exception as e:
logger.info('原神实时便签', '➤➤', {'用户': sub.user_id, 'UID': sub.uid}, f'发送提醒失败,{e}', False) logger.info('原神实时便签', '➤➤', {'用户': sub.user_id, 'UID': sub.uid}, f'发送提醒失败,{e}', False)
await sub.delete() await sub.delete()
elif data['retcode'] == 1034:
logger.info('原神实时便签', '', {'用户': sub.user_id, 'UID': sub.uid},
'获取数据失败状态码为1034 疑似验证码', False)
elif data['retcode'] != 0: elif data['retcode'] != 0:
logger.info('原神实时便签', '', {'用户': sub.user_id, 'UID': sub.uid}, logger.info('原神实时便签', '', {'用户': sub.user_id, 'UID': sub.uid},
f'获取数据失败code为{data["retcode"]} msg为{data["message"]}', False) f'获取数据失败,状态码{data["retcode"]} msg为{data["message"]}', False)
else: else:
result = result_log = '' result = result_log = ''
if sub.resin_num is not None and data['data']['current_resin'] > sub.resin_num: if sub.resin_num is not None and data['data']['current_resin'] > sub.resin_num:

View File

@ -26,7 +26,7 @@ update_log = on_command('更新抽卡记录', aliases={'抽卡记录更新', '
'pm_usage': '更新抽卡记录(uid)', 'pm_usage': '更新抽卡记录(uid)',
'pm_priority': 1 'pm_priority': 1
}) })
show_log = on_command('查看抽卡记录', aliases={'抽卡记录'}, priority=12, block=True, state={ show_log = on_command('查看抽卡记录', aliases={'抽卡记录', '查询抽卡记录'}, priority=12, block=True, state={
'pm_name': '查看抽卡记录', 'pm_name': '查看抽卡记录',
'pm_description': '*查看你的抽卡记录分析', 'pm_description': '*查看你的抽卡记录分析',
'pm_usage': '查看抽卡记录(uid)', 'pm_usage': '查看抽卡记录(uid)',

View File

@ -0,0 +1 @@
from .draw import draw_pot_materials

View File

@ -0,0 +1,77 @@
from typing import List, Union, Optional, Tuple
from LittlePaimon.database import PublicCookie, PrivateCookie
from LittlePaimon.utils import logger
from LittlePaimon.utils.requests import aiorequests
from pydantic import parse_obj_as
from .models import Item
HEADER = {
'Host': 'api-takumi.mihoyo.com',
'Origin': 'https://webstatic.mihoyo.com',
'Connection': 'keep-alive',
'Accept': 'application/json, text/plain, */*',
'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/2.11.1',
'Accept-Language': 'zh-CN,zh-Hans;q=0.9',
'Referer': 'https://webstatic.mihoyo.com/',
'Accept-Encoding': 'gzip, deflate, br'
}
CODE_API = 'https://api-takumi.mihoyo.com/event/e20200928calculate/v1/furniture/blueprint'
MATERIAL_API = 'https://api-takumi.mihoyo.com/event/e20200928calculate/v1/furniture/compute'
async def get_blueprint_data(share_code: int, user_id: Optional[str]) -> Tuple[Union[List[Item], str, int, None], Optional[str]]:
cookies: List[Union[PrivateCookie, PublicCookie]] = []
if user_id and (private_cookies := await PrivateCookie.filter(status=1).all()):
cookies.extend(private_cookies)
if public_cookies := await PublicCookie.filter(status__in=[1, 2]).all():
cookies.extend(public_cookies)
if not cookies:
return None, None
headers = HEADER.copy()
for cookie in cookies:
headers['Cookie'] = cookie.cookie
resp = await aiorequests.get(CODE_API,
params={
'share_code': share_code
},
headers=headers)
data = resp.json()
if data['retcode'] == 0:
items = parse_obj_as(List[Item], data['data']['list'])
items.sort(key=lambda i: (i.num, i.level), reverse=True)
logger.info('尘歌壶摹本', f'摹数<m>{share_code}</m>数据<g>获取成功</g>')
return items, cookie.cookie
elif data['retcode'] != -100:
logger.info('尘歌壶摹本', f'摹数<m>{share_code}</m>数据<r>获取失败,{data["message"] or str(data["retcode"])}</r>')
return data['message'] or str(data['retcode']), None
logger.info('尘歌壶摹本', f'摹数<m>{share_code}</m>数据<r>获取失败没有可用的Cookie</r>')
return None, None
async def get_blueprint_material_data(items: List[Item], cookie: Optional[str]) -> Union[List[Item], str, int, None]:
if cookie is None:
return None
item_list = {
'list': [
{
'cnt': item.num,
'id': item.id
} for item in items
]
}
headers = HEADER.copy()
headers['Cookie'] = cookie
resp = await aiorequests.post(MATERIAL_API,
json=item_list,
headers=headers)
data = resp.json()
if data['retcode'] == 0:
items = parse_obj_as(List[Item], data['data']['list'])
items.sort(key=lambda i: (i.num, i.level), reverse=True)
return items
elif data['retcode'] != -100:
return data['message'] or str(data['retcode'])
else:
return None

View File

@ -0,0 +1,105 @@
import asyncio
import math
from typing import Optional
from nonebot.adapters.onebot.v11 import MessageSegment
from LittlePaimon.utils.files import load_image
from LittlePaimon.utils.image import PMImage, font_manager as fm
from LittlePaimon.utils.message import MessageBuild
from LittlePaimon.utils.path import RESOURCE_BASE_PATH
from LittlePaimon.utils.requests import aiorequests
from .api import get_blueprint_data, get_blueprint_material_data
from .models import Item
FURNITURE_PATH = RESOURCE_BASE_PATH / 'furniture'
FURNITURE_PATH.mkdir(parents=True, exist_ok=True)
num_color = [('#f6b9c9', '#a90d35'), ('#f2cab9', '#ff6f30'), ('#b9d8f2', '#157eaa'), ('#dedede', '#707070')]
async def draw_item(item: Item, total_num: int):
# 背景图
bg = PMImage(RESOURCE_BASE_PATH / 'icon' / f'star{item.level if item.level != 0 else 2}.png')
await bg.resize((180, 180))
# 摆设图标
if (path := FURNITURE_PATH / item.icon_url.split('/')[-1]).exists():
icon = await load_image(path, size=(180, 180))
else:
icon = await aiorequests.get_img(item.icon_url, size=(180, 180), save_path=path)
await bg.paste(icon, (0, 0))
# 摆设名称
if bg.text_length(item.name, fm.get('hywh', 24)) >= 180:
c = len(item.name) // 2
await bg.text(item.name[:c], (1, 181), 126, fm.get('hywh', 24), 'black', 'center')
await bg.text(item.name[:c], (0, 180), 125, fm.get('hywh', 24), 'white', 'center')
await bg.text(item.name[c:], (1, 181), 151, fm.get('hywh', 24), 'black', 'center')
await bg.text(item.name[c:], (0, 180), 150, fm.get('hywh', 24), 'white', 'center')
else:
await bg.text(item.name, (1, 181), 151, fm.get('hywh', 24), 'black', 'center')
await bg.text(item.name, (0, 180), 150, fm.get('hywh', 24), 'white', 'center')
# 摆设数量
if item.level == 0:
color = num_color[3]
else:
# 根据占总数量的百分比来决定颜色
percent = item.num / total_num
if percent >= 0.2:
color = num_color[0]
elif 0.1 <= percent < 0.2:
color = num_color[1]
elif 0.05 <= percent <= 0.1:
color = num_color[2]
else:
color = num_color[3]
num_card_length = 60 if item.num < 100 else 120
await bg.draw_rounded_rectangle2((180 - num_card_length, 1), (num_card_length - 1, 33), 17, color[0], ['ur', 'll'])
await bg.text(str(item.num), (180 - num_card_length, 179), 1, fm.get('bahnschrift_regular', 40, 'Bold'), color[1],
'center')
return bg
async def draw_pot_materials(share_code: int, user_id: Optional[str] = None):
save_path = FURNITURE_PATH / f'摹本{share_code}.png'
if save_path.exists():
return MessageSegment.image(file=save_path)
items, cookie = await get_blueprint_data(share_code, user_id)
material_items = await get_blueprint_material_data(items, cookie)
if items is None:
return '没有能够查询尘歌壶摹本的Cookie请联系超管添加~'
elif isinstance(items, str):
return items
img = PMImage(RESOURCE_BASE_PATH / 'general' / 'bg.png')
row = math.ceil(len(items) / 5)
if isinstance(material_items, list):
row += math.ceil(len(material_items) / 5)
await img.stretch((210, 1890), 205 * row + (120 if isinstance(material_items, list) else 0) - 30, 'height')
# 标题
await img.text('尘歌壶摹数材料图', 36, 29, fm.get('优设标题黑', 72), '#40342d')
bubble = PMImage(await load_image(RESOURCE_BASE_PATH / 'general' / 'bubble.png'))
code_length = img.text_length(str(share_code), fm.get('SourceHanSansCN-Bold.otf', 30))
await bubble.stretch((15, int(bubble.width) - 15), code_length, 'width')
await img.paste(bubble, (560, 38))
await img.text(str(share_code), 573, 41, fm.get('SourceHanSansCN-Bold.otf', 30), 'white')
# 摆设提示线
item_line = await load_image(RESOURCE_BASE_PATH / 'general' / 'line.png')
await img.paste(item_line, (40, 144))
await img.text('摆设一览', 63, 156, fm.get('SourceHanSansCN-Bold.otf', 30), 'white')
# logo和生成时间
await img.text(f'CREATED BY LITTLEPAIMON', 1025, 158, fm.get('bahnschrift_regular.ttf', 30), '#8c4c2e', 'right')
total_num = sum(item.num for item in items)
# 摆设一览
await asyncio.gather(*[
img.paste(await draw_item(item, total_num), (40 + 205 * (i % 5), 230 + 205 * (i // 5)))
for i, item in enumerate(items)])
if isinstance(material_items, list):
now_height = 240 + 205 * math.ceil(len(items) / 5)
# 材料提示线
await img.paste(item_line, (40, now_height))
await img.text('材料一览', 63, now_height + 12, fm.get('SourceHanSansCN-Bold.otf', 30), 'white')
# 材料一览
await asyncio.gather(*[
img.paste(await draw_item(item, total_num), (40 + 205 * (i % 5), now_height + 80 + 205 * (i // 5)))
for i, item in enumerate(material_items)])
await img.save(save_path, mode='RGB', quality=85)
return MessageBuild.Image(img, mode='RGB', quality=85)

View File

@ -0,0 +1,13 @@
from typing import Optional
from pydantic import BaseModel
class Item(BaseModel):
id: int
name: str
icon: Optional[str]
num: int
wiki_url: Optional[str]
level: int
icon_url: str

View File

@ -17,6 +17,7 @@ from LittlePaimon.utils.path import RESOURCE_BASE_PATH
from LittlePaimon.utils.tool import freq_limiter from LittlePaimon.utils.tool import freq_limiter
from .draw_daily_material import draw_material from .draw_daily_material import draw_material
from .draw_map import init_map, draw_map, get_full_map from .draw_map import init_map, draw_map, get_full_map
from .SereniteaPot import draw_pot_materials
__paimon_help__ = { __paimon_help__ = {
'type': '原神Wiki', 'type': '原神Wiki',
@ -68,6 +69,12 @@ generate_map = on_command('生成地图', priority=1, block=True, permission=SUP
'pm_usage': '生成地图', 'pm_usage': '生成地图',
'pm_priority': 11 'pm_priority': 11
}) })
pot_material = on_command('尘歌壶摹本', aliases={'摹本材料', '尘歌壶材料', '尘歌壶摹本材料'}, priority=11, block=True, state={
'pm_name': '尘歌壶摹本材料',
'pm_description': '查看尘歌壶摹本所需要的材料总览',
'pm_usage': '尘歌壶材料<摹数>',
'pm_priority': 12
})
week_str = ['周一', '周二', '周三', '周四', '周五', '周六'] week_str = ['周一', '周二', '周三', '周四', '周五', '周六']
@ -162,6 +169,20 @@ async def _(event: MessageEvent):
await generate_map.finish(result) await generate_map.finish(result)
@pot_material.handle()
async def _(event: MessageEvent, msg: Message = CommandArg()):
msg = msg.extract_plain_text().strip()
if len(msg) != 10 and not msg.isdigit():
await pot_material.finish('这个尘歌壶摹数不对哦,必须是十位数的数字')
else:
try:
code = int(msg)
result = await draw_pot_materials(code, user_id=str(event.user_id))
except Exception as e:
result = f'绘制尘歌壶摹本材料失败,错误信息:{e}'
await pot_material.finish(result, at_sender=True)
def create_wiki_matcher(pattern: str, help_fun: str, help_name: str): def create_wiki_matcher(pattern: str, help_fun: str, help_name: str):
maps = on_regex(pattern, priority=11, block=True, state={ maps = on_regex(pattern, priority=11, block=True, state={
'pm_name': help_fun, 'pm_name': help_fun,

View File

@ -32,8 +32,8 @@ AUTHKEY_API = 'https://api-takumi.mihoyo.com/binding/api/genAuthKey'
def md5(text: str) -> str: def md5(text: str) -> str:
""" """
md5加密 md5加密
:param text: 文本 :param text: 文本
:return: md5加密后的文本 :return: md5加密后的文本
""" """
md5_ = hashlib.md5() md5_ = hashlib.md5()
md5_.update(text.encode()) md5_.update(text.encode())
@ -43,8 +43,8 @@ def md5(text: str) -> str:
def random_hex(length: int) -> str: def random_hex(length: int) -> str:
""" """
生成指定长度的随机字符串 生成指定长度的随机字符串
:param length: 长度 :param length: 长度
:return: 随机字符串 :return: 随机字符串
""" """
result = hex(random.randint(0, 16 ** length)).replace('0x', '').upper() result = hex(random.randint(0, 16 ** length)).replace('0x', '').upper()
if len(result) < length: if len(result) < length:
@ -55,8 +55,8 @@ def random_hex(length: int) -> str:
def random_text(length: int) -> str: def random_text(length: int) -> str:
""" """
生成指定长度的随机字符串 生成指定长度的随机字符串
:param length: 长度 :param length: 长度
:return: 随机字符串 :return: 随机字符串
""" """
return ''.join(random.sample(string.ascii_lowercase + string.digits, length)) return ''.join(random.sample(string.ascii_lowercase + string.digits, length))
@ -64,10 +64,10 @@ def random_text(length: int) -> str:
def get_ds(q: str = '', b: dict = None, mhy_bbs_sign: bool = False) -> str: def get_ds(q: str = '', b: dict = None, mhy_bbs_sign: bool = False) -> str:
""" """
生成米游社headers的ds_token 生成米游社headers的ds_token
:param q: 查询 :param q: 查询
:param b: 请求体 :param b: 请求体
:param mhy_bbs_sign: 是否为米游社讨论区签到 :param mhy_bbs_sign: 是否为米游社讨论区签到
:return: ds_token :return: ds_token
""" """
br = json.dumps(b) if b else '' br = json.dumps(b) if b else ''
if mhy_bbs_sign: if mhy_bbs_sign:
@ -97,10 +97,10 @@ def get_old_version_ds(mhy_bbs: bool = False) -> str:
def mihoyo_headers(cookie, q='', b=None) -> dict: def mihoyo_headers(cookie, q='', b=None) -> dict:
""" """
生成米游社headers 生成米游社headers
:param cookie: cookie :param cookie: cookie
:param q: 查询 :param q: 查询
:param b: 请求体 :param b: 请求体
:return: headers :return: headers
""" """
return { return {
'DS': get_ds(q, b), 'DS': get_ds(q, b),
@ -117,9 +117,9 @@ def mihoyo_headers(cookie, q='', b=None) -> dict:
def mihoyo_sign_headers(cookie: str, extra_headers: Optional[dict] = None) -> dict: def mihoyo_sign_headers(cookie: str, extra_headers: Optional[dict] = None) -> dict:
""" """
生成米游社签到headers 生成米游社签到headers
:param cookie: cookie :param cookie: cookie
:param extra_headers: 额外的headers参数 :param extra_headers: 额外的headers参数
:return: headers :return: headers
""" """
header = { header = {
'User_Agent': 'Mozilla/5.0 (Linux; Android 12; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) ' 'User_Agent': 'Mozilla/5.0 (Linux; Android 12; Unspecified Device) AppleWebKit/537.36 (KHTML, like Gecko) '
@ -142,12 +142,12 @@ def mihoyo_sign_headers(cookie: str, extra_headers: Optional[dict] = None) -> di
async def check_retcode(data: dict, cookie_info, cookie_type: str, user_id: str, uid: str) -> bool: async def check_retcode(data: dict, cookie_info, cookie_type: str, user_id: str, uid: str) -> bool:
""" """
检查数据响应状态冰进行响应处理 检查数据响应状态冰进行响应处理
:param data: 数据 :param data: 数据
:param cookie_info: cookie信息 :param cookie_info: cookie信息
:param cookie_type: cookie类型 :param cookie_type: cookie类型
:param user_id: 用户id :param user_id: 用户id
:param uid: 原神uid :param uid: 原神uid
:return: 数据是否有效 :return: 数据是否有效
""" """
if not data: if not data:
return False return False
@ -194,11 +194,10 @@ async def get_cookie(user_id: str, uid: str, check: bool = True, own: bool = Fal
Union[None, PrivateCookie, PublicCookie, CookieCache], str]: Union[None, PrivateCookie, PublicCookie, CookieCache], str]:
""" """
获取可用的cookie 获取可用的cookie
:param user_id: 用户id :param user_id: 用户id
:param uid: 原神uid :param uid: 原神uid
:param check: 是否获取疑似失效的cookie :param check: 是否获取疑似失效的cookie
:param own: 是否只获取和uid对应的cookie :param own: 是否只获取和uid对应的cookie
:return:
""" """
query = Q(status=1) | Q(status=0) if check else Q(status=1) query = Q(status=1) | Q(status=0) if check else Q(status=1)
if private_cookie := await PrivateCookie.filter(Q(Q(query) & Q(user_id=user_id) & Q(uid=uid))).first(): if private_cookie := await PrivateCookie.filter(Q(Q(query) & Q(user_id=user_id) & Q(uid=uid))).first():