Merge pull request #1 from Balon/master

Refactorization, socket replacement
This commit is contained in:
2011-06-05 12:54:42 -07:00
3 changed files with 115 additions and 87 deletions

View File

@@ -5,3 +5,36 @@ python-ts3
python-ts3 is a abstraction library around the Teamspeak 3 ServerQuery API. It 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 allows native access to the ServerQuery API with most of the formatting
headaches avoided. 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.data['keys']['cid']
# create a sub-channel
server.send_command('channelcreate', keys={'channel_name': 'Just some sub-channel', 'cpid': channel_id})

View File

@@ -3,7 +3,7 @@
from distutils.core import setup from distutils.core import setup
setup(name = "python-ts3", setup(name = "python-ts3",
version = "0.01", version = "0.1",
description = "TS3 ServerQuery library for Python", description = "TS3 ServerQuery library for Python",
author = "Andrew Willaims", author = "Andrew Willaims",
author_email = "andy@tensixtyone.com", author_email = "andy@tensixtyone.com",

161
ts3.py
View File

@@ -26,19 +26,23 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import time import time
import socket import telnetlib
import logging import logging
class ConnectionError(): class ConnectionError(Exception):
def __init__(self, ip, port): def __init__(self, ip, port):
self.ip = ip self.ip = ip
self.port = port self.port = port
def __str__(): 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'\s',
'|': r'\p', '|': r'\p',
"\a": r'\a', "\a": r'\a',
@@ -49,64 +53,66 @@ ts3_escape = { '/': r"\/",
"\t": r'\t', "\t": r'\t',
"\v": r'\v' } "\v": r'\v' }
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):
return self.response['keys']['msg'] == 'ok'
class TS3Proto(): class TS3Proto():
def connect(self, ip, port, timeout=5):
bytesin = 0
bytesout = 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: try:
s.connect((ip, port)) self._telnet = telnetlib.Telnet(ip, port)
except: except telnetlib.socket.error:
#raise ConnectionError(ip, port) raise ConnectionError(ip, port)
raise
else:
self._sock = s
self._sockfile = s.makefile('r', 0)
data = self._sockfile.readline() self._timeout = timeout
if data.strip() == "TS3": self._connected = False
self._sockfile.readline()
data = self._telnet.read_until("\n\r", self._timeout)
if data.endswith("TS3\n\r"):
self._connected = True self._connected = True
return True
return self._connected
def disconnect(self): def disconnect(self):
self.check_connection()
self.send_command("quit") self.send_command("quit")
self._sock.close() self._telnet.close()
self._sock = None
self._connected = False self._connected = False
self._log.info('Disconnected')
def send_command(self, command, keys=None, opts=None): def send_command(self, command, keys=None, opts=None):
cmd = self.construct_command(command, keys=keys, opts=opts) self.check_connection()
self.send('%s\n' % cmd)
data = [] self._telnet.write("%s\n\r" % self.construct_command(command, keys=keys, opts=opts))
while True: data = ""
resp = self._sockfile.readline() response = self._telnet.read_until("\n\r", self._timeout)
resp = self.parse_command(resp)
if not 'command' in resp:
data.append(resp)
else:
break
if resp['command'] == 'error': if not response.startswith("error"):
if data and resp['keys']['id'] == '0': # what we just got was extra data
if len(data) > 1: data = response
return data response = self._telnet.read_until("\n\r", self._timeout)
else:
return data[0] return TS3Response(response, data)
else:
return resp['keys']['id'] def check_connection(self):
if not self.is_connected:
raise NoConnectionError
def is_connected(self):
return self._connected
def construct_command(self, command, keys=None, opts=None): def construct_command(self, command, keys=None, opts=None):
""" """
@@ -142,18 +148,21 @@ class TS3Proto():
return " ".join(cstr) return " ".join(cstr)
def parse_command(self, commandstr): @staticmethod
def parse_command(commandstr):
""" """
Parses a TS3 command string into command/keys/opts tuple Parses a TS3 command string into command/keys/opts tuple
@param commandstr: Command string @param commandstr: Command string
@type commandstr: string @type commandstr: string
""" """
if commandstr.strip() == "":
return {}
if len(commandstr.split('|')) > 1: if len(commandstr.split('|')) > 1:
vals = [] vals = []
for cmd in commandstr.split('|'): for cmd in commandstr.split('|'):
vals.append(self.parse_command(cmd)) vals.append(TS3Proto.parse_command(cmd))
return vals return vals
cmdlist = commandstr.strip().split(' ') cmdlist = commandstr.strip().split(' ')
@@ -169,7 +178,7 @@ class TS3Proto():
# Fix the stupidities in TS3 escaping # Fix the stupidities in TS3 escaping
v = [v[0], '='.join(v[1:])] v = [v[0], '='.join(v[1:])]
key, value = v key, value = v
keys[key] = self._unescape_str(value) keys[key] = TS3Proto._unescape_str(value)
elif v[0][0] == '-': elif v[0][0] == '-':
# Option # Option
opts.append(v[0][1:]) opts.append(v[0][1:])
@@ -191,10 +200,12 @@ class TS3Proto():
""" """
if isinstance(value, int): return "%d" % value if isinstance(value, int):
value = value.replace("\\", r'\\') return str(value)
for i, j in ts3_escape.iteritems(): for i, j in ts3_escape.iteritems():
value = value.replace(i, j) value = value.replace(i, j)
return value return value
@staticmethod @staticmethod
@@ -207,21 +218,17 @@ class TS3Proto():
""" """
if isinstance(value, int): return "%d" % value if isinstance(value, int):
value = value.replace(r"\\", "\\") return str(value)
for i, j in ts3_escape.iteritems(): for i, j in ts3_escape.iteritems():
value = value.replace(j, i) value = value.replace(j, i)
return value return value
def send(self, payload):
if self._connected:
self._log.debug('Sent: %s' % payload)
self._sockfile.write(payload)
class TS3Server(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 Abstraction class for TS3 Servers
@@ -231,15 +238,8 @@ class TS3Server(TS3Proto):
@type port: int @type port: int
""" """
TS3Proto.__init__(self) if self.connect(ip, port) and id > 0:
self.use(id)
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
def login(self, username, password): def login(self, username, password):
""" """
@@ -250,18 +250,15 @@ class TS3Server(TS3Proto):
@param password: Password @param password: Password
@type password: str @type password: str
""" """
d = self.send_command('login', keys={'client_login_name': username, 'client_login_password': password })
if d == 0: response = self.send_command('login', keys={'client_login_name': username, 'client_login_password': password })
self._log.info('Login Successful') return response.is_successful()
return True
return False
def serverlist(self): def serverlist(self):
""" """
Get a list of all Virtual Servers on the connected TS3 instance 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): def gm(self, msg):
""" """
@@ -270,8 +267,7 @@ class TS3Server(TS3Proto):
@param msg: Message @param msg: Message
@type ip: str @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): def use(self, id):
""" """
@@ -280,5 +276,4 @@ class TS3Server(TS3Proto):
@param id: Virtual Server ID @param id: Virtual Server ID
@type id: int @type id: int
""" """
if self._connected and id > 0: self.send_command('use', keys={'sid': id})
self.send_command('use', keys={'sid': id})