From 3d73a72842adf00b7cb39870bda91fe4fa91198e Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 18:49:21 +0200 Subject: [PATCH 01/15] replacing socket lib with telnetlib --- ts3.py | 84 +++++++++++++++++++++------------------------------------- 1 file changed, 30 insertions(+), 54 deletions(-) diff --git a/ts3.py b/ts3.py index 03df844..0d34dd1 100644 --- a/ts3.py +++ b/ts3.py @@ -26,7 +26,7 @@ # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import time -import socket +import telnetlib import logging class ConnectionError(): @@ -48,65 +48,40 @@ ts3_escape = { '/': r"\/", "\r": r'\r', "\t": r'\t', "\v": r'\v' } - class TS3Proto(): - - bytesin = 0 - bytesout = 0 - + _timeout = 0 _connected = False def __init__(self): self._log = logging.getLogger('%s.%s' % (__name__, self.__class__.__name__)) pass - def connect(self, ip, port): - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - s.connect((ip, port)) - except: - #raise ConnectionError(ip, port) - raise - else: - self._sock = s - self._sockfile = s.makefile('r', 0) - - data = self._sockfile.readline() - if data.strip() == "TS3": - self._sockfile.readline() + def connect(self, ip, port, timeout=5): + self._telnet = telnetlib.Telnet(ip, port) + self._timeout = timeout + + data = self._telnet.read_until("TS3\n", self._timeout) + + if data.endswith("TS3\n"): self._connected = True return True def disconnect(self): self.send_command("quit") - self._sock.close() - self._sock = None + self._connected = False self._log.info('Disconnected') def send_command(self, command, keys=None, opts=None): cmd = self.construct_command(command, keys=keys, opts=opts) - self.send('%s\n' % cmd) + + self._telnet.write("%s\n" % cmd) - data = [] - - while True: - resp = self._sockfile.readline() - resp = self.parse_command(resp) - if not 'command' in resp: - data.append(resp) - else: - break - - if resp['command'] == 'error': - if data and resp['keys']['id'] == '0': - if len(data) > 1: - return data - else: - return data[0] - else: - return resp['keys']['id'] + resp = self._telnet.read_until("\n", self._timeout) + resp = self.parse_command(resp) + + return resp def construct_command(self, command, keys=None, opts=None): """ @@ -221,7 +196,7 @@ class TS3Proto(): class TS3Server(TS3Proto): - def __init__(self, ip, port, id=0, sock=None): + def __init__(self, ip, port, id=0): """ Abstraction class for TS3 Servers @@ -233,13 +208,8 @@ class TS3Server(TS3Proto): """ TS3Proto.__init__(self) - if not sock: - if self.connect(ip, port) and id > 0: - self.use(id) - else: - self._sock = sock - self._sockfile = sock.makefile('r', 0) - self._connected = True + if self.connect(ip, port) and id > 0: + self.use(id) def login(self, username, password): """ @@ -250,11 +220,17 @@ class TS3Server(TS3Proto): @param password: Password @type password: str """ - d = self.send_command('login', keys={'client_login_name': username, 'client_login_password': password }) - if d == 0: - self._log.info('Login Successful') + + response = self.send_command('login', keys={'client_login_name': username, 'client_login_password': password }) + + print response + + if response['key']['msg'] != 'ok': + self._log.info('Login error: %s.' % response['keys']['msg']) + return False + else: + self._log.info('Login successful.') return True - return False def serverlist(self): """ @@ -281,4 +257,4 @@ class TS3Server(TS3Proto): @type id: int """ if self._connected and id > 0: - self.send_command('use', keys={'sid': id}) + self.send_command('use', keys={'sid': id}) \ No newline at end of file From 1d7d351e28c472f4db0adae235a91fa5ffa5146d Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 19:12:48 +0200 Subject: [PATCH 02/15] wrapping response in TS3Response class for usability --- ts3.py | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/ts3.py b/ts3.py index 0d34dd1..4abf34a 100644 --- a/ts3.py +++ b/ts3.py @@ -49,6 +49,17 @@ ts3_escape = { '/': r"\/", "\t": r'\t', "\v": r'\v' } +class TS3Response(): + def __init__(self, response): + self.response = TS3Proto.parse_command(response) + + def successful(self): + if isinstance(self.response, dict): + return self.response['keys']['msg'] == 'ok' + + # if the response is a list, it has to be successful + return True + class TS3Proto(): _timeout = 0 _connected = False @@ -77,11 +88,8 @@ class TS3Proto(): cmd = self.construct_command(command, keys=keys, opts=opts) self._telnet.write("%s\n" % cmd) - - resp = self._telnet.read_until("\n", self._timeout) - resp = self.parse_command(resp) - return resp + return TS3Response(self._telnet.read_until("\n", self._timeout)) def construct_command(self, command, keys=None, opts=None): """ @@ -117,7 +125,8 @@ class TS3Proto(): return " ".join(cstr) - def parse_command(self, commandstr): + @staticmethod + def parse_command(commandstr): """ Parses a TS3 command string into command/keys/opts tuple @@ -128,7 +137,7 @@ class TS3Proto(): if len(commandstr.split('|')) > 1: vals = [] for cmd in commandstr.split('|'): - vals.append(self.parse_command(cmd)) + vals.append(TS3Proto.parse_command(cmd)) return vals cmdlist = commandstr.strip().split(' ') @@ -144,7 +153,7 @@ class TS3Proto(): # Fix the stupidities in TS3 escaping v = [v[0], '='.join(v[1:])] key, value = v - keys[key] = self._unescape_str(value) + keys[key] = TS3Proto._unescape_str(value) elif v[0][0] == '-': # Option opts.append(v[0][1:]) @@ -225,8 +234,8 @@ class TS3Server(TS3Proto): print response - if response['key']['msg'] != 'ok': - self._log.info('Login error: %s.' % response['keys']['msg']) + if response.successful(): + self._log.info('Login error: %s.') return False else: self._log.info('Login successful.') From 6bb33791a1cb7c538ee27e2aa341fbb2c4ffee38 Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 19:28:11 +0200 Subject: [PATCH 03/15] cleanup --- ts3.py | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/ts3.py b/ts3.py index 4abf34a..32c5078 100644 --- a/ts3.py +++ b/ts3.py @@ -30,7 +30,6 @@ import telnetlib import logging class ConnectionError(): - def __init__(self, ip, port): self.ip = ip self.port = port @@ -53,17 +52,17 @@ class TS3Response(): def __init__(self, response): self.response = TS3Proto.parse_command(response) - def successful(self): + def is_successful(self): if isinstance(self.response, dict): return self.response['keys']['msg'] == 'ok' # if the response is a list, it has to be successful return True + + def response(self): + return self.response class TS3Proto(): - _timeout = 0 - _connected = False - def __init__(self): self._log = logging.getLogger('%s.%s' % (__name__, self.__class__.__name__)) pass @@ -71,12 +70,14 @@ class TS3Proto(): def connect(self, ip, port, timeout=5): self._telnet = telnetlib.Telnet(ip, port) self._timeout = timeout + self._connected = False data = self._telnet.read_until("TS3\n", self._timeout) if data.endswith("TS3\n"): self._connected = True - return True + + return self._connected def disconnect(self): self.send_command("quit") @@ -91,6 +92,9 @@ class TS3Proto(): return TS3Response(self._telnet.read_until("\n", self._timeout)) + def is_connected(self): + return self._connected + def construct_command(self, command, keys=None, opts=None): """ Constructs a TS3 formatted command string @@ -198,12 +202,6 @@ class TS3Proto(): return value - def send(self, payload): - if self._connected: - self._log.debug('Sent: %s' % payload) - self._sockfile.write(payload) - - class TS3Server(TS3Proto): def __init__(self, ip, port, id=0): """ @@ -231,10 +229,8 @@ class TS3Server(TS3Proto): """ response = self.send_command('login', keys={'client_login_name': username, 'client_login_password': password }) - - print response - if response.successful(): + if response.is_successful(): self._log.info('Login error: %s.') return False else: @@ -245,8 +241,7 @@ class TS3Server(TS3Proto): """ Get a list of all Virtual Servers on the connected TS3 instance """ - if self._connected: - return self.send_command('serverlist') + return self.send_command('serverlist') def gm(self, msg): """ @@ -255,8 +250,7 @@ class TS3Server(TS3Proto): @param msg: Message @type ip: str """ - if self._connected: - return self.send_command('gm', keys={'msg': msg}) + return self.send_command('gm', keys={'msg': msg}) def use(self, id): """ @@ -265,5 +259,4 @@ class TS3Server(TS3Proto): @param id: Virtual Server ID @type id: int """ - if self._connected and id > 0: - self.send_command('use', keys={'sid': id}) \ No newline at end of file + self.send_command('use', keys={'sid': id}) \ No newline at end of file From 3831319f57b52a946c18db33661e83fc1a946a4e Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 19:37:44 +0200 Subject: [PATCH 04/15] cleanup in escape and unescape functions --- ts3.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/ts3.py b/ts3.py index 32c5078..c915945 100644 --- a/ts3.py +++ b/ts3.py @@ -38,6 +38,7 @@ class ConnectionError(): return 'Error connecting to host %s port %s' % (self.ip, self.port) ts3_escape = { '/': r"\/", + "\\", r'\\', ' ': r'\s', '|': r'\p', "\a": r'\a', @@ -179,10 +180,12 @@ class TS3Proto(): """ - if isinstance(value, int): return "%d" % value - value = value.replace("\\", r'\\') + if isinstance(value, int): + return str(value) + for i, j in ts3_escape.iteritems(): value = value.replace(i, j) + return value @staticmethod @@ -195,10 +198,12 @@ class TS3Proto(): """ - if isinstance(value, int): return "%d" % value - value = value.replace(r"\\", "\\") + if isinstance(value, int): + return str(value) + for i, j in ts3_escape.iteritems(): value = value.replace(j, i) + return value From 15c8e3d87e088aab0dff982c2b652eade41444ea Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 19:38:28 +0200 Subject: [PATCH 05/15] small fix --- ts3.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ts3.py b/ts3.py index c915945..80f0ce0 100644 --- a/ts3.py +++ b/ts3.py @@ -37,8 +37,8 @@ class ConnectionError(): def __str__(): return 'Error connecting to host %s port %s' % (self.ip, self.port) -ts3_escape = { '/': r"\/", - "\\", r'\\', +ts3_escape = { "\\", r'\\', + '/': r"\/", ' ': r'\s', '|': r'\p', "\a": r'\a', From 4911948cc3f67c77b4332b6b298ec166ddd0a528 Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 20:13:13 +0200 Subject: [PATCH 06/15] better error reporting --- ts3.py | 45 +++++++++++++++++++++++---------------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/ts3.py b/ts3.py index 80f0ce0..494fb65 100644 --- a/ts3.py +++ b/ts3.py @@ -29,15 +29,19 @@ import time import telnetlib import logging -class ConnectionError(): +class ConnectionError(Exception): def __init__(self, ip, port): self.ip = ip self.port = port def __str__(): - return 'Error connecting to host %s port %s' % (self.ip, self.port) + return 'Error connecting to host %s port %s.' % (self.ip, self.port,) -ts3_escape = { "\\", r'\\', +class NoConnection(Exception): + def __str__(): + return 'No connection established.' % (self.ip, self.port,) + +ts3_escape = { "\\": r'\\', '/': r"\/", ' ': r'\s', '|': r'\p', @@ -64,16 +68,16 @@ class TS3Response(): return self.response class TS3Proto(): - def __init__(self): - self._log = logging.getLogger('%s.%s' % (__name__, self.__class__.__name__)) - pass - def connect(self, ip, port, timeout=5): - self._telnet = telnetlib.Telnet(ip, port) + try: + self._telnet = telnetlib.Telnet(ip, port) + except telnetlib.socket.error: + raise ConnectionError(ip, port) + self._timeout = timeout self._connected = False - data = self._telnet.read_until("TS3\n", self._timeout) + data = self._telnet.read_until("\n", self._timeout) if data.endswith("TS3\n"): self._connected = True @@ -81,17 +85,22 @@ class TS3Proto(): return self._connected def disconnect(self): + self.check_connection() + self.send_command("quit") + self._telnet.close() self._connected = False - self._log.info('Disconnected') def send_command(self, command, keys=None, opts=None): - cmd = self.construct_command(command, keys=keys, opts=opts) - - self._telnet.write("%s\n" % cmd) + self.check_connection() + self._telnet.write("%s\n" % self.construct_command(command, keys=keys, opts=opts)) return TS3Response(self._telnet.read_until("\n", self._timeout)) + + def check_connection(self): + if not self.is_connected: + raise NoConnectionError def is_connected(self): return self._connected @@ -218,8 +227,6 @@ class TS3Server(TS3Proto): @type port: int """ - TS3Proto.__init__(self) - if self.connect(ip, port) and id > 0: self.use(id) @@ -234,13 +241,7 @@ class TS3Server(TS3Proto): """ response = self.send_command('login', keys={'client_login_name': username, 'client_login_password': password }) - - if response.is_successful(): - self._log.info('Login error: %s.') - return False - else: - self._log.info('Login successful.') - return True + return response.is_successful() def serverlist(self): """ From c95820390aa623f1e135942f63d75301a352f14f Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 20:15:27 +0200 Subject: [PATCH 07/15] version bump --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 87ba809..d783096 100755 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from distutils.core import setup setup(name = "python-ts3", - version = "0.01", + version = "0.1", description = "TS3 ServerQuery library for Python", author = "Andrew Willaims", author_email = "andy@tensixtyone.com", From e12a490aeeb5337d5dc71ce722fff06c630da80c Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 20:28:07 +0200 Subject: [PATCH 08/15] removing this useless method --- ts3.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/ts3.py b/ts3.py index 494fb65..7ff988e 100644 --- a/ts3.py +++ b/ts3.py @@ -63,9 +63,6 @@ class TS3Response(): # if the response is a list, it has to be successful return True - - def response(self): - return self.response class TS3Proto(): def connect(self, ip, port, timeout=5): From 70ee4f052cf570c4481d4f1171d1e3af85c8a603 Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 20:29:07 +0200 Subject: [PATCH 09/15] example of using python-ts3 and install tutorial --- README.rst | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/README.rst b/README.rst index d9c645a..442dce5 100644 --- a/README.rst +++ b/README.rst @@ -5,3 +5,36 @@ python-ts3 python-ts3 is a abstraction library around the Teamspeak 3 ServerQuery API. It allows native access to the ServerQuery API with most of the formatting headaches avoided. + + +Install +======== + +Download the most recent sourcecode and install it: + + git clone git://github.com/Balon/python-ts3.git + cd python-ts3 + python setup.py install # sudo this + + +Example +======== + +Example showing how to create a channel and sub-channel for it using python-ts3 library: + + import ts3 + + server = ts3.TS3Server('127.0.0.1', 10011) + server.login('serveradmin', 'secretpassword') + + # choose virtual server + server.use(1) + + # create a channel + response = server.send_command('channelcreate', keys={'channel_name': 'Just some channel'}) + + # id of the newly created channel + channel_id = response.response['keys']['cid'] + + # create a channel + server.send_command('channelcreate', keys={'channel_name': 'Just some sub-channel', 'cpid': channel_id}) From 8483e68f5ed17d015a2d06816be55eaa927ea796 Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 20:30:46 +0200 Subject: [PATCH 10/15] nice code blocks --- README.rst | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.rst b/README.rst index 442dce5..b727919 100644 --- a/README.rst +++ b/README.rst @@ -12,9 +12,9 @@ Install Download the most recent sourcecode and install it: - git clone git://github.com/Balon/python-ts3.git - cd python-ts3 - python setup.py install # sudo this + git clone git://github.com/Balon/python-ts3.git + cd python-ts3 + python setup.py install # sudo this Example @@ -22,19 +22,19 @@ Example Example showing how to create a channel and sub-channel for it using python-ts3 library: - import ts3 + import ts3 - server = ts3.TS3Server('127.0.0.1', 10011) - server.login('serveradmin', 'secretpassword') - - # choose virtual server - server.use(1) + server = ts3.TS3Server('127.0.0.1', 10011) + server.login('serveradmin', 'secretpassword') - # create a channel - response = server.send_command('channelcreate', keys={'channel_name': 'Just some channel'}) - - # id of the newly created channel - channel_id = response.response['keys']['cid'] - - # create a channel - server.send_command('channelcreate', keys={'channel_name': 'Just some sub-channel', 'cpid': channel_id}) + # choose virtual server + server.use(1) + + # create a channel + response = server.send_command('channelcreate', keys={'channel_name': 'Just some channel'}) + + # id of the newly created channel + channel_id = response.response['keys']['cid'] + + # create a channel + server.send_command('channelcreate', keys={'channel_name': 'Just some sub-channel', 'cpid': channel_id}) From 9ca0dbe5988a0debb409a7910009f268165eebfd Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 20:33:27 +0200 Subject: [PATCH 11/15] nice code blocks, once again --- README.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index b727919..3ec1a95 100644 --- a/README.rst +++ b/README.rst @@ -10,7 +10,7 @@ headaches avoided. Install ======== -Download the most recent sourcecode and install it: +Download the most recent sourcecode and install it:: git clone git://github.com/Balon/python-ts3.git cd python-ts3 @@ -20,7 +20,7 @@ Download the most recent sourcecode and install it: Example ======== -Example showing how to create a channel and sub-channel for it using python-ts3 library: +Example showing how to create a channel and sub-channel for it using python-ts3 library:: import ts3 From 9c493be83deeb071b107c5fd559ed51bda2af950 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Jagie=C5=82=C5=82o?= Date: Sat, 4 Jun 2011 11:57:56 -0700 Subject: [PATCH 12/15] Missing word --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 3ec1a95..371fea5 100644 --- a/README.rst +++ b/README.rst @@ -36,5 +36,5 @@ Example showing how to create a channel and sub-channel for it using python-ts3 # id of the newly created channel channel_id = response.response['keys']['cid'] - # create a channel + # create a sub-channel server.send_command('channelcreate', keys={'channel_name': 'Just some sub-channel', 'cpid': channel_id}) From 4efb49c114c0cc777f5f513980eaf0fa587b5571 Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 22:55:06 +0200 Subject: [PATCH 13/15] fixed problems with receiving responses from the server --- ts3.py | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/ts3.py b/ts3.py index 7ff988e..906f357 100644 --- a/ts3.py +++ b/ts3.py @@ -54,8 +54,9 @@ ts3_escape = { "\\": r'\\', "\v": r'\v' } class TS3Response(): - def __init__(self, response): + def __init__(self, response, data): self.response = TS3Proto.parse_command(response) + self.data = TS3Proto.parse_command(data) def is_successful(self): if isinstance(self.response, dict): @@ -74,9 +75,9 @@ class TS3Proto(): self._timeout = timeout self._connected = False - data = self._telnet.read_until("\n", self._timeout) + data = self._telnet.read_until("\n\r", self._timeout) - if data.endswith("TS3\n"): + if data.endswith("TS3\n\r"): self._connected = True return self._connected @@ -91,9 +92,18 @@ class TS3Proto(): def send_command(self, command, keys=None, opts=None): self.check_connection() - self._telnet.write("%s\n" % self.construct_command(command, keys=keys, opts=opts)) + + self._telnet.write("%s\n\r" % self.construct_command(command, keys=keys, opts=opts)) + + data = "" + response = self._telnet.read_until("\n\r", self._timeout) + + if not response.startswith("error"): + # what we just got was extra data + data = response + response = self._telnet.read_until("\n\r", self._timeout) - return TS3Response(self._telnet.read_until("\n", self._timeout)) + return TS3Response(response, data) def check_connection(self): if not self.is_connected: @@ -144,7 +154,9 @@ class TS3Proto(): @param commandstr: Command string @type commandstr: string """ - + if commandstr.strip() == "": + return {} + if len(commandstr.split('|')) > 1: vals = [] for cmd in commandstr.split('|'): From a870615553b5ffa59fc463ac1a992cd18f8103f1 Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sat, 4 Jun 2011 23:03:21 +0200 Subject: [PATCH 14/15] updated docs --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 371fea5..3dc7a4e 100644 --- a/README.rst +++ b/README.rst @@ -34,7 +34,7 @@ Example showing how to create a channel and sub-channel for it using python-ts3 response = server.send_command('channelcreate', keys={'channel_name': 'Just some channel'}) # id of the newly created channel - channel_id = response.response['keys']['cid'] + channel_id = response.data['keys']['cid'] # create a sub-channel server.send_command('channelcreate', keys={'channel_name': 'Just some sub-channel', 'cpid': channel_id}) From f8581a5bbbe441ce463f791ccfb3fa4d009b0a0a Mon Sep 17 00:00:00 2001 From: Krzysztof Jagiello Date: Sun, 5 Jun 2011 13:17:27 +0200 Subject: [PATCH 15/15] return response data always as a list for --- ts3.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/ts3.py b/ts3.py index 906f357..98d8872 100644 --- a/ts3.py +++ b/ts3.py @@ -57,13 +57,15 @@ class TS3Response(): def __init__(self, response, data): self.response = TS3Proto.parse_command(response) self.data = TS3Proto.parse_command(data) + + if isinstance(self.data, dict): + if self.data: + self.data = [self.data] + else: + self.data = [] def is_successful(self): - if isinstance(self.response, dict): - return self.response['keys']['msg'] == 'ok' - - # if the response is a list, it has to be successful - return True + return self.response['keys']['msg'] == 'ok' class TS3Proto(): def connect(self, ip, port, timeout=5):