From 79a9f7c76cc95e1926d8a9b5afac86e0790f4e6c Mon Sep 17 00:00:00 2001 From: Janne Hakonen Date: Sun, 26 Mar 2017 19:49:38 +0300 Subject: [PATCH] Issue #29: Added ClientQuery API key handling. - Added settings option for the API key ([TSClientQueryService] -> api_key). - Added notification which gives instruction of where to get the key and where to set it. - Added notification which lets user know when the authentication is ok. - Added low level ClientQuery support for the authentication. --- .../scripts/client/gui/mods/mod_tessumod.py | 33 +++++++++++++++++++ .../client/gui/mods/tessumod/notifications.py | 6 +++- .../client/gui/mods/tessumod/settings.py | 10 ++++++ .../scripts/client/gui/mods/tessumod/ts3.py | 26 +++++++++++++-- 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/tessumod/src/scripts/client/gui/mods/mod_tessumod.py b/tessumod/src/scripts/client/gui/mods/mod_tessumod.py index 38a7baf..bdbf2fd 100644 --- a/tessumod/src/scripts/client/gui/mods/mod_tessumod.py +++ b/tessumod/src/scripts/client/gui/mods/mod_tessumod.py @@ -43,6 +43,9 @@ def init(): '''Mod's main entry point. Called by WoT's built-in mod loader.''' try: global g_ts, g_talk_states, g_minimap_ctrl, g_user_cache, g_positional_audio, g_keyvaluestorage + global g_authentication_error + + g_authentication_error = False # make sure that ini-folder exists try: @@ -74,11 +77,13 @@ def init(): load_settings() + g_ts.set_apikey(settings().get_client_query_apikey()) g_ts.connect() g_ts.on_connected += on_connected_to_ts3 g_ts.on_disconnected += on_disconnected_from_ts3 g_ts.on_connected_to_server += on_connected_to_ts3_server g_ts.on_disconnected_from_server += on_disconnected_from_ts3_server + g_ts.on_authenticate_error += on_ts3_authenticate_error g_ts.users_in_my_channel.on_added += on_ts3_user_in_my_channel_added g_ts.users_in_my_channel.on_modified += on_ts3_user_in_my_channel_modified utils.call_in_loop(settings().get_client_query_interval(), g_ts.check_events) @@ -103,6 +108,7 @@ def init(): notifications.add_event_handler(notifications.TSPLUGIN_INSTALL, on_tsplugin_install) notifications.add_event_handler(notifications.TSPLUGIN_IGNORED, on_tsplugin_ignore_toggled) notifications.add_event_handler(notifications.TSPLUGIN_MOREINFO, on_tsplugin_moreinfo_clicked) + notifications.add_event_handler(notifications.SETTINGS_PATH, on_settings_path_clicked) g_keyvaluestorage = KeyValueStorage(utils.get_states_dir_path()) @@ -202,6 +208,12 @@ def on_connected_to_ts3(): doesn't mean that the client is connected to any TeamSpeak server. ''' LOG_NOTE("Connected to TeamSpeak client") + + global g_authentication_error + if g_authentication_error: + notifications.push_warning_message("Permission granted, connected to TeamSpeak client") + g_authentication_error = False + installer_path = utils.get_plugin_installer_path() # plugin doesn't work in WinXP so check that we are running on @@ -259,6 +271,23 @@ def on_disconnected_from_ts3_server(): LOG_NOTE("Disconnected from TeamSpeak server") clear_speak_statuses() +def on_ts3_authenticate_error(): + '''Called when ClientQuery protocol tries to authenticate but the required + API key is either not set or is wrong. + ''' + global g_authentication_error + if g_authentication_error: + return + g_authentication_error = True + LOG_NOTE("Failed to authenticate to TeamSpeak client") + settings_link = "{1}".format(notifications.SETTINGS_PATH, os.path.abspath(settings().get_filepath())) + notifications.push_warning_message("TessuMod needs permission to access your TeamSpeak client.\n\n" + + "Plese enter ClientQuery API key (see TeamSpeak -> Tools -> Options -> Addons -> Plugins -> ClientQuery -> Settings) " + + "to option api_key within section [TSClientQueryService] in TessuMod's settings file ({0}).\n\n".format(settings_link) + + "NOTE: If your current settings file doesn't have this option, you can add it there yourself. " + + "Alternatively you can delete the file and restart World of Tanks. " + + "TessuMod will generate a new file on game start which will include the option.") + def on_ts3_user_in_my_channel_added(client_id): on_speak_status_changed(g_ts.users[client_id]) @@ -275,6 +304,7 @@ def load_settings(): utils.CURRENT_LOG_LEVEL = settings().get_log_level() g_ts.HOST = settings().get_client_query_host() g_ts.PORT = settings().get_client_query_port() + g_ts.set_apikey(settings().get_client_query_apikey()) def sync_configs(): g_user_cache.sync() @@ -331,3 +361,6 @@ def on_tsplugin_ignore_toggled(type_id, msg_id, data): def on_tsplugin_moreinfo_clicked(type_id, msg_id, data): subprocess.call(["start", data["moreinfo_url"]], shell=True) + +def on_settings_path_clicked(type_id, msg_id, data): + subprocess.call(["start", os.path.abspath(settings().get_filepath())], shell=True) diff --git a/tessumod/src/scripts/client/gui/mods/tessumod/notifications.py b/tessumod/src/scripts/client/gui/mods/tessumod/notifications.py index aca5732..02fd292 100644 --- a/tessumod/src/scripts/client/gui/mods/tessumod/notifications.py +++ b/tessumod/src/scripts/client/gui/mods/tessumod/notifications.py @@ -34,6 +34,7 @@ TSPLUGIN_INSTALL = "TessuModTSPluginInstall" TSPLUGIN_MOREINFO = "TessuModTSPluginMoreInfo" TSPLUGIN_IGNORED = "TessuModTSPluginIgnore" +SETTINGS_PATH = "TessuModSettingsPath" _event_handlers = [] _is_plugin_install_shown = False @@ -174,7 +175,10 @@ def _handleAction(original_method, self, typeID, entityID, action): if notification: for handler_action, handler in _event_handlers: if action == handler_action: - handler(typeID, entityID, notification.get_item()) + item = None + if hasattr(notification, "get_item"): + item = notification.get_item() + handler(typeID, entityID, item) else: original_method(typeID, entityID, action) diff --git a/tessumod/src/scripts/client/gui/mods/tessumod/settings.py b/tessumod/src/scripts/client/gui/mods/tessumod/settings.py index 0ffc399..78bac6f 100644 --- a/tessumod/src/scripts/client/gui/mods/tessumod/settings.py +++ b/tessumod/src/scripts/client/gui/mods/tessumod/settings.py @@ -104,6 +104,9 @@ ;ts_nickname: wot_nickname [TSClientQueryService] +; API key (must be set with TeamSpeak version 3.1.3 or newer) +api_key: + ; Host and port of the TeamSpeak clientquery plugin host: localhost port: 25639 @@ -222,6 +225,10 @@ def sync(self): self._load_parser() self.on_reloaded() + def get_filepath(self): + '''Returns path to the settings file.''' + return self._ini_path + def _is_modified(self): return self._load_time < self._get_modified_time() @@ -256,6 +263,9 @@ def get_name_mappings(self): results[option.lower()] = self._parser.get("NameMappings", option).lower() return results + def get_client_query_apikey(self): + return self._parser.get("TSClientQueryService", "api_key") + def get_client_query_host(self): return self._parser.get("TSClientQueryService", "host") diff --git a/tessumod/src/scripts/client/gui/mods/tessumod/ts3.py b/tessumod/src/scripts/client/gui/mods/tessumod/ts3.py index b6bef31..938d4b4 100644 --- a/tessumod/src/scripts/client/gui/mods/tessumod/ts3.py +++ b/tessumod/src/scripts/client/gui/mods/tessumod/ts3.py @@ -75,6 +75,7 @@ def __init__(self): self.on_disconnected = Event.Event() self.on_connected_to_server = Event.Event() self.on_disconnected_from_server = Event.Event() + self.on_authenticate_error = Event.Event() # public models self.users = UserModel() @@ -89,6 +90,7 @@ def __init__(self): self._my_client_id = None self._my_channel_id = None self._schandler_id = None + self.__apikey = None self._protocol = _ClientQueryProtocol(self, self._socket_map) self._protocol.on_ready += functools.partial(self._send_sm_event, "protocol_ready") @@ -98,12 +100,15 @@ def __init__(self): not_connected_state = self._sm.add_state("Not Connected") connecting_to_ts_state = self._sm.add_state("Connecting to TS", on_enter=self._on_connecting_to_ts_state) + authenticate_state = self._sm.add_state("Authenticate", on_enter=self._on_authenticate_state) connecting_failed_state = self._sm.add_state("Connecting Failed", on_enter=self._on_connect_failed_state) connected_to_ts_state = self._sm.add_state("Connected to TS", on_enter=self._on_connected_to_ts_state) connected_to_ts_server_state = self._sm.add_state("Connected to TS Server", on_enter=self._on_connected_to_ts_server_state) self._sm.add_transition(not_connected_state, connecting_to_ts_state, "connect") - self._sm.add_transition(connecting_to_ts_state, connected_to_ts_state, "protocol_ready", on_transit=self.on_connected) + self._sm.add_transition(connecting_to_ts_state, authenticate_state, "protocol_ready") + self._sm.add_transition(authenticate_state, connected_to_ts_state, "authenticated", on_transit=self.on_connected) + self._sm.add_transition(authenticate_state, connecting_failed_state, "protocol_closed") self._sm.add_transition(connecting_to_ts_state, connecting_failed_state, "protocol_closed") self._sm.add_transition(connected_to_ts_state, connected_to_ts_server_state, "ping_ok") self._sm.add_transition(connected_to_ts_state, connecting_to_ts_state, "protocol_closed", on_transit=self.on_disconnected) @@ -117,6 +122,10 @@ def __init__(self): self._sm.tick() + def set_apikey(self, apikey): + '''Sets API key for ClientQuery.''' + self.__apikey = apikey + def connect(self): '''Starts connect attempt and continues to try until succesfully connected. @@ -148,6 +157,20 @@ def on_command_finish(err, lines): callback(err, lines) self._protocol.send_command(command, on_command_finish, timeout) + def _on_authenticate_state(self): + '''Authenticates to client query, required with TeamSpeak 3.1.3 or newer.''' + def on_finish(err, lines): + if not err: + self._send_sm_event("authenticated") + elif err[0] == 256: + # Command not found, TeamSpeak is version 3.1.2 or older + self._send_sm_event("authenticated") + else: + # In other error cases the API key is likely not set or is wrong + self.on_authenticate_error() + self._protocol.close() + self._send_command("auth apikey={0}".format(self.__apikey), on_finish) + def _on_connected_to_ts_state(self): self._my_client_id = None self._my_channel_id = None @@ -501,7 +524,6 @@ def _handle_in_data_proto_test(self, line): nothing something totally else. ''' if "ts3 client" in line.lower(): - LOG_NOTE("TS client query protocol detected") self._data_in_handler = self._handle_in_data_welcome_message else: LOG_ERROR("Not TS client query protocol")