Compare commits

..

2 Commits

Author SHA1 Message Date
5b916025f3 feat(cli): add --silent option 2023-06-30 20:55:53 +07:00
69a53da8ae feat: wrap all objects on /resource endpoint 2023-06-30 20:55:30 +07:00
5 changed files with 218 additions and 26 deletions

View File

@ -3,26 +3,33 @@ from vollerei import __version__
from vollerei.cli.hsr import HSR from vollerei.cli.hsr import HSR
from vollerei.hsr import PatchType from vollerei.hsr import PatchType
from vollerei.cli import utils from vollerei.cli import utils
from vollerei.cli.utils import msg
class CLI: class CLI:
def __init__( def __init__(
self, game_path: str = None, patch_type=None, noconfirm: bool = False self,
game_path: str = None,
patch_type=None,
noconfirm: bool = False,
silent: bool = False,
) -> None: ) -> None:
""" """
Vollerei CLI Vollerei CLI
""" """
print(f"Vollerei v{__version__}") utils.silent_message = silent
msg(f"Vollerei v{__version__}")
if noconfirm: if noconfirm:
print("User requested to automatically answer yes to all questions.") msg("User requested to automatically answer yes to all questions.")
utils.no_confirm = noconfirm utils.no_confirm = noconfirm
if not game_path: if not game_path:
game_path = Path.cwd() game_path = Path.cwd()
game_path = Path(game_path) game_path = Path(game_path)
hsr_patch_type = patch_type
if patch_type is None: if patch_type is None:
patch_type = PatchType.Jadeite hsr_patch_type = PatchType.Jadeite
elif isinstance(patch_type, str): elif isinstance(patch_type, str):
patch_type = PatchType[patch_type] hsr_patch_type = PatchType[patch_type]
elif isinstance(patch_type, int): elif isinstance(patch_type, int):
patch_type = PatchType(patch_type) hsr_patch_type = PatchType(patch_type)
self.hsr = HSR(game_path=game_path, patch_type=patch_type) self.hsr = HSR(game_path=game_path, patch_type=hsr_patch_type)

View File

@ -1,6 +1,6 @@
from traceback import print_exc from traceback import print_exc
from platform import system from platform import system
from vollerei.cli.utils import ask from vollerei.cli.utils import ask, msg
from vollerei.hsr import Game, Patcher from vollerei.hsr import Game, Patcher
from vollerei.exceptions.patcher import PatcherError, PatchUpdateError from vollerei.exceptions.patcher import PatcherError, PatchUpdateError
from vollerei.hsr.patcher import PatchType from vollerei.hsr.patcher import PatchType
@ -11,8 +11,8 @@ class HSR:
self, game_path=None, patch_type: PatchType = PatchType.Jadeite self, game_path=None, patch_type: PatchType = PatchType.Jadeite
) -> None: ) -> None:
self._game = Game(game_path) self._game = Game(game_path)
print("Game directory:", self._game.path) msg("Game directory:", self._game.path)
print("Game version:", self._game.get_version_str()) msg("Game version:", self._game.get_version_str())
self._patcher = Patcher() self._patcher = Patcher()
self._patcher.patch_type = patch_type self._patcher.patch_type = patch_type
@ -115,3 +115,6 @@ class HSR:
self.__patch_jadeite() self.__patch_jadeite()
case PatchType.Astra: case PatchType.Astra:
self.__patch_astra() self.__patch_astra()
def get_version(self):
print(self._game.get_version_str())

View File

@ -1,4 +1,5 @@
no_confirm = False no_confirm = False
silent_message = False
def ask(question: str): def ask(question: str):
@ -12,3 +13,12 @@ def ask(question: str):
# Pacman way, treat all other answers as no # Pacman way, treat all other answers as no
else: else:
return False return False
def msg(*args, **kwargs):
"""
Print but silentable
"""
if silent_message:
return
print(*args, **kwargs)

View File

@ -4,11 +4,35 @@ Class wrapper for API endpoint /resource
class Segment: class Segment:
"""
A segment of the game archive.
Attributes:
path (str): Segment download path.
md5 (str): Segment md5 checksum.
package_size (int | None): Segment package size.
"""
path: str path: str
md5: str md5: str
# str -> int and checked if int is 0 then None # str -> int and checked if int is 0 then None
package_size: int | None package_size: int | None
def __init__(self, path: str, md5: str, package_size: int | None) -> None:
self.path = path
self.md5 = md5
self.package_size = package_size
@staticmethod
def from_dict(data: dict) -> "Segment":
return Segment(
data["path"],
data["md5"],
int(data["package_size"])
if data["package_size"] and data["package_size"] != "0"
else None,
)
class VoicePack: class VoicePack:
""" """
@ -17,7 +41,12 @@ class VoicePack:
`name` maybe converted from `path` if the server returns empty string. `name` maybe converted from `path` if the server returns empty string.
Attributes: Attributes:
TODO language (str): Language of the voice pack.
name (str): Voice pack archive name.
path (str): Voice pack download path.
size (int): Voice pack size.
md5 (str): Voice pack md5 checksum.
package_size (int): Voice pack package size.
""" """
language: str language: str
@ -29,6 +58,33 @@ class VoicePack:
# str -> int # str -> int
package_size: int package_size: int
def __init__(
self,
language: str,
name: str,
path: str,
size: int,
md5: str,
package_size: int,
) -> None:
self.language = language
self.name = name
self.path = path
self.size = size
self.md5 = md5
self.package_size = package_size
@staticmethod
def from_dict(data: dict) -> "VoicePack":
return VoicePack(
data["language"],
data["name"],
data["path"],
int(data["size"]),
data["md5"],
int(data["package_size"]),
)
class Diff: class Diff:
""" """
@ -49,6 +105,39 @@ class Diff:
# str -> int # str -> int
package_size: int package_size: int
def __init__(
self,
name: str,
version: str,
path: str,
size: int,
md5: str,
is_recommended_update: bool,
voice_packs: list[VoicePack],
package_size: int,
) -> None:
self.name = name
self.version = version
self.path = path
self.size = size
self.md5 = md5
self.is_recommended_update = is_recommended_update
self.voice_packs = voice_packs
self.package_size = package_size
@staticmethod
def from_dict(data: dict) -> "Diff":
return Diff(
data["name"],
data["version"],
data["path"],
int(data["size"]),
data["md5"],
data["is_recommended_update"],
[VoicePack.from_dict(i) for i in data["voice_packs"]],
int(data["package_size"]),
)
class Latest: class Latest:
""" """
@ -58,13 +147,35 @@ class Latest:
and if `path` is empty too then it'll convert the name from the first and if `path` is empty too then it'll convert the name from the first
segment of `segments` list. segment of `segments` list.
`path` maybe None if the server returns empty string, in that case
you'll have to download the game using `segments` list and merge them.
`voice_packs` will be empty for Star Rail, they force you to download
in-game instead.
`decompressed_path` is useful for repairing game files by only having
to re-download the corrupted files.
`segments` is a list of game archive segments, you'll have to download
them and merge them together to get the full game archive. Not available
on Star Rail.
Attributes: Attributes:
TODO name (str): Game archive name.
version (str): Game version in the archive.
path (str | None): Game archive download path.
size (int): Game archive size in bytes.
md5 (str): Game archive MD5 checksum.
entry (str): Game entry file (e.g. GenshinImpact.exe).
voice_packs (list[VoicePack]): Game voice packs.
decompressed_path (str | None): Game archive decompressed path.
segments (list[Segment]): Game archive segments.
package_size (int): Game archive package size in bytes.
""" """
name: str name: str
version: str version: str
path: str path: str | None
# str -> int # str -> int
size: int size: int
md5: str md5: str
@ -110,14 +221,14 @@ class Latest:
return Latest( return Latest(
data["name"], data["name"],
data["version"], data["version"],
data["path"], data["path"] if data["path"] != "" else None,
data["size"], int(data["size"]),
data["md5"], data["md5"],
data["entry"], data["entry"],
[VoicePack.from_dict(i) for i in data["voice_packs"]], [VoicePack.from_dict(i) for i in data["voice_packs"]],
data["decompressed_path"], data["decompressed_path"] if data["decompressed_path"] != "" else None,
[Segment.from_dict(i) for i in data["segments"]], [Segment.from_dict(i) for i in data["segments"]],
data["package_size"], int(data["package_size"]),
) )
@ -149,27 +260,96 @@ class Plugin:
# str -> int # str -> int
package_size: int package_size: int
def __init__(
self,
name: str,
version: str | None,
path: str,
size: int,
md5: str,
entry: str | None,
package_size: int,
) -> None:
self.name = name
self.version = version
self.path = path
self.size = size
self.md5 = md5
self.entry = entry
self.package_size = package_size
@staticmethod
def from_dict(data: dict) -> "Plugin":
return Plugin(
data["name"],
data["version"] if data["version"] != "" else None,
data["path"],
int(data["size"]),
data["md5"],
data["entry"] if data["entry"] != "" else None,
int(data["package_size"]),
)
class LauncherPlugin: class LauncherPlugin:
plugins: list[Plugin] plugins: list[Plugin]
# str -> int # str -> int
version: int version: int
def __init__(self, plugins: list[Plugin], version: int) -> None:
self.plugins = plugins
self.version = version
@staticmethod
def from_dict(data: dict) -> "LauncherPlugin":
return LauncherPlugin(
[Plugin.from_dict(i) for i in data["plugins"]], int(data["version"])
)
class DeprecatedPackage: class DeprecatedPackage:
name: str name: str
md5: str md5: str
def __init__(self, name: str, md5: str) -> None:
self.name = name
self.md5 = md5
@staticmethod
def from_dict(data: dict) -> "DeprecatedPackage":
return DeprecatedPackage(data["name"], data["md5"])
class DeprecatedFile: class DeprecatedFile:
path: str path: str
# str but checked for empty string # str but checked for empty string
md5: str | None md5: str | None
def __init__(self, path: str, md5: str | None) -> None:
self.path = path
self.md5 = md5
@staticmethod
def from_dict(data: dict) -> "DeprecatedFile":
return DeprecatedFile(data["path"], data["md5"] if data["md5"] != "" else None)
class Resource: class Resource:
""" """
Data class for /resource endpoint Data class for /resource endpoint
I'm still unclear about `force_update` and `sdk` attributes, so I'll
leave them as None for now.
Attributes:
game (Game): Game resource information.
plugin (LauncherPlugin): Launcher plugin information.
web_url (str): Game official launcher web URL.
force_update (None): Not used by official launcher I guess?
pre_download_game (Game | None): Pre-download game resource information.
deprecated_packages (list[DeprecatedPackage]): Deprecated game packages.
sdk (None): Maybe for Bilibili version of Genshin?
deprecated_files (list[DeprecatedFile]): Deprecated game files.
""" """
# I'm generous enough to convert the string into int # I'm generous enough to convert the string into int
@ -198,14 +378,6 @@ class Resource:
sdk: None, sdk: None,
deprecated_files: list[DeprecatedFile], deprecated_files: list[DeprecatedFile],
) -> None: ) -> None:
# Fixups
game_latest_path = game.latest.path
if game.latest.name == "":
print("A")
if game_latest_path == "":
game.latest.name = game.latest.segments[0].path.split("/")[-1]
else:
game.latest.name = game_latest_path.split("/")[-1]
self.game = game self.game = game
self.plugin = plugin self.plugin = plugin
self.web_url = web_url self.web_url = web_url

View File

@ -10,7 +10,7 @@ def get_resource(channel: GameChannel = GameChannel.Overseas) -> Resource:
Get game resource information from the launcher API. Get game resource information from the launcher API.
Args: Args:
channel: Game channel to get the resource information from. (os, cn) channel: Game channel to get the resource information from.
Returns: Returns:
Resource: Game resource information. Resource: Game resource information.