Commit c2dd84c0 authored by m!nus's avatar m!nus

first release version

parent 12659053
#!/usr/bin/python
from __future__ import print_function
from teeworlds import Teeworlds
# set up stuff
tw = Teeworlds(timeout=2)
# ask the masters for servers
tw.query_masters()
# query servers, wait for responses
# stops if no packet is received for `timeout` seconds
tw.run_loop()
# filter the serverlist
servers = tw.serverlist.find(name="^C", # Server whose name begins with "C", regex style
gametype="CTF", # gametype contains "CTF"
maxping=0.1) # ping is lower or equal to 100ms
# sort by ping
servers.sort(key=lambda s: s.latency)
# display a nice list
for server in servers:
print("{server: <64} [{gametype: ^16}] on {master}: {clients: >2}/{max_clients: <2} - {latency: >4.0f} ms" \
.format(server=server.name, gametype=server.gametype, master=server.master.name, clients=server.clients, \
max_clients=server.max_clients, latency=server.latency*1000))
# check if m!nus is currently playing
minus_list = tw.playerlist.find(name="^m!nus$")
if minus_list.players:
minus = minus_list.players[0]
print("m!nus is currently playing on {server} ({address}) with {players} other player(s)."
.format(server=minus.server.name, address=minus.server.address, players=(minus.server.players-1)))
else:
print("m!nus isn't playing at the moment.")
#!/usr/bin/python
from __future__ import print_function
from teeworlds import Teeworlds
import sys
# set up stuff
tw = Teeworlds(timeout=2)
# ask the masters for servers
tw.query_masters()
# query servers, wait for responses
# stops if no packet is received for `timeout` seconds
tw.run_loop()
# filter the serverlist
ip_filter = "^"+sys.argv[1].replace(".", "\\.")
servers = tw.serverlist.find(address=ip_filter)
# sort by ping
servers.sort(key=lambda s: s.address)
# display a nice list
for server in servers:
print("{server: <64} {address: <15} [{gametype: ^16}] on {master}: {clients: >2}/{max_clients: <2} - {latency: >4.0f} ms" \
.format(server=server.name, address=server.address, gametype=server.gametype, master=server.master.name, clients=server.clients, \
max_clients=server.max_clients, latency=server.latency*1000))
# check if m!nus is currently playing
#minus_list = tw.playerlist.find(name="^m!nus$")
#if minus_list.players:
# minus = minus_list.players[0]
# print("m!nus is currently playing on {server} ({address}) with {players} other player(s)."
# .format(server=minus.server.name, address=minus.server.address, players=(minus.server.players-1)))
#else:
# print("m!nus isn't playing at the moment.")
#!/usr/bin/python
from __future__ import print_function
from teeworlds import Teeworlds
import sys
# set up stuff
tw = Teeworlds(timeout=2)
# ask the masters for servers
tw.query_masters()
# query servers, wait for responses
# stops if no packet is received for `timeout` seconds
tw.run_loop()
servers = tw.serverlist
# sort by ping
servers.sort(key=lambda s: s.address)
# display a nice list
for server in servers:
print("{server: <64} {address: <15} [{gametype: ^16}] on {master}: {clients: >2}/{max_clients: <2} - {latency: >4.0f} ms" \
.format(server=server.name, address=server.address, gametype=server.gametype, master=server.master.name, clients=server.clients, \
max_clients=server.max_clients, latency=server.latency*1000))
#!/usr/bin/python
# A library to get the serverlist & information for Teeworlds servers
# Copyright (C) 2011 m!nus <m1nus@online.de>
#
# This software is provided 'as-is', without any express or implied
# warranty. In no event will the authors be held liable for any damages
# arising from the use of this software.
#
# Permission is granted to anyone to use this software for any purpose,
# including commercial applications, and to alter it and redistribute it
# freely, subject to the following restrictions:
#
# 1. The origin of this software must not be misrepresented; you must not
# claim that you wrote the original software. If you use this software
# in a product, an acknowledgment in the product documentation would be
# appreciated but is not required.
# 2. Altered source versions must be plainly marked as such, and must not be
# misrepresented as being the original software.
# 3. This notice may not be removed or altered from any source distribution.
from __future__ import print_function
import sys
import socket
......@@ -6,8 +27,10 @@ import time
from random import randint
from struct import unpack
import select
import Queue as queue
import re
# fuck you i need UTF-8
# UTF-8 is required as default encoding
reload(sys)
sys.setdefaultencoding('utf8')
......@@ -17,54 +40,72 @@ def log(level, str):
print("[{0: <5}] {1}".format(level, str), file=sys.stderr)
def is_ipv6(address):
if isinstance(address, tuple): address = address[0]
return True if ':' in address else False
class MultiSocket(object):
READ = 1
WRITE = 2
EXCEPTION = 4
def __init__(self, timeout=None):
def __init__(self, timeout=None, interval=0):
self.sockets = {}
self.families = {}
self.queue_out = queue.Queue()
self.timeout = timeout
def get(self, family):
if not self.sockets.has_key(family):
self.sockets[family] = socket.socket(family, socket.SOCK_DGRAM)
fileno = self.sockets[family].fileno()
self.families[fileno] = family
#self.sockets[family].settimeout(self.timeout)
return self.sockets[family]
def getfamily(self, socket):
fileno = socket.fileno()
if fileno in self.families:
return self.families[fileno]
else:
raise Exception('No family found for socket')
def select(self, type=None):
self.interval = interval
self.sockets[socket.AF_INET] = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.SOL_UDP)
self.has_ipv6 = socket.has_ipv6
if self.has_ipv6:
self.sockets[socket.AF_INET6] = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM, socket.SOL_UDP)
def select(self, type=None, timeout=None):
timeout = self.timeout if timeout == None else timeout
list_r = self.sockets.values() if (type&self.READ or type==None) else []
list_w = self.sockets.values() if type&self.WRITE else []
list_x = self.sockets.values() if type&self.EXCEPTION else []
return select.select(list_r, list_w, list_x, self.timeout)
ret = select.select(list_r, list_w, list_x, timeout)
if ret == ([], [], []):
raise socket.timeout('select timed out')
else:
return ret
def sendto(self, data, address):
self.get(address[0]).sendto(data, address[1])
if is_ipv6(address):
if not self.has_ipv6: return 0
return self.sockets[socket.AF_INET6].sendto(data, address)
else:
return self.sockets[socket.AF_INET].sendto(data, address)
def recvfrom(self, len=1492):
def sendto_q(self, data, address, callback=None):
self.queue_out.put((data, address, callback))
def recvfrom(self, len):
try:
(r, w, x) = select.select([self.sockets], [], [], self.timeout)
s = self.sockets.values()
(r, w, x) = select.select(s, [], [], self.timeout)
if not r and not w and not x:
return None
raise socket.timeout('select timed out')
for sock in r:
(data, address) = sock.recvfrom(len)
return (data, (self.sockets.getfamily(sock), address))
except socket.timeout:
return None
return sock.recvfrom(len)
except socket.error as e:
# Errno 10054 happens when we get ICMP port unreachable, we don't care about that
if e.errno != 10054:
raise
# in case if error 10054 just retry
# TODO: might reach maximum recursion
return self.recvfrom(len)
def process_queue(self, amount):
for _ in range(amount):
if not self.queue_out.empty():
(data, address, callback) = self.queue_out.get()
if self.sendto(data, address) == len(data):
if hasattr(callback, '__call__'): callback(time.time())
else:
log('warning', 'failed to send whole packet, requeuing')
self.queue_out.put((data, address, callback))
class Handler(object):
......@@ -110,8 +151,10 @@ class MasterServer(Handler):
_serveraddr_size = 18
def __init__(self, parent, address, name='none given'):
self.address = address
self._parent = parent
self._address = address
self.address = ("[{host}]:{port}" if is_ipv6(address) else "{host}:{port}") \
.format(host=address[0], port=address[1])
#self.data = self._packet_list_response
self.name = name
self.latency = -1
......@@ -120,9 +163,12 @@ class MasterServer(Handler):
def request(self):
self.request_time = time.time()
self._parent.sockets.get(self.address[0]).sendto(10 * b'\xff' + b'req2', self.address[1])
self._parent.socket.sendto_q(10 * b'\xff' + b'req2', self._address, self.request_callback)
self.server_count = 0
def request_callback(self, request_time):
self.request_time = request_time
def add_from_serverlist(self, data):
if len(data) % self._serveraddr_size != 0:
raise Exception("Address packet's size not multiple of the server " + \
......@@ -132,14 +178,13 @@ class MasterServer(Handler):
data=' '.join([ "{0:2x}".format(ord(x)) for x in data ])))
for i in xrange(0, len(data), self._serveraddr_size):
if data[0:12] == b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff": # ::ffff:... == IPv4
serverAddress = (socket.AF_INET, (socket.inet_ntoa(data[i+12:i+16]), unpack("!H", data[i+16:i+18])[0]))
serverAddress = (socket.inet_ntoa(data[i+12:i+16]), unpack("!H", data[i+16:i+18])[0])
else:
# TODO: workaround for windows as inet_ntop doesn't exist there
if sys.platform == "win32":
log('warning', "Can't make IPv6 address on windows from binary: {0!r}".format(data[i:i+16]))
continue
address_family = socket.AF_INET6
serverAddress = (address_family (socket.inet_ntop(address_family, data[i:i+16]), unpack("!H", data[i+16:i+18])[0]))
serverAddress = (socket.inet_ntop(socket.AF_INET6, data[i:i+16]), unpack("!H", data[i+16:i+18])[0])
server = Server(self._parent, serverAddress, master=self)
server.request()
self._parent.serverlist.add(server)
......@@ -153,17 +198,15 @@ class MasterServer(Handler):
self.add_from_serverlist(data[len(self._packet_list_response):])
def match(self, **kwargs):
if not kwargs.has_key("address") or kwargs["address"] != self.address:
if not kwargs.has_key("_address") or kwargs["_address"] != self._address:
return False
if not kwargs.has_key("data") or kwargs["data"][0:len(self._packet_list_response)] != self._packet_list_response:
if not kwargs.has_key("_data") or kwargs["_data"][0:len(self._packet_list_response)] != self._packet_list_response:
return False
return True
def __repr__(self):
address = ("[{host}]:{port}" if self.address[0] == socket.AF_INET6 else "{host}:{port}") \
.format(host=self.address[1][0], port=self.address[1][1])
return "<MasterServer name='{name}' address='{address}' servers='{servers}'>" \
.format(name=self.name, address=address, servers=self.server_count)
.format(name=self.name, address=self.address, servers=self.server_count)
class Server(Handler):
......@@ -171,15 +214,19 @@ class Server(Handler):
_packet_response = 10*b'\xff' + b'inf3'
def __init__(self, parent, address, master=None):
self.address = address
self._address = address
self.address = ("[{host}]:{port}" if is_ipv6(address) else "{host}:{port}") \
.format(host=address[0], port=address[1])
self._parent = parent
self.data = None
self.master = master
self.data = None
self.reset()
def reset(self):
self.latency = -1
self.playerlist = PlayerList()
self.version = None
self.name = ("[{host}]:{port}" if self.address[0] == socket.AF_INET6 else "{host}:{port}") \
.format(host=self.address[1][0], port=self.address[1][1])
self.name = self.address
self.map = None
self.gametype = None
self.password = None
......@@ -189,25 +236,28 @@ class Server(Handler):
self.max_clients = -1
def request(self):
#log('debug', "Server-ping to " + str(self.address))
#log('debug', "Server-ping to " + self.address)
self.token = chr(randint(1,255))
self.data = self._packet_response + str(ord(self.token)) + b'\x00'
self.request_time = time.time()
self._parent.sockets.get(self.address[0]).sendto(self._packet_request + self.token, self.address[1])
self._parent.socket.sendto_q(self._packet_request + self.token, self._address, self.request_callback)
self._parent.add_handler(self)
def request_callback(self, request_time):
self.request_time = request_time
def call(self, address, data):
#log('debug', "Server-callback hit from " + str(address))
#log('debug', "Server-callback hit from " + address)
self.parse(data[len(self.data):])
def parse(self, data):
self.latency = time.time() - self.request_time
data = iter(data.split(b'\x00'))
try:
self.version = data.next().decode('utf8')
self.name = data.next().decode('utf8')
self.map = data.next().decode('utf8')
self.gametype = data.next().decode('utf8')
self.version = data.next() #.decode('utf8')
self.name = data.next() #.decode('utf8')
self.map = data.next() #.decode('utf8')
self.gametype = data.next() #.decode('utf8')
self.password = (data.next()=='1')
self.players = int(data.next())
self.max_players = int(data.next())
......@@ -215,28 +265,39 @@ class Server(Handler):
self.max_clients = int(data.next())
for _ in range(self.clients):
player = Player()
player.name=data.next().decode('utf8')
player.clan=data.next().decode('utf8')
player.name=data.next() #.decode('utf8')
player.clan=data.next() #.decode('utf8')
player.country = int(data.next())
player.score = int(data.next())
player.playing = (data.next()=='1')
player.server = self
self.playerlist.add(player)
except StopIteration:
self.reset()
log('warning', 'unexpected end of data for server ' + str(self))
for player in self.playerlist:
self._parent.playerlist.add(player)
def match(self, **kwargs):
if not kwargs.has_key("address") or kwargs["address"] != self.address:
if kwargs.has_key("hideInvalid") and kwargs["invalid"] and self.latency == -1:
return False
if kwargs.has_key("_address") and kwargs["_address"] != self._address:
return False
if kwargs.has_key("_data") and kwargs["_data"][0:len(self.data)] != self.data:
return False
if kwargs.has_key("name") and not re.search(kwargs["name"], self.name):
return False
if not kwargs.has_key("data") or kwargs["data"][0:len(self.data)] != self.data:
if kwargs.has_key("address") and not re.search(kwargs["address"], self.address):
return False
if kwargs.has_key("gametype") and not re.search(kwargs["gametype"], self.gametype):
return False
if kwargs.has_key("maxping") and self.ping > kwargs["maxping"]:
return False
return True
def __repr__(self):
address = ("[{host}]:{port}" if self.address[0] == socket.AF_INET6 else "{host}:{port}") \
.format(host=self.address[1][0], port=self.address[1][1])
return "<MasterServer name='{name}' address='{address}'>" \
.format(name=self.name, address=address)
return "<Server name='{name}' address='{address}'>" \
.format(name=self.name, address=self.address)
class Player(object):
......@@ -261,16 +322,22 @@ class ServerList(object):
raise Exception('Trying to add non-Server object')
self.servers.append(server)
def find(self, name=None, gametype=None, maxping=None):
output = []
def find(self, **kwargs):
output = ServerList()
for server in self.servers:
if (server.latency != -1) and \
(name == None or server.name == name) and \
(maxping == None or server.ping <= maxping) and \
(gametype == None or server.gametype == gametype):
output.append(server)
if server.match(**kwargs):
output.add(server)
return output
def sort(self, cmp=None, key=None, reverse=False):
self.servers = sorted(self.servers, cmp, key, reverse)
def reverse(self):
self.players.reverse()
def __iter__(self):
return iter(self.servers)
def __repr__(self):
return str(self.servers)
......@@ -284,16 +351,28 @@ class PlayerList(object):
raise Exception('Trying to add non-Player-object')
self.players.append(player)
def find(self, name=None, clan=None, country=None, playing=None):
output = []
def find(self, name=None, clan=None, country=None, playing=None, server=None):
output = PlayerList()
if name: name = re.compile(name, re.IGNORECASE)
if clan: clan = re.compile(clan, re.IGNORECASE)
for player in self.players:
if (name == None or player.name == name) and \
(clan == None or player.clan == clan) and \
if (name == None or name.search(player.name)) and \
(clan == None or clan.search(player.clan)) and \
(country == None or player.country == country) and \
(server == None or player.server == server) and \
(playing == None or player.playing == playing):
output.append(player)
output.add(player)
return output
def sort(self, cmp=None, key=None, reverse=False):
self.players = sorted(self.players, cmp, key, reverse)
def reverse(self):
self.players.reverse()
def __iter__(self):
return iter(self.players)
def __repr__(self):
return str(self.players)
......@@ -303,22 +382,24 @@ class Teeworlds(object):
#self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#self.socket.setblocking(0)
#self._socket.settimeout(2)
self.timeout = timeout
self.handlers = HandlerStorage()
self.serverlist = ServerList()
self.playerlist = PlayerList()
self.masterlist = []
self.sockets = MultiSocket(timeout)
self.socket = MultiSocket(timeout=0.001)
def query_masters(self):
masters = ['master3.teeworlds.com'] #["master{0}.teeworlds.com".format(i) for i in range(2, 4+1)]
masters = ["master{0}.teeworlds.com".format(i) for i in range(2, 4+1)]
for mastername in masters:
# resolves host and picks the first address
try:
info = socket.getaddrinfo(mastername, 8300, 0, socket.SOCK_DGRAM)
except socket.gaierror as e:
log('warning', '[GAIError ' + e.errno + '] ' + e.strerror)
log('warning', 'getaddrinfo failed: ' + str(e))
continue
else:
master_addr = (info[0][0], info[0][4])
master_addr = info[0][4]
log('debug', "requesting " + mastername + " " + str(master_addr))
master = MasterServer(self, master_addr, mastername.partition(".")[0])
master.request()
......@@ -326,24 +407,40 @@ class Teeworlds(object):
self.masterlist.append(master)
def run_loop(self):
last_recv = time.time()
last_send = 0
while True:
try:
(r, w, x) = self.sockets.select(MultiSocket.READ)
if not r and not w and not x:
break
for sock in r:
(data, address) = sock.recvfrom(1500)
address = (self.sockets.getfamily(sock), address)
log('debug', "received data from socket: byteslen=" + str(len(data)) + " bytes=" + ' '.join([ "{0:2x}".format(ord(x)) for x in data[0:20] ]))
for handler in self.handlers.find(data=data, address=address):
log('debug', "calling handler " + repr(handler) + "with address=" + str(address))
handler.call(address, data)
#(data, address) = self.socket.recvfrom(1492)
#log('debug', "received data from socket: byteslen=" + str(len(data)) + " bytes=" + ' '.join([ "{0:2x}".format(ord(x)) for x in data[0:20] ]))
#for handler in self.handlers.find(data=data, address=address):
# log('debug', "calling handler " + repr(handler) + "with address=" + str(address))
# handler.call(address, data)
#self.socket.process_queue()
(r, w, x) = self.socket.select(MultiSocket.READ | MultiSocket.WRITE)
cur_time = time.time()
if w and cur_time > last_send + 0.005:
last_send = cur_time
self.socket.process_queue(1)
if not r:
if cur_time > last_recv + self.timeout:
break
time.sleep(0.001)
else:
last_recv = cur_time
for sock in r:
try:
(data, address) = sock.recvfrom(1492)
log('debug', "received data from socket: byteslen=" + str(len(data)) + " bytes=" + ' '.join([ "{0:2x}".format(ord(x)) for x in data[0:20] ]))
for handler in self.handlers.find(_data=data, _address=address):
log('debug', "calling handler " + repr(handler) + " with address=" + str(address))
handler.call(address, data)
except socket.error as e:
# Errno 10054 happens when we get ICMP port unreachable, we don't care about that
if e.errno != 10054:
raise
except socket.timeout:
break
except socket.error as e:
# Errno 10054 happens when we get ICMP port unreachable, we don't care about that
if e.errno != 10054:
raise
def add_handler(self, handler):
# improve this
......@@ -355,12 +452,10 @@ class Teeworlds(object):
if __name__ == "__main__":
tw = Teeworlds(timeout=2)
tw.query_masters()
#s = Server(tw, (2, ('178.63.226.59', 8303)))
#tw.serverlist.add(s)
#s.request()
tw.run_loop()
for server in tw.serverlist.find():
print("{server: <64} on {master}: {clients: >2}/{max_clients: >2} - {latency: >4.0f} ms" \
.format(server=server.name, master=server.master.name, clients=server.clients, \
servers = tw.serverlist.find(name="^C", gametype="CTF", maxping=0.1)
servers.sort(key=lambda s: s.latency)
for server in servers:
print("{server: <64} [{gametype: ^16}] on {master}: {clients: >2}/{max_clients: >2} - {latency: >4.0f} ms" \
.format(server=server.name, gametype=server.gametype, master=server.master.name, clients=server.clients, \
max_clients=server.max_clients, latency=server.latency*1000))
#print(str(tw.handlers))
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment