1 # Copyright (C) 2007 Felix Rabe <public@felixrabe.textdriven.com>
2 # This file is based on Galbot/src/galcon_protocol.py (r95)
3 # Copyright (C) 2007 Michael Carter
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
6 # of this software and associated documentation files (the "Software"), to deal
7 # in the Software without restriction, including without limitation the rights
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 # copies of the Software, and to permit persons to whom the Software is
10 # furnished to do so, subject to the following conditions:
12 # The above copyright notice and this permission notice shall be included in
13 # all copies or substantial portions of the Software.
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23 # A description of the galcon protocol can be found at
24 # http://galbot.zevv.nl/index.php/Galcon_protocol
30 from twisted
.internet
.protocol
import DatagramProtocol
, ClientFactory
31 from twisted
.web
import http
32 from twisted
.internet
import reactor
, defer
36 class GalconUDPProtocol(DatagramProtocol
):
37 # header = "[HEADER]\tpasswd\t\n[HEADER]\tsecret\tb6c131db95965b6f6bcb7466525fac72\n[HEADER]\tname\tinca\n"
38 header
= "[HEADER]\tpasswd\t%s\n[HEADER]\tsecret\t%s\n[HEADER]\tname\t%s\n"
39 def __init__(self
, client
, address
, port
, server_name
, digest
, server_password
, name
, user_password
):#, digest="b6c131db95965b6f6bcb7466525fac72", passwd='', name='inca'):
40 self
.server_name
= server_name
41 self
.server_password
= server_password
42 self
.user_password
= user_password
46 self
.address
= address
49 self
.awaiting_ack
= dict()
54 self
.players_details
= None
56 def decode(self
, data
):
57 return zlib
.decompress(base64
.decodestring(data
))
58 def startProtocol(self
):
59 self
.transport
.connect(self
.address
, self
.port
)
60 self
.send_commands([(1, "[RESET]"), (1, "[RESET]"), (1, "version", "0.18.0")])
63 cmd
, timer
= self
.awaiting_ack
[int(id)]
64 self
.send_command(cmd
)
66 def send_commands(self
, cmds
):
69 # print "PRESEND: %s" % (cmds,)
73 if cmd
[0] not in self
.awaiting_ack
:
75 cmd
= (self
.id,) + cmd
77 self
.awaiting_ack
.pop(cmd
[0])
80 d
= reactor
.callLater(1, self
.resend
, cmd
[0])
81 self
.awaiting_ack
[cmd
[0]] = cmd
, d
85 packet
= self
.header
% (self
.server_password
, self
.digest
, self
.name
) + '\n'.join(['\t'.join([str(i
) for i
in cmd
]) for cmd
in cmds
]) + '\n'
86 self
.transport
.write(packet
)
87 # print "SEND: %s" % packet.replace("\n", "\\n").replace("\t", "\\t")
89 def send_command(self
, cmd
):
90 self
.send_commands([cmd
])
92 def datagramReceived(self
, datagram
, host
):
93 # print "RECV: %s" % datagram
94 cmds
= [ line
.split('\t', 3) for line
in datagram
.split('\n') ]
95 cmds
= [ i
for i
in cmds
if len(i
) > 1 ]
96 # print "RECV: %s" % (cmds,)
97 to_ack
= [ cmd
[0] for cmd
in cmds
if cmd
[1] == '1' ]
98 self
.send_commands([(0, "[ACK]", id) for id in to_ack
])
101 if cmd
[0] in self
.received
:
103 self
.received
.append(cmd
[0])
104 cmd
[2] = cmd
[2].replace("[", "_").replace("]", "_")
107 rest
= cmd
[3].split('\t')
108 f
= getattr(self
, "recv_%s" % cmd
[2], self
.default
)
109 if f
== self
.default
:
114 def default(self
, cmd
, *data
):
115 print "DEFAULT HANDLER: COMMAND [%s]: %s" % (cmd
, data
,)
117 def recv_version(self
, data
):
118 # print "GOT VERSION"
119 self
.send_command((1, "login"))
121 def recv_topic(self
, topic
):
122 # print "TOPIC IS %s" % topic
123 self
.client
.topic(self
.server_name
, topic
)
125 def recv__ACK_(self
, id):
127 cmd
, timer
= self
.awaiting_ack
.pop(int(id), (None, None))
132 def recv_message(self
, sender
, msg
):
133 self
.client
.message(self
.server_name
, sender
, msg
)
135 def recv_players(self
, *players
):
138 for player
in players
:
139 num
, name
, junk1
, junk2
, state
, junk3
, junk4
, color
= player
.split(',')
140 self
.players
[name
] = (name
, state
, color
)
141 self
.client
.players(self
.server_name
, players
)
143 def recv__PING_(self
):
144 self
.client
.ping(self
.server_name
)
147 def recv_mfleet(self
, *args
):
148 self
.client
.mfleet(self
.server_name
, *args
)
150 def recv_redirect(self
, *args
):
151 self
.client
.redirect(self
.server_name
, *args
)
156 self
.players_details
= None
158 self
.client
.stop(self
.server_name
)
160 def recv_start(self
, players
, time
, universe
):
162 self
.players_details
= {}
163 self
.universe
= { 'players': {}, 'planets': {}, 'options': None }
164 for player
in players
.split(';'):
165 num
, name
, junk
, color
= player
.split(',')
166 self
.players_details
[num
] = name
, num
, color
168 universe_data
= self
.decode(universe
)
169 lines
= universe_data
.split('\n')
170 self
.universe
['options'] = lines
[1].split('\t')
171 delta
= self
.parse_state(lines
[2:])
172 self
.universe
['players'].update(delta
['players'])
173 self
.universe
['planets'].update(delta
['planets'])
174 self
.client
.start(self
.server_name
, self
.universe
)
176 def parse_state(self
, state_lines
, is_delta
=False):
177 delta
= {'players': {}, 'planets': {} }
178 for i
, line
in enumerate(state_lines
):
181 junk
, rest
= line
.split('\t', 1)
183 type, rest
= line
.split('\t', 1)
186 id, rest
= rest
.split('\t', 1)
187 delta
['players'][id] = {
188 'name': self
.players_details
[id][0],
189 'id': self
.players_details
[id][1],
190 'color': self
.players_details
[id][2], }
191 elif type == 'planet':
192 id, x
, y
, owner_id
, prod
, troops
, junk
= rest
.split('\t', 6)
197 p
= self
.universe
['planets'][id]
198 x
,y
,prod
= p
['x'], p
['y'], p
['prod']
200 owner_id
= p
['owner_id']
202 delta
['planets'][id] = {
206 'owner_id': owner_id
,
212 def recv_delta(self
, delta
):
213 universe_delta
= self
.decode(delta
)
214 planets
= universe_delta
.split('\n')[-len(self
.universe
['planets']):]
215 delta
= self
.parse_state(planets
, True)
216 self
.universe
['planets'].update(delta
['planets'])
217 self
.client
.delta(self
.server_name
, self
.universe
)
220 def list_servers(email
, name
, passwd
, platform
="linux2", version
="1.2.0", host
="imitationpickles.org"):
222 factory
= GalconHTTPFactory(d
, email
, name
, passwd
, platform
, version
)
223 reactor
.connectTCP(host
, 80, factory
)
226 class GalconHTTPProtocol(http
.HTTPClient
):
227 url
= "/galcon/api.php"
228 def __init__(self
, deferred
, email
, name
, passwd
, platform
, version
):
229 self
.deferred
= deferred
230 self
.version
= version
231 self
.platform
= platform
236 def connectionMade(self
):
240 'version': self
.version
,
241 'platform': self
.platform
,
242 'reg_email': self
.email
,
244 'passwd': self
.passwd
,
246 url
= self
.url
+ '?' + urllib
.urlencode(options
.items())
247 self
.sendCommand('GET', url
)
248 self
.sendHeader('Accept-Encoding', 'identity')
249 self
.sendHeader('Host', 'www.imitationpickles.org')
250 self
.sendHeader('Connection', 'close')
251 self
.sendHeader('User-agent', 'Python-urllib/2.4')
254 def handleResponse(self
, response
):
256 lines
= response
.split('\n')
257 current_version
= lines
[0].split()[1].split(".")
260 for server
in servers
:
262 owner_allowed
, name
, version
, more
= server
.split('|')
263 owner
, allowed
= owner_allowed
.split('\t')
264 num
, pwd_protected
, addr
, digest
, status
= more
.split('\t')
270 'pwd_protected': bool(int(pwd_protected
)),
274 'bots_ok': owner
.lower() == 'korin',
278 self
.deferred
.callback((current_version
, server_list
))
280 self
.deferred
.errback()
282 class GalconHTTPFactory(ClientFactory
):
283 protocol
= GalconHTTPProtocol
284 def __init__(self
, d
, email
, name
, passwd
, platform
, version
):
289 self
.platform
= platform
290 self
.version
= version
292 def buildProtocol(self
, addr
):
293 return self
.protocol(self
.d
, self
.email
, self
.name
, self
.passwd
, self
.platform
, self
.version
)