Core: have webhost slot name links go through the launcher (#2779)
* Core: have webhost slot name links go through the launcher so that components can use them * fix query handling, remove debug prints, and change mousover text for new behavior * remove a missed debug and unused function * filter room id to suuid since that's what everything else uses * pass args to common client correctly * add GUI to select which client to open * remove args parsing and "require" components to parse it themselves * support for messenger since it was basically already done * use "proper" args argparsing and clean up uri handling * use a timer and auto launch text client if no component is found * change the timer to be a bit more appealing. also found a bug lmao * don't hold 5 hostage and capitalize URI ig --------- Co-authored-by: NewSoupVi <57900059+NewSoupVi@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									a40744e6db
								
							
						
					
					
						commit
						430b71a092
					
				|  | @ -994,7 +994,7 @@ def get_base_parser(description: typing.Optional[str] = None): | |||
|     return parser | ||||
| 
 | ||||
| 
 | ||||
| def run_as_textclient(): | ||||
| def run_as_textclient(*args): | ||||
|     class TextContext(CommonContext): | ||||
|         # Text Mode to use !hint and such with games that have no text entry | ||||
|         tags = CommonContext.tags | {"TextOnly"} | ||||
|  | @ -1033,7 +1033,7 @@ def run_as_textclient(): | |||
|     parser = get_base_parser(description="Gameless Archipelago Client, for text interfacing.") | ||||
|     parser.add_argument('--name', default=None, help="Slot Name to connect as.") | ||||
|     parser.add_argument("url", nargs="?", help="Archipelago connection url") | ||||
|     args = parser.parse_args() | ||||
|     args = parser.parse_args(args if args else None)  # this is necessary as long as CommonClient itself is launchable | ||||
| 
 | ||||
|     if args.url: | ||||
|         url = urllib.parse.urlparse(args.url) | ||||
|  |  | |||
							
								
								
									
										98
									
								
								Launcher.py
								
								
								
								
							
							
						
						
									
										98
									
								
								Launcher.py
								
								
								
								
							|  | @ -16,10 +16,11 @@ import multiprocessing | |||
| import shlex | ||||
| import subprocess | ||||
| import sys | ||||
| import urllib.parse | ||||
| import webbrowser | ||||
| from os.path import isfile | ||||
| from shutil import which | ||||
| from typing import Callable, Sequence, Union, Optional | ||||
| from typing import Callable, Optional, Sequence, Tuple, Union | ||||
| 
 | ||||
| import Utils | ||||
| import settings | ||||
|  | @ -107,7 +108,81 @@ components.extend([ | |||
| ]) | ||||
| 
 | ||||
| 
 | ||||
| def identify(path: Union[None, str]): | ||||
| def handle_uri(path: str, launch_args: Tuple[str, ...]) -> None: | ||||
|     url = urllib.parse.urlparse(path) | ||||
|     queries = urllib.parse.parse_qs(url.query) | ||||
|     launch_args = (path, *launch_args) | ||||
|     client_component = None | ||||
|     text_client_component = None | ||||
|     if "game" in queries: | ||||
|         game = queries["game"][0] | ||||
|     else:  # TODO around 0.6.0 - this is for pre this change webhost uri's | ||||
|         game = "Archipelago" | ||||
|     for component in components: | ||||
|         if component.supports_uri and component.game_name == game: | ||||
|             client_component = component | ||||
|         elif component.display_name == "Text Client": | ||||
|             text_client_component = component | ||||
| 
 | ||||
|     from kvui import App, Button, BoxLayout, Label, Clock, Window | ||||
| 
 | ||||
|     class Popup(App): | ||||
|         timer_label: Label | ||||
|         remaining_time: Optional[int] | ||||
| 
 | ||||
|         def __init__(self): | ||||
|             self.title = "Connect to Multiworld" | ||||
|             self.icon = r"data/icon.png" | ||||
|             super().__init__() | ||||
| 
 | ||||
|         def build(self): | ||||
|             layout = BoxLayout(orientation="vertical") | ||||
| 
 | ||||
|             if client_component is None: | ||||
|                 self.remaining_time = 7 | ||||
|                 label_text = (f"A game client able to parse URIs was not detected for {game}.\n" | ||||
|                               f"Launching Text Client in 7 seconds...") | ||||
|                 self.timer_label = Label(text=label_text) | ||||
|                 layout.add_widget(self.timer_label) | ||||
|                 Clock.schedule_interval(self.update_label, 1) | ||||
|             else: | ||||
|                 layout.add_widget(Label(text="Select client to open and connect with.")) | ||||
|                 button_row = BoxLayout(orientation="horizontal", size_hint=(1, 0.4)) | ||||
| 
 | ||||
|                 text_client_button = Button( | ||||
|                     text=text_client_component.display_name, | ||||
|                     on_release=lambda *args: run_component(text_client_component, *launch_args) | ||||
|                 ) | ||||
|                 button_row.add_widget(text_client_button) | ||||
| 
 | ||||
|                 game_client_button = Button( | ||||
|                     text=client_component.display_name, | ||||
|                     on_release=lambda *args: run_component(client_component, *launch_args) | ||||
|                 ) | ||||
|                 button_row.add_widget(game_client_button) | ||||
| 
 | ||||
|                 layout.add_widget(button_row) | ||||
| 
 | ||||
|             return layout | ||||
| 
 | ||||
|         def update_label(self, dt): | ||||
|             if self.remaining_time > 1: | ||||
|                 # countdown the timer and string replace the number | ||||
|                 self.remaining_time -= 1 | ||||
|                 self.timer_label.text = self.timer_label.text.replace( | ||||
|                     str(self.remaining_time + 1), str(self.remaining_time) | ||||
|                 ) | ||||
|             else: | ||||
|                 # our timer is finished so launch text client and close down | ||||
|                 run_component(text_client_component, *launch_args) | ||||
|                 Clock.unschedule(self.update_label) | ||||
|                 App.get_running_app().stop() | ||||
|                 Window.close() | ||||
| 
 | ||||
|     Popup().run() | ||||
| 
 | ||||
| 
 | ||||
| def identify(path: Union[None, str]) -> Tuple[Union[None, str], Union[None, Component]]: | ||||
|     if path is None: | ||||
|         return None, None | ||||
|     for component in components: | ||||
|  | @ -299,20 +374,24 @@ def main(args: Optional[Union[argparse.Namespace, dict]] = None): | |||
|     elif not args: | ||||
|         args = {} | ||||
| 
 | ||||
|     if args.get("Patch|Game|Component", None) is not None: | ||||
|         file, component = identify(args["Patch|Game|Component"]) | ||||
|     path = args.get("Patch|Game|Component|url", None) | ||||
|     if path is not None: | ||||
|         if path.startswith("archipelago://"): | ||||
|             handle_uri(path, args.get("args", ())) | ||||
|             return | ||||
|         file, component = identify(path) | ||||
|         if file: | ||||
|             args['file'] = file | ||||
|         if component: | ||||
|             args['component'] = component | ||||
|         if not component: | ||||
|             logging.warning(f"Could not identify Component responsible for {args['Patch|Game|Component']}") | ||||
|             logging.warning(f"Could not identify Component responsible for {path}") | ||||
| 
 | ||||
|     if args["update_settings"]: | ||||
|         update_settings() | ||||
|     if 'file' in args: | ||||
|     if "file" in args: | ||||
|         run_component(args["component"], args["file"], *args["args"]) | ||||
|     elif 'component' in args: | ||||
|     elif "component" in args: | ||||
|         run_component(args["component"], *args["args"]) | ||||
|     elif not args["update_settings"]: | ||||
|         run_gui() | ||||
|  | @ -326,8 +405,9 @@ if __name__ == '__main__': | |||
|     run_group = parser.add_argument_group("Run") | ||||
|     run_group.add_argument("--update_settings", action="store_true", | ||||
|                            help="Update host.yaml and exit.") | ||||
|     run_group.add_argument("Patch|Game|Component", type=str, nargs="?", | ||||
|                            help="Pass either a patch file, a generated game or the name of a component to run.") | ||||
|     run_group.add_argument("Patch|Game|Component|url", type=str, nargs="?", | ||||
|                            help="Pass either a patch file, a generated game, the component name to run, or a url to " | ||||
|                                 "connect with.") | ||||
|     run_group.add_argument("args", nargs="*", | ||||
|                            help="Arguments to pass to component.") | ||||
|     main(parser.parse_args()) | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ | |||
|             {% for patch in room.seed.slots|list|sort(attribute="player_id") %} | ||||
|                 <tr> | ||||
|                     <td>{{ patch.player_id }}</td> | ||||
|                     <td data-tooltip="Connect via TextClient"><a href="archipelago://{{ patch.player_name | e}}:None@{{ config['HOST_ADDRESS'] }}:{{ room.last_port }}">{{ patch.player_name }}</a></td> | ||||
|                     <td data-tooltip="Connect via Game Client"><a href="archipelago://{{ patch.player_name | e}}:None@{{ config['HOST_ADDRESS'] }}:{{ room.last_port }}?game={{ patch.game }}&room={{ room.id | suuid }}">{{ patch.player_name }}</a></td> | ||||
|                     <td>{{ patch.game }}</td> | ||||
|                     <td> | ||||
|                         {% if patch.data %} | ||||
|  |  | |||
|  | @ -228,8 +228,8 @@ Root: HKCR; Subkey: "{#MyAppName}worlddata\shell\open\command"; ValueData: """{a | |||
| 
 | ||||
| Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueData: "Archipegalo Protocol"; Flags: uninsdeletekey; | ||||
| Root: HKCR; Subkey: "archipelago"; ValueType: "string"; ValueName: "URL Protocol"; ValueData: ""; | ||||
| Root: HKCR; Subkey: "archipelago\DefaultIcon"; ValueType: "string"; ValueData: "{app}\ArchipelagoTextClient.exe,0"; | ||||
| Root: HKCR; Subkey: "archipelago\shell\open\command"; ValueType: "string"; ValueData: """{app}\ArchipelagoTextClient.exe"" ""%1"""; | ||||
| Root: HKCR; Subkey: "archipelago\DefaultIcon"; ValueType: "string"; ValueData: "{app}\ArchipelagoLauncher.exe,0"; | ||||
| Root: HKCR; Subkey: "archipelago\shell\open\command"; ValueType: "string"; ValueData: """{app}\ArchipelagoLauncher.exe"" ""%1"""; | ||||
| 
 | ||||
| [Code] | ||||
| // See: https://stackoverflow.com/a/51614652/2287576 | ||||
|  |  | |||
|  | @ -26,10 +26,13 @@ class Component: | |||
|     cli: bool | ||||
|     func: Optional[Callable] | ||||
|     file_identifier: Optional[Callable[[str], bool]] | ||||
|     game_name: Optional[str] | ||||
|     supports_uri: Optional[bool] | ||||
| 
 | ||||
|     def __init__(self, display_name: str, script_name: Optional[str] = None, frozen_name: Optional[str] = None, | ||||
|                  cli: bool = False, icon: str = 'icon', component_type: Optional[Type] = None, | ||||
|                  func: Optional[Callable] = None, file_identifier: Optional[Callable[[str], bool]] = None): | ||||
|                  func: Optional[Callable] = None, file_identifier: Optional[Callable[[str], bool]] = None, | ||||
|                  game_name: Optional[str] = None, supports_uri: Optional[bool] = False): | ||||
|         self.display_name = display_name | ||||
|         self.script_name = script_name | ||||
|         self.frozen_name = frozen_name or f'Archipelago{script_name}' if script_name else None | ||||
|  | @ -45,6 +48,8 @@ class Component: | |||
|             Type.ADJUSTER if "Adjuster" in display_name else Type.MISC) | ||||
|         self.func = func | ||||
|         self.file_identifier = file_identifier | ||||
|         self.game_name = game_name | ||||
|         self.supports_uri = supports_uri | ||||
| 
 | ||||
|     def handles_file(self, path: str): | ||||
|         return self.file_identifier(path) if self.file_identifier else False | ||||
|  | @ -56,10 +61,10 @@ class Component: | |||
| processes = weakref.WeakSet() | ||||
| 
 | ||||
| 
 | ||||
| def launch_subprocess(func: Callable, name: str = None): | ||||
| def launch_subprocess(func: Callable, name: str = None, args: Tuple[str, ...] = ()): | ||||
|     global processes | ||||
|     import multiprocessing | ||||
|     process = multiprocessing.Process(target=func, name=name) | ||||
|     process = multiprocessing.Process(target=func, name=name, args=args) | ||||
|     process.start() | ||||
|     processes.add(process) | ||||
| 
 | ||||
|  | @ -78,9 +83,9 @@ class SuffixIdentifier: | |||
|         return False | ||||
| 
 | ||||
| 
 | ||||
| def launch_textclient(): | ||||
| def launch_textclient(*args): | ||||
|     import CommonClient | ||||
|     launch_subprocess(CommonClient.run_as_textclient, name="TextClient") | ||||
|     launch_subprocess(CommonClient.run_as_textclient, "TextClient", args) | ||||
| 
 | ||||
| 
 | ||||
| def _install_apworld(apworld_src: str = "") -> Optional[Tuple[pathlib.Path, pathlib.Path]]: | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ from .shop import FIGURINES, PROG_SHOP_ITEMS, SHOP_ITEMS, USEFUL_SHOP_ITEMS, shu | |||
| from .subclasses import MessengerEntrance, MessengerItem, MessengerRegion, MessengerShopLocation | ||||
| 
 | ||||
| components.append( | ||||
|     Component("The Messenger", component_type=Type.CLIENT, func=launch_game)#, game_name="The Messenger", supports_uri=True) | ||||
|     Component("The Messenger", component_type=Type.CLIENT, func=launch_game, game_name="The Messenger", supports_uri=True) | ||||
| ) | ||||
| 
 | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,3 +1,4 @@ | |||
| import argparse | ||||
| import io | ||||
| import logging | ||||
| import os.path | ||||
|  | @ -17,7 +18,7 @@ from Utils import is_windows, messagebox, tuplize_version | |||
| MOD_URL = "https://api.github.com/repos/alwaysintreble/TheMessengerRandomizerModAP/releases/latest" | ||||
| 
 | ||||
| 
 | ||||
| def launch_game(url: Optional[str] = None) -> None: | ||||
| def launch_game(*args) -> None: | ||||
|     """Check the game installation, then launch it""" | ||||
|     def courier_installed() -> bool: | ||||
|         """Check if Courier is installed""" | ||||
|  | @ -150,15 +151,19 @@ def launch_game(url: Optional[str] = None) -> None: | |||
|                 install_mod() | ||||
|             elif should_update is None: | ||||
|                 return | ||||
| 
 | ||||
|     parser = argparse.ArgumentParser(description="Messenger Client Launcher") | ||||
|     parser.add_argument("url", type=str, nargs="?", help="Archipelago Webhost uri to auto connect to.") | ||||
|     args = parser.parse_args(args) | ||||
|     if not is_windows: | ||||
|         if url: | ||||
|             open_file(f"steam://rungameid/764790//{url}/") | ||||
|         if args.url: | ||||
|             open_file(f"steam://rungameid/764790//{args.url}/") | ||||
|         else: | ||||
|             open_file("steam://rungameid/764790") | ||||
|     else: | ||||
|         os.chdir(game_folder) | ||||
|         if url: | ||||
|             subprocess.Popen([MessengerWorld.settings.game_path, str(url)]) | ||||
|         if args.url: | ||||
|             subprocess.Popen([MessengerWorld.settings.game_path, str(args.url)]) | ||||
|         else: | ||||
|             subprocess.Popen(MessengerWorld.settings.game_path) | ||||
|         os.chdir(working_directory) | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue