summary refs log tree commit diff
path: root/contrib/jitsimeetbridge/jitsimeetbridge.py
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/jitsimeetbridge/jitsimeetbridge.py')
-rw-r--r--contrib/jitsimeetbridge/jitsimeetbridge.py260
1 files changed, 260 insertions, 0 deletions
diff --git a/contrib/jitsimeetbridge/jitsimeetbridge.py b/contrib/jitsimeetbridge/jitsimeetbridge.py
new file mode 100644
index 0000000000..dbc6f6ffa5
--- /dev/null
+++ b/contrib/jitsimeetbridge/jitsimeetbridge.py
@@ -0,0 +1,260 @@
+#!/usr/bin/env python
+
+"""
+This is an attempt at bridging matrix clients into a Jitis meet room via Matrix
+video call.  It uses hard-coded xml strings overg XMPP BOSH. It can display one
+of the streams from the Jitsi bridge until the second lot of SDP comes down and
+we set the remote SDP at which point the stream ends. Our video never gets to
+the bridge.
+
+Requires:
+npm install jquery jsdom 
+"""
+
+import gevent
+import grequests
+from BeautifulSoup import BeautifulSoup
+import json
+import urllib
+import subprocess
+import time
+
+#ACCESS_TOKEN="" #
+
+MATRIXBASE = 'https://matrix.org/_matrix/client/api/v1/'
+MYUSERNAME = '@davetest:matrix.org'
+
+HTTPBIND = 'https://meet.jit.si/http-bind'
+#HTTPBIND = 'https://jitsi.vuc.me/http-bind'
+#ROOMNAME = "matrix"
+ROOMNAME = "pibble"
+
+HOST="guest.jit.si"
+#HOST="jitsi.vuc.me"
+
+TURNSERVER="turn.guest.jit.si"
+#TURNSERVER="turn.jitsi.vuc.me"
+
+ROOMDOMAIN="meet.jit.si"
+#ROOMDOMAIN="conference.jitsi.vuc.me"
+
+class TrivialMatrixClient:
+	def __init__(self, access_token):
+		self.token = None
+		self.access_token = access_token
+
+	def getEvent(self):
+		while True:
+			url = MATRIXBASE+'events?access_token='+self.access_token+"&timeout=60000"
+			if self.token:
+				url += "&from="+self.token
+			req = grequests.get(url)
+			resps = grequests.map([req])
+			obj = json.loads(resps[0].content)
+			print "incoming from matrix",obj
+			if 'end' not in obj:
+				continue
+			self.token = obj['end']
+			if len(obj['chunk']):
+				return obj['chunk'][0]
+
+	def joinRoom(self, roomId):
+		url = MATRIXBASE+'rooms/'+roomId+'/join?access_token='+self.access_token
+		print url
+		headers={ 'Content-Type': 'application/json' }
+		req = grequests.post(url, headers=headers, data='{}')
+		resps = grequests.map([req])
+		obj = json.loads(resps[0].content)
+		print "response: ",obj
+
+	def sendEvent(self, roomId, evType, event):
+		url = MATRIXBASE+'rooms/'+roomId+'/send/'+evType+'?access_token='+self.access_token
+		print url
+		print json.dumps(event)
+		headers={ 'Content-Type': 'application/json' }
+		req = grequests.post(url, headers=headers, data=json.dumps(event))
+		resps = grequests.map([req])
+		obj = json.loads(resps[0].content)
+		print "response: ",obj
+
+
+
+xmppClients = {}
+
+
+def matrixLoop():
+	while True:
+		ev = matrixCli.getEvent()
+		print ev
+		if ev['type'] == 'm.room.member':
+			print 'membership event'
+			if ev['membership'] == 'invite' and ev['state_key'] == MYUSERNAME:
+				roomId = ev['room_id']
+				print "joining room %s" % (roomId)
+				matrixCli.joinRoom(roomId)
+		elif ev['type'] == 'm.room.message':
+			if ev['room_id'] in xmppClients:
+				print "already have a bridge for that user, ignoring"
+				continue
+			print "got message, connecting"
+			xmppClients[ev['room_id']] = TrivialXmppClient(ev['room_id'], ev['user_id'])
+			gevent.spawn(xmppClients[ev['room_id']].xmppLoop)
+		elif ev['type'] == 'm.call.invite':
+			print "Incoming call"
+			#sdp = ev['content']['offer']['sdp']
+			#print "sdp: %s" % (sdp)
+			#xmppClients[ev['room_id']] = TrivialXmppClient(ev['room_id'], ev['user_id'])
+			#gevent.spawn(xmppClients[ev['room_id']].xmppLoop)
+		elif ev['type'] == 'm.call.answer':
+			print "Call answered"
+			sdp = ev['content']['answer']['sdp']
+			if ev['room_id'] not in xmppClients:
+				print "We didn't have a call for that room"
+				continue
+			# should probably check call ID too
+			xmppCli = xmppClients[ev['room_id']]
+			xmppCli.sendAnswer(sdp)
+		elif ev['type'] == 'm.call.hangup':
+			if ev['room_id'] in xmppClients:
+				xmppClients[ev['room_id']].stop()
+				del xmppClients[ev['room_id']]
+			
+class TrivialXmppClient:
+	def __init__(self, matrixRoom, userId):
+		self.rid = 0
+		self.matrixRoom = matrixRoom
+		self.userId = userId
+		self.running = True
+
+	def stop(self):
+		self.running = False
+
+	def nextRid(self):
+		self.rid += 1
+		return '%d' % (self.rid)
+
+	def sendIq(self, xml):
+		fullXml = "<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' sid='%s'>%s</body>" % (self.nextRid(), self.sid, xml)
+		#print "\t>>>%s" % (fullXml)
+		return self.xmppPoke(fullXml)
+		
+	def xmppPoke(self, xml):
+		headers = {'Content-Type': 'application/xml'}
+		req = grequests.post(HTTPBIND, verify=False, headers=headers, data=xml)
+		resps = grequests.map([req])
+		obj = BeautifulSoup(resps[0].content)
+		return obj
+
+	def sendAnswer(self, answer):
+		print "sdp from matrix client",answer
+		p = subprocess.Popen(['node', 'unjingle/unjingle.js', '--sdp'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+		jingle, out_err = p.communicate(answer)
+		jingle = jingle % {
+			'tojid': self.callfrom,
+			'action': 'session-accept',
+			'initiator': self.callfrom,
+			'responder': self.jid,
+			'sid': self.callsid
+		}
+		print "answer jingle from sdp",jingle
+		res = self.sendIq(jingle)
+		print "reply from answer: ",res
+		
+		self.ssrcs = {}
+		jingleSoup = BeautifulSoup(jingle)
+		for cont in jingleSoup.iq.jingle.findAll('content'):
+			if cont.description:
+				self.ssrcs[cont['name']] = cont.description['ssrc']
+		print "my ssrcs:",self.ssrcs
+
+		gevent.joinall([
+ 		       gevent.spawn(self.advertiseSsrcs)
+		])
+		
+	def advertiseSsrcs(self):
+                time.sleep(7)
+		print "SSRC spammer started"
+		while self.running:
+			ssrcMsg = "<presence to='%(tojid)s' xmlns='jabber:client'><x xmlns='http://jabber.org/protocol/muc'/><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://jitsi.org/jitsimeet' ver='0WkSdhFnAUxrz4ImQQLdB80GFlE='/><nick xmlns='http://jabber.org/protocol/nick'>%(nick)s</nick><stats xmlns='http://jitsi.org/jitmeet/stats'><stat name='bitrate_download' value='175'/><stat name='bitrate_upload' value='176'/><stat name='packetLoss_total' value='0'/><stat name='packetLoss_download' value='0'/><stat name='packetLoss_upload' value='0'/></stats><media xmlns='http://estos.de/ns/mjs'><source type='audio' ssrc='%(assrc)s' direction='sendre'/><source type='video' ssrc='%(vssrc)s' direction='sendre'/></media></presence>" % { 'tojid': "%s@%s/%s" % (ROOMNAME, ROOMDOMAIN, self.shortJid), 'nick': self.userId, 'assrc': self.ssrcs['audio'], 'vssrc': self.ssrcs['video'] }
+			res = self.sendIq(ssrcMsg)
+			print "reply from ssrc announce: ",res
+			time.sleep(10)
+		
+		
+
+	def xmppLoop(self):
+		self.matrixCallId = time.time()
+		res = self.xmppPoke("<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' to='%s' xml:lang='en' wait='60' hold='1' content='text/xml; charset=utf-8' ver='1.6' xmpp:version='1.0' xmlns:xmpp='urn:xmpp:xbosh'/>" % (self.nextRid(), HOST))
+
+		print res
+		self.sid = res.body['sid']
+		print "sid %s" % (self.sid)
+
+		res = self.sendIq("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>")
+
+		res = self.xmppPoke("<body rid='%s' xmlns='http://jabber.org/protocol/httpbind' sid='%s' to='%s' xml:lang='en' xmpp:restart='true' xmlns:xmpp='urn:xmpp:xbosh'/>" % (self.nextRid(), self.sid, HOST))
+	
+		res = self.sendIq("<iq type='set' id='_bind_auth_2' xmlns='jabber:client'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/></iq>")
+		print res
+
+		self.jid = res.body.iq.bind.jid.string
+		print "jid: %s" % (self.jid)
+		self.shortJid = self.jid.split('-')[0]
+
+		res = self.sendIq("<iq type='set' id='_session_auth_2' xmlns='jabber:client'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>")
+
+		#randomthing = res.body.iq['to']
+		#whatsitpart = randomthing.split('-')[0]
+
+		#print "other random bind thing: %s" % (randomthing)
+
+		# advertise preence to the jitsi room, with our nick
+		res = self.sendIq("<iq type='get' to='%s' xmlns='jabber:client' id='1:sendIQ'><services xmlns='urn:xmpp:extdisco:1'><service host='%s'/></services></iq><presence to='%s@%s/d98f6c40' xmlns='jabber:client'><x xmlns='http://jabber.org/protocol/muc'/><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://jitsi.org/jitsimeet' ver='0WkSdhFnAUxrz4ImQQLdB80GFlE='/><nick xmlns='http://jabber.org/protocol/nick'>%s</nick></presence>" % (HOST, TURNSERVER, ROOMNAME, ROOMDOMAIN, self.userId))
+		self.muc = {'users': []}
+		for p in res.body.findAll('presence'):
+			u = {}
+			u['shortJid'] = p['from'].split('/')[1]
+			if p.c and p.c.nick:
+				u['nick'] = p.c.nick.string
+			self.muc['users'].append(u)
+		print "muc: ",self.muc
+
+		# wait for stuff
+		while True:
+			print "waiting..."
+			res = self.sendIq("")
+			print "got from stream: ",res
+			if res.body.iq:
+				jingles = res.body.iq.findAll('jingle')
+				if len(jingles):
+					self.callfrom = res.body.iq['from']
+					self.handleInvite(jingles[0])
+			elif 'type' in res.body and res.body['type'] == 'terminate':
+				self.running = False
+				del xmppClients[self.matrixRoom]
+            			return 
+
+	def handleInvite(self, jingle):
+		self.initiator = jingle['initiator']
+		self.callsid = jingle['sid']
+		p = subprocess.Popen(['node', 'unjingle/unjingle.js', '--jingle'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+		print "raw jingle invite",str(jingle)
+		sdp, out_err = p.communicate(str(jingle))
+		print "transformed remote offer sdp",sdp
+		inviteEvent = {
+			'offer': {
+				'type': 'offer',
+				'sdp': sdp
+			},
+			'call_id': self.matrixCallId,
+			'version': 0,
+			'lifetime': 30000
+		}
+		matrixCli.sendEvent(self.matrixRoom, 'm.call.invite', inviteEvent)
+		
+matrixCli = TrivialMatrixClient(ACCESS_TOKEN)
+
+gevent.joinall([
+	gevent.spawn(matrixLoop)
+])
+