You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

225 lines
6.9 KiB

# -*- coding: utf-8 -*-
from Queue import Queue
from twisted.internet.protocol import ClientFactory
from twisted.protocols.basic import LineReceiver
from twisted.internet.task import LoopingCall
from twisted.python import log
from frn.utility import *
class FRNClient(LineReceiver):
client_version = 2010002
def connectionMade(self):
self.txq = Queue()
self.login()
def ready(self):
self.status = 'READY'
self.msgbuffer = []
self.phase = 0
self.setRawMode()
def startMultiLineMessage(self, msgtype, rest=''):
self.status = msgtype
self.phase = None
self.setLineMode(rest)
def collectMultiLineMessage(self, line):
if self.phase is None:
while line[0] not in '0123456789': # needed for client list
line = line[1:]
self.expected_lines = int(line.strip())
self.msgbuffer = []
self.phase = 0
else:
self.msgbuffer.append(line)
self.phase += 1
if self.phase >= self.expected_lines:
handler = getattr(self, 'decode'+self.status, self.unimplemented)
message = self.msgbuffer
self.ready()
handler(message)
def startAudioMessage(self, rest=''):
self.status = 'AUDIO'
self.phase = None
self.msgbuffer = ''
if len(rest) > 0:
self.collectAudioMessage(rest)
def collectAudioMessage(self, data):
needed = min(327, max(0, 327-len(self.msgbuffer)))
if len(data) > 0:
self.msgbuffer += data[:needed]
if len(self.msgbuffer) >= 327:
audio_data = self.msgbuffer
source = ord(audio_data[0])*256+ord(audio_data[1])
self.ready()
self.decodeAUDIO(source, audio_data[2:])
if len(data) > needed:
self.dataReceived(data[needed:])
def lineReceived(self, line):
if self.status == 'AUTH':
if self.phase == 0:
self.latest_client_version = int(line.strip())
self.phase = 1
else:
self.serverdata = parseSimpleXML(line.strip())
self.loginResponse(self.serverdata)
if int(self.serverdata['sv']) > 2009004:
self.sendLine(makeAuthKey(self.serverdata['kp']))
self.ready()
else:
self.collectMultiLineMessage(line)
def rawDataReceived(self, data):
if self.status == 'READY':
packet_type = ord(data[0])
if packet_type == 0: # Keepalive
self.pong()
elif packet_type == 1: # TX ack
self.status = 'TX'
self.phase = 0
if len(data) > 1:
self.dataReceived(data[1:])
elif packet_type == 2: # Audio
self.startAudioMessage(data[1:])
elif packet_type == 3: # Client list
self.startMultiLineMessage('CLIENTS', data[1:])
elif packet_type == 4: # Text
self.startMultiLineMessage('TEXT', data[1:])
elif packet_type == 5: # Channel list
self.startMultiLineMessage('NETWORKS', data[1:])
else:
log.err("Unknown packet type %d" % packet_type)
elif self.status == 'AUDIO':
self.collectAudioMessage(data)
elif self.status == 'TX':
if self.phase == 0:
self.phase = 1
self.decodeTX(ord(data[0])*256+ord(data[1]))
self.ready()
if len(data) > 2:
self.dataReceived(data[2:])
def login(self):
d = self.factory.client_id
fields = [
('VX', self.client_version),
('EA', d['email']),
('PW', d['password']),
('ON', d['operator']),
('BC', d['transmission']),
('DS', d['description']),
('NN', d['country']),
('CT', d['city']),
('NT', d['network'])
]
ap = "CT:"+formatSimpleXML(fields)
self.status = 'AUTH'
self.phase = 0
self.sendLine(ap)
def pong(self):
self.sendLine('P')
def setStatus(self, status):
self.sendLine('ST:%s' % str(status))
def stopTransmission(self):
self.sendLine('RX0')
def startTransmission(self):
self.sendLine('TX0')
def sendAudioFrame(self, frame):
self.sendLine('TX1')
self.transport.write(frame)
def streamStep(self, count):
if count > 1:
log.msg("WARNING: lost %d ticks" % (count-1))
for i in range(count):
self.sendAudioFrame(self.txq.get_nowait())
def stopStreaming(self):
self.txtimer.stop()
def _streamAck(self):
self.txtimer = LoopingCall.withCount(self.streamStep)
self.txtimer.start(0.20).addCallback(
lambda _: self.stopTransmission()).addErrback(
lambda _: self.stopTransmission())
def feedStreaming(self, frames):
if type(frames) == list:
for frame in frames:
self.txq.put_nowait(frame)
else:
self.txq.put_nowait(frames)
def startStreaming(self):
self.startTransmission()
def sendTextMessage(self, dest, text):
self.sendLine('TM:'+formatSimpleXML(dict(ID=dest, MS=text)))
def unimplemented(self, msg):
log.msg("Unimplemented: %s" % msg)
def decodeAUDIO(self, from_id, frames):
self.audioFrameReceived(from_id, frames)
def decodeTX(self, my_id):
self._streamAck()
def decodeTEXT(self, msg):
self.textMessageReceived(msg[0], msg[1], msg[2])
def decodeCLIENTS(self, msg):
self.clientsListUpdated([parseSimpleXML(x) for x in msg])
def decodeNETWORKS(self, msg):
self.networksListUpdated(msg)
def loginResponse(self, info):
pass
def audioFrameReceived(self, from_id, frame):
pass
def textMessageReceived(self, client, message, target):
pass
def clientsListUpdated(self, clients):
pass
def networksListUpdated(self, networks):
pass
class FRNClientFactory(ClientFactory):
protocol = FRNClient
def __init__(self, **kw):
self.client_id = kw
def startedConnecting(self, connector):
log.msg('Started to connect')
def buildProtocol(self, addr):
log.msg('Connected')
return ClientFactory.buildProtocol(self, addr)
def clientConnectionLost(self, connector, reason):
log.msg('Lost connection. Reason: %s' % reason)
def clientConnectionFailed(self, connector, reason):
log.err('Connection failed. Reason: %s' % reason)
# vim: set et ai sw=4 ts=4 sts=4: