Skip to content

Commit

Permalink
Issue #29: Added ClientQuery API key handling.
Browse files Browse the repository at this point in the history
 - 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.
  • Loading branch information
jhakonen committed Mar 26, 2017
1 parent b17934e commit 79a9f7c
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 3 deletions.
33 changes: 33 additions & 0 deletions tessumod/src/scripts/client/gui/mods/mod_tessumod.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand All @@ -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())

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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 = "<a href=\"event:{0}\">{1}</a>".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 <b>api_key</b> within section <b>[TSClientQueryService]</b> in TessuMod's settings file ({0}).\n\n".format(settings_link)
+ "<b>NOTE:</b> 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])

Expand All @@ -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()
Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
TSPLUGIN_INSTALL = "TessuModTSPluginInstall"
TSPLUGIN_MOREINFO = "TessuModTSPluginMoreInfo"
TSPLUGIN_IGNORED = "TessuModTSPluginIgnore"
SETTINGS_PATH = "TessuModSettingsPath"

_event_handlers = []
_is_plugin_install_shown = False
Expand Down Expand Up @@ -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)

Expand Down
10 changes: 10 additions & 0 deletions tessumod/src/scripts/client/gui/mods/tessumod/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()

Expand Down Expand Up @@ -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")

Expand Down
26 changes: 24 additions & 2 deletions tessumod/src/scripts/client/gui/mods/tessumod/ts3.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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")
Expand All @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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")
Expand Down

0 comments on commit 79a9f7c

Please sign in to comment.