summaryrefslogtreecommitdiff
path: root/pjsip-apps/src/confbot/confbot.py
diff options
context:
space:
mode:
Diffstat (limited to 'pjsip-apps/src/confbot/confbot.py')
-rw-r--r--pjsip-apps/src/confbot/confbot.py575
1 files changed, 575 insertions, 0 deletions
diff --git a/pjsip-apps/src/confbot/confbot.py b/pjsip-apps/src/confbot/confbot.py
new file mode 100644
index 0000000..572c50c
--- /dev/null
+++ b/pjsip-apps/src/confbot/confbot.py
@@ -0,0 +1,575 @@
+# $Id: confbot.py 2912 2009-08-24 11:56:13Z bennylp $
+#
+# SIP Conference Bot
+#
+# Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+import pjsua as pj
+import string
+import sys
+
+CFG_FILE = "config"
+
+INFO = 1
+TRACE = 2
+
+# Call callback. This would just forward the event to the Member class
+class CallCb(pj.CallCallback):
+ def __init__(self, member, call=None):
+ pj.CallCallback.__init__(self, call)
+ self.member = member
+
+ def on_state(self):
+ self.member.on_call_state(self.call)
+
+ def on_media_state(self):
+ self.member.on_call_media_state(self.call)
+
+ def on_dtmf_digit(self, digits):
+ self.member.on_call_dtmf_digit(self.call, digits)
+
+ def on_transfer_request(self, dst, code):
+ return self.member.on_call_transfer_request(self.call, dst, code)
+
+ def on_transfer_status(self, code, reason, final, cont):
+ return self.member.on_call_transfer_status(self.call, code, reason, final, cont)
+
+ def on_replace_request(self, code, reason):
+ return self.member.on_call_replace_request(self.call, code, reason)
+
+ def on_replaced(self, new_call):
+ self.member.on_call_replaced(self.call, new_call)
+
+ def on_typing(self, is_typing):
+ self.member.on_typing(is_typing, call=self.call)
+
+ def on_pager(self, mime_type, body):
+ self.member.on_pager(mime_type, body, call=self.call)
+
+ def on_pager_status(self, body, im_id, code, reason):
+ self.member.on_pager_status(body, im_id, code, reason, call=self.call)
+
+# Buddy callback. This would just forward the event to Member class
+class BuddyCb(pj.BuddyCallback):
+ def __init__(self, member, buddy=None):
+ pj.BuddyCallback.__init__(self, buddy)
+ self.member = member
+
+ def on_pager(self, mime_type, body):
+ self.member.on_pager(mime_type, body, buddy=self.buddy)
+
+ def on_pager_status(self, body, im_id, code, reason):
+ self.member.on_pager_status(body, im_id, code, reason, buddy=self.buddy)
+
+ def on_state(self):
+ self.member.on_pres_state(self.buddy)
+
+ def on_typing(self, is_typing):
+ self.member.on_typing(is_typing, buddy=self.buddy)
+
+
+
+
+##############################################################################
+#
+#
+# This class represents individual room member (either/both chat and voice conf)
+#
+#
+class Member:
+ def __init__(self, bot, uri):
+ self.uri = uri
+ self.bot = bot
+ self.call = None
+ self.buddy = None
+ self.bi = pj.BuddyInfo()
+ self.in_chat = False
+ self.in_voice = False
+ self.im_error = False
+ self.html = False
+
+ def __str__(self):
+ str = string.ljust(self.uri, 30) + " -- "
+ if self.buddy:
+ bi = self.buddy.info()
+ str = str + bi.online_text
+ else:
+ str = str + "Offline"
+ str = str + " ["
+ if (self.in_voice):
+ str = str + " voice"
+ if (self.in_chat):
+ str = str + " chat"
+ if (self.html):
+ str = str + " html"
+ else:
+ str = str + " plain"
+
+ if (self.im_error):
+ str = str + " im_error"
+ str = str + "]"
+ return str
+
+ def join_call(self, call):
+ if self.call:
+ self.call.hangup(603, "You have been disconnected for making another call")
+ self.call = call
+ call.set_callback(CallCb(self, call))
+ msg = "%(uri)s is attempting to join the voice conference" % \
+ {'uri': self.uri}
+ self.bot.DEBUG(msg + "\n", INFO)
+ self.bot.broadcast_pager(None, msg)
+
+ def join_chat(self):
+ if not self.buddy:
+ self.bot.DEBUG(self.uri + " joining chatroom...\n", INFO)
+ self.buddy = self.bot.acc.add_buddy(self.uri)
+ self.buddy.set_callback(BuddyCb(self, self.buddy))
+ self.buddy.subscribe()
+ else:
+ self.bot.DEBUG(self.uri + " already in chatroom, resubscribing..\n", INFO)
+ self.buddy.subscribe()
+
+ def send_pager(self, body, mime="text/plain"):
+ self.bot.DEBUG("send_pager() to " + self.uri)
+ if self.in_chat and not self.im_error and self.buddy:
+ if self.html:
+ #This will make us receive html!
+ #mime = "text/html"
+ body = body.replace("<", "&lt;")
+ body = body.replace(">", "&gt;")
+ body = body.replace('"', "&quot;")
+ body = body.replace("\n", "<BR>\n")
+ self.buddy.send_pager(body, content_type=mime)
+ self.bot.DEBUG("..sent\n")
+ else:
+ self.bot.DEBUG("..not sent!\n")
+
+ def on_call_state(self, call):
+ ci = call.info()
+ if ci.state==pj.CallState.DISCONNECTED:
+ if self.in_voice:
+ msg = "%(uri)s has left the voice conference (%(1)d/%(2)s)" % \
+ {'uri': self.uri, '1': ci.last_code, '2': ci.last_reason}
+ self.bot.DEBUG(msg + "\n", INFO)
+ self.bot.broadcast_pager(None, msg)
+ self.in_voice = False
+ self.call = None
+ self.bot.on_member_left(self)
+ elif ci.state==pj.CallState.CONFIRMED:
+ msg = "%(uri)s has joined the voice conference" % \
+ {'uri': self.uri}
+ self.bot.DEBUG(msg + "\n", INFO)
+ self.bot.broadcast_pager(None, msg)
+
+ def on_call_media_state(self, call):
+ self.bot.DEBUG("Member.on_call_media_state\n")
+ ci = call.info()
+ if ci.conf_slot!=-1:
+ if not self.in_voice:
+ msg = self.uri + " call media is active"
+ self.bot.broadcast_pager(None, msg)
+ self.in_voice = True
+ self.bot.add_to_voice_conf(self)
+ else:
+ if self.in_voice:
+ msg = self.uri + " call media is inactive"
+ self.bot.broadcast_pager(None, msg)
+ self.in_voice = False
+
+ def on_call_dtmf_digit(self, call, digits):
+ msg = "%(uri)s sent DTMF digits %(dig)s" % \
+ {'uri': self.uri, 'dig': digits}
+ self.bot.broadcast_pager(None, msg)
+
+ def on_call_transfer_request(self, call, dst, code):
+ msg = "%(uri)s is transfering the call to %(dst)s" % \
+ {'uri': self.uri, 'dst': dst}
+ self.bot.broadcast_pager(None, msg)
+ return 202
+
+ def on_call_transfer_status(self, call, code, reason, final, cont):
+ msg = "%(uri)s call transfer status is %(code)d/%(res)s" % \
+ {'uri': self.uri, 'code': code, 'res': reason}
+ self.bot.broadcast_pager(None, msg)
+ return True
+
+ def on_call_replace_request(self, call, code, reason):
+ msg = "%(uri)s is requesting call replace" % \
+ {'uri': self.uri}
+ self.bot.broadcast_pager(None, msg)
+ return (code, reason)
+
+ def on_call_replaced(self, call, new_call):
+ msg = "%(uri)s call is replaced" % \
+ {'uri': self.uri}
+ self.bot.broadcast_pager(None, msg)
+
+ def on_pres_state(self, buddy):
+ old_bi = self.bi
+ self.bi = buddy.info()
+ msg = "%(uri)s status is %(st)s" % \
+ {'uri': self.uri, 'st': self.bi.online_text}
+ self.bot.DEBUG(msg + "\n", INFO)
+ self.bot.broadcast_pager(self, msg)
+
+ if self.bi.sub_state==pj.SubscriptionState.ACTIVE:
+ if not self.in_chat:
+ self.in_chat = True
+ buddy.send_pager("Welcome to chatroom")
+ self.bot.broadcast_pager(self, self.uri + " has joined the chat room")
+ else:
+ self.in_chat = True
+ elif self.bi.sub_state==pj.SubscriptionState.NULL or \
+ self.bi.sub_state==pj.SubscriptionState.TERMINATED or \
+ self.bi.sub_state==pj.SubscriptionState.UNKNOWN:
+ self.buddy.delete()
+ self.buddy = None
+ if self.in_chat:
+ self.in_chat = False
+ self.bot.broadcast_pager(self, self.uri + " has left the chat room")
+ else:
+ self.in_chat = False
+ self.bot.on_member_left(self)
+
+ def on_typing(self, is_typing, call=None, buddy=None):
+ if is_typing:
+ msg = self.uri + " is typing..."
+ else:
+ msg = self.uri + " has stopped typing"
+ self.bot.broadcast_pager(self, msg)
+
+ def on_pager(self, mime_type, body, call=None, buddy=None):
+ if not self.bot.handle_cmd(self, None, body):
+ msg = self.uri + ": " + body
+ self.bot.broadcast_pager(self, msg, mime_type)
+
+ def on_pager_status(self, body, im_id, code, reason, call=None, buddy=None):
+ self.im_error = (code/100 != 2)
+
+
+
+##############################################################################
+#
+#
+# The Bot instance (singleton)
+#
+#
+class Bot(pj.AccountCallback):
+ def __init__(self):
+ pj.AccountCallback.__init__(self, None)
+ self.lib = pj.Lib()
+ self.acc = None
+ self.calls = []
+ self.members = {}
+ self.cfg = None
+
+ def DEBUG(self, msg, level=TRACE):
+ print msg,
+
+ def helpstring(self):
+ return """
+--h[elp] Display this help screen
+--j[oin] Join the chat room
+--html on|off Set to receive HTML or plain text
+
+Participant commands:
+--s[how] Show confbot settings
+--leave Leave the chatroom
+--l[ist] List all members
+
+Admin commands:
+--a[dmin] <CMD> Where <CMD> are:
+ list List the admins
+ add <URI> Add URI as admin
+ del <URI> Remove URI as admin
+ rr Reregister account to server
+ call <URI> Make call to the URI and add to voice conf
+ dc <URI> Disconnect call to URI
+ hold <URI> Hold call with that URI
+ update <URI> Send UPDATE to call with that URI
+ reinvite <URI> Send re-INVITE to call with that URI
+"""
+
+ def listmembers(self):
+ msg = ""
+ for uri, m in self.members.iteritems():
+ msg = msg + str(m) + "\n"
+ return msg
+
+ def showsettings(self):
+ ai = self.acc.info()
+ msg = """
+ConfBot status and settings:
+ URI: %(uri)s
+ Status: %(pres)s
+ Reg Status: %(reg_st)d
+ Reg Reason: %(reg_res)s
+""" % {'uri': ai.uri, 'pres': ai.online_text, \
+ 'reg_st': ai.reg_status, 'reg_res': ai.reg_reason}
+ return msg
+
+ def main(self, cfg_file):
+ try:
+ cfg = self.cfg = __import__(cfg_file)
+
+ self.lib.init(ua_cfg=cfg.ua_cfg, log_cfg=cfg.log_cfg, media_cfg=cfg.media_cfg)
+ self.lib.set_null_snd_dev()
+
+ transport = None
+ if cfg.udp_cfg:
+ transport = self.lib.create_transport(pj.TransportType.UDP, cfg.udp_cfg)
+ if cfg.tcp_cfg:
+ t = self.lib.create_transport(pj.TransportType.TCP, cfg.tcp_cfg)
+ if not transport:
+ transport = t
+
+ self.lib.start()
+
+ if cfg.acc_cfg:
+ self.DEBUG("Creating account %(uri)s..\n" % {'uri': cfg.acc_cfg.id}, INFO)
+ self.acc = self.lib.create_account(cfg.acc_cfg, cb=self)
+ else:
+ self.DEBUG("Creating account for %(t)s..\n" % \
+ {'t': transport.info().description}, INFO)
+ self.acc = self.lib.create_account_for_transport(transport, cb=self)
+
+ self.acc.set_basic_status(True)
+
+ # Wait for ENTER before quitting
+ print "Press q to quit or --help/--h for help"
+ while True:
+ input = sys.stdin.readline().strip(" \t\r\n")
+ if not self.handle_cmd(None, None, input):
+ if input=="q":
+ break
+
+ self.lib.destroy()
+ self.lib = None
+
+ except pj.Error, e:
+ print "Exception: " + str(e)
+ if self.lib:
+ self.lib.destroy()
+ self.lib = None
+
+ def broadcast_pager(self, exclude_member, body, mime_type="text/plain"):
+ self.DEBUG("Broadcast: " + body + "\n")
+ for uri, m in self.members.iteritems():
+ if m != exclude_member:
+ m.send_pager(body, mime_type)
+
+ def add_to_voice_conf(self, member):
+ if not member.call:
+ return
+ src_ci = member.call.info()
+ self.DEBUG("bot.add_to_voice_conf\n")
+ for uri, m in self.members.iteritems():
+ if m==member:
+ continue
+ if not m.call:
+ continue
+ dst_ci = m.call.info()
+ if dst_ci.media_state==pj.MediaState.ACTIVE and dst_ci.conf_slot!=-1:
+ self.lib.conf_connect(src_ci.conf_slot, dst_ci.conf_slot)
+ self.lib.conf_connect(dst_ci.conf_slot, src_ci.conf_slot)
+
+ def on_member_left(self, member):
+ if not member.call and not member.buddy:
+ del self.members[member.uri]
+ del member
+
+ def handle_admin_cmd(self, member, body):
+ if member and self.cfg.admins and not member.uri in self.cfg.admins:
+ member.send_pager("You are not admin")
+ return
+ args = body.split()
+ msg = ""
+
+ if len(args)==1:
+ args.append(" ")
+
+ if args[1]=="list":
+ if not self.cfg.admins:
+ msg = "Everyone is admin!"
+ else:
+ msg = str(self.cfg.admins)
+ elif args[1]=="add":
+ if len(args)!=3:
+ msg = "Usage: add <URI>"
+ else:
+ self.cfg.admins.append(args[2])
+ msg = args[2] + " added as admin"
+ elif args[1]=="del":
+ if len(args)!=3:
+ msg = "Usage: del <URI>"
+ elif args[2] not in self.cfg.admins:
+ msg = args[2] + " is not admin"
+ else:
+ self.cfg.admins.remove(args[2])
+ msg = args[2] + " has been removed from admins"
+ elif args[1]=="rr":
+ msg = "Reregistering.."
+ self.acc.set_registration(True)
+ elif args[1]=="call":
+ if len(args)!=3:
+ msg = "Usage: call <URI>"
+ else:
+ uri = args[2]
+ try:
+ call = self.acc.make_call(uri)
+ except pj.Error, e:
+ msg = "Error: " + str(e)
+ call = None
+
+ if call:
+ if not uri in self.members:
+ m = Member(self, uri)
+ self.members[m.uri] = m
+ else:
+ m = self.members[uri]
+ msg = "Adding " + m.uri + " to voice conference.."
+ m.join_call(call)
+ elif args[1]=="dc" or args[1]=="hold" or args[1]=="update" or args[1]=="reinvite":
+ if len(args)!=3:
+ msg = "Usage: " + args[1] + " <URI>"
+ else:
+ uri = args[2]
+ if not uri in self.members:
+ msg = "Member not found/URI doesn't match (note: case matters!)"
+ else:
+ m = self.members[uri]
+ if m.call:
+ if args[1]=="dc":
+ msg = "Disconnecting.."
+ m.call.hangup(603, "You're disconnected by admin")
+ elif args[1]=="hold":
+ msg = "Holding the call"
+ m.call.hold()
+ elif args[1]=="update":
+ msg = "Sending UPDATE"
+ m.call.update()
+ elif args[1]=="reinvite":
+ msg = "Sending re-INVITE"
+ m.call.reinvite()
+ else:
+ msg = "He is not in call"
+ else:
+ msg = "Unknown admin command " + body
+
+ #print "msg is '%(msg)s'" % {'msg': msg}
+
+ if True:
+ if member:
+ member.send_pager(msg)
+ else:
+ print msg
+
+ def handle_cmd(self, member, from_uri, body):
+ body = body.strip(" \t\r\n")
+ msg = ""
+ handled = True
+ if body=="--l" or body=="--list":
+ msg = self.listmembers()
+ if msg=="":
+ msg = "Nobody is here"
+ elif body[0:3]=="--s":
+ msg = self.showsettings()
+ elif body[0:6]=="--html" and member:
+ if body[8:11]=="off":
+ member.html = False
+ else:
+ member.html = True
+ elif body=="--h" or body=="--help":
+ msg = self.helpstring()
+ elif body=="--leave":
+ if not member or not member.buddy:
+ msg = "You are not in chatroom"
+ else:
+ member.buddy.unsubscribe()
+ elif body[0:3]=="--j":
+ if not from_uri in self.members:
+ m = Member(self, from_uri)
+ self.members[m.uri] = m
+ self.DEBUG("Adding " + m.uri + " to chatroom\n")
+ m.join_chat()
+ else:
+ m = self.members[from_uri]
+ self.DEBUG("Adding " + m.uri + " to chatroom\n")
+ m.join_chat()
+ elif body[0:3]=="--a":
+ self.handle_admin_cmd(member, body)
+ handled = True
+ else:
+ handled = False
+
+ if msg:
+ if member:
+ member.send_pager(msg)
+ elif from_uri:
+ self.acc.send_pager(from_uri, msg);
+ else:
+ print msg
+ return handled
+
+ def on_incoming_call(self, call):
+ self.DEBUG("on_incoming_call from %(uri)s\n" % {'uri': call.info().remote_uri}, INFO)
+ ci = call.info()
+ if not ci.remote_uri in self.members:
+ m = Member(self, ci.remote_uri)
+ self.members[m.uri] = m
+ m.join_call(call)
+ else:
+ m = self.members[ci.remote_uri]
+ m.join_call(call)
+ call.answer(200)
+
+ def on_incoming_subscribe(self, buddy, from_uri, contact_uri, pres_obj):
+ self.DEBUG("on_incoming_subscribe from %(uri)s\n" % from_uri, INFO)
+ return (200, 'OK')
+
+ def on_reg_state(self):
+ ai = self.acc.info()
+ self.DEBUG("Registration state: %(code)d/%(reason)s\n" % \
+ {'code': ai.reg_status, 'reason': ai.reg_reason}, INFO)
+ if ai.reg_status/100==2 and ai.reg_expires > 0:
+ self.acc.set_basic_status(True)
+
+ def on_pager(self, from_uri, contact, mime_type, body):
+ body = body.strip(" \t\r\n")
+ if not self.handle_cmd(None, from_uri, body):
+ self.acc.send_pager(from_uri, "You have not joined the chat room. Type '--join' to join or '--help' for the help")
+
+ def on_pager_status(self, to_uri, body, im_id, code, reason):
+ pass
+
+ def on_typing(self, from_uri, contact, is_typing):
+ pass
+
+
+
+
+##############################################################################
+#
+#
+# main()
+#
+#
+if __name__ == "__main__":
+ bot = Bot()
+ bot.main(CFG_FILE)
+