💥 移除gsuid_utils(使用gs全部更新) (#526)

This commit is contained in:
Wuyi无疑 2023-04-27 01:24:04 +08:00
parent 569c774be6
commit 821be7c4f5
65 changed files with 184 additions and 10137 deletions

View File

@ -5,12 +5,12 @@ from typing import Union, Optional
from PIL import Image, ImageDraw
from gsuid_core.logger import logger
from gsuid_core.utils.api.mys.models import AbyssBattleAvatar
from ..utils.convert import GsCookie
from ..utils.error_reply import get_error
from ..utils.image.convert import convert_img
from ..utils.resource.download_url import download_file
from ..gsuid_utils.api.mys.models import AbyssBattleAvatar
from ..utils.resource.generate_char_card import create_single_char_card
from ..utils.resource.RESOURCE_PATH import CHAR_CARD_PATH, CHAR_SIDE_PATH
from ..utils.image.image_tools import (

View File

@ -6,11 +6,11 @@ from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.aps import scheduler
from gsuid_core.utils.database.models import GsUser
from ..utils.mys_api import mys_api
from .backup_data import data_backup
from ..utils.database import get_sqla
from ..gsuid_utils.database.models import GsUser
sv_data_manger = SV('数据管理', pm=2)

View File

@ -2,11 +2,11 @@ from pathlib import Path
from typing import Dict, Tuple, Union, Literal
from PIL import Image, ImageDraw
from gsuid_core.utils.api.mys.models import IndexData
from ..utils.convert import GsCookie
from ..utils.image.convert import convert_img
from ..utils.map.GS_MAP_PATH import avatarId2Name
from ..gsuid_utils.api.mys.models import IndexData
from ..utils.fonts.genshin_fonts import gs_font_30, gs_font_40
from ..utils.image.image_tools import (
draw_bar,

View File

@ -53,6 +53,8 @@ async def send_char_info(bot: Bot, ev: Event):
if im[1]:
with open(TEMP_PATH / f'{ev.msg_id}.jpg', 'wb') as f:
f.write(im[1])
elif im is None:
return
else:
await bot.send('发生未知错误')

View File

@ -3,6 +3,12 @@ from typing import Dict, List, Tuple, Optional
from httpx import ConnectTimeout
from gsuid_core.logger import logger
from gsuid_core.utils.api.minigg.request import (
get_weapon_info,
get_weapon_stats,
get_character_info,
get_character_stats,
)
from .Power import sp_prop
from ..etc.beta_weapon import beta_weapons
@ -24,12 +30,6 @@ from ..etc.base_info import (
baseFightProp,
baseWeaponInfo,
)
from ...gsuid_utils.api.minigg.request import (
get_weapon_info,
get_weapon_stats,
get_character_info,
get_character_stats,
)
class Character:

View File

@ -3,10 +3,10 @@ from pathlib import Path
from typing import List, Union, Optional
from PIL import Image, ImageDraw
from gsuid_core.utils.api.enka.models import EnkaData
from .to_data import enka_to_dict
from ..utils.image.convert import convert_img
from ..gsuid_utils.api.enka.models import EnkaData
from ..utils.resource.RESOURCE_PATH import CHAR_PATH
from ..utils.fonts.genshin_fonts import gs_font_18, gs_font_58
from ..utils.map.name_covert import name_to_avatar_id, avatar_id_to_char_star

View File

@ -3,13 +3,13 @@ import time
from typing import List, Union, Literal, Optional
from httpx import ReadTimeout, ConnectTimeout
from gsuid_core.utils.api.enka.models import EnkaData
from gsuid_core.utils.api.enka.request import get_enka_info
from gsuid_core.utils.api.minigg.request import get_weapon_info
from ..utils.error_reply import UID_HINT
from ..gsuid_utils.api.enka.models import EnkaData
from ..utils.resource.RESOURCE_PATH import PLAYER_PATH
from ..gsuid_utils.api.enka.request import get_enka_info
from ..utils.ambr_to_minigg import convert_ambr_to_weapon
from ..gsuid_utils.api.minigg.request import get_weapon_info
from ..utils.map.GS_MAP_PATH import (
icon2Name,
propId2Name,
@ -264,10 +264,10 @@ async def enka_to_dict(
if not isinstance(effect_raw, List) and not isinstance(
effect_raw, int
):
effect = effect_raw['effect'].format(
*effect_raw[
effect = effect_raw['effect'].format( # type:ignore
*effect_raw[ # type:ignore
'r{}'.format(str(weapon_info['weaponAffix']))
] # type:ignore
]
)
else:
effect = '无特效。'

View File

@ -5,12 +5,12 @@ from typing import List, Literal
from httpx import get
from PIL import Image, ImageDraw
from gsuid_core.utils.api.ambr.request import get_ambr_event_info
from ..version import Genshin_version
from ..utils.image.convert import convert_img
from ..utils.image.image_tools import get_color_bg
from ..utils.fonts.genshin_fonts import genshin_font_origin
from ..gsuid_utils.api.ambr.request import get_ambr_event_info
TEXT_PATH = Path(__file__).parent / 'texture2d'
EVENT_IMG_PATH = Path(__file__).parent / 'event.jpg'

View File

@ -5,10 +5,10 @@ import httpx
import aiofiles
from gsuid_core.logger import logger
from urllib3 import encode_multipart_formdata
from gsuid_core.utils.api.mys.request import RECOGNIZE_SERVER
from ..utils.mys_api import mys_api
from ..utils.error_reply import get_error
from ..gsuid_utils.api.mys.request import RECOGNIZE_SERVER
from .export_and_import import export_gachalogs, import_gachalogs

View File

@ -4,13 +4,13 @@ from typing import Dict, List, Union
import aiofiles
from gsuid_core.logger import logger
from .abyss_history import history_data
from ..gsuid_utils.api.hhw.request import (
from gsuid_core.utils.api.hhw.request import (
get_abyss_review,
get_abyss_review_raw,
)
from .abyss_history import history_data
REVIEW_PATH = Path(__file__).parent / 'review.json'

View File

@ -1,8 +1,8 @@
from pathlib import Path
from typing import Union
from ..gsuid_utils.api.minigg.request import get_map_data
from ..gsuid_utils.api.minigg.exception import MiniggNotFoundError
from gsuid_core.utils.api.minigg.request import get_map_data
from gsuid_core.utils.api.minigg.exception import MiniggNotFoundError
MAP_DATA = Path(__file__).parent / 'map_data'

View File

@ -5,15 +5,13 @@ from typing import Dict, Literal
from httpx import AsyncClient
from gsuid_core.logger import logger
from ..utils.mys_api import mys_api
from ..gsuid_utils.api.mys.tools import (
from gsuid_core.utils.api.mys.tools import (
random_hex,
random_text,
get_ds_token,
get_web_ds_token,
)
from ..gsuid_utils.api.mys.api import (
from gsuid_core.utils.api.mys.api import (
BBS_LIKE_URL,
BBS_LIST_URL,
BBS_SIGN_URL,
@ -22,6 +20,8 @@ from ..gsuid_utils.api.mys.api import (
BBS_TASKS_LIST,
)
from ..utils.mys_api import mys_api
mihoyobbs_List = [
{
'id': '1',

View File

@ -5,12 +5,12 @@ from pathlib import Path
from PIL import Image, ImageDraw
from gsuid_core.logger import logger
from gsuid_core.utils.api.mys.models import Expedition
from ..utils.mys_api import mys_api
from ..utils.database import get_sqla
from ..utils.image.convert import convert_img
from ..genshinuid_enka.to_data import get_enka_info
from ..gsuid_utils.api.mys.models import Expedition
from ..utils.image.image_tools import get_simple_bg
from ..utils.map.name_covert import enName_to_avatarId
from ..utils.resource.RESOURCE_PATH import PLAYER_PATH, CHAR_SIDE_PATH

View File

@ -2,11 +2,11 @@ from typing import Dict
from gsuid_core.gss import gss
from gsuid_core.logger import logger
from gsuid_core.utils.api.mys.models import DailyNoteData
from ..utils.mys_api import mys_api
from ..utils.database import get_sqla
from ..genshinuid_config.gs_config import gsconfig
from ..gsuid_utils.api.mys.models import DailyNoteData
MR_NOTICE = '\n可发送[mr]或者[每日]来查看更多信息!\n'

View File

@ -3,12 +3,12 @@ from pathlib import Path
from PIL import Image, ImageDraw
from gsuid_core.logger import logger
from gsuid_core.utils.api.mys.models import MihoyoAvatar
from ..utils.mys_api import mys_api
from ..utils.convert import GsCookie
from ..utils.error_reply import get_error
from ..utils.image.convert import convert_img
from ..gsuid_utils.api.mys.models import MihoyoAvatar
from ..utils.resource.download_url import download_file
from ..utils.fonts.genshin_fonts import genshin_font_origin
from ..utils.resource.RESOURCE_PATH import (

View File

@ -10,12 +10,12 @@ from gsuid_core.bot import Bot
from qrcode import ERROR_CORRECT_L
from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment
from gsuid_core.utils.api.mys.models import MysOrder
from ..utils.mys_api import mys_api
from ..utils.database import get_sqla
from ..utils.error_reply import get_error
from .draw_topup_img import draw_wx, draw_ali
from ..gsuid_utils.api.mys.models import MysOrder
disnote = '''免责声明:
该充值接口由米游社提供,不对充值结果负责

View File

@ -116,8 +116,14 @@ async def _deal_ck(bot_id: str, mes: str, user_id: str) -> str:
sqla = get_sqla(bot_id)
simp_dict = SimpleCookie(mes)
uid = await sqla.get_bind_uid(user_id)
if uid is None:
return UID_HINT
sr_uid = await sqla.get_bind_sruid(user_id)
if uid is None and sr_uid is None:
if uid is None:
return UID_HINT
elif sr_uid is None:
return '请绑定星穹铁道UID...'
im_list = []
is_add_stoken = False
status = True
@ -193,7 +199,7 @@ async def _deal_ck(bot_id: str, mes: str, user_id: str) -> str:
account_cookie = f'account_id={account_id};cookie_token={cookie_token}'
try:
if int(uid[0]) < 6:
if sr_uid or (uid and int(uid[0]) < 6):
mys_data = await mys_api.get_mihoyo_bbs_info(
account_id, account_cookie
)
@ -206,9 +212,13 @@ async def _deal_ck(bot_id: str, mes: str, user_id: str) -> str:
for i in mys_data:
if i['game_id'] == 2:
uid = i['game_role_id']
elif i['game_id'] == 6:
sr_uid = i['game_role_id']
if uid and sr_uid:
break
else:
return f'你的米游社账号{account_id}尚未绑定原神账号,请前往米游社操作!'
if not (uid or sr_uid):
return f'你的米游社账号{account_id}尚未绑定原神/星铁账号,请前往米游社操作!'
except Exception:
pass
@ -218,7 +228,9 @@ async def _deal_ck(bot_id: str, mes: str, user_id: str) -> str:
await sqla.refresh_cache(uid)
if is_add_stoken:
im_list.append(f'添加Stoken成功,stuid={account_id},stoken={stoken}')
await sqla.insert_user_data(user_id, uid, account_cookie, app_cookie)
await sqla.insert_user_data(
user_id, uid, sr_uid, account_cookie, app_cookie
)
im_list.append(
f'添加Cookies成功,account_id={account_id},cookie_token={cookie_token}'

View File

@ -2,10 +2,10 @@ from pathlib import Path
from typing import List, Tuple, Optional
from PIL import Image, ImageDraw
from gsuid_core.utils.database.models import GsUser
from ..utils.database import get_sqla
from ..utils.image.convert import convert_img
from ..gsuid_utils.database.models import GsUser
from ..utils.colors import sec_color, first_color
from ..utils.fonts.genshin_fonts import gs_font_15, gs_font_30, gs_font_36
from ..utils.image.image_tools import (

View File

@ -3,13 +3,13 @@ from typing import Tuple, Union
import aiofiles
from PIL import Image, ImageDraw
from gsuid_core.utils.api.minigg.models import Artifact
from gsuid_core.utils.api.minigg.request import get_others_info
from .path import TEXT_PATH
from ..utils.error_reply import get_error
from ..utils.image.image_tools import get_pic
from ..gsuid_utils.api.minigg.models import Artifact
from ..utils.image.convert import str_lenth, convert_img
from ..gsuid_utils.api.minigg.request import get_others_info
from ..utils.resource.RESOURCE_PATH import REL_PATH, WIKI_REL_PATH
from ..utils.fonts.genshin_fonts import (
gs_font_14,

View File

@ -2,16 +2,21 @@ from typing import Dict, List, Tuple, Union
import aiofiles
from PIL import Image, ImageDraw
from gsuid_core.utils.api.minigg.request import (
get_character_info,
get_constellation_info,
)
from gsuid_core.utils.api.minigg.models import (
Character,
CharacterConstellation,
CharacterConstellations,
)
from .path import TEXT_PATH
from ..utils.error_reply import get_error
from ..utils.resource.download_url import download
from ..utils.map.name_covert import name_to_avatar_id
from ..utils.image.convert import str_lenth, convert_img
from ..gsuid_utils.api.minigg.request import (
get_character_info,
get_constellation_info,
)
from ..utils.resource.RESOURCE_PATH import (
CHAR_PATH,
ICON_PATH,
@ -23,11 +28,6 @@ from ..utils.fonts.genshin_fonts import (
gs_font_28,
gs_font_32,
)
from ..gsuid_utils.api.minigg.models import (
Character,
CharacterConstellation,
CharacterConstellations,
)
from ..utils.image.image_tools import (
get_star_png,
get_simple_bg,

View File

@ -2,6 +2,12 @@ from typing import Dict, List, Tuple, Union
import aiofiles
from PIL import Image, ImageDraw
from gsuid_core.utils.api.minigg.models import Character, CharacterTalents
from gsuid_core.utils.api.minigg.request import (
get_others_info,
get_talent_info,
get_character_info,
)
from .path import TEXT_PATH
from ..utils.colors import white_color
@ -9,12 +15,12 @@ from ..utils.error_reply import get_error
from ..utils.get_assets import get_assets_from_ambr
from ..utils.map.name_covert import name_to_avatar_id
from ..utils.image.convert import str_lenth, convert_img
from ..gsuid_utils.api.minigg.models import Character, CharacterTalents
from ..utils.resource.RESOURCE_PATH import CHAR_PATH, WIKI_COST_CHAR_PATH
from ..gsuid_utils.api.minigg.request import (
get_others_info,
get_talent_info,
get_character_info,
from ..utils.image.image_tools import (
get_star_png,
get_simple_bg,
get_unknown_png,
draw_pic_with_ring,
)
from ..utils.fonts.genshin_fonts import (
gs_font_24,
@ -23,12 +29,6 @@ from ..utils.fonts.genshin_fonts import (
gs_font_36,
gs_font_44,
)
from ..utils.image.image_tools import (
get_star_png,
get_simple_bg,
get_unknown_png,
draw_pic_with_ring,
)
async def get_char_cost_wiki_img(name: str) -> Union[str, bytes]:

View File

@ -2,15 +2,15 @@ from typing import List, Union
import aiofiles
from PIL import Image, ImageDraw
from gsuid_core.utils.api.minigg.models import Food
from gsuid_core.utils.api.minigg.request import get_others_info
from .path import TEXT_PATH
from ..utils.colors import white_color
from ..utils.error_reply import get_error
from ..gsuid_utils.api.minigg.models import Food
from ..utils.get_assets import get_assets_from_ambr
from ..utils.resource.RESOURCE_PATH import WIKI_FOOD_PATH
from ..utils.image.convert import convert_img, get_str_size
from ..gsuid_utils.api.minigg.request import get_others_info
from ..utils.image.image_tools import (
get_star_png,
get_simple_bg,

View File

@ -3,6 +3,12 @@ from typing import Dict, List, Union
import aiofiles
from PIL import Image, ImageDraw
from gsuid_core.utils.api.minigg.models import Weapon, WeaponStats
from gsuid_core.utils.api.minigg.request import (
get_others_info,
get_weapon_info,
get_weapon_stats,
)
from .path import TEXT_PATH
from ..utils.colors import white_color
@ -10,17 +16,11 @@ from ..utils.error_reply import get_error
from ..utils.get_assets import get_assets_from_ambr
from ..utils.image.convert import convert_img, get_str_size
from ..utils.resource.RESOURCE_PATH import WIKI_WEAPON_PATH
from ..gsuid_utils.api.minigg.models import Weapon, WeaponStats
from ..utils.image.image_tools import (
get_star_png,
get_simple_bg,
get_unknown_png,
)
from ..gsuid_utils.api.minigg.request import (
get_others_info,
get_weapon_info,
get_weapon_stats,
)
from ..utils.fonts.genshin_fonts import (
gs_font_18,
gs_font_22,

View File

@ -4,9 +4,7 @@ from typing import List, Union
from gsuid_core.models import Message
from gsuid_core.segment import MessageSegment
from .get_wiki_template import food_im, weapon_im, artifacts_im, char_info_im
from ..gsuid_utils.api.minigg.request import (
from gsuid_core.utils.api.minigg.request import (
get_others_info,
get_talent_info,
get_weapon_info,
@ -18,6 +16,8 @@ from ..gsuid_utils.api.minigg.request import (
get_constellation_info,
)
from .get_wiki_template import food_im, weapon_im, artifacts_im, char_info_im
async def artifacts_wiki(name: str) -> str:
data = await get_others_info('artifacts', name)

View File

@ -2,10 +2,10 @@ from pathlib import Path
from typing import Tuple
from PIL import Image, ImageDraw
from gsuid_core.utils.api.akashadata.request import get_akasha_abyss_info
from ..utils.image.image_tools import get_color_bg
from ..utils.resource.RESOURCE_PATH import CHAR_PATH
from ..gsuid_utils.api.akashadata.request import get_akasha_abyss_info
from ..utils.fonts.genshin_fonts import gs_font_24, gs_font_26, gs_font_30
TEXT_PATH = Path(__file__).parent / 'texture2d'

View File

@ -2,19 +2,19 @@ import json
from typing import Dict, List, Tuple, Optional
import aiofiles
from ..utils.resource.RESOURCE_PATH import DATA_PATH
from ..utils.map.name_covert import name_to_avatar_id, alias_to_char_name
from ..gsuid_utils.api.akashadata.models import (
from gsuid_core.utils.api.akashadata.models import (
AKaShaRank,
AKaShaUsage,
AKaShaCharData,
)
from ..gsuid_utils.api.akashadata.request import (
from gsuid_core.utils.api.akashadata.request import (
get_akasha_abyss_rank,
get_akasha_all_char_info,
)
from ..utils.resource.RESOURCE_PATH import DATA_PATH
from ..utils.map.name_covert import name_to_avatar_id, alias_to_char_name
all_char_info_path = DATA_PATH / 'all_char_info.json'
abyss_rank_path = DATA_PATH / 'abyss_rank.json'

View File

@ -1,4 +0,0 @@
from .version import __version__ as __version__ # noqa
from .version import genshin_impact_version as genshin_impact_version # noqa
__all__ = ["version"]

View File

@ -1,10 +0,0 @@
"""
虚空数据库 API 包装
深渊出场数据
"""
from .models import AkashaAbyssData as AkashaAbyssData # noqa: F401
from .request import ( # noqa: F401
get_akasha_abyss_info as get_akasha_abyss_info,
)
__all__ = ["request", "models"]

View File

@ -1,9 +0,0 @@
AKASHA_ABYSS_URL = (
'https://akashadata.feixiaoqiu.com/static/data/abyss_total.js'
)
AKASHA_RANK_URL = (
'https://akashadata.feixiaoqiu.com/static/data/abyss_record_list.js'
)
AKASHA_CHAR_URL = (
'https://akashadata.feixiaoqiu.com/static/data/card_details.js?v='
)

View File

@ -1,134 +0,0 @@
from __future__ import annotations
from typing import List, TypedDict
class TeamListItem(TypedDict):
ac: int
mr: str
uc: str
dc: str
ud: str
umr: str
dmr: str
tl: list[int]
class AbyssTotalView(TypedDict):
avg_star: str
avg_battle_count: str
avg_maxstar_battle_count: str
pass_rate: str
maxstar_rate: str
maxstar_12_rate: str
person_war: str
person_pass: int
maxstar_person: int
class LastRate(TypedDict):
avg_star: str
pass_rate: str
maxstar_rate: str
avg_battle_count: str
avg_maxstar_battle_count: str
maxstar_12_rate: str
class MaxstarPlayerData(TypedDict):
title: str
y_list: list[str]
x_list: list[str]
class PassPlayerData(TypedDict):
title: str
y_list: list[str]
x_list: list[str]
class PlayerLevelData(TypedDict):
maxstar_player_data: MaxstarPlayerData
pass_player_data: PassPlayerData
class PalyerCountLevelData(TypedDict):
player_count_data: list[int]
level_data: list[str]
class LevelData(TypedDict):
player_level_data: PlayerLevelData
palyer_count_level_data: PalyerCountLevelData
class CharacterUsedListItem(TypedDict):
avatar_id: int
maxstar_person_had_count: int
maxstar_person_use_count: int
value: float
used_index: int
name: str
en_name: str
icon: str
element: str
rarity: int
class AkashaAbyssData(TypedDict):
schedule_id: int
modify_time: str
schedule_version_desc: str
team_list: list[TeamListItem]
team_up_list: list[TeamListItem]
team_down_list: list[TeamListItem]
abyss_total_view: AbyssTotalView
last_rate: LastRate
level_data: LevelData
character_used_list: list[CharacterUsedListItem]
class AKaShaUsage(TypedDict):
i: int
v: str
d: str
r: int
class AKaShaRank(TypedDict):
usage_list: List[AKaShaUsage]
maxrate_list: List[AKaShaUsage]
out_list: List[AKaShaUsage]
class AKaShaWeaponRate(TypedDict):
name: str
id: int
weapon_icon: str
rarity: int
rate: str
class AKaShaOneEquipRate(TypedDict):
set: str
name: str
count: str
class AKaShaEquipRate(TypedDict):
rate: str
set_list: List[AKaShaOneEquipRate]
class AKaShaAbyssChar(TypedDict):
use_rate: str
maxstar_rate: str
come_rate: float
avg_level: float
avg_constellation: float
class AKaShaCharData(TypedDict):
weapons: List[AKaShaWeaponRate]
equips: List[AKaShaEquipRate]
abyss: AKaShaAbyssChar

View File

@ -1,73 +0,0 @@
'''
虚空数据库 API 请求模块
'''
from __future__ import annotations
import json
from typing import Dict, Literal, Optional
from httpx import AsyncClient
from ..types import AnyDict
from ...version import __version__
from .models import AKaShaRank, AKaShaCharData, AkashaAbyssData
from .api import AKASHA_CHAR_URL, AKASHA_RANK_URL, AKASHA_ABYSS_URL
_HEADER = {'User-Agent': f'gsuid-utils/{__version__}'}
async def get_akasha_abyss_info() -> AkashaAbyssData:
'''请求虚空数据库 API 深渊出场数据
Returns:
AkashaAbyssData: 虚空数据库 API 深渊出场数据响应数据
''' # noqa: E501
raw_data = await _akasha_request(AKASHA_ABYSS_URL)
raw_data = raw_data.lstrip('var static_abyss_total =')
data = json.loads(raw_data)
return data
async def get_akasha_all_char_info() -> Dict[str, AKaShaCharData]:
raw_data = await _akasha_request(AKASHA_CHAR_URL)
raw_data = (
raw_data.replace('\\', '')
.lstrip('var static_card_details =')
.replace('"[', '[')
.replace(']"', ']')
.replace('"{', '{')
.replace('}"', '}')
)
data = json.loads(raw_data)
return data
async def get_akasha_abyss_rank(is_info: bool = False) -> AKaShaRank:
raw_data = await _akasha_request(AKASHA_RANK_URL)
raw_data = raw_data.lstrip('var static_abyss_total =')
data_list = raw_data.split(';')
data1 = data_list[0].lstrip('var static_schedule_version_dict =')
data2 = data_list[1].lstrip('var static_abyss_record_dict =')
schedule_version_dict = json.loads(data1)
abyss_record_dict = json.loads(data2)
if is_info:
return schedule_version_dict
return abyss_record_dict
async def _akasha_request(
url: str,
method: Literal['GET', 'POST'] = 'GET',
header: AnyDict = _HEADER,
params: Optional[AnyDict] = None,
data: Optional[AnyDict] = None,
) -> str:
async with AsyncClient(
headers=header,
verify=False,
timeout=None,
) as client:
req = await client.request(
method=method, url=url, params=params, data=data
)
return req.text

View File

@ -1,18 +0,0 @@
"""
安柏计划 API 包装
书籍信息
角色信息;
武器信息;
"""
from .models import AmbrBook as AmbrBook # noqa: F401
from .models import AmbrWeapon as AmbrWeapon # noqa: F401
from .models import AmbrCharacter as AmbrCharacter # noqa: F401
from .models import AmbrBookDetail as AmbrBookDetail # noqa: F401
from .request import get_story_data as get_story_data # noqa: F401
from .request import get_all_book_id as get_all_book_id # noqa: F401
from .request import get_book_volume as get_book_volume # noqa: F401
from .request import get_ambr_char_data as get_ambr_char_data # noqa: F401
from .request import get_ambr_event_info as get_ambr_event_info # noqa: F401
from .request import get_ambr_weapon_data as get_ambr_weapon_data # noqa: F401
__all__ = ["request", "models", "utils"]

View File

@ -1,10 +0,0 @@
AMBR_EVENT_URL = 'https://api.ambr.top/assets/data/event.json'
AMBR_CHAR_URL = 'https://api.ambr.top/v2/chs/avatar/{}?vh=32F2'
AMBR_WEAPON_URL = 'https://api.ambr.top/v2/CHS/weapon/{}?vh=32F6'
AMBR_BOOK_URL = 'https://api.ambr.top/v2/chs/book?vh=34F5'
AMBR_BOOK_DETAILS_URL = 'https://api.ambr.top/v2/CHS/book/{}?vh=34F5'
AMBR_BOOK_DATA_URL = 'https://api.ambr.top/v2/CHS/readable/Book{}?vh=34F5'
AMBR_MONSTER_URL = 'https://api.ambr.top/v2/chs/monster/{}?vh=37F4'
AMBR_GCG_LIST_URL = 'https://api.ambr.top/v2/chs/gcg?vh=37F4'
AMBR_GCG_DETAIL = 'https://api.ambr.top/v2/chs/gcg/{}?vh=37F4'
AMBR_MONSTER_LIST = 'https://api.ambr.top/v2/chs/monster?vh=37F4'

View File

@ -1,266 +0,0 @@
from __future__ import annotations
from typing import Dict, List, Union, Literal, Optional, TypedDict
class AmbrLanguageData(TypedDict):
EN: str
RU: str
CHS: str
KR: str
JP: str
class AmbrEvent(TypedDict):
id: int
name: AmbrLanguageData
nameFull: AmbrLanguageData
description: AmbrLanguageData
banner: AmbrLanguageData
endAt: str
class AmbrFetter(TypedDict):
title: str
detail: str
constellation: str
native: str
cv: AmbrLanguageData
class AmbrProp(TypedDict):
propType: str
initValue: float
type: str
class AmbrPromote(TypedDict):
promoteLevel: int
costItems: Dict[str, int]
unlockMaxLevel: int
addProps: Dict[str, float]
requiredPlayerLevel: int
coinCost: int
class AmbrUpgrade(TypedDict):
prop: List[AmbrProp]
promote: List[AmbrPromote]
class AmbrWeaponUpgrade(AmbrUpgrade):
awakenCost: List[int]
class AmbrNameCard(TypedDict):
id: int
name: str
description: str
icon: str
class AmbrFood(TypedDict):
id: int
name: str
rank: int
effectIcon: str
icon: str
class AmbrCharOther(TypedDict):
nameCard: AmbrNameCard
specialFood: AmbrFood
class AmbrTalentPromote(TypedDict):
level: int
costItems: Optional[Dict[str, int]]
coinCost: Optional[int]
description: List[str]
params: List[int]
class AmbrTalent(TypedDict):
type: int
name: str
description: str
icon: str
promote: Dict[str, AmbrTalentPromote]
cooldown: int
cost: int
class AmbrConstellation(TypedDict):
name: str
description: str
icon: str
class AmbrCharacter(TypedDict):
id: int
rank: int
name: str
element: Literal[
'Electric', 'Ice', 'Wind', 'Grass', 'Water', 'Rock', 'Fire'
]
weaponType: Literal[
'WEAPON_SWORD_ONE_HAND',
'WEAPON_CATALYST',
'WEAPON_CLAYMORE',
'WEAPON_BOW',
'WEAPON_POLE',
]
icon: str
birthday: List[int]
release: int
route: str
fetter: AmbrFetter
upgrade: AmbrUpgrade
other: AmbrCharOther
ascension: Dict[str, int]
talent: Dict[str, AmbrTalent]
constellation: Dict[str, AmbrConstellation]
class AmbrAffix(TypedDict):
name: str
upgrade: Dict[str, str]
class AmbrWeapon(TypedDict):
id: int
rank: int
type: str
name: str
description: str
icon: str
storyId: int
affix: Dict[str, AmbrAffix]
route: str
upgrade: AmbrWeaponUpgrade
ascension: Dict[str, int]
class AmbrBook(TypedDict):
id: int
name: str
rank: int
icon: str
route: str
class AmbrVolume(TypedDict):
id: int
name: str
description: str
storyId: str
class AmbrBookDetail(TypedDict):
id: int
name: str
rank: int
icon: str
volume: List[AmbrVolume]
route: str
class AmbrMonsterAffix(TypedDict):
name: str
description: str
abilityName: List[str]
isCommon: bool
class AmbrHpDrop(TypedDict):
id: int
hpPercent: int
class AmbrReward(TypedDict):
rank: int
icon: str
count: str
class AmbrEntry(TypedDict):
id: str
type: str
affix: List[AmbrMonsterAffix]
hpDrops: List[AmbrHpDrop]
prop: List[AmbrProp]
resistance: Dict[str, float]
reward: Dict[str, AmbrReward]
class AmbrMonster(TypedDict):
id: int
name: str
icon: str
route: str
title: str
specialName: str
description: str
entries: Dict[str, AmbrEntry]
tips: Optional[str]
class AmbrMonsterSimple(TypedDict):
id: int
name: str
icon: str
route: str
type: str
class AmbrGCGList(TypedDict):
types: Dict[str, Literal['characterCard', 'actionCard']]
items: Dict[str, AmbrGCGCard]
class AmbrGCGCard(TypedDict):
id: int
name: str
type: Literal['characterCard', 'actionCard']
tags: Dict[str, str]
props: Dict[str, int]
icon: str
route: str
sortOrder: int
class AmbrGCGDict(TypedDict):
name: str
description: str
class AmbrGCGTalent(TypedDict):
name: str
description: str
cost: Dict[str, int]
params: Dict[str, Union[int, str]]
tags: Dict[str, str]
icon: str
subSkills: Optional[str]
keywords: Dict[str, str]
class AmbrGCGEntry(TypedDict):
id: int
name: str
type: Literal['gcg']
icon: str
class AmbrGCGDetail(AmbrGCGCard):
storyTitle: str
storyDetail: str
source: str
dictionary: Dict[str, AmbrGCGDict]
talent: Dict[str, AmbrGCGTalent]
relatedEntries: List[AmbrGCGCard]
class AmbrMonsterList(TypedDict):
types: Dict[str, str]
items: Dict[str, AmbrMonsterSimple]

View File

@ -1,131 +0,0 @@
'''
安柏计划 API 请求模块
'''
from __future__ import annotations
from typing import Dict, Union, Literal, Optional, cast
from httpx import AsyncClient
from ..types import AnyDict
from ...version import __version__
from .models import (
AmbrBook,
AmbrEvent,
AmbrWeapon,
AmbrGCGList,
AmbrMonster,
AmbrCharacter,
AmbrGCGDetail,
AmbrBookDetail,
AmbrMonsterList,
)
from .api import (
AMBR_BOOK_URL,
AMBR_CHAR_URL,
AMBR_EVENT_URL,
AMBR_GCG_DETAIL,
AMBR_WEAPON_URL,
AMBR_MONSTER_URL,
AMBR_GCG_LIST_URL,
AMBR_MONSTER_LIST,
AMBR_BOOK_DATA_URL,
AMBR_BOOK_DETAILS_URL,
)
_HEADER = {'User-Agent': f'gsuid-utils/{__version__}'}
async def get_ambr_event_info() -> Optional[Dict[str, AmbrEvent]]:
data = await _ambr_request(url=AMBR_EVENT_URL)
if isinstance(data, Dict):
return cast(Dict[str, AmbrEvent], data)
return None
async def get_ambr_char_data(id: Union[int, str]) -> Optional[AmbrCharacter]:
data = await _ambr_request(url=AMBR_CHAR_URL.format(id))
if isinstance(data, Dict) and data['response'] == 200:
data = data['data']
return cast(AmbrCharacter, data)
return None
async def get_ambr_monster_data(id: Union[int, str]) -> Optional[AmbrMonster]:
data = await _ambr_request(url=AMBR_MONSTER_URL.format(id))
if isinstance(data, Dict) and data['response'] == 200:
data = data['data']
return cast(AmbrMonster, data)
return None
async def get_ambr_gcg_detail(id: Union[int, str]) -> Optional[AmbrGCGDetail]:
data = await _ambr_request(url=AMBR_GCG_DETAIL.format(id))
if isinstance(data, Dict) and data['response'] == 200:
data = data['data']
return cast(AmbrGCGDetail, data)
return None
async def get_ambr_gcg_list() -> Optional[AmbrGCGList]:
data = await _ambr_request(url=AMBR_GCG_LIST_URL)
if isinstance(data, Dict) and data['response'] == 200:
data = data['data']
return cast(AmbrGCGList, data)
return None
async def get_ambr_monster_list() -> Optional[AmbrMonsterList]:
data = await _ambr_request(url=AMBR_MONSTER_LIST)
if isinstance(data, Dict) and data['response'] == 200:
data = data['data']
return cast(AmbrMonsterList, data)
return None
async def get_ambr_weapon_data(id: Union[int, str]) -> Optional[AmbrWeapon]:
data = await _ambr_request(url=AMBR_WEAPON_URL.format(id))
if isinstance(data, Dict) and data['response'] == 200:
data = data['data']
return cast(AmbrWeapon, data)
return None
async def get_all_book_id() -> Optional[Dict[str, AmbrBook]]:
data = await _ambr_request(url=AMBR_BOOK_URL)
if isinstance(data, Dict) and data['response'] == 200:
data = data['data']['items']
return cast(Dict[str, AmbrBook], data)
return None
async def get_book_volume(id: Union[int, str]) -> Optional[AmbrBookDetail]:
data = await _ambr_request(url=AMBR_BOOK_DETAILS_URL.format(id))
if isinstance(data, Dict) and data['response'] == 200:
data = data['data']
return cast(AmbrBookDetail, data)
return None
async def get_story_data(story_id: Union[int, str]) -> Optional[str]:
data = await _ambr_request(url=AMBR_BOOK_DATA_URL.format(story_id))
if isinstance(data, Dict) and data['response'] == 200:
return data['data']
return None
async def _ambr_request(
url: str,
method: Literal['GET', 'POST'] = 'GET',
header: AnyDict = _HEADER,
params: Optional[AnyDict] = None,
data: Optional[AnyDict] = None,
) -> Optional[AnyDict]:
async with AsyncClient(timeout=None) as client:
req = await client.request(
method, url=url, headers=header, params=params, json=data
)
data = req.json()
if data and 'code' in data:
data['response'] = data['code']
return data

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +0,0 @@
"""Enka Network 包装
参考https://api.enka.network
"""
from .models import EnkaData as EnkaData # noqa: F401
from .request import get_enka_info as get_enka_info # noqa: F401
__all__ = ["request", "models"]

View File

@ -1,113 +0,0 @@
from __future__ import annotations
import sys
from typing import List, Literal, TypedDict
# https://peps.python.org/pep-0655/#usage-in-python-3-11
if sys.version_info >= (3, 11):
from typing import NotRequired
else:
from typing_extensions import NotRequired
class EnkaData(TypedDict):
playerInfo: PlayerInfo
avatarInfoList: List[AvatarInfoListItem]
ttl: int
uid: str
class PlayerInfo(TypedDict):
nickname: str
level: int
signature: str
worldLevel: int
nameCardId: int
finishAchievementNum: int
towerFloorIndex: int
towerLevelIndex: int
showAvatarInfoList: List[ShowAvatarInfoListItem]
showNameCardIdList: List[int]
profilePicture: ProfilePicture
class ShowAvatarInfoListItem(TypedDict):
avatarId: int
level: int
costumeId: NotRequired[int]
class ProfilePicture(TypedDict):
avatarId: int
class AvatarInfoListItem(TypedDict):
avatarId: int
propMap: dict[str, PropMap]
talentIdList: List[int]
fightPropMap: dict[str, float]
skillDepotId: int
inherentProudSkillList: List[int]
skillLevelMap: dict[str, int]
equipList: List[Equip]
fetterInfo: FetterInfo
class Equip(TypedDict):
itemId: int
reliquary: Reliquary
weapon: Weapon
flat: Flat
class Flat(TypedDict):
# l10n
nameTextMapHash: str
setNameTextMapHash: str
# artifact
reliquaryMainstat: Stat
reliquarySubstats: List[Stat]
equipType: Literal[
"EQUIP_BRACER",
"EQUIP_NECKLACE",
"EQUIP_SHOES",
"EQUIP_RING",
"EQUIP_DRESS",
]
# weapon
weaponStats: List[Stat]
rankLevel: Literal[3, 4, 5]
itemType: Literal["ITEM_WEAPON", "ITEM_RELIQUARY"]
icon: str # https://enka.network/ui/{Icon}.png
class Stat(TypedDict):
mainPropId: str
appendPropId: str
statName: str
statValue: float | int
class Weapon(TypedDict):
level: int
promoteLevel: int
affixMap: dict[str, int]
class Reliquary(TypedDict):
level: int
mainPropId: int
appendPropIdList: List[int]
class PropMap(TypedDict):
type: int
ival: str # Ignore It!
val: str
class FetterInfo(TypedDict):
expLevel: int

View File

@ -1,37 +0,0 @@
'''Enka Network 请求模块。
MiniGG Enka 加速服务在此模块内
'''
from __future__ import annotations
from typing import Literal
from httpx import AsyncClient
from .models import EnkaData
from ...version import __version__
ADDRESS = {
'enka': 'https://enka.network',
'microgg': 'http://profile.microgg.cn',
}
async def get_enka_info(
uid: str, address: Literal['enka', 'microgg'] = 'enka'
) -> EnkaData:
'''请求 Enka Network
Args:
uid (str): 原神 UID
address (Literal[&quot;enka&quot;, &quot;microgg&quot;, &quot;minigg&quot;], optional): API 地址. Defaults to 'enka'.
Returns:
EnkaData: Enka Network 响应数据
''' # noqa: E501
async with AsyncClient(
base_url=ADDRESS[address],
headers={'User-Agent': f'gsuid-utils/{__version__}'},
timeout=None,
) as client:
req = await client.get(url=f'/api/uid/{uid}')
return req.json()

View File

@ -1,7 +0,0 @@
"""
内鬼网(hhw) API 包装
深渊出场数据
"""
from .request import get_abyss_review as get_abyss_review # noqa: F401
__all__ = ["request"]

View File

@ -1,54 +0,0 @@
'''
内鬼网(hhw) API 请求模块
'''
from __future__ import annotations
from typing import Dict, Union, Optional
from httpx import AsyncClient
from bs4 import BeautifulSoup, NavigableString
from ...version import __version__
HHW_ABYSS = 'https://genshin.honeyhunterworld.com/d_1001/?lang=CHS'
async def get_abyss_review(
raw_data: bytes, _id: Union[str, int], floor: Union[str, int]
) -> Optional[Dict[str, str]]:
'''请求内鬼网 API 深渊怪物数据
Returns:
Dict: 内鬼网 API 深渊怪物数据
''' # noqa: E501
bs = BeautifulSoup(raw_data, 'lxml')
data = bs.find('section', {'id': f'Variant #{_id}'})
if data is None or isinstance(data, NavigableString):
return None
floor_data = data.find_all('td')
abyss_list = []
result = {}
for index, td in enumerate(floor_data):
temp = []
if 'Monsters' in td.text:
monsters = floor_data[index + 1].find_all('a')
for monster in monsters:
if monster.text:
temp.append(monster.text)
if temp:
abyss_list.append(temp)
for index, half in enumerate(['-1上', '-1下', '-2上', '-2下', '-3上', '-3下']):
result[f'{floor}{half}'] = abyss_list[index]
return result
async def get_abyss_review_raw() -> bytes:
async with AsyncClient(
headers={'User-Agent': f'gsuid-utils/{__version__}'}, timeout=None
) as client:
req = await client.get(url=HHW_ABYSS)
return req.read()

View File

@ -1,36 +0,0 @@
"""
MiniGG API 包装
原神基础信息 v4/v5
原神语音
原神地图
"""
from .models import Food as Food # noqa: F401
from .models import Costs as Costs # noqa: F401
from .models import Enemy as Enemy # noqa: F401
from .models import Domain as Domain # noqa: F401
from .models import Weapon as Weapon # noqa: F401
from .models import Artifact as Artifact # noqa: F401
from .models import Material as Material # noqa: F401
from .models import Character as Character # noqa: F401
from .models import WeaponStats as WeaponStats # noqa: F401
from .request import get_map_data as get_map_data # noqa: F401
from .models import CharacterStats as CharacterStats # noqa: F401
from .request import get_audio_info as get_audio_info # noqa: F401
from .request import get_others_info as get_others_info # noqa: F401
from .request import get_talent_info as get_talent_info # noqa: F401
from .request import get_weapon_info as get_weapon_info # noqa: F401
from .models import CharacterTalents as CharacterTalents # noqa: F401
from .request import get_weapon_costs as get_weapon_costs # noqa: F401
from .request import get_weapon_stats as get_weapon_stats # noqa: F401
from .request import get_character_info as get_character_info # noqa: F401
from .request import get_character_costs as get_character_costs # noqa: F401
from .request import get_character_stats as get_character_stats # noqa: F401
from .exception import MiniggNotFoundError as MiniggNotFoundError # noqa: F401
from .request import ( # noqa: F401
get_constellation_info as get_constellation_info,
)
from .models import ( # noqa: F401
CharacterConstellations as CharacterConstellations,
)
__all__ = ["request", "exception", "models"]

View File

@ -1,17 +0,0 @@
from typing import Any
from ..types import AnyDict
class MiniggNotFoundError(Exception):
"""MiniGG API 未找到报错"""
def __init__(self, **raw: Any) -> None:
self.raw: AnyDict = raw
def __repr__(self) -> str:
raw = self.raw
return f"<MiniggNotFoundError {raw=}>"
def __str__(self) -> str:
return repr(self)

View File

@ -1,363 +0,0 @@
"""
MiniGG API 响应模型
"""
# TODO: - @KimigaiiWuyi 补文档
from __future__ import annotations
import sys
from typing import Dict, List, Literal, TypedDict
# https://peps.python.org/pep-0655/#usage-in-python-3-11
if sys.version_info >= (3, 11):
from typing import NotRequired
else:
from typing_extensions import NotRequired
# https://peps.python.org/pep-0613
if sys.version_info >= (3, 10):
from typing import TypeAlias
else:
from typing_extensions import TypeAlias
R: TypeAlias = List[str]
class FandomUrl(TypedDict):
fandom: str
class WeaponImage(TypedDict):
image: str
nameicon: str
namegacha: str
icon: str
nameawakenicon: str
awakenicon: str
class AscendItem(TypedDict):
name: str
count: int
class Costs(TypedDict):
ascend1: List[AscendItem]
ascend2: List[AscendItem]
ascend3: List[AscendItem]
ascend4: List[AscendItem]
ascend5: List[AscendItem]
ascend6: List[AscendItem]
class Weapon(TypedDict):
name: str
description: str
weapontype: str
rarity: str
baseatk: int
substat: str
subvalue: str
effectname: str
effect: str
r1: R
r2: R
r3: R
r4: R
r5: R
weaponmaterialtype: str
images: WeaponImage
url: NotRequired[FandomUrl]
version: str
costs: Costs
class WeaponStats(TypedDict):
level: int
ascension: int
attack: float
specialized: float
class Character(TypedDict):
name: str
fullname: str
title: str
description: str
rarity: str
element: str
weapontype: str
substat: str
gender: Literal['', '']
body: str
association: str
region: Literal['蒙德', '璃月', '稻妻', '须弥', '枫丹', '纳塔', '至冬', '穆纳塔']
affiliation: str
birthdaymmdd: str
birthday: str
constellation: str
cv: CharacterCv
costs: Costs
image: CharacterImage
url: FandomUrl
version: str
class CharacterCv(TypedDict):
english: str
chinese: str
japanese: str
korean: str
class CharacterImage(TypedDict):
card: str
portrait: str
icon: str
sideicon: str
cover1: str
cover2: str
hoyolab_avatar: str
nameicon: str
nameiconcard: str
namegachasplash: str
namegachaslice: str
namesideicon: str
class CharacterStats(TypedDict):
level: int
ascension: int
hp: float
attack: float
defense: float
specialized: float
class CharacterConstellations(TypedDict):
name: str
c1: CharacterConstellation
c2: CharacterConstellation
c3: CharacterConstellation
c4: CharacterConstellation
c5: CharacterConstellation
c6: CharacterConstellation
images: ConstellationsImage
version: str
class CharacterConstellation(TypedDict):
name: str
effect: str
class ConstellationsImage(TypedDict):
c1: str
c2: str
c3: str
c4: str
c5: str
c6: str
class MiniGGError(TypedDict):
retcode: int
error: str
class CharacterTalents(TypedDict):
name: str
combat1: TalentCombat
combat2: TalentCombat
combat3: TalentCombat
passive1: TalentPassive
passive2: TalentPassive
passive3: TalentPassive
passive4: NotRequired[TalentPassive]
costs: TalentsCosts
images: TalentsImages
class TalentsCosts(TypedDict):
lvl2: List[AscendItem]
lvl3: List[AscendItem]
lvl4: List[AscendItem]
lvl5: List[AscendItem]
lvl6: List[AscendItem]
lvl7: List[AscendItem]
lvl8: List[AscendItem]
lvl9: List[AscendItem]
lvl10: List[AscendItem]
class TalentsImages(TypedDict):
combat1: str
combat2: str
combat3: str
passive1: str
passive2: str
passive3: str
passive4: NotRequired[str]
class TalentCombat(TypedDict):
name: str
info: str
description: NotRequired[str]
attributes: TalentAttr
class TalentPassive(TypedDict):
name: str
info: str
class TalentAttr(TypedDict):
labels: List[str]
parameters: Dict[str, List[float]]
class Food(TypedDict):
name: str
rarity: str
foodtype: str
foodfilter: str
foodcategory: str
effect: str
description: str
suspicious: FoodEffect
normal: FoodEffect
delicious: FoodEffect
ingredients: List[AscendItem]
images: Image
url: FandomUrl
version: str
class FoodEffect(TypedDict):
effect: str
description: str
class Image(TypedDict):
nameicon: str
class Enemy(TypedDict):
name: str
specialname: str
enemytype: str
category: str
description: str
investigation: EnemyInvest
rewardpreview: List[EnemyReward]
images: Image
version: str
class EnemyReward(TypedDict):
name: str
count: NotRequired[float]
class EnemyInvest(TypedDict):
name: str
category: str
description: str
class Domain(TypedDict):
name: str
region: Literal['蒙德', '璃月', '稻妻', '须弥', '枫丹', '纳塔', '至冬', '穆纳塔']
domainentrance: str
domaintype: str
description: str
recommendedlevel: int
recommendedelements: List[
Literal['冰元素', '火元素', '雷元素', '水元素', '草元素', '岩元素', '风元素']
]
daysofweek: List[Literal['周日', '周一', '周二', '周三', '周四', '周五', '周六']]
unlockrank: int
rewardpreview: List[EnemyReward]
disorder: List[str]
monsterlist: List[str]
images: Image
version: str
class Piece(TypedDict):
name: str
description: str
story: str
class PieceFlower(Piece):
relictype: Literal['生之花']
class PiecePlume(Piece):
relictype: Literal['死之羽']
class PieceSands(Piece):
relictype: Literal['时之沙']
class PieceGoblet(Piece):
relictype: Literal['空之杯']
class PieceCirclet(Piece):
relictype: Literal['理之冠']
class PieceImages(TypedDict):
flower: str
plume: str
sands: str
goblet: str
circlet: str
nameflower: str
nameplume: str
namesands: str
namegoblet: str
namecirclet: str
Artifact = TypedDict(
'Artifact',
{
'name': str,
'rarity': List[str],
'1pc': str,
'2pc': str,
'4pc': str,
'flower': PieceFlower,
'plume': PiecePlume,
'sands': PieceSands,
'goblet': PieceGoblet,
'circlet': PieceCirclet,
'images': PieceImages,
'url': FandomUrl,
'version': str,
},
)
class MaterialImage(TypedDict):
nameicon: str
redirect: str
class Material(TypedDict):
name: str
description: str
sortorder: int
rarity: str
category: str
materialtype: str
source: List[str]
images: MaterialImage
version: str
# 下面两个当且仅当materialtype是xx突破素材的情况才有
dropdomain: str
daysofweek: List[str]

View File

@ -1,452 +0,0 @@
'''
MiniGG API v4/v5 请求模块
参考https://blog.minigg.cn/g/18
MiniGG Enka 加速服务不在此模块内
'''
from __future__ import annotations
import json
import warnings
from enum import Enum
from typing import Any, Dict, List, Union, Literal, Optional, cast, overload
from httpx import AsyncClient
from ..types import AnyDict
from .exception import MiniggNotFoundError
from .models import (
Food,
Costs,
Enemy,
Domain,
Weapon,
Artifact,
Material,
Character,
WeaponStats,
CharacterStats,
CharacterTalents,
CharacterConstellations,
)
MINIGG_AUDIO_URL = 'https://genshin.minigg.cn/'
MINIGG_URL = 'https://info.minigg.cn'
MINIGG_MAP_URL = 'https://map.minigg.cn/map/get_map'
class APILanguages(str, Enum):
'''API 语言列表'''
CHS = 'CN'
'''简体中文'''
CN = 'CN'
'''简体中文'''
JP = 'JP'
'''日语'''
JA = 'JP'
'''日语'''
EN = 'EN'
'''英语'''
ENG = 'EN'
'''英语'''
KR = 'KR'
'''韩语'''
KA = 'KR'
'''韩语'''
CHT = 'CHT'
'''繁体中文'''
FR = 'FR'
'''法语'''
DE = 'DE'
'''德语'''
ID = 'ID'
'''印度尼西亚语'''
PT = 'PT'
'''葡萄牙语'''
RU = 'RU'
'''俄语'''
ES = 'ES'
'''西班牙语'''
TH = 'TH'
'''泰语'''
VI = 'VI'
'''越南语'''
async def get_map_data(
resource_name: str, map_id: str, is_cluster: bool = False
) -> bytes:
'''返回地图信息。
Args:
resource_name (str): 资源点名称
map_id (str): 地图ID
is_cluster (bool, optional): 是否使用 K-Means 聚类算法 Defaults to False.
Raises:
MiniggNotFoundError: 资源未找到
Returns:
bytes: 图片
'''
async with AsyncClient(timeout=None) as client:
req = await client.get(
url=MINIGG_MAP_URL,
params={
'resource_name': resource_name,
'map_id': map_id,
'is_cluster': is_cluster,
},
)
if req.headers['content-type'] == 'image/jpeg':
return req.content
else:
raise MiniggNotFoundError(**req.json())
async def get_audio_info(
name: str, audio_id: str, language: str = 'cn'
) -> str:
'''`@deprecated: API is invalid` 访问 MiniGG API 获得原神角色音频信息。
Args:
name (str): 原神角色名称
audio_id (str): 语音id
language (str, optional): 语言 Defaults to 'cn'.
Returns:
str: 语音 URL
'''
warnings.warn('Audio API is already deprecated.', DeprecationWarning)
async with AsyncClient(timeout=None) as client:
req = await client.get(
url=MINIGG_AUDIO_URL,
params={
'characters': name,
'audioid': audio_id,
'language': language,
},
)
return req.text
async def minigg_request(
endpoint: str,
query: str,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
match_categories: bool = False,
**kwargs: Any,
) -> Union[AnyDict, List[str], int]:
'''请求 MiniGG API。
Args:
endpoint (str): 终结点
query (str): 查询名称
query_languages (APILanguages, optional): 查询语言
Defaults to APILanguages.CHS.
result_languages (APILanguages, optional): 返回语言
Defaults to APILanguages.CHS.
match_categories (bool, optional): 是否查询类别 Defaults to False.
Raises:
MiniggNotFoundError: 查询内容未找到
Returns:
AnyDict | list[str]: 返回列表时列表每一项元素都符合根据名称匹配的实际名称返回字典则是此名称的实际数据
'''
params = {
'query': query,
'queryLanguages': query_languages.value,
'resultLanguage': result_languages.value,
**kwargs,
}
if match_categories:
params['matchCategories'] = '1'
async with AsyncClient(base_url=MINIGG_URL, timeout=1.3) as client:
req = await client.get(endpoint, params=params)
try:
data = req.json()
except json.decoder.JSONDecodeError:
return -11
if 'retcode' in data:
retcode: int = data['retcode']
return retcode
if req.status_code == 404:
raise MiniggNotFoundError(**data)
return data
async def get_weapon_info(
name: str,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
) -> Union[Weapon, List[str], int]:
'''获取武器信息
Args:
name (str): 武器名称或类别名称
**其他参数另见 `minigg_request`**
Raises:
MiniggNotFoundError: 武器未找到
Returns:
Weapon | list[str]: 武器信息如果为列表则每个元素都是武器名
`get_weapon_costs` `get_weapon_stats`
'''
data = await minigg_request(
'/weapons',
name,
query_languages=query_languages,
result_languages=result_languages,
)
if isinstance(data, int):
return data
elif isinstance(data, Dict):
data = cast(Weapon, data)
return data
async def get_weapon_costs(
name: str,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
) -> Union[Weapon, List[str], int]:
'''获取武器信息(花费)
Args:
name (str): 武器名称或类别名称
**其他参数另见 `minigg_request`**
Raises:
MiniggNotFoundError: 武器未找到
Returns:
WeaponCosts | list[str]: 武器花费
'''
data = await minigg_request(
'/weapons',
name,
query_languages=query_languages,
result_languages=result_languages,
costs=True,
)
if isinstance(data, int):
return data
elif isinstance(data, Dict):
data = cast(Weapon, data)
return data
async def get_weapon_stats(
name: str,
stats: int,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
) -> Union[WeaponStats, List[str], int]:
'''_summary_
Args:
name (str): 武器名称或类别名称
stats (int): 查询指定武器在这个等级的基础面板
**其他参数另见 `minigg_request`**
Raises:
MiniggNotFoundError: 武器未找到
ValueError: stats 大于 90 或小于等于 0
Returns:
WeaponStats: 武器等级基础面板
'''
if stats > 90 or stats <= 0:
raise ValueError('stats must <= 90 and > 0')
data = await minigg_request(
'/weapons',
name,
query_languages=query_languages,
result_languages=result_languages,
stats=stats,
)
if isinstance(data, int):
return data
elif isinstance(data, Dict):
data = cast(WeaponStats, data)
return data
async def get_character_info(
name: str,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
) -> Union[Character, List[str], int]:
data = await minigg_request(
'/characters',
name,
query_languages=query_languages,
result_languages=result_languages,
)
if isinstance(data, int):
return data
elif isinstance(data, Dict):
data = cast(Character, data)
return data
async def get_character_costs(
name: str,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
) -> Union[Costs, int]:
data = await minigg_request(
'/characters',
name,
query_languages=query_languages,
result_languages=result_languages,
Costs=True,
)
if isinstance(data, int):
return data
elif isinstance(data, Dict):
return cast(Costs, data['costs'])
else:
return -1
async def get_character_stats(
name: str,
stats: int,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
) -> Union[CharacterStats, int]:
if stats > 90 or stats <= 0:
raise ValueError('stats must <= 90 and > 0')
data = await minigg_request(
'/characters',
name,
query_languages=query_languages,
result_languages=result_languages,
stats=stats,
)
if isinstance(data, int):
return data
elif isinstance(data, Dict):
return cast(CharacterStats, data)
else:
return -1
async def get_constellation_info(
name: str,
c: Optional[int] = None,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
) -> Union[CharacterConstellations, int]:
if c and (c > 6 or c <= 0):
raise ValueError('c must <= 6 and > 0')
data = await minigg_request(
'/constellations',
name,
query_languages=query_languages,
result_languages=result_languages,
c=c,
)
if isinstance(data, int):
return data
elif isinstance(data, Dict):
return cast(CharacterConstellations, data)
else:
return -1
async def get_talent_info(
name: str,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
) -> Union[CharacterTalents, int]:
data = await minigg_request(
'/talents',
name,
query_languages=query_languages,
result_languages=result_languages,
)
if isinstance(data, int):
return data
elif isinstance(data, Dict):
return cast(CharacterTalents, data)
else:
return -1
@overload
async def get_others_info(
type: Literal['foods'], name: str
) -> Union[Food, int]:
...
@overload
async def get_others_info(
type: Literal['enemies'], name: str
) -> Union[Enemy, int]:
...
@overload
async def get_others_info(
type: Literal['domains'], name: str
) -> Union[Domain, int]:
...
@overload
async def get_others_info(
type: Literal['artifacts'], name: str
) -> Union[Artifact, int]:
...
@overload
async def get_others_info(
type: Literal['materials'], name: str
) -> Union[Material, int]:
...
async def get_others_info(
type: Literal['foods', 'enemies', 'domains', 'artifacts', 'materials'],
name: str,
query_languages: APILanguages = APILanguages.CHS,
result_languages: APILanguages = APILanguages.CHS,
) -> Union[Food, Material, Domain, Artifact, Enemy, int]:
data = await minigg_request(
f'/{type}',
name,
query_languages=query_languages,
result_languages=result_languages,
)
if isinstance(data, int):
return data
elif isinstance(data, Dict):
if type == 'foods':
return cast(Food, data)
elif type == 'materials':
return cast(Material, data)
elif type == 'domains':
return cast(Domain, data)
elif type == 'artifacts':
return cast(Artifact, data)
elif type == 'enemies':
return cast(Enemy, data)
else:
return -1

View File

@ -1,17 +0,0 @@
"""
米游社 API 包装
"""
from .request import MysApi # noqa: F401
from .models import ( # noqa: F401
AbyssData,
IndexData,
MihoyoRole,
MihoyoAvatar,
MihoyoWeapon,
DailyNoteData,
MihoyoReliquary,
MihoyoConstellation,
)
__all__ = ["models", 'request']

View File

@ -1,137 +0,0 @@
# flake8: noqa
OLD_URL = 'https://api-takumi.mihoyo.com'
NEW_URL = 'https://api-takumi-record.mihoyo.com'
BBS_URL = 'https://bbs-api.mihoyo.com'
HK4_URL = 'https://hk4e-api.mihoyo.com'
NEW_BBS_URL = 'https://bbs-api.miyoushe.com'
OLD_URL_OS = 'https://api-os-takumi.mihoyo.com'
NEW_URL_OS = 'https://bbs-api-os.hoyolab.com'
BBS_URL_OS = 'https://bbs-api-os.hoyolab.com'
HK4_URL_OS = 'https://hk4e-api-os.hoyoverse.com'
SIGN_URL_OS = 'https://sg-hk4e-api.hoyolab.com'
ACT_URL_OS = 'https://sg-hk4e-api.hoyoverse.com'
HK4E_LOGIN_URL = f'{OLD_URL}/common/badge/v1/login/account'
HK4E_LOGIN_URL_OS = f'{OLD_URL_OS}/common/badge/v1/login/account'
BBS_TASKLIST = f'{BBS_URL}/apihub/sapi/getUserMissionsState'
PASSPORT_URL = 'https://passport-api.mihoyo.com'
HK4_SDK_URL = 'https://hk4e-sdk.mihoyo.com'
'''GT'''
# AJAX 无感验证
GT_TEST = 'https://api.geetest.com/ajax.php?'
GT_TEST_V6 = 'https://apiv6.geetest.com/ajax.php?'
GT_QUERY = 'gt={}&challenge={}&lang=zh-cn&pt=3&client_type=web_mobile'
GT_TEST_URL = GT_TEST + GT_QUERY
GT_TEST_URL_V6 = GT_TEST_V6 + GT_QUERY
GT_TPYE_URL = 'https://api.geetest.com/gettype.php?gt={}'
VERIFICATION_URL = (
f'{NEW_URL}/game_record/app/card/wapi/createVerification?is_high=false'
)
BBS_VERIFICATION_URL = (
f'{NEW_BBS_URL}/game_record/app/card/wapi/createVerification?is_high=false'
)
VERIFY_URL = f'{NEW_URL}/game_record/app/card/wapi/verifyVerification'
'''账号相关'''
# 通过LoginTicket获取Stoken
GET_STOKEN_URL = f'{OLD_URL}/auth/api/getMultiTokenByLoginTicket'
# 通过Stoken获取Cookie_token
GET_COOKIE_TOKEN_URL = f'{OLD_URL}/auth/api/getCookieAccountInfoBySToken'
# 通过Stoken获取AuthKey
GET_AUTHKEY_URL = f'{OLD_URL}/binding/api/genAuthKey'
# 通过AuthKey获取gachalogs
GET_GACHA_LOG_URL = f'{HK4_URL}/event/gacha_info/api/getGachaLog'
GET_GACHA_LOG_URL_OS = f'{HK4_URL_OS}/event/gacha_info/api/getGachaLog'
# 通过GameToken获取Stoken
GET_STOKEN = f'{PASSPORT_URL}/account/ma-cn-session/app/getTokenByGameToken'
# 创建登录URL
CREATE_QRCODE = f'{HK4_SDK_URL}/hk4e_cn/combo/panda/qrcode/fetch'
# 检查二维码扫描状态
CHECK_QRCODE = f'{HK4_SDK_URL}/hk4e_cn/combo/panda/qrcode/query'
# 通过GameToken获取Cookie_token
GET_COOKIE_TOKEN_BY_GAME_TOKEN = (
f'{OLD_URL}/auth/api/getCookieAccountInfoByGameToken'
)
'''米游社相关'''
# 获取签到列表
SIGN_LIST_URL = f'{OLD_URL}/event/bbs_sign_reward/home'
SIGN_LIST_URL_OS = f'{SIGN_URL_OS}/event/sol/home'
# 获取签到信息
SIGN_INFO_URL = f'{OLD_URL}/event/bbs_sign_reward/info'
SIGN_INFO_URL_OS = f'{SIGN_URL_OS}/event/sol/info'
# 执行签到
SIGN_URL = f'{OLD_URL}/event/bbs_sign_reward/sign'
SIGN_URL_OS += '/event/sol/sign'
'''原神相关'''
# 每日信息 树脂 派遣等
DAILY_NOTE_URL = f'{NEW_URL}/game_record/app/genshin/api/dailyNote'
DAILY_NOTE_URL_OS = f'{NEW_URL_OS}/game_record/genshin/api/dailyNote'
# 每月札记
MONTHLY_AWARD_URL = f'{HK4_URL}/event/ys_ledger/monthInfo'
MONTHLY_AWARD_URL_OS = f'{HK4_URL_OS}/event/ysledgeros/month_info'
# 获取角色基本信息
PLAYER_INFO_URL = f'{NEW_URL}/game_record/app/genshin/api/index'
PLAYER_INFO_URL_OS = f'{NEW_URL_OS}/game_record/genshin/api/index'
# 获取深渊信息
PLAYER_ABYSS_INFO_URL = f'{NEW_URL}/game_record/app/genshin/api/spiralAbyss'
PLAYER_ABYSS_INFO_URL_OS = f'{NEW_URL_OS}/game_record/genshin/api/spiralAbyss'
# 获取详细角色信息
PLAYER_DETAIL_INFO_URL = f'{NEW_URL}/game_record/app/genshin/api/character'
PLAYER_DETAIL_INFO_URL_OS = f'{NEW_URL_OS}/game_record/genshin/api/character'
# 天赋计算器API 获取天赋等级信息
CALCULATE_INFO_URL = (
f'{OLD_URL}/event/e20200928calculate/v1/sync/avatar/detail'
)
CALCULATE_INFO_URL_OS = (
'https://sg-public-api.hoyoverse.com/event/calculateos/sync/avatar/detail'
)
# 获取米游社内的角色信息 mysid -> uid
MIHOYO_BBS_PLAYER_INFO_URL = (
f'{NEW_URL}/game_record/card/wapi/getGameRecordCard'
)
MIHOYO_BBS_PLAYER_INFO_URL_OS = (
f'{NEW_URL_OS}/game_record/card/wapi/getGameRecordCard'
)
# 获取七圣召唤相关信息
GCG_INFO = f'{NEW_URL}/game_record/app/genshin/api/gcg/basicInfo'
GCG_INFO_OS = f'{NEW_URL_OS}/game_record/genshin/api/gcg/basicInfo'
GCG_DECK_URL = f'{NEW_URL}/game_record/app/genshin/api/gcg/deckList'
GCG_DECK_URL_OS = f'{NEW_URL_OS}/game_record/app/genshin/api/gcg/deckList'
# 获取注册时间API 绘忆星辰
REG_TIME = f'{HK4_URL}/event/e20220928anniversary/game_data?'
REG_TIME_OS = f'{ACT_URL_OS}/event/e20220928anniversary/game_data?'
# 米游社的API列表
BBS_TASKS_LIST = f'{BBS_URL}/apihub/sapi/getUserMissionsState'
BBS_SIGN_URL = f'{BBS_URL}/apihub/app/api/signIn'
BBS_LIST_URL = (
BBS_URL + '/post/api/getForumPostList?'
'forum_id={}&is_good=false&is_hot=false&page_size=20&sort_type=1'
)
BBS_DETAIL_URL = BBS_URL + '/post/api/getPostFull?post_id={}'
BBS_SHARE_URL = BBS_URL + '/apihub/api/getShareConf?entity_id={}&entity_type=1'
BBS_LIKE_URL = f'{BBS_URL}/apihub/sapi/upvotePost'
# 原神充值中心
fetchGoodsurl = f'{HK4_SDK_URL}/hk4e_cn/mdk/shopwindow/shopwindow/fetchGoods'
CreateOrderurl = f'{HK4_SDK_URL}/hk4e_cn/mdk/atropos/api/createOrder'
CheckOrderurl = f'{HK4_SDK_URL}/hk4e_cn/mdk/atropos/api/checkOrder'
PriceTierurl = f'{HK4_SDK_URL}/hk4e_cn/mdk/shopwindow/shopwindow/listPriceTier'
# 留影叙佳期
DRAW_BASE_URL = f'{HK4_URL}/event/birthdaystar/account'
CALENDAR_URL = f'{DRAW_BASE_URL}/calendar'
RECEIVE_URL = f'{DRAW_BASE_URL}/post_my_draw'
BS_INDEX_URL = f'{DRAW_BASE_URL}/index'
_API = locals()

View File

@ -1,780 +0,0 @@
from __future__ import annotations
import sys
from typing import List, Literal, Optional, TypedDict
# https://peps.python.org/pep-0655/#usage-in-python-3-11
if sys.version_info >= (3, 11):
from typing import NotRequired
else:
from typing_extensions import NotRequired
# Response about
# https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/index
# https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/character
# 玩家、武器、圣遗物、角色模型
class MihoyoRole(TypedDict):
AvatarUrl: str
nickname: str
region: str
level: int
class MihoyoWeapon(TypedDict):
id: int
name: str
icon: str
type: int
rarity: int
level: int
promote_level: int
type_name: Literal['单手剑', '双手剑', '长柄武器', '', '法器']
desc: str
affix_level: int
class ReliquaryAffix(TypedDict):
activation_number: int
effect: str
class ReliquarySet(TypedDict):
id: int
name: str
affixes: List[ReliquaryAffix]
class MihoyoReliquary(TypedDict):
id: int
name: str
icon: str
pos: int
rarity: int
level: int
set: ReliquarySet
pos_name: str
class MihoyoConstellation(TypedDict):
id: int
name: str
icon: str
effect: str
is_actived: bool
pos: int
class MihoyoCostume(TypedDict):
id: int
name: str
icon: str
class MihoyoAvatar(TypedDict):
id: int
image: str
icon: str
'''在api/character接口有'''
name: str
element: Literal[
'Geo', 'Anemo', 'Dendro', 'Electro', 'Pyro', 'Cryo', 'Hydro'
]
fetter: int
level: int
rarity: int
weapon: MihoyoWeapon
'''在api/character接口有'''
reliquaries: List[MihoyoReliquary]
'''在api/character接口有'''
constellations: List[MihoyoConstellation]
'''在api/character接口有'''
actived_constellation_num: int
costumes: List[MihoyoCostume]
'''在api/character接口有'''
card_image: str
'''在api/index接口有'''
is_chosen: bool
'''在api/index接口有'''
# Response
# https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/spiralAbyss
class AbyssAvatar(TypedDict):
avatar_id: int
avatar_icon: str
value: int
rarity: int
class AbyssBattleAvatar(TypedDict):
id: int
icon: str
level: int
rarity: int
class AbyssBattle(TypedDict):
index: int
timestamp: str
avatars: List[AbyssBattleAvatar]
class AbyssLevel(TypedDict):
index: int
star: int
max_star: int
battles: List[AbyssBattle]
class AbyssFloor(TypedDict):
index: int
icon: str
is_unlock: bool
settle_time: str
star: int
max_star: int
levels: List[AbyssLevel]
class AbyssData(TypedDict):
schedule_id: int
start_time: str
end_time: str
total_battle_times: int
total_win_times: int
max_floor: str
reveal_rank: List[AbyssAvatar]
defeat_rank: List[AbyssAvatar]
damage_rank: List[AbyssAvatar]
take_damage_rank: List[AbyssAvatar]
normal_skill_rank: List[AbyssAvatar]
energy_skill_rank: List[AbyssAvatar]
floors: List[AbyssFloor]
total_star: int
is_unlock: bool
# Response
# https://api-takumi-record.mihoyo.com/game_record/app/genshin/api/dailyNote
class Expedition(TypedDict):
avatar_side_icon: str
status: Literal['Ongoing', 'Finished']
remained_time: int
class RecoveryTime(TypedDict):
Day: int
Hour: int
Minute: int
Second: int
reached: bool
class Transformer(TypedDict):
obtained: bool
recovery_time: RecoveryTime
wiki: str
noticed: bool
latest_job_id: str
class DailyNoteData(TypedDict):
current_resin: int
max_resin: int
resin_recovery_time: int
finished_task_num: int
total_task_num: int
is_extra_task_reward_received: bool
remain_resin_discount_num: int
resin_discount_num_limit: int
current_expedition_num: int
max_expedition_num: int
expeditions: List[Expedition]
current_home_coin: int
max_home_coin: int
home_coin_recovery_time: int
calendar_url: str
transformer: Transformer
# Response from https://api-takumi.mihoyo.com/game_record/app/genshin/api/index
class Stats(TypedDict):
active_day_number: int
achievement_number: int
anemoculus_number: int
geoculus_number: int
avatar_number: int
way_point_number: int
domain_number: int
spiral_abyss: str
precious_chest_number: int
luxurious_chest_number: int
exquisite_chest_number: int
common_chest_number: int
electroculus_number: int
magic_chest_number: int
dendroculus_number: int
class Offering(TypedDict):
name: str
level: int
icon: str
class WorldExploration(TypedDict):
level: int
exploration_percentage: int
icon: str
name: str
type: str
offerings: List[Offering]
id: int
parent_id: int
map_url: str
strategy_url: str
background_image: str
inner_icon: str
cover: str
class Home(TypedDict):
level: int
visit_num: int
comfort_num: int
item_num: int
name: str
icon: str
comfort_level_name: str
comfort_level_icon: str
class IndexData(TypedDict):
role: MihoyoRole
avatars: List[MihoyoAvatar]
stats: Stats
city_explorations: List
world_explorations: List[WorldExploration]
homes: List[Home]
class CharDetailData(TypedDict):
avatars: List[MihoyoAvatar]
################
# Token Models #
################
class CookieTokenInfo(TypedDict):
uid: str
cookie_token: str
class StokenInfo(TypedDict):
token_type: NotRequired[int]
name: NotRequired[str]
token: str
class GameTokenInfo(TypedDict):
token: StokenInfo
user_info: UserInfo
class LoginTicketInfo(TypedDict):
list: List[StokenInfo]
class AuthKeyInfo(TypedDict):
sign_type: int
authkey_ver: int
authkey: str
class Hk4eLoginInfo(TypedDict):
game: str
region: str
game_uid: str
game_biz: str
level: int
nickname: str
region_name: str
################
# 扫码登录相关 #
################
class QrCodeUrl(TypedDict):
url: str
class QrPayload(TypedDict):
proto: str
raw: str
ext: str
class QrCodeStatus(TypedDict):
stat: Literal['Init', 'Scanned', 'Confirmed']
payload: QrPayload
################
# UserInfo相关 #
################
class UserLinks(TypedDict):
thirdparty: str
union_id: str
nickname: str
class UserInfo(TypedDict):
aid: str
mid: str
account_name: str
email: str
is_email_verify: int
area_code: str
mobile: str
safe_area_code: str
safe_mobile: str
realname: str
identity_code: str
rebind_area_code: str
rebind_mobile: str
rebind_mobile_time: str
links: List[UserLinks]
################
# 抽卡记录相关 #
################
class SingleGachaLog(TypedDict):
uid: str
gacha_type: str
item_id: str
count: str
time: str
name: str
lang: str
item_type: str
rank_type: str
id: str
class GachaLog(TypedDict):
page: str
size: str
total: str
list: List[SingleGachaLog]
region: str
################
# 注册时间相关 #
################
class CardOpts(TypedDict):
adjs: List[int]
titles: List[int]
items: List[int]
data_version: str
Props = TypedDict(
'Props',
{
'66a': str,
'50a': str,
'53b': str,
'pre_69b': str,
'49a': str,
'52b': str,
'pre_71b': str,
'37': str,
'48a': str,
'57': str,
},
)
class RegTime(TypedDict):
data: str
card_opts: CardOpts
props: Props
data_version: int
prop_version: int
################
# 七圣召唤相关 #
################
class CardCovers(TypedDict):
id: int
image: str
class GcgInfo(TypedDict):
level: int
nickname: str
avatar_card_num_gained: int
avatar_card_num_total: int
action_card_num_gained: int
action_card_num_total: int
covers: List[CardCovers]
################
# 每月札记相关 #
################
class DayData(TypedDict):
current_primogems: int
current_mora: int
last_primogems: int
last_mora: int
class GroupBy(TypedDict):
action_id: int
action: str
num: int
percent: int
class MonthData(TypedDict):
current_primogems: int
current_mora: int
last_primogems: int
last_mora: int
current_primogems_level: int
primogems_rate: int
mora_rate: int
group_by: List[GroupBy]
class MonthlyAward(TypedDict):
uid: int
region: str
account_id: str
nickname: str
date: str
month: str
optional_month: List[int]
data_month: int
data_last_month: int
day_data: DayData
month_data: MonthData
lantern: bool
################
# 签到相关 #
################
class MysSign(TypedDict):
code: str
risk_code: int
gt: str
challenge: str
success: int
message: str
class SignInfo(TypedDict):
total_sign_day: int
today: str
is_sign: bool
first_bind: bool
is_sub: bool
month_first: bool
sign_cnt_missed: int
month_last_day: bool
class SignAward(TypedDict):
icon: str
name: str
cnt: int
class SignList(TypedDict):
month: int
awards: List[SignAward]
resign: bool
################
# 养成计算器部分 #
################
class CalculateInfo(TypedDict):
skill_list: List[CalculateSkill]
weapon: CalculateWeapon
reliquary_list: List[CalculateReliquary]
class CalculateBaseData(TypedDict):
id: int
name: str
icon: str
max_level: int
level_current: int
class CalculateWeapon(CalculateBaseData):
weapon_cat_id: int
weapon_level: int
class CalculateReliquary(CalculateBaseData):
reliquary_cat_id: int
reliquary_level: int
class CalculateSkill(CalculateBaseData):
group_id: int
################
# RecordCard #
################
class MysGame(TypedDict):
has_role: bool
game_id: int # 2是原神
game_role_id: str # UID
nickname: str
region: str
level: int
background_image: str
is_public: bool
data: List[MysGameData]
region_name: str
url: str
data_switches: List[MysGameSwitch]
h5_data_switches: Optional[List]
background_color: str # 十六进制颜色代码
class MysGameData(TypedDict):
name: str
type: int
value: str
class MysGameSwitch(TypedDict):
switch_id: int
is_public: bool
switch_name: str
'''支付相关'''
class MysGoods(TypedDict):
goods_id: str
goods_name: str
goods_name_i18n_key: str
goods_desc: str
goods_desc_i18n_key: str
goods_type: Literal['Normal', 'Special']
goods_unit: str
goods_icon: str
currency: Literal['CNY']
price: str
symbol: Literal['']
tier_id: Literal['Tier_1']
bonus_desc: MysGoodsBonus
once_bonus_desc: MysGoodsBonus
available: bool
tips_desc: str
tips_i18n_key: str
battle_pass_limit: str
class MysGoodsBonus(TypedDict):
bonus_desc: str
bonus_desc_i18n_key: str
bonus_unit: int
bonus_goods_id: str
bonus_icon: str
class MysOrderCheck(TypedDict):
status: int # 900为成功
amount: str
goods_title: str
goods_num: str
order_no: str
pay_plat: Literal['alipay', 'weixin']
class MysOrder(TypedDict):
goods_id: str
order_no: str
currency: Literal['CNY']
amount: str
redirect_url: str
foreign_serial: str
encode_order: str
account: str # mysid
create_time: str
ext_info: str
balance: str
method: str
action: str
session_cookie: str
'''七圣召唤牌组'''
class GcgDeckInfo(TypedDict):
deck_list: List[GcgDeck]
role_id: str # uid
level: int # 世界等级
nickname: str
class GcgDeck(TypedDict):
id: int
name: str
is_valid: bool
avatar_cards: List[GcgAvatar]
action_cards: List[GcgAction]
class GcgAvatarSkill(TypedDict):
id: int
name: str
desc: str
tag: Literal['普通攻击', '元素战技', '元素爆发', '被动技能']
class GcgAvatar(TypedDict):
id: int
name: str
image: str
desc: str
card_type: Literal['CardTypeCharacter']
num: int
tags: List[str] # 元素和武器类型icon
proficiency: int
use_count: int
hp: int
card_skills: List[GcgAvatarSkill]
action_cost: List[GcgCost]
card_sources: List[str]
rank_id: int
deck_recommend: str
card_wiki: str
class GcgCost(TypedDict):
cost_type: Literal['CostTypeSame', 'CostTypeVoid']
cost_value: int
class GcgAction(TypedDict):
id: int
name: str
image: str
desc: str
card_type: str
num: int
tags: List[str] # 元素和武器类型icon
proficiency: int
use_count: int
hp: int
card_skills: List[GcgAvatarSkill]
action_cost: List[GcgCost]
card_sources: List[str]
rank_id: int
deck_recommend: str
card_wiki: str
# 留影叙佳期
class GsRoleBirthDay(TypedDict):
role_id: int
name: str
jump_tpye: str
jump_target: str
jump_start_time: str
jump_end_time: str
role_gender: int
take_picture: str
gal_xml: str
gal_resource: str
is_partake: bool
bgm: str
class BsIndex(TypedDict):
nick_name: str
uid: int
region: str
role: List[GsRoleBirthDay]
draw_notice: bool
CurrentTime: str
gender: int
is_show_remind: bool
class RolesCalendar(TypedDict):
calendar_role_infos: MonthlyRoleCalendar
is_pre: bool
is_next: bool
is_year_subscribe: bool
class RoleCalendar(TypedDict):
role_id: int
name: str
role_birthday: str
head_icon: str
is_subscribe: bool
MonthlyRoleCalendar = TypedDict(
'MonthlyRoleCalendar',
{
'1': List[RoleCalendar],
'2': List[RoleCalendar],
'3': List[RoleCalendar],
'4': List[RoleCalendar],
'5': List[RoleCalendar],
'6': List[RoleCalendar],
'7': List[RoleCalendar],
'8': List[RoleCalendar],
'9': List[RoleCalendar],
'10': List[RoleCalendar],
'11': List[RoleCalendar],
'12': List[RoleCalendar],
},
)

View File

@ -1,942 +0,0 @@
'''
米游社 API 请求模块
'''
from __future__ import annotations
import copy
import time
import uuid
import random
from abc import abstractmethod
from string import digits, ascii_letters
from typing import Any, Dict, List, Union, Literal, Optional, cast
from aiohttp import ClientSession, ContentTypeError
from .api import _API
from .tools import (
random_hex,
random_text,
get_ds_token,
generate_os_ds,
gen_payment_sign,
get_web_ds_token,
generate_passport_ds,
)
from .models import (
BsIndex,
GcgInfo,
MysGame,
MysSign,
RegTime,
GachaLog,
MysGoods,
MysOrder,
SignInfo,
SignList,
AbyssData,
IndexData,
AuthKeyInfo,
GcgDeckInfo,
MonthlyAward,
QrCodeStatus,
CalculateInfo,
DailyNoteData,
GameTokenInfo,
MysOrderCheck,
RolesCalendar,
CharDetailData,
CookieTokenInfo,
LoginTicketInfo,
)
mysVersion = '2.44.1'
_HEADER = {
'x-rpc-app_version': mysVersion,
'User-Agent': (
'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) '
f'AppleWebKit/605.1.15 (KHTML, like Gecko) miHoYoBBS/{mysVersion}'
),
'x-rpc-client_type': '5',
'Referer': 'https://webstatic.mihoyo.com/',
'Origin': 'https://webstatic.mihoyo.com',
}
_HEADER_OS = {
'x-rpc-app_version': '1.5.0',
'x-rpc-client_type': '4',
'x-rpc-language': 'zh-cn',
}
RECOGNIZE_SERVER = {
'1': 'cn_gf01',
'2': 'cn_gf01',
'5': 'cn_qd01',
'6': 'os_usa',
'7': 'os_euro',
'8': 'os_asia',
'9': 'os_cht',
}
class MysApi:
proxy_url: Optional[str] = None
@abstractmethod
async def _upass(self, header: Dict):
...
@abstractmethod
async def _pass(self, gt: str, ch: str, header: Dict):
...
@abstractmethod
async def get_ck(
self, uid: str, mode: Literal['OWNER', 'RANDOM'] = 'RANDOM'
) -> Optional[str]:
...
@abstractmethod
async def get_stoken(self, uid: str) -> Optional[str]:
...
async def get_upass_link(self, header: Dict) -> Union[int, Dict]:
header['DS'] = get_ds_token('is_high=false')
return await self._mys_request(
url=_API['VERIFICATION_URL'],
method='GET',
header=header,
)
async def get_bbs_upass_link(self, header: Dict) -> Union[int, Dict]:
header['DS'] = get_ds_token('is_high=false')
return await self._mys_request(
url=_API['BBS_VERIFICATION_URL'],
method='GET',
header=header,
)
async def get_header_and_vl(self, header: Dict, ch, vl):
header['DS'] = get_ds_token(
'',
{
'geetest_challenge': ch,
'geetest_validate': vl,
'geetest_seccode': f'{vl}|jordan',
},
)
_ = await self._mys_request(
url=_API['VERIFY_URL'],
method='POST',
header=header,
data={
'geetest_challenge': ch,
'geetest_validate': vl,
'geetest_seccode': f'{vl}|jordan',
},
)
def check_os(self, uid: str) -> bool:
return False if int(str(uid)[0]) < 6 else True
async def get_info(self, uid, ck: Optional[str]) -> Union[IndexData, int]:
data = await self.simple_mys_req('PLAYER_INFO_URL', uid, cookie=ck)
if isinstance(data, Dict):
data = cast(IndexData, data['data'])
return data
async def get_daily_data(self, uid: str) -> Union[DailyNoteData, int]:
data = await self.simple_mys_req('DAILY_NOTE_URL', uid)
if isinstance(data, Dict):
data = cast(DailyNoteData, data['data'])
return data
async def get_gcg_info(self, uid: str) -> Union[GcgInfo, int]:
data = await self.simple_mys_req('GCG_INFO', uid)
if isinstance(data, Dict):
data = cast(GcgInfo, data['data'])
return data
async def get_gcg_deck(self, uid: str) -> Union[GcgDeckInfo, int]:
data = await self.simple_mys_req('GCG_DECK_URL', uid)
if isinstance(data, Dict):
data = cast(GcgDeckInfo, data['data'])
return data
async def get_cookie_token(
self, token: str, uid: str
) -> Union[CookieTokenInfo, int]:
data = await self._mys_request(
_API['GET_COOKIE_TOKEN_BY_GAME_TOKEN'],
'GET',
params={
'game_token': token,
'account_id': uid,
},
)
if isinstance(data, Dict):
data = cast(CookieTokenInfo, data['data'])
return data
async def get_sign_list(self, uid) -> Union[SignList, int]:
is_os = self.check_os(uid)
if is_os:
params = {
'act_id': 'e202102251931481',
'lang': 'zh-cn',
}
else:
params = {'act_id': 'e202009291139501'}
data = await self._mys_req_get('SIGN_LIST_URL', is_os, params)
if isinstance(data, Dict):
data = cast(SignList, data['data'])
return data
async def get_sign_info(self, uid) -> Union[SignInfo, int]:
server_id = RECOGNIZE_SERVER.get(str(uid)[0])
is_os = self.check_os(uid)
if is_os:
params = {
'act_id': 'e202102251931481',
'lang': 'zh-cn',
'region': server_id,
'uid': uid,
}
header = {
'DS': generate_os_ds(),
}
else:
params = {
'act_id': 'e202009291139501',
'region': server_id,
'uid': uid,
}
header = {}
data = await self._mys_req_get('SIGN_INFO_URL', is_os, params, header)
if isinstance(data, Dict):
data = cast(SignInfo, data['data'])
return data
async def mys_sign(
self, uid, header={}, server_id='cn_gf01'
) -> Union[MysSign, int]:
server_id = RECOGNIZE_SERVER.get(str(uid)[0])
ck = await self.get_ck(uid, 'OWNER')
if ck is None:
return -51
if int(str(uid)[0]) < 6:
HEADER = copy.deepcopy(_HEADER)
HEADER['Cookie'] = ck
HEADER['x-rpc-device_id'] = random_hex(32)
HEADER['x-rpc-app_version'] = '2.44.1'
HEADER['x-rpc-client_type'] = '5'
HEADER['X_Requested_With'] = 'com.mihoyo.hyperion'
HEADER['DS'] = get_web_ds_token(True)
HEADER['Referer'] = (
'https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html'
'?bbs_auth_required=true&act_id=e202009291139501'
'&utm_source=bbs&utm_medium=mys&utm_campaign=icon'
)
HEADER.update(header)
data = await self._mys_request(
url=_API['SIGN_URL'],
method='POST',
header=HEADER,
data={
'act_id': 'e202009291139501',
'uid': uid,
'region': server_id,
},
)
else:
HEADER = copy.deepcopy(_HEADER_OS)
HEADER['Cookie'] = ck
HEADER['DS'] = generate_os_ds()
HEADER.update(header)
data = await self._mys_request(
url=_API['SIGN_URL_OS'],
method='POST',
header=HEADER,
data={
'act_id': 'e202102251931481',
'lang': 'zh-cn',
'uid': uid,
'region': server_id,
},
use_proxy=True,
)
if isinstance(data, Dict):
data = cast(MysSign, data['data'])
return data
async def get_award(self, uid) -> Union[MonthlyAward, int]:
server_id = RECOGNIZE_SERVER.get(str(uid)[0])
ck = await self.get_ck(uid, 'OWNER')
if ck is None:
return -51
if int(str(uid)[0]) < 6:
HEADER = copy.deepcopy(_HEADER)
HEADER['Cookie'] = ck
HEADER['DS'] = get_web_ds_token(True)
HEADER['x-rpc-device_id'] = random_hex(32)
data = await self._mys_request(
url=_API['MONTHLY_AWARD_URL'],
method='GET',
header=HEADER,
params={
'act_id': 'e202009291139501',
'bind_region': server_id,
'bind_uid': uid,
'month': '0',
'bbs_presentation_style': 'fullscreen',
'bbs_auth_required': 'true',
'utm_source': 'bbs',
'utm_medium': 'mys',
'utm_campaign': 'icon',
},
)
else:
HEADER = copy.deepcopy(_HEADER_OS)
HEADER['Cookie'] = ck
HEADER['x-rpc-device_id'] = random_hex(32)
HEADER['DS'] = generate_os_ds()
data = await self._mys_request(
url=_API['MONTHLY_AWARD_URL_OS'],
method='GET',
header=HEADER,
params={
'act_id': 'e202009291139501',
'region': server_id,
'uid': uid,
'month': '0',
},
use_proxy=True,
)
if isinstance(data, Dict):
data = cast(MonthlyAward, data['data'])
return data
async def get_draw_calendar(self, uid: str) -> Union[int, RolesCalendar]:
server_id = RECOGNIZE_SERVER.get(uid[0])
ck = await self.get_ck(uid, 'OWNER')
if ck is None:
return -51
hk4e_token = await self.get_hk4e_token(uid)
header = {}
header['Cookie'] = f'{ck};{hk4e_token}'
params = {
'lang': 'zh-cn',
'badge_uid': uid,
'badge_region': server_id,
'game_biz': 'hk4e_cn',
'activity_id': 20220301153521,
'year': 2023,
}
data = await self._mys_request(
_API['CALENDAR_URL'], 'GET', header, params
)
if isinstance(data, Dict):
return cast(RolesCalendar, data['data'])
return data
async def get_bs_index(self, uid: str) -> Union[int, BsIndex]:
server_id = RECOGNIZE_SERVER.get(uid[0])
ck = await self.get_ck(uid, 'OWNER')
if ck is None:
return -51
hk4e_token = await self.get_hk4e_token(uid)
header = {}
header['Cookie'] = f'{ck};{hk4e_token}'
data = await self._mys_request(
_API['BS_INDEX_URL'],
'GET',
header,
{
'lang': 'zh-cn',
'badge_uid': uid,
'badge_region': server_id,
'game_biz': 'hk4e_cn',
'activity_id': 20220301153521,
},
)
if isinstance(data, Dict):
return cast(BsIndex, data['data'])
return data
async def post_draw(self, uid: str, role_id: int) -> Union[int, Dict]:
server_id = RECOGNIZE_SERVER.get(uid[0])
ck = await self.get_ck(uid, 'OWNER')
if ck is None:
return -51
hk4e_token = await self.get_hk4e_token(uid)
header = {}
header['Cookie'] = f'{ck};{hk4e_token}'
data = await self._mys_request(
_API['RECEIVE_URL'],
'POST',
header,
{
'lang': 'zh-cn',
'badge_uid': uid,
'badge_region': server_id,
'game_biz': 'hk4e_cn',
'activity_id': 20220301153521,
},
{'role_id': role_id},
)
if isinstance(data, Dict):
return data
elif data == -512009:
return {'data': None, 'message': '这张画片已经被收录啦~', 'retcode': -512009}
else:
return -999
async def get_spiral_abyss_info(
self, uid: str, schedule_type='1', ck: Optional[str] = None
) -> Union[AbyssData, int]:
server_id = RECOGNIZE_SERVER.get(uid[0])
data = await self.simple_mys_req(
'PLAYER_ABYSS_INFO_URL',
uid,
{
'role_id': uid,
'schedule_type': schedule_type,
'server': server_id,
},
cookie=ck,
)
if isinstance(data, Dict):
data = cast(AbyssData, data['data'])
return data
async def get_character(
self, uid, character_ids, ck
) -> Union[CharDetailData, int]:
server_id = RECOGNIZE_SERVER.get(str(uid)[0])
if int(str(uid)[0]) < 6:
HEADER = copy.deepcopy(_HEADER)
HEADER['Cookie'] = ck
HEADER['DS'] = get_ds_token(
'',
{
'character_ids': character_ids,
'role_id': uid,
'server': server_id,
},
)
data = await self._mys_request(
_API['PLAYER_DETAIL_INFO_URL'],
'POST',
HEADER,
data={
'character_ids': character_ids,
'role_id': uid,
'server': server_id,
},
)
else:
HEADER = copy.deepcopy(_HEADER_OS)
HEADER['Cookie'] = ck
HEADER['DS'] = generate_os_ds()
data = await self._mys_request(
_API['PLAYER_DETAIL_INFO_URL_OS'],
'POST',
HEADER,
data={
'character_ids': character_ids,
'role_id': uid,
'server': server_id,
},
use_proxy=True,
)
if isinstance(data, Dict):
data = cast(CharDetailData, data['data'])
return data
async def get_calculate_info(
self, uid, char_id: int
) -> Union[CalculateInfo, int]:
server_id = RECOGNIZE_SERVER.get(str(uid)[0])
data = await self.simple_mys_req(
'CALCULATE_INFO_URL',
uid,
{'avatar_id': char_id, 'uid': uid, 'region': server_id},
)
if isinstance(data, Dict):
data = cast(CalculateInfo, data['data'])
return data
async def get_mihoyo_bbs_info(
self,
mys_id: str,
cookie: Optional[str] = None,
is_os: bool = False,
) -> Union[List[MysGame], int]:
if not cookie:
cookie = await self.get_ck(mys_id, 'OWNER')
data = await self.simple_mys_req(
'MIHOYO_BBS_PLAYER_INFO_URL',
is_os,
{'uid': mys_id},
{'Cookie': cookie},
)
if isinstance(data, Dict):
data = cast(List[MysGame], data['data']['list'])
return data
async def create_qrcode_url(self) -> Union[Dict, int]:
device_id: str = ''.join(random.choices(ascii_letters + digits, k=64))
app_id: str = '8'
data = await self._mys_request(
_API['CREATE_QRCODE'],
'POST',
header={},
data={'app_id': app_id, 'device': device_id},
)
if isinstance(data, Dict):
url: str = data['data']['url']
ticket = url.split('ticket=')[1]
return {
'app_id': app_id,
'ticket': ticket,
'device': device_id,
'url': url,
}
return data
async def check_qrcode(
self, app_id: str, ticket: str, device: str
) -> Union[QrCodeStatus, int]:
data = await self._mys_request(
_API['CHECK_QRCODE'],
'POST',
data={
'app_id': app_id,
'ticket': ticket,
'device': device,
},
)
if isinstance(data, Dict):
data = cast(QrCodeStatus, data['data'])
return data
async def get_gacha_log_by_authkey(
self,
uid: str,
gacha_type: str = '301',
page: int = 1,
end_id: str = '0',
) -> Union[int, GachaLog]:
server_id = 'cn_qd01' if uid[0] == '5' else 'cn_gf01'
authkey_rawdata = await self.get_authkey_by_cookie(uid)
if isinstance(authkey_rawdata, int):
return authkey_rawdata
authkey = authkey_rawdata['authkey']
data = await self._mys_request(
url=_API['GET_GACHA_LOG_URL'],
method='GET',
header=_HEADER,
params={
'authkey_ver': '1',
'sign_type': '2',
'auth_appid': 'webview_gacha',
'init_type': '200',
'gacha_id': 'fecafa7b6560db5f3182222395d88aaa6aaac1bc',
'timestamp': str(int(time.time())),
'lang': 'zh-cn',
'device_type': 'mobile',
'plat_type': 'ios',
'region': server_id,
'authkey': authkey,
'game_biz': 'hk4e_cn',
'gacha_type': gacha_type,
'page': page,
'size': '20',
'end_id': end_id,
},
)
if isinstance(data, Dict):
data = cast(GachaLog, data['data'])
return data
async def get_cookie_token_by_game_token(
self, token: str, uid: str
) -> Union[CookieTokenInfo, int]:
data = await self._mys_request(
_API['GET_COOKIE_TOKEN_BY_GAME_TOKEN'],
'GET',
params={
'game_token': token,
'account_id': uid,
},
)
if isinstance(data, Dict):
data = cast(CookieTokenInfo, data['data'])
return data
async def get_cookie_token_by_stoken(
self, stoken: str, mys_id: str, full_sk: Optional[str] = None
) -> Union[CookieTokenInfo, int]:
HEADER = copy.deepcopy(_HEADER)
if full_sk:
HEADER['Cookie'] = full_sk
else:
HEADER['Cookie'] = f'stuid={mys_id};stoken={stoken}'
data = await self._mys_request(
url=_API['GET_COOKIE_TOKEN_URL'],
method='GET',
header=HEADER,
params={
'stoken': stoken,
'uid': mys_id,
},
)
if isinstance(data, Dict):
data = cast(CookieTokenInfo, data['data'])
return data
async def get_stoken_by_login_ticket(
self, lt: str, mys_id: str
) -> Union[LoginTicketInfo, int]:
data = await self._mys_request(
url=_API['GET_STOKEN_URL'],
method='GET',
header=_HEADER,
params={
'login_ticket': lt,
'token_types': '3',
'uid': mys_id,
},
)
if isinstance(data, Dict):
data = cast(LoginTicketInfo, data['data'])
return data
async def get_stoken_by_game_token(
self, account_id: int, game_token: str
) -> Union[GameTokenInfo, int]:
_data = {
'account_id': account_id,
'game_token': game_token,
}
data = await self._mys_request(
_API['GET_STOKEN'],
'POST',
{
'x-rpc-app_version': '2.41.0',
'DS': generate_passport_ds(b=_data),
'x-rpc-aigis': '',
'Content-Type': 'application/json',
'Accept': 'application/json',
'x-rpc-game_biz': 'bbs_cn',
'x-rpc-sys_version': '11',
'x-rpc-device_id': uuid.uuid4().hex,
'x-rpc-device_fp': ''.join(
random.choices(ascii_letters + digits, k=13)
),
'x-rpc-device_name': 'GenshinUid_login_device_lulu',
'x-rpc-device_model': 'GenshinUid_login_device_lulu',
'x-rpc-app_id': 'bll8iq97cem8',
'x-rpc-client_type': '2',
'User-Agent': 'okhttp/4.8.0',
},
data=_data,
)
if isinstance(data, Dict):
data = cast(GameTokenInfo, data['data'])
return data
async def get_authkey_by_cookie(self, uid: str) -> Union[AuthKeyInfo, int]:
server_id = RECOGNIZE_SERVER.get(str(uid)[0])
HEADER = copy.deepcopy(_HEADER)
stoken = await self.get_stoken(uid)
if stoken is None:
return -51
HEADER['Cookie'] = stoken
HEADER['DS'] = get_web_ds_token(True)
HEADER['User-Agent'] = 'okhttp/4.8.0'
HEADER['x-rpc-app_version'] = '2.44.1'
HEADER['x-rpc-sys_version'] = '12'
HEADER['x-rpc-client_type'] = '5'
HEADER['x-rpc-channel'] = 'mihoyo'
HEADER['x-rpc-device_id'] = random_hex(32)
HEADER['x-rpc-device_name'] = random_text(random.randint(1, 10))
HEADER['x-rpc-device_model'] = 'Mi 10'
HEADER['Referer'] = 'https://app.mihoyo.com'
HEADER['Host'] = 'api-takumi.mihoyo.com'
data = await self._mys_request(
url=_API['GET_AUTHKEY_URL'],
method='POST',
header=HEADER,
data={
'auth_appid': 'webview_gacha',
'game_biz': 'hk4e_cn',
'game_uid': uid,
'region': server_id,
},
)
if isinstance(data, Dict):
data = cast(AuthKeyInfo, data['data'])
return data
async def get_hk4e_token(self, uid: str):
# 获取e_hk4e_token
server_id = RECOGNIZE_SERVER.get(uid[0])
header = {
'Cookie': await self.get_ck(uid, 'OWNER'),
'Content-Type': 'application/json;charset=UTF-8',
'Referer': 'https://webstatic.mihoyo.com/',
'Origin': 'https://webstatic.mihoyo.com',
}
use_proxy = False
data = {
'game_biz': 'hk4e_cn',
'lang': 'zh-cn',
'uid': f'{uid}',
'region': f'{server_id}',
}
if int(str(uid)[0]) < 6:
url = _API['HK4E_LOGIN_URL']
else:
url = _API['HK4E_LOGIN_URL_OS']
data['game_biz'] = 'hk4e_global'
use_proxy = True
async with ClientSession() as client:
async with client.request(
method='POST',
url=url,
headers=header,
json=data,
proxy=self.proxy_url if use_proxy else None,
timeout=300,
) as resp:
raw_data = await resp.json()
if 'retcode' in raw_data and raw_data['retcode'] == 0:
_k = resp.cookies['e_hk4e_token'].key
_v = resp.cookies['e_hk4e_token'].value
ck = f'{_k}={_v}'
return ck
else:
return None
async def get_regtime_data(self, uid: str) -> Union[RegTime, int]:
hk4e_token = await self.get_hk4e_token(uid)
ck_token = await self.get_ck(uid, 'OWNER')
params = {
'game_biz': 'hk4e_cn',
'lang': 'zh-cn',
'badge_uid': uid,
'badge_region': RECOGNIZE_SERVER.get(uid[0]),
}
data = await self.simple_mys_req(
'REG_TIME',
uid,
params,
{'Cookie': f'{hk4e_token};{ck_token}' if int(uid[0]) <= 5 else {}},
)
if isinstance(data, Dict):
return cast(RegTime, data['data'])
else:
return data
'''充值相关'''
async def get_fetchgoods(self) -> Union[int, List[MysGoods]]:
data = {
'released_flag': True,
'game': 'hk4e_cn',
'region': 'cn_gf01',
'uid': '1',
'account': '1',
}
resp = await self._mys_request(
url=_API['fetchGoodsurl'],
method='POST',
data=data,
)
if isinstance(resp, int):
return resp
return cast(List[MysGoods], resp['data']['goods_list'])
async def topup(
self,
uid: str,
goods: MysGoods,
method: Literal['weixin', 'alipay'] = 'alipay',
) -> Union[int, MysOrder]:
device_id = str(uuid.uuid4())
HEADER = copy.deepcopy(_HEADER)
ck = await self.get_ck(uid, 'OWNER')
if ck is None:
return -51
HEADER['Cookie'] = ck
account = HEADER['Cookie'].split('account_id=')[1].split(';')[0]
order = {
'account': str(account),
'region': 'cn_gf01',
'uid': uid,
'delivery_url': '',
'device': device_id,
'channel_id': 1,
'client_ip': '',
'client_type': 4,
'game': 'hk4e_cn',
'amount': goods['price'],
# 'amount': 600,
'goods_num': 1,
'goods_id': goods['goods_id'],
'goods_title': f'{goods["goods_name"]}×{str(goods["goods_unit"])}'
if int(goods['goods_unit']) > 0
else goods['goods_name'],
'price_tier': goods['tier_id'],
# 'price_tier': 'Tier_1',
'currency': 'CNY',
'pay_plat': method,
}
data = {'order': order, 'sign': gen_payment_sign(order)}
HEADER['x-rpc-device_id'] = device_id
HEADER['x-rpc-client_type'] = '4'
resp = await self._mys_request(
url=_API['CreateOrderurl'],
method='POST',
header=HEADER,
data=data,
)
if isinstance(resp, int):
return resp
return cast(MysOrder, resp['data'])
async def check_order(
self, order: MysOrder, uid: str
) -> Union[int, MysOrderCheck]:
HEADER = copy.deepcopy(_HEADER)
ck = await self.get_ck(uid, 'OWNER')
if ck is None:
return -51
HEADER['Cookie'] = ck
data = {
'order_no': order['order_no'],
'game': 'hk4e_cn',
'region': 'cn_gf01',
'uid': uid,
}
resp = await self._mys_request(
url=_API['CheckOrderurl'],
method='GET',
header=HEADER,
params=data,
)
if isinstance(resp, int):
return resp
return cast(MysOrderCheck, resp['data'])
async def simple_mys_req(
self,
URL: str,
uid: Union[str, bool],
params: Dict = {},
header: Dict = {},
cookie: Optional[str] = None,
) -> Union[Dict, int]:
if isinstance(uid, bool):
is_os = uid
server_id = 'cn_qd01' if is_os else 'cn_gf01'
else:
server_id = RECOGNIZE_SERVER.get(uid[0])
is_os = False if int(uid[0]) < 6 else True
ex_params = '&'.join([f'{k}={v}' for k, v in params.items()])
if is_os:
_URL = _API[f'{URL}_OS']
HEADER = copy.deepcopy(_HEADER_OS)
HEADER['DS'] = generate_os_ds()
else:
_URL = _API[URL]
HEADER = copy.deepcopy(_HEADER)
HEADER['DS'] = get_ds_token(
ex_params if ex_params else f'role_id={uid}&server={server_id}'
)
HEADER.update(header)
if cookie is not None:
HEADER['Cookie'] = cookie
elif 'Cookie' not in HEADER and isinstance(uid, str):
ck = await self.get_ck(uid)
if ck is None:
return -51
HEADER['Cookie'] = ck
data = await self._mys_request(
url=_URL,
method='GET',
header=HEADER,
params=params if params else {'server': server_id, 'role_id': uid},
use_proxy=True if is_os else False,
)
return data
async def _mys_req_get(
self,
url: str,
is_os: bool,
params: Dict,
header: Optional[Dict] = None,
) -> Union[Dict, int]:
if is_os:
_URL = _API[f'{url}_OS']
HEADER = copy.deepcopy(_HEADER_OS)
use_proxy = True
else:
_URL = _API[url]
HEADER = copy.deepcopy(_HEADER)
use_proxy = False
if header:
HEADER.update(header)
if 'Cookie' not in HEADER and 'uid' in params:
ck = await self.get_ck(params['uid'])
if ck is None:
return -51
HEADER['Cookie'] = ck
data = await self._mys_request(
url=_URL,
method='GET',
header=HEADER,
params=params,
use_proxy=use_proxy,
)
return data
async def _mys_request(
self,
url: str,
method: Literal['GET', 'POST'] = 'GET',
header: Dict[str, Any] = _HEADER,
params: Optional[Dict[str, Any]] = None,
data: Optional[Dict[str, Any]] = None,
use_proxy: Optional[bool] = False,
) -> Union[Dict, int]:
async with ClientSession() as client:
async with client.request(
method,
url=url,
headers=header,
params=params,
json=data,
proxy=self.proxy_url if use_proxy else None,
timeout=300,
) as resp:
try:
raw_data = await resp.json()
except ContentTypeError:
_raw_data = await resp.text()
raw_data = {'retcode': -999, 'data': _raw_data}
print(raw_data)
if 'retcode' in raw_data:
retcode: int = raw_data['retcode']
elif 'code' in raw_data:
retcode: int = raw_data['code']
else:
retcode = 0
if retcode == 1034:
await self._upass(header)
return retcode
elif retcode != 0:
return retcode
return raw_data

View File

@ -1,95 +0,0 @@
import hmac
import json
import time
import random
import string
import hashlib
from typing import Any, Dict, Optional
_S = {
'2.44.1': {
'LK2': 'IEIZiKYaput2OCKQprNuGsog1NZc1FkS',
'K2': 'dZAwGk4e9aC0MXXItkwnHamjA1x30IYw',
'22': 't0qEgfub6cvueAPgR5m9aQWWVciEer7v',
'25': 'xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs',
},
'os': '6cqshh5dhw73bzxn20oexa9k516chk7s',
'PD': 'JwYDpKvLj6MrMqqYU6jTKF17KNO2PXoS',
}
def random_hex(length):
result = hex(random.randint(0, 16**length)).replace('0x', '').upper()
if len(result) < length:
result = '0' * (length - len(result)) + result
return result
def md5(text):
md5_func = hashlib.md5()
md5_func.update(text.encode())
return md5_func.hexdigest()
def random_text(num: int) -> str:
return ''.join(random.sample(string.ascii_lowercase + string.digits, num))
def _random_str_ds(
salt: str,
sets: str = string.ascii_lowercase + string.digits,
with_body: bool = False,
q: str = '',
b: Optional[Dict[str, Any]] = None,
):
i = str(int(time.time()))
r = ''.join(random.sample(sets, 6))
s = f'salt={salt}&t={i}&r={r}'
if with_body:
s += f'&b={json.dumps(b) if b else ""}&q={q}'
c = md5(s)
return f'{i},{r},{c}'
def _random_int_ds(salt: str, q: str = '', b: Optional[Dict[str, Any]] = None):
br = json.dumps(b) if b else ''
s = salt
t = str(int(time.time()))
r = str(random.randint(100000, 200000))
c = md5(f'salt={s}&t={t}&r={r}&b={br}&q={q}')
return f'{t},{r},{c}'
def get_ds_token(
q: str = '',
b: Optional[Dict[str, Any]] = None,
salt_id: str = '25',
):
salt = _S['2.44.1'][salt_id]
return _random_int_ds(salt, q, b)
def get_web_ds_token(web=False):
return _random_str_ds(_S['2.44.1']['LK2'] if web else _S['2.44.1']['K2'])
def generate_os_ds(salt: str = '') -> str:
return _random_str_ds(salt or _S['os'], sets=string.ascii_letters)
def generate_passport_ds(q: str = '', b: Optional[Dict[str, Any]] = None):
return _random_str_ds(_S['PD'], string.ascii_letters, True, q, b)
def HMCASHA256(data: str, key: str):
key_bytes = key.encode('utf-8')
message = data.encode('utf-8')
sign = hmac.new(key_bytes, message, digestmod=hashlib.sha256).digest()
return sign.hex()
def gen_payment_sign(data):
data = dict(sorted(data.items(), key=lambda x: x[0]))
value = ''.join([str(i) for i in data.values()])
sign = HMCASHA256(value, '6bdc3982c25f3f3c38668a32d287d16b')
return sign

View File

@ -1,5 +0,0 @@
from __future__ import annotations
from typing import Any, Dict
AnyDict = Dict[str, Any]

View File

@ -1,511 +0,0 @@
import re
import asyncio
from typing import Dict, List, Literal, Optional
from sqlmodel import SQLModel
from sqlalchemy.future import select
from sqlalchemy import delete, update
from sqlalchemy.orm import sessionmaker
from sqlalchemy.sql.expression import func
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from .utils import SERVER
from .models import GsBind, GsPush, GsUser, GsCache
class SQLA:
def __init__(self, url: str, bot_id: str):
self.bot_id = bot_id
self.url = f'sqlite+aiosqlite:///{url}'
self.engine = create_async_engine(self.url, pool_recycle=1500)
self.async_session = sessionmaker(
self.engine, expire_on_commit=False, class_=AsyncSession
)
def create_all(self):
try:
asyncio.create_task(self._create_all())
except RuntimeError:
loop = asyncio.get_event_loop()
loop.run_until_complete(self._create_all())
loop.close()
async def _create_all(self):
async with self.engine.begin() as conn:
await conn.run_sync(SQLModel.metadata.create_all)
#####################
# GsBind 部分 #
#####################
async def select_bind_data(self, user_id: str) -> Optional[GsBind]:
async with self.async_session() as session:
async with session.begin():
result = await session.execute(
select(GsBind).where(
GsBind.user_id == user_id, GsBind.bot_id == self.bot_id
)
)
data = result.scalars().all()
return data[0] if data else None
async def insert_bind_data(self, user_id: str, **data) -> int:
async with self.async_session() as session:
async with session.begin():
new_uid: str = data['uid'] if 'uid' in data else ''
new_uid = new_uid.strip()
if len(new_uid) != 9:
return -1
elif not new_uid.isdigit():
return -3
if await self.bind_exists(user_id):
uid_list = await self.get_bind_uid_list(user_id)
if new_uid not in uid_list:
uid_list.append(new_uid)
else:
return -2
data['uid'] = '_'.join(uid_list)
await self.update_bind_data(user_id, data)
else:
new_data = GsBind(
user_id=user_id, bot_id=self.bot_id, **data
)
session.add(new_data)
await session.commit()
return 0
async def delete_bind_data(self, user_id: str, **data) -> int:
async with self.async_session() as session:
async with session.begin():
_uid = data['uid'] if 'uid' in data else ''
if await self.bind_exists(user_id):
uid_list = await self.get_bind_uid_list(user_id)
if uid_list and _uid in uid_list:
uid_list.remove(_uid)
else:
return -1
data['uid'] = '_'.join(uid_list)
await self.update_bind_data(user_id, data)
await session.commit()
return 0
else:
return -1
async def update_bind_data(self, user_id: str, data: Optional[Dict]):
async with self.async_session() as session:
async with session.begin():
sql = update(GsBind).where(
GsBind.user_id == user_id, GsBind.bot_id == self.bot_id
)
if data is not None:
query = sql.values(**data)
query.execution_options(synchronize_session='fetch')
await session.execute(query)
async def bind_exists(self, user_id: str) -> bool:
return bool(await self.select_bind_data(user_id))
async def get_all_uid_list(self) -> List[str]:
async with self.async_session() as session:
async with session.begin():
sql = select(GsBind).where(GsBind.bot_id == self.bot_id)
result = await session.execute(sql)
data: List[GsBind] = result.scalars().all()
uid_list: List[str] = []
for item in data:
uid_list.extend(item.uid.split("_") if item.uid else [])
return uid_list
async def get_bind_uid_list(self, user_id: str) -> List[str]:
data = await self.select_bind_data(user_id)
return data.uid.split("_") if data and data.uid else []
async def get_bind_uid(self, user_id: str) -> Optional[str]:
data = await self.get_bind_uid_list(user_id)
return data[0] if data else None
async def switch_uid(
self, user_id: str, uid: Optional[str] = None
) -> Optional[List]:
uid_list = await self.get_bind_uid_list(user_id)
if uid_list and len(uid_list) >= 1:
if uid and uid not in uid_list:
return None
elif uid:
pass
else:
uid = uid_list[1]
uid_list.remove(uid)
uid_list.insert(0, uid)
await self.update_bind_data(user_id, {'uid': '_'.join(uid_list)})
return uid_list
else:
return None
#####################
# GsUser、GsCache 部分 #
#####################
async def select_user_data(self, uid: str) -> Optional[GsUser]:
async with self.async_session() as session:
async with session.begin():
sql = select(GsUser).where(GsUser.uid == uid)
result = await session.execute(sql)
return data[0] if (data := result.scalars().all()) else None
async def select_cache_cookie(self, uid: str) -> Optional[str]:
async with self.async_session() as session:
async with session.begin():
sql = select(GsCache).where(GsCache.uid == uid)
result = await session.execute(sql)
data: List[GsCache] = result.scalars().all()
return data[0].cookie if len(data) >= 1 else None
async def delete_error_cache(self) -> bool:
async with self.async_session() as session:
async with session.begin():
data = await self.get_all_error_cookie()
for cookie in data:
sql = delete(GsCache).where(GsCache.cookie == cookie)
await session.execute(sql)
return True
async def insert_cache_data(
self,
cookie: str,
uid: Optional[str] = None,
mys_id: Optional[str] = None,
) -> bool:
async with self.async_session() as session:
async with session.begin():
new_data = GsCache(cookie=cookie, uid=uid, mys_id=mys_id)
session.add(new_data)
await session.commit()
return True
async def insert_user_data(
self,
user_id: str,
uid: str,
cookie: str,
stoken: Optional[str] = None,
) -> bool:
async with self.async_session() as session:
async with session.begin():
if await self.user_exists(uid):
sql = (
update(GsUser)
.where(GsUser.uid == uid)
.values(
cookie=cookie,
status=None,
stoken=stoken,
bot_id=self.bot_id,
user_id=user_id,
)
)
await session.execute(sql)
else:
account_id = re.search(r'account_id=(\d*)', cookie)
assert account_id is not None
account_id = str(account_id.group(1))
user_data = GsUser(
uid=uid,
mys_id=account_id,
cookie=cookie,
stoken=stoken if stoken else None,
user_id=user_id,
bot_id=self.bot_id,
sign_switch='off',
push_switch='off',
bbs_switch='off',
region=SERVER.get(uid[0], 'cn_gf01'),
)
session.add(user_data)
await session.commit()
return True
async def update_user_data(self, uid: str, data: Optional[Dict]):
async with self.async_session() as session:
async with session.begin():
sql = update(GsUser).where(
GsUser.uid == uid, GsUser.bot_id == self.bot_id
)
if data is not None:
query = sql.values(**data)
query.execution_options(synchronize_session='fetch')
await session.execute(query)
await session.commit()
async def delete_user_data(self, uid: str):
async with self.async_session() as session:
async with session.begin():
if await self.user_exists(uid):
sql = delete(GsUser).where(GsUser.uid == uid)
await session.execute(sql)
await session.commit()
return True
return False
async def delete_cache(self):
async with self.async_session() as session:
async with session.begin():
sql = (
update(GsUser)
.where(GsUser.status == 'limit30')
.values(status=None)
)
empty_sql = delete(GsCache)
await session.execute(sql)
await session.execute(empty_sql)
await session.commit()
async def mark_invalid(self, cookie: str, mark: str):
async with self.async_session() as session:
async with session.begin():
sql = (
update(GsUser)
.where(GsUser.cookie == cookie)
.values(status=mark)
)
await session.execute(sql)
await session.commit()
async def user_exists(self, uid: str) -> bool:
data = await self.select_user_data(uid)
return True if data else False
async def update_user_stoken(
self, uid: str, stoken: Optional[str]
) -> bool:
async with self.async_session() as session:
async with session.begin():
if await self.user_exists(uid):
sql = (
update(GsUser)
.where(GsUser.uid == uid)
.values(stoken=stoken)
)
await session.execute(sql)
await session.commit()
return True
return False
async def update_user_cookie(
self, uid: str, cookie: Optional[str]
) -> bool:
async with self.async_session() as session:
async with session.begin():
if await self.user_exists(uid):
sql = (
update(GsUser)
.where(GsUser.uid == uid)
.values(cookie=cookie)
)
await session.execute(sql)
await session.commit()
return True
return False
async def update_switch_status(self, uid: str, data: Dict) -> bool:
async with self.async_session() as session:
async with session.begin():
if await self.user_exists(uid):
sql = (
update(GsUser).where(GsUser.uid == uid).values(**data)
)
await session.execute(sql)
await session.commit()
return True
return False
async def update_error_status(self, cookie: str, err: str) -> bool:
async with self.async_session() as session:
async with session.begin():
sql = (
update(GsUser)
.where(GsUser.cookie == cookie)
.values(status=err)
)
await session.execute(sql)
await session.commit()
return True
async def get_user_cookie(self, uid: str) -> Optional[str]:
data = await self.select_user_data(uid)
return data.cookie if data else None
async def cookie_validate(self, uid: str) -> bool:
data = await self.select_user_data(uid)
return True if data and data.status is None else False
async def get_user_stoken(self, uid: str) -> Optional[str]:
data = await self.select_user_data(uid)
return data.stoken if data and data.stoken else None
async def get_all_user(self) -> List[GsUser]:
async with self.async_session() as session:
async with session.begin():
sql = select(GsUser).where(
GsUser.cookie is not None, GsUser.cookie != ''
)
result = await session.execute(sql)
data: List[GsUser] = result.scalars().all()
return data
async def get_all_cookie(self) -> List[str]:
data = await self.get_all_user()
return [_u.cookie for _u in data if _u.cookie]
async def get_all_stoken(self) -> List[str]:
data = await self.get_all_user()
return [_u.stoken for _u in data if _u.stoken]
async def get_all_error_cookie(self) -> List[str]:
data = await self.get_all_user()
return [_u.cookie for _u in data if _u.cookie and _u.status]
async def get_all_push_user_list(self) -> List[GsUser]:
data = await self.get_all_user()
return [user for user in data if user.push_switch != 'off']
async def get_random_cookie(self, uid: str) -> Optional[str]:
async with self.async_session() as session:
async with session.begin():
# 有绑定自己CK 并且该CK有效的前提下优先使用自己CK
if await self.user_exists(uid) and await self.cookie_validate(
uid
):
return await self.get_user_cookie(uid)
# 自动刷新缓存
await self.delete_error_cache()
# 获得缓存库Ck
cache_data = await self.select_cache_cookie(uid)
if cache_data is not None:
return cache_data
# 随机取CK
server = SERVER.get(uid[0], 'cn_gf01')
sql = (
select(GsUser)
.where(GsUser.region == server)
.order_by(func.random())
)
data = await session.execute(sql)
user_list: List[GsUser] = data.scalars().all()
for user in user_list:
if not user.status and user.cookie:
await self.insert_cache_data(user.cookie, uid) # 进入缓存
return user.cookie
continue
else:
return None
async def get_switch_status_list(
self, switch: Literal['push', 'sign', 'bbs']
) -> List[GsUser]:
async with self.async_session() as session:
async with session.begin():
_switch = getattr(GsUser, switch, GsUser.push_switch)
sql = select(GsUser).filter(_switch != 'off')
data = await session.execute(sql)
data_list: List[GsUser] = data.scalars().all()
return [user for user in data_list]
#####################
# GsPush 部分 #
#####################
async def insert_push_data(self, uid: str):
async with self.async_session() as session:
async with session.begin():
push_data = GsPush(
bot_id=self.bot_id,
uid=uid,
coin_push='off',
coin_value=2100,
coin_is_push='off',
resin_push='on',
resin_value=140,
resin_is_push='off',
go_push='off',
go_value=120,
go_is_push='off',
transform_push='off',
transform_value=140,
transform_is_push='off',
)
session.add(push_data)
await session.commit()
async def update_push_data(self, uid: str, data: dict) -> bool:
async with self.async_session() as session:
async with session.begin():
await self.push_exists(uid)
sql = (
update(GsPush)
.where(GsPush.uid == uid, GsPush.bot_id == self.bot_id)
.values(**data)
)
await session.execute(sql)
await session.commit()
return True
async def change_push_status(
self,
mode: Literal['coin', 'resin', 'go', 'transform'],
uid: str,
status: str,
):
await self.update_push_data(uid, {f'{mode}_is_push': status})
async def select_push_data(self, uid: str) -> Optional[GsPush]:
async with self.async_session() as session:
async with session.begin():
await self.push_exists(uid)
sql = select(GsPush).where(
GsPush.uid == uid, GsPush.bot_id == self.bot_id
)
result = await session.execute(sql)
data = result.scalars().all()
return data[0] if len(data) >= 1 else None
async def push_exists(self, uid: str) -> bool:
async with self.async_session() as session:
async with session.begin():
sql = select(GsPush).where(
GsPush.uid == uid, GsPush.bot_id == self.bot_id
)
result = await session.execute(sql)
data = result.scalars().all()
if not data:
await self.insert_push_data(uid)
return True
#####################
# 杂项部分 #
#####################
async def refresh_cache(self, uid: str):
async with self.async_session() as session:
async with session.begin():
sql = delete(GsCache).where(GsCache.uid == uid)
await session.execute(sql)
return True
async def close(self):
async with self.async_session() as session:
async with session.begin():
await session.close()
async def insert_new_bind(self, **kwargs):
async with self.async_session() as session:
async with session.begin():
new_data = GsBind(**kwargs)
session.add(new_data)
await session.commit()
async def insert_new_user(self, **kwargs):
async with self.async_session() as session:
async with session.begin():
new_data = GsUser(**kwargs)
session.add(new_data)
await session.commit()

View File

@ -1,55 +0,0 @@
from typing import Optional
from sqlmodel import Field, SQLModel
class GsBind(SQLModel, table=True):
__table_args__ = {'keep_existing': True}
id: Optional[int] = Field(default=None, primary_key=True, title='序号')
bot_id: str = Field(title='平台')
user_id: str = Field(title='账号')
uid: Optional[str] = Field(default=None, title='UID')
mys_id: Optional[str] = Field(default=None, title='米游社通行证')
class GsUser(SQLModel, table=True):
__table_args__ = {'keep_existing': True}
id: Optional[int] = Field(default=None, primary_key=True, title='序号')
bot_id: str = Field(title='平台')
uid: str = Field(title='UID')
mys_id: Optional[str] = Field(default=None, title='米游社通行证')
region: Optional[str] = Field(default=None, title='地区')
cookie: Optional[str] = Field(default=None, title='Cookie')
stoken: Optional[str] = Field(default=None, title='Stoken')
user_id: str = Field(title='账号')
push_switch: str = Field(title='全局推送开关')
sign_switch: str = Field(title='自动签到')
bbs_switch: str = Field(title='自动米游币')
status: Optional[str] = Field(default=None, title='状态')
class GsCache(SQLModel, table=True):
__table_args__ = {'keep_existing': True}
id: Optional[int] = Field(default=None, primary_key=True, title='序号')
cookie: str = Field(default=None, title='Cookie')
uid: Optional[str] = Field(default=None, title='UID')
mys_id: Optional[str] = Field(default=None, title='米游社通行证')
class GsPush(SQLModel, table=True):
__table_args__ = {'keep_existing': True}
id: Optional[int] = Field(default=None, primary_key=True, title='序号')
bot_id: str = Field(title='平台')
uid: str = Field(title='UID')
coin_push: Optional[str] = Field(title='洞天宝钱推送')
coin_value: Optional[int] = Field(title='洞天宝钱阈值')
coin_is_push: Optional[str] = Field(title='洞天宝钱是否已推送')
resin_push: Optional[str] = Field(title='体力推送')
resin_value: Optional[int] = Field(title='体力阈值')
resin_is_push: Optional[str] = Field(title='体力是否已推送')
go_push: Optional[str] = Field(title='派遣推送')
go_value: Optional[int] = Field(title='派遣阈值')
go_is_push: Optional[str] = Field(title='派遣是否已推送')
transform_push: Optional[str] = Field(title='质变仪推送')
transform_value: Optional[int] = Field(title='质变仪阈值')
transform_is_push: Optional[str] = Field(title='质变仪是否已推送')

View File

@ -1,9 +0,0 @@
SERVER = {
'1': 'cn_gf01',
'2': 'cn_gf01',
'5': 'cn_qd01',
'6': 'os_usa',
'7': 'os_euro',
'8': 'os_asia',
'9': 'os_cht',
}

View File

@ -1,148 +0,0 @@
from typing import Union
from color import Color, check_if_color
class BaseTextContainer(list):
@property
def len(self):
_len_count = 0
for i_ in self:
_len_count += len(i_)
return _len_count
def __and__(self, other):
self.append(other)
def __repr__(self):
return f'BaseTextContainer({list(self)})'
class ColorText:
def _set_color(self, color):
self.color = Color(color)
def __init__(self, text: str, color: Union[str, tuple] = 'black'):
self._check_color = check_if_color
if not self._check_color(color):
print(f'Color: {color}\n' f'Type: {str(type(color))}')
# assert self._check_color(color)
self._set_color(color)
self.text = text
def __len__(self):
return len(self.text)
def __repr__(self):
return f'ColorText(\'{self.text}\', {self.color})'
def __str__(self):
return self.text
def __format__(self, format_spec):
return self.__repr__()
def __lshift__(self, other):
self._set_color(other)
def __getitem__(self, index):
return ColorText(self.text[index], color=self.color)
class ColorTextGroup(BaseTextContainer):
def __init__(self, texts=None):
if texts is None:
texts = []
super().__init__(texts)
def append(self, text):
if isinstance(text, (str, ColorText)):
super().append(text)
else:
raise TypeError('The text parameter is neither str nor ColorText')
class TextBuffer(BaseTextContainer):
def __init__(self, seq, length: int):
super().__init__(seq)
self.max_length = length
@property
def free_size(self):
return self.max_length - self.len
def split_ep(
text: Union[str, ColorText], length: int, pre_len: int = 0
): # Cut strings by length but discard the first N characters
# https://github.com/PyCQA/pycodestyle/issues/373
return text[:pre_len], [
text[i : i + length] for i in range(pre_len, len(text), length) # noqa
]
def split_ctg(
group: Union[list, ColorTextGroup], length: int
) -> list: # Cut strings from ColorTextGroup
result = []
buffer = TextBuffer([], length)
if isinstance(group, list):
group = ColorTextGroup(group)
for t in group:
if len(t) <= buffer.free_size:
# buffer & t
continue
if len(t) > buffer.free_size:
_long_text_result = split_ep(t, length, buffer.free_size)
# buffer & _long_text_result[0]
result.append(buffer.copy())
buffer.clear()
if len(_long_text_result[1][-1]) < length:
buffer = TextBuffer(_long_text_result[1][-1:], length)
result.append(_long_text_result[1][:-1])
else:
result.append(_long_text_result[1])
if buffer.free_size == 0:
result.append(buffer.copy())
buffer.clear()
return result
if __name__ == '__main__':
from pprint import pformat
from colorama import Fore, Style
def test_ctg(length: int, *params):
print(
f'{Fore.GREEN}> running split_ctg(){Style.RESET_ALL}\
\n length: {length}\
\n texts: {params}'
)
groups_ = ColorTextGroup(list(params))
f_ = pformat(split_ctg(groups_, length)).split('\n')
print(Fore.CYAN, '\t', f_[0], '\n\t'.join(f_[0:]))
print('*** ColorTextGroup cutting test ***')
test_ctg(
5,
'test',
ColorText('i am red', 'red'),
'foo',
ColorText('bar', color='cyan'),
)
test_ctg(
10,
'获得',
ColorText('12%/15%/18%/21%/24%', color='rgb(69,113,236)'),
'所有元素伤害加成;队伍中附近的其他角色在施放元素战技时,'
'会为装备该武器的角色产生1层「波穗」效果至多叠加2层'
'每0.3秒最多触发1次。装备该武器的角色施放元素战技时'
'如果有积累的「波穗」效果,则将消耗已有的「波穗」,'
'获得「波乱」:根据消耗的层数,每层提升',
ColorText('20%/25%/30%/35%/40%', (69, 113, 236)),
'普通攻击伤害持续8秒。',
)

View File

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -1,2 +0,0 @@
# Image
Image utils split from GsUtils

View File

@ -1,123 +0,0 @@
from enum import Enum
from typing import Tuple, Union
from PIL import ImageColor
class ColorCodes(Enum):
HEX = 'hex'
RGB = 'rgb'
HSV = 'hsv'
class ConvertableColor:
def __init__(self, v_color: tuple):
self.v_color = v_color
def __call__(self, space):
if space == ColorCodes.HEX:
return self.hex
elif space == ColorCodes.RGB:
return self.rgb
elif space == ColorCodes.HSV:
return self.hsv
else:
raise ValueError('Invalid color-code type')
@property
def hex(self):
if len(self.v_color) == 3:
r, g, b = self.v_color
return f'#{r:02x}{g:02x}{b:02x}'
elif len(self.v_color) == 4:
r, g, b, a = self.v_color
return f'#{r:02x}{g:02x}{b:02x}{a:02x}'
else:
raise ValueError('Invalid color value')
@property
def rgb(self):
if len(self.v_color) == 3:
r, g, b = self.v_color
return f'rgb({r}, {g}, {b})'
elif len(self.v_color) == 4:
r, g, b, a = self.v_color
return f'rgba({r}, {g}, {b}, {a})'
else:
raise ValueError('Invalid color value')
@property
def hsv(self):
r, g, b = self.v_color[:3]
r, g, b = r / 255.0, g / 255.0, b / 255.0
mx = max(r, g, b)
mn = min(r, g, b)
df = mx - mn
if mx == mn:
h = 0
elif mx == r:
h = (60 * ((g - b) / df) + 360) % 360
elif mx == g:
h = (60 * ((b - r) / df) + 120) % 360
elif mx == b:
h = (60 * ((r - g) / df) + 240) % 360
else:
h = 360
if mx == 0:
s = 0
else:
s = df / mx
v = mx
return f'hsv({h}, {s}, {v})'
class Color(tuple):
@property
def to(self):
return ConvertableColor(self)
def __new__(
cls,
_color: Union[str, Tuple[int, int, int], Tuple[int, int, int, int]],
):
assert check_if_color(_color)
if isinstance(_color, str):
_color = ImageColor.getrgb(_color)
return super().__new__(cls, _color)
def __str__(self):
return self.to.hex
def __repr__(self):
return f"Color('{self.to.rgb}')"
def __setitem__(self, key, value):
if 0 <= value <= 255:
super().__setitem__(key, value) # type: ignore
else:
raise ValueError("Color value must be between 0 and 255")
def check_if_color(color: Union[str, tuple]):
if isinstance(color, str):
try:
return ImageColor.getrgb(color)
except ValueError:
return False
if isinstance(color, (tuple, Color)):
if len(color) <= 4:
return all(isinstance(d, int) and 0 <= d <= 255 for d in color)
return False
if __name__ == '__main__':
red = Color((1, 1, 1))
print(f'HEX: {red.to.hex}\nHSV: {red.to.hsv}\nRGB: {red.to.rgb}')
print(
f'rgb(123, 23, -1) \
{check_if_color("rgb(123, 23, -1)")}\
\n(100, 200, 255): \
{check_if_color((100, 200, 256))}'
)
print(check_if_color('#ff0000'))

View File

@ -1,2 +0,0 @@
__version__ = "0.1.0"
genshin_impact_version = "3.3"

View File

@ -1,13 +1,14 @@
import re
from typing import List, Union, Optional, TypedDict, cast
from ..gsuid_utils.api.minigg.models import CharacterTalents
from .map.grow_curve import GROW_CURVE_LIST, WEAPON_GROW_CURVE
from ..gsuid_utils.api.ambr.request import (
from gsuid_core.utils.api.minigg.models import CharacterTalents
from gsuid_core.utils.api.ambr.request import (
get_ambr_char_data,
get_ambr_weapon_data,
)
from .map.grow_curve import GROW_CURVE_LIST, WEAPON_GROW_CURVE
PROP_MAP = {
'FIGHT_PROP_BASE_HP': '基础生命值',
'FIGHT_PROP_HP': '总生命值',

View File

@ -3,11 +3,11 @@ from typing import Tuple, Union, Optional, overload
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.utils.api.mys.models import AbyssData, IndexData
from .mys_api import mys_api
from .database import get_sqla
from .error_reply import VERIFY_HINT
from ..gsuid_utils.api.mys.models import AbyssData, IndexData
@overload

View File

@ -2,8 +2,7 @@ from typing import Dict
from sqlalchemy import event
from gsuid_core.data_store import get_res_path
from ..gsuid_utils.database.dal import SQLA
from gsuid_core.utils.database.dal import SQLA
is_wal = False

View File

@ -1,7 +1,8 @@
from typing import Dict, Literal, Optional
from gsuid_core.utils.api.mys import MysApi
from .database import get_sqla
from ..gsuid_utils.api.mys import MysApi
from ..genshinuid_config.gs_config import gsconfig

170
poetry.lock generated
View File

@ -965,14 +965,14 @@ socks = ["socksio (>=1.0.0,<2.0.0)"]
[[package]]
name = "identify"
version = "2.5.22"
version = "2.5.23"
description = "File identification library for Python"
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "identify-2.5.22-py2.py3-none-any.whl", hash = "sha256:f0faad595a4687053669c112004178149f6c326db71ee999ae4636685753ad2f"},
{file = "identify-2.5.22.tar.gz", hash = "sha256:f7a93d6cf98e29bd07663c60728e7a4057615068d7a639d132dc883b2d54d31e"},
{file = "identify-2.5.23-py2.py3-none-any.whl", hash = "sha256:17d9351c028a781456965e781ed2a435755cac655df1ebd930f7186b54399312"},
{file = "identify-2.5.23.tar.gz", hash = "sha256:50b01b9d5f73c6b53e5fa2caf9f543d3e657a9d0bbdeb203ebb8d45960ba7433"},
]
[package.extras]
@ -1551,14 +1551,14 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa
[[package]]
name = "platformdirs"
version = "3.3.0"
version = "3.4.0"
description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"."
category = "dev"
optional = false
python-versions = ">=3.7"
files = [
{file = "platformdirs-3.3.0-py3-none-any.whl", hash = "sha256:ea61fd7b85554beecbbd3e9b37fb26689b227ffae38f73353cbcc1cf8bd01878"},
{file = "platformdirs-3.3.0.tar.gz", hash = "sha256:64370d47dc3fca65b4879f89bdead8197e93e05d696d6d1816243ebae8595da5"},
{file = "platformdirs-3.4.0-py3-none-any.whl", hash = "sha256:01437886022decaf285d8972f9526397bfae2ac55480ed372ed6d9eca048870a"},
{file = "platformdirs-3.4.0.tar.gz", hash = "sha256:a5e1536e5ea4b1c238a1364da17ff2993d5bd28e15600c2c8224008aff6bbcad"},
]
[package.extras]
@ -1898,14 +1898,14 @@ test = ["coverage", "pytest"]
[[package]]
name = "requests"
version = "2.28.2"
version = "2.29.0"
description = "Python HTTP for Humans."
category = "dev"
optional = false
python-versions = ">=3.7, <4"
python-versions = ">=3.7"
files = [
{file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"},
{file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"},
{file = "requests-2.29.0-py3-none-any.whl", hash = "sha256:e8f3c9be120d3333921d213eef078af392fba3933ab7ed2d1cba3b56f2568c3b"},
{file = "requests-2.29.0.tar.gz", hash = "sha256:f2e34a75f4749019bb0e3effb66683630e4ffeaf75819fb51bebef1bf5aef059"},
]
[package.dependencies]
@ -2307,86 +2307,86 @@ dev = ["black (>=19.3b0)", "pytest (>=4.6.2)"]
[[package]]
name = "yarl"
version = "1.9.1"
version = "1.9.2"
description = "Yet another URL library"
category = "main"
optional = false
python-versions = ">=3.7"
files = [
{file = "yarl-1.9.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e124b283a04cc06d22443cae536f93d86cd55108fa369f22b8fe1f2288b2fe1c"},
{file = "yarl-1.9.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:56956b13ec275de31fe4fb991510b735c4fb3e1b01600528c952b9ac90464430"},
{file = "yarl-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ecaa5755a39f6f26079bf13f336c67af589c222d76b53cd3824d3b684b84d1f1"},
{file = "yarl-1.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:92a101f6d5a9464e86092adc36cd40ef23d18a25bfb1eb32eaeb62edc22776bb"},
{file = "yarl-1.9.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92e37999e36f9f3ded78e9d839face6baa2abdf9344ea8ed2735f495736159de"},
{file = "yarl-1.9.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef7e2f6c47c41e234600a02e1356b799761485834fe35d4706b0094cb3a587ee"},
{file = "yarl-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1d7a0075a55380b19aa43b9e8056e128b058460d71d75018a4f9d60ace01e78c"},
{file = "yarl-1.9.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2f01351b7809182822b21061d2a4728b7b9e08f4585ba90ee4c5c4d3faa0812"},
{file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6cf47fe9df9b1ededc77e492581cdb6890a975ad96b4172e1834f1b8ba0fc3ba"},
{file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:098bdc06ffb4db39c73883325b8c738610199f5f12e85339afedf07e912a39af"},
{file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:6cdb47cbbacae8e1d7941b0d504d0235d686090eef5212ca2450525905e9cf02"},
{file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:73a4b46689f2d59c8ec6b71c9a0cdced4e7863dd6eb98a8c30ea610e191f9e1c"},
{file = "yarl-1.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:65d952e464df950eed32bb5dcbc1b4443c7c2de4d7abd7265b45b1b3b27f5fa2"},
{file = "yarl-1.9.1-cp310-cp310-win32.whl", hash = "sha256:39a7a9108e9fc633ae381562f8f0355bb4ba00355218b5fb19cf5263fcdbfa68"},
{file = "yarl-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b63d41e0eecf3e3070d44f97456cf351fff7cb960e97ecb60a936b877ff0b4f6"},
{file = "yarl-1.9.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:4295790981630c4dab9d6de7b0f555a4c8defe3ed7704a8e9e595a321e59a0f5"},
{file = "yarl-1.9.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b2b2382d59dec0f1fdca18ea429c4c4cee280d5e0dbc841180abb82e188cf6e9"},
{file = "yarl-1.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:575975d28795a61e82c85f114c02333ca54cbd325fd4e4b27598c9832aa732e7"},
{file = "yarl-1.9.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bb794882818fae20ff65348985fdf143ea6dfaf6413814db1848120db8be33e"},
{file = "yarl-1.9.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89da1fd6068553e3a333011cc17ad91c414b2100c32579ddb51517edc768b49c"},
{file = "yarl-1.9.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4d817593d345fefda2fae877accc8a0d9f47ada57086da6125fa02a62f6d1a94"},
{file = "yarl-1.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85aa6fd779e194901386709e0eedd45710b68af2709f82a84839c44314b68c10"},
{file = "yarl-1.9.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eed9827033b7f67ad12cb70bd0cb59d36029144a7906694317c2dbf5c9eb5ddd"},
{file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:df747104ef27ab1aa9a1145064fa9ea26ad8cf24bfcbdba7db7abf0f8b3676b9"},
{file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:efec77851231410125cb5be04ec96fa4a075ca637f415a1f2d2c900b09032a8a"},
{file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d5c407e530cf2979ea383885516ae79cc4f3c3530623acf5e42daf521f5c2564"},
{file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f76edb386178a54ea7ceffa798cb830c3c22ab50ea10dfb25dc952b04848295f"},
{file = "yarl-1.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:75676110bce59944dd48fd18d0449bd37eaeb311b38a0c768f7670864b5f8b68"},
{file = "yarl-1.9.1-cp311-cp311-win32.whl", hash = "sha256:9ba5a18c4fbd408fe49dc5da85478a76bc75c1ce912d7fd7b43ed5297c4403e1"},
{file = "yarl-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:b20a5ddc4e243cbaa54886bfe9af6ffc4ba4ef58f17f1bb691e973eb65bba84d"},
{file = "yarl-1.9.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:791357d537a09a194f92b834f28c98d074e7297bac0a8f1d5b458a906cafa17c"},
{file = "yarl-1.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89099c887338608da935ba8bee027564a94f852ac40e472de15d8309517ad5fe"},
{file = "yarl-1.9.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:395ea180257a3742d09dcc5071739682a95f7874270ebe3982d6696caec75be0"},
{file = "yarl-1.9.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:90ebaf448b5f048352ec7c76cb8d452df30c27cb6b8627dfaa9cf742a14f141a"},
{file = "yarl-1.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f878a78ed2ccfbd973cab46dd0933ecd704787724db23979e5731674d76eb36f"},
{file = "yarl-1.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:74390c2318d066962500045aa145f5412169bce842e734b8c3e6e3750ad5b817"},
{file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f8e73f526140c1c32f5fca4cd0bc3b511a1abcd948f45b2a38a95e4edb76ca72"},
{file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ac8e593df1fbea820da7676929f821a0c7c2cecb8477d010254ce8ed54328ea8"},
{file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:01cf88cb80411978a14aa49980968c1aeb7c18a90ac978c778250dd234d8e0ba"},
{file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:97d76a3128f48fa1c721ef8a50e2c2f549296b2402dc8a8cde12ff60ed922f53"},
{file = "yarl-1.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:01a073c9175481dfed6b40704a1b67af5a9435fc4a58a27d35fd6b303469b0c7"},
{file = "yarl-1.9.1-cp37-cp37m-win32.whl", hash = "sha256:ecad20c3ef57c513dce22f58256361d10550a89e8eaa81d5082f36f8af305375"},
{file = "yarl-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f5bcb80006efe9bf9f49ae89711253dd06df8053ff814622112a9219346566a7"},
{file = "yarl-1.9.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e7ddebeabf384099814353a2956ed3ab5dbaa6830cc7005f985fcb03b5338f05"},
{file = "yarl-1.9.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:13a1ad1f35839b3bb5226f59816b71e243d95d623f5b392efaf8820ddb2b3cd5"},
{file = "yarl-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f0cd87949d619157a0482c6c14e5011f8bf2bc0b91cb5087414d9331f4ef02dd"},
{file = "yarl-1.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d21887cbcf6a3cc5951662d8222bc9c04e1b1d98eebe3bb659c3a04ed49b0eec"},
{file = "yarl-1.9.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4764114e261fe49d5df9b316b3221493d177247825c735b2aae77bc2e340d800"},
{file = "yarl-1.9.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3abe37fd89a93ebe0010417ca671f422fa6fcffec54698f623b09f46b4d4a512"},
{file = "yarl-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9fe3a1c073ab80a28a06f41d2b623723046709ed29faf2c56bea41848597d86"},
{file = "yarl-1.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b3b5f8da07a21f2e57551f88a6709c2d340866146cf7351e5207623cfe8aad16"},
{file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:88f6413ff5edfb9609e2769e32ce87a62353e66e75d264bf0eaad26fb9daa8f2"},
{file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b5d5fb6c94b620a7066a3adb7c246c87970f453813979818e4707ac32ce4d7bd"},
{file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:f206adb89424dca4a4d0b31981869700e44cd62742527e26d6b15a510dd410a2"},
{file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:44fa6158e6b4b8ccfa2872c3900a226b29e8ce543ce3e48aadc99816afa8874d"},
{file = "yarl-1.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:08c8599d6aa8a24425f8635f6c06fa8726afe3be01c8e53e236f519bcfa5db5b"},
{file = "yarl-1.9.1-cp38-cp38-win32.whl", hash = "sha256:6b09cce412386ea9b4dda965d8e78d04ac5b5792b2fa9cced3258ec69c7d1c16"},
{file = "yarl-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:09c56a32c26e24ef98d5757c5064e252836f621f9a8b42737773aa92936b8e08"},
{file = "yarl-1.9.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b86e98c3021b7e2740d8719bf074301361bf2f51221ca2765b7a58afbfbd9042"},
{file = "yarl-1.9.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5faf3ec98747318cb980aaf9addf769da68a66431fc203a373d95d7ee9c1fbb4"},
{file = "yarl-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a21789bdf28549d4eb1de6910cabc762c9f6ae3eef85efc1958197c1c6ef853b"},
{file = "yarl-1.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a8b8d4b478a9862447daef4cafc89d87ea4ed958672f1d11db7732b77ead49cc"},
{file = "yarl-1.9.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:307a782736ebf994e7600dcaeea3b3113083584da567272f2075f1540919d6b3"},
{file = "yarl-1.9.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:46c4010de941e2e1365c07fb4418ddca10fcff56305a6067f5ae857f8c98f3a7"},
{file = "yarl-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bab67d041c78e305ff3eef5e549304d843bd9b603c8855b68484ee663374ce15"},
{file = "yarl-1.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1baf8cdaaab65d9ccedbf8748d626ad648b74b0a4d033e356a2f3024709fb82f"},
{file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:27efc2e324f72df02818cd72d7674b1f28b80ab49f33a94f37c6473c8166ce49"},
{file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ca14b84091700ae7c1fcd3a6000bd4ec1a3035009b8bcb94f246741ca840bb22"},
{file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c3ca8d71b23bdf164b36d06df2298ec8a5bd3de42b17bf3e0e8e6a7489195f2c"},
{file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:8c72a1dc7e2ea882cd3df0417c808ad3b69e559acdc43f3b096d67f2fb801ada"},
{file = "yarl-1.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d966cd59df9a4b218480562e8daab39e87e746b78a96add51a3ab01636fc4291"},
{file = "yarl-1.9.1-cp39-cp39-win32.whl", hash = "sha256:518a92a34c741836a315150460b5c1c71ae782d569eabd7acf53372e437709f7"},
{file = "yarl-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:78755ce43b6e827e65ec0c68be832f86d059fcf05d4b33562745ebcfa91b26b1"},
{file = "yarl-1.9.1.tar.gz", hash = "sha256:5ce0bcab7ec759062c818d73837644cde567ab8aa1e0d6c45db38dfb7c284441"},
{file = "yarl-1.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:8c2ad583743d16ddbdf6bb14b5cd76bf43b0d0006e918809d5d4ddf7bde8dd82"},
{file = "yarl-1.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82aa6264b36c50acfb2424ad5ca537a2060ab6de158a5bd2a72a032cc75b9eb8"},
{file = "yarl-1.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0c77533b5ed4bcc38e943178ccae29b9bcf48ffd1063f5821192f23a1bd27b9"},
{file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee4afac41415d52d53a9833ebae7e32b344be72835bbb589018c9e938045a560"},
{file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9bf345c3a4f5ba7f766430f97f9cc1320786f19584acc7086491f45524a551ac"},
{file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2a96c19c52ff442a808c105901d0bdfd2e28575b3d5f82e2f5fd67e20dc5f4ea"},
{file = "yarl-1.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:891c0e3ec5ec881541f6c5113d8df0315ce5440e244a716b95f2525b7b9f3608"},
{file = "yarl-1.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c3a53ba34a636a256d767c086ceb111358876e1fb6b50dfc4d3f4951d40133d5"},
{file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:566185e8ebc0898b11f8026447eacd02e46226716229cea8db37496c8cdd26e0"},
{file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2b0738fb871812722a0ac2154be1f049c6223b9f6f22eec352996b69775b36d4"},
{file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:32f1d071b3f362c80f1a7d322bfd7b2d11e33d2adf395cc1dd4df36c9c243095"},
{file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:e9fdc7ac0d42bc3ea78818557fab03af6181e076a2944f43c38684b4b6bed8e3"},
{file = "yarl-1.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:56ff08ab5df8429901ebdc5d15941b59f6253393cb5da07b4170beefcf1b2528"},
{file = "yarl-1.9.2-cp310-cp310-win32.whl", hash = "sha256:8ea48e0a2f931064469bdabca50c2f578b565fc446f302a79ba6cc0ee7f384d3"},
{file = "yarl-1.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:50f33040f3836e912ed16d212f6cc1efb3231a8a60526a407aeb66c1c1956dde"},
{file = "yarl-1.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:646d663eb2232d7909e6601f1a9107e66f9791f290a1b3dc7057818fe44fc2b6"},
{file = "yarl-1.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:aff634b15beff8902d1f918012fc2a42e0dbae6f469fce134c8a0dc51ca423bb"},
{file = "yarl-1.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a83503934c6273806aed765035716216cc9ab4e0364f7f066227e1aaea90b8d0"},
{file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b25322201585c69abc7b0e89e72790469f7dad90d26754717f3310bfe30331c2"},
{file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22a94666751778629f1ec4280b08eb11815783c63f52092a5953faf73be24191"},
{file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ec53a0ea2a80c5cd1ab397925f94bff59222aa3cf9c6da938ce05c9ec20428d"},
{file = "yarl-1.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:159d81f22d7a43e6eabc36d7194cb53f2f15f498dbbfa8edc8a3239350f59fe7"},
{file = "yarl-1.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:832b7e711027c114d79dffb92576acd1bd2decc467dec60e1cac96912602d0e6"},
{file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:95d2ecefbcf4e744ea952d073c6922e72ee650ffc79028eb1e320e732898d7e8"},
{file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:d4e2c6d555e77b37288eaf45b8f60f0737c9efa3452c6c44626a5455aeb250b9"},
{file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:783185c75c12a017cc345015ea359cc801c3b29a2966c2655cd12b233bf5a2be"},
{file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:b8cc1863402472f16c600e3e93d542b7e7542a540f95c30afd472e8e549fc3f7"},
{file = "yarl-1.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:822b30a0f22e588b32d3120f6d41e4ed021806418b4c9f0bc3048b8c8cb3f92a"},
{file = "yarl-1.9.2-cp311-cp311-win32.whl", hash = "sha256:a60347f234c2212a9f0361955007fcf4033a75bf600a33c88a0a8e91af77c0e8"},
{file = "yarl-1.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:be6b3fdec5c62f2a67cb3f8c6dbf56bbf3f61c0f046f84645cd1ca73532ea051"},
{file = "yarl-1.9.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38a3928ae37558bc1b559f67410df446d1fbfa87318b124bf5032c31e3447b74"},
{file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac9bb4c5ce3975aeac288cfcb5061ce60e0d14d92209e780c93954076c7c4367"},
{file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3da8a678ca8b96c8606bbb8bfacd99a12ad5dd288bc6f7979baddd62f71c63ef"},
{file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:13414591ff516e04fcdee8dc051c13fd3db13b673c7a4cb1350e6b2ad9639ad3"},
{file = "yarl-1.9.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf74d08542c3a9ea97bb8f343d4fcbd4d8f91bba5ec9d5d7f792dbe727f88938"},
{file = "yarl-1.9.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e7221580dc1db478464cfeef9b03b95c5852cc22894e418562997df0d074ccc"},
{file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:494053246b119b041960ddcd20fd76224149cfea8ed8777b687358727911dd33"},
{file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:52a25809fcbecfc63ac9ba0c0fb586f90837f5425edfd1ec9f3372b119585e45"},
{file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:e65610c5792870d45d7b68c677681376fcf9cc1c289f23e8e8b39c1485384185"},
{file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:1b1bba902cba32cdec51fca038fd53f8beee88b77efc373968d1ed021024cc04"},
{file = "yarl-1.9.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:662e6016409828ee910f5d9602a2729a8a57d74b163c89a837de3fea050c7582"},
{file = "yarl-1.9.2-cp37-cp37m-win32.whl", hash = "sha256:f364d3480bffd3aa566e886587eaca7c8c04d74f6e8933f3f2c996b7f09bee1b"},
{file = "yarl-1.9.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6a5883464143ab3ae9ba68daae8e7c5c95b969462bbe42e2464d60e7e2698368"},
{file = "yarl-1.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5610f80cf43b6202e2c33ba3ec2ee0a2884f8f423c8f4f62906731d876ef4fac"},
{file = "yarl-1.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9a4e67ad7b646cd6f0938c7ebfd60e481b7410f574c560e455e938d2da8e0f4"},
{file = "yarl-1.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:83fcc480d7549ccebe9415d96d9263e2d4226798c37ebd18c930fce43dfb9574"},
{file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fcd436ea16fee7d4207c045b1e340020e58a2597301cfbcfdbe5abd2356c2fb"},
{file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:84e0b1599334b1e1478db01b756e55937d4614f8654311eb26012091be109d59"},
{file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3458a24e4ea3fd8930e934c129b676c27452e4ebda80fbe47b56d8c6c7a63a9e"},
{file = "yarl-1.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:838162460b3a08987546e881a2bfa573960bb559dfa739e7800ceeec92e64417"},
{file = "yarl-1.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f4e2d08f07a3d7d3e12549052eb5ad3eab1c349c53ac51c209a0e5991bbada78"},
{file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:de119f56f3c5f0e2fb4dee508531a32b069a5f2c6e827b272d1e0ff5ac040333"},
{file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:149ddea5abf329752ea5051b61bd6c1d979e13fbf122d3a1f9f0c8be6cb6f63c"},
{file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:674ca19cbee4a82c9f54e0d1eee28116e63bc6fd1e96c43031d11cbab8b2afd5"},
{file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:9b3152f2f5677b997ae6c804b73da05a39daa6a9e85a512e0e6823d81cdad7cc"},
{file = "yarl-1.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:5415d5a4b080dc9612b1b63cba008db84e908b95848369aa1da3686ae27b6d2b"},
{file = "yarl-1.9.2-cp38-cp38-win32.whl", hash = "sha256:f7a3d8146575e08c29ed1cd287068e6d02f1c7bdff8970db96683b9591b86ee7"},
{file = "yarl-1.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:63c48f6cef34e6319a74c727376e95626f84ea091f92c0250a98e53e62c77c72"},
{file = "yarl-1.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:75df5ef94c3fdc393c6b19d80e6ef1ecc9ae2f4263c09cacb178d871c02a5ba9"},
{file = "yarl-1.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c027a6e96ef77d401d8d5a5c8d6bc478e8042f1e448272e8d9752cb0aff8b5c8"},
{file = "yarl-1.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f3b078dbe227f79be488ffcfc7a9edb3409d018e0952cf13f15fd6512847f3f7"},
{file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:59723a029760079b7d991a401386390c4be5bfec1e7dd83e25a6a0881859e716"},
{file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b03917871bf859a81ccb180c9a2e6c1e04d2f6a51d953e6a5cdd70c93d4e5a2a"},
{file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c1012fa63eb6c032f3ce5d2171c267992ae0c00b9e164efe4d73db818465fac3"},
{file = "yarl-1.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a74dcbfe780e62f4b5a062714576f16c2f3493a0394e555ab141bf0d746bb955"},
{file = "yarl-1.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c56986609b057b4839968ba901944af91b8e92f1725d1a2d77cbac6972b9ed1"},
{file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:2c315df3293cd521033533d242d15eab26583360b58f7ee5d9565f15fee1bef4"},
{file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b7232f8dfbd225d57340e441d8caf8652a6acd06b389ea2d3222b8bc89cbfca6"},
{file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:53338749febd28935d55b41bf0bcc79d634881195a39f6b2f767870b72514caf"},
{file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:066c163aec9d3d073dc9ffe5dd3ad05069bcb03fcaab8d221290ba99f9f69ee3"},
{file = "yarl-1.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8288d7cd28f8119b07dd49b7230d6b4562f9b61ee9a4ab02221060d21136be80"},
{file = "yarl-1.9.2-cp39-cp39-win32.whl", hash = "sha256:b124e2a6d223b65ba8768d5706d103280914d61f5cae3afbc50fc3dfcc016623"},
{file = "yarl-1.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:61016e7d582bc46a5378ffdd02cd0314fb8ba52f40f9cf4d9a5e7dbef88dee18"},
{file = "yarl-1.9.2.tar.gz", hash = "sha256:04ab9d4b9f587c06d801c2abfe9317b77cdf996c65a90d5e84ecc45010823571"},
]
[package.dependencies]

View File

@ -60,4 +60,4 @@ typing-extensions==4.5.0 ; python_full_version >= "3.8.1" and python_version < "
tzdata==2023.3 ; python_full_version >= "3.8.1" and python_version < "4.0"
tzlocal==4.3 ; python_full_version >= "3.8.1" and python_version < "4.0"
win32-setctime==1.1.0 ; python_full_version >= "3.8.1" and python_version < "4.0" and sys_platform == "win32"
yarl==1.9.1 ; python_full_version >= "3.8.1" and python_version < "4.0"
yarl==1.9.2 ; python_full_version >= "3.8.1" and python_version < "4.0"