1
# $Id: confbot.py 2912 2009-08-24 11:56:13Z bennylp $
5
# Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com)
7
# This program is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 2 of the License, or
10
# (at your option) any later version.
12
# This program is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
17
# You should have received a copy of the GNU General Public License
18
# along with this program; if not, write to the Free Software
19
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30
# Call callback. This would just forward the event to the Member class
31
class CallCb(pj.CallCallback):
32
def __init__(self, member, call=None):
33
pj.CallCallback.__init__(self, call)
37
self.member.on_call_state(self.call)
39
def on_media_state(self):
40
self.member.on_call_media_state(self.call)
42
def on_dtmf_digit(self, digits):
43
self.member.on_call_dtmf_digit(self.call, digits)
45
def on_transfer_request(self, dst, code):
46
return self.member.on_call_transfer_request(self.call, dst, code)
48
def on_transfer_status(self, code, reason, final, cont):
49
return self.member.on_call_transfer_status(self.call, code, reason, final, cont)
51
def on_replace_request(self, code, reason):
52
return self.member.on_call_replace_request(self.call, code, reason)
54
def on_replaced(self, new_call):
55
self.member.on_call_replaced(self.call, new_call)
57
def on_typing(self, is_typing):
58
self.member.on_typing(is_typing, call=self.call)
60
def on_pager(self, mime_type, body):
61
self.member.on_pager(mime_type, body, call=self.call)
63
def on_pager_status(self, body, im_id, code, reason):
64
self.member.on_pager_status(body, im_id, code, reason, call=self.call)
66
# Buddy callback. This would just forward the event to Member class
67
class BuddyCb(pj.BuddyCallback):
68
def __init__(self, member, buddy=None):
69
pj.BuddyCallback.__init__(self, buddy)
72
def on_pager(self, mime_type, body):
73
self.member.on_pager(mime_type, body, buddy=self.buddy)
75
def on_pager_status(self, body, im_id, code, reason):
76
self.member.on_pager_status(body, im_id, code, reason, buddy=self.buddy)
79
self.member.on_pres_state(self.buddy)
81
def on_typing(self, is_typing):
82
self.member.on_typing(is_typing, buddy=self.buddy)
87
##############################################################################
90
# This class represents individual room member (either/both chat and voice conf)
94
def __init__(self, bot, uri):
99
self.bi = pj.BuddyInfo()
101
self.in_voice = False
102
self.im_error = False
106
str = string.ljust(self.uri, 30) + " -- "
108
bi = self.buddy.info()
109
str = str + bi.online_text
111
str = str + "Offline"
123
str = str + " im_error"
127
def join_call(self, call):
129
self.call.hangup(603, "You have been disconnected for making another call")
131
call.set_callback(CallCb(self, call))
132
msg = "%(uri)s is attempting to join the voice conference" % \
134
self.bot.DEBUG(msg + "\n", INFO)
135
self.bot.broadcast_pager(None, msg)
139
self.bot.DEBUG(self.uri + " joining chatroom...\n", INFO)
140
self.buddy = self.bot.acc.add_buddy(self.uri)
141
self.buddy.set_callback(BuddyCb(self, self.buddy))
142
self.buddy.subscribe()
144
self.bot.DEBUG(self.uri + " already in chatroom, resubscribing..\n", INFO)
145
self.buddy.subscribe()
147
def send_pager(self, body, mime="text/plain"):
148
self.bot.DEBUG("send_pager() to " + self.uri)
149
if self.in_chat and not self.im_error and self.buddy:
151
#This will make us receive html!
153
body = body.replace("<", "<")
154
body = body.replace(">", ">")
155
body = body.replace('"', """)
156
body = body.replace("\n", "<BR>\n")
157
self.buddy.send_pager(body, content_type=mime)
158
self.bot.DEBUG("..sent\n")
160
self.bot.DEBUG("..not sent!\n")
162
def on_call_state(self, call):
164
if ci.state==pj.CallState.DISCONNECTED:
166
msg = "%(uri)s has left the voice conference (%(1)d/%(2)s)" % \
167
{'uri': self.uri, '1': ci.last_code, '2': ci.last_reason}
168
self.bot.DEBUG(msg + "\n", INFO)
169
self.bot.broadcast_pager(None, msg)
170
self.in_voice = False
172
self.bot.on_member_left(self)
173
elif ci.state==pj.CallState.CONFIRMED:
174
msg = "%(uri)s has joined the voice conference" % \
176
self.bot.DEBUG(msg + "\n", INFO)
177
self.bot.broadcast_pager(None, msg)
179
def on_call_media_state(self, call):
180
self.bot.DEBUG("Member.on_call_media_state\n")
183
if not self.in_voice:
184
msg = self.uri + " call media is active"
185
self.bot.broadcast_pager(None, msg)
187
self.bot.add_to_voice_conf(self)
190
msg = self.uri + " call media is inactive"
191
self.bot.broadcast_pager(None, msg)
192
self.in_voice = False
194
def on_call_dtmf_digit(self, call, digits):
195
msg = "%(uri)s sent DTMF digits %(dig)s" % \
196
{'uri': self.uri, 'dig': digits}
197
self.bot.broadcast_pager(None, msg)
199
def on_call_transfer_request(self, call, dst, code):
200
msg = "%(uri)s is transfering the call to %(dst)s" % \
201
{'uri': self.uri, 'dst': dst}
202
self.bot.broadcast_pager(None, msg)
205
def on_call_transfer_status(self, call, code, reason, final, cont):
206
msg = "%(uri)s call transfer status is %(code)d/%(res)s" % \
207
{'uri': self.uri, 'code': code, 'res': reason}
208
self.bot.broadcast_pager(None, msg)
211
def on_call_replace_request(self, call, code, reason):
212
msg = "%(uri)s is requesting call replace" % \
214
self.bot.broadcast_pager(None, msg)
215
return (code, reason)
217
def on_call_replaced(self, call, new_call):
218
msg = "%(uri)s call is replaced" % \
220
self.bot.broadcast_pager(None, msg)
222
def on_pres_state(self, buddy):
224
self.bi = buddy.info()
225
msg = "%(uri)s status is %(st)s" % \
226
{'uri': self.uri, 'st': self.bi.online_text}
227
self.bot.DEBUG(msg + "\n", INFO)
228
self.bot.broadcast_pager(self, msg)
230
if self.bi.sub_state==pj.SubscriptionState.ACTIVE:
233
buddy.send_pager("Welcome to chatroom")
234
self.bot.broadcast_pager(self, self.uri + " has joined the chat room")
237
elif self.bi.sub_state==pj.SubscriptionState.NULL or \
238
self.bi.sub_state==pj.SubscriptionState.TERMINATED or \
239
self.bi.sub_state==pj.SubscriptionState.UNKNOWN:
244
self.bot.broadcast_pager(self, self.uri + " has left the chat room")
247
self.bot.on_member_left(self)
249
def on_typing(self, is_typing, call=None, buddy=None):
251
msg = self.uri + " is typing..."
253
msg = self.uri + " has stopped typing"
254
self.bot.broadcast_pager(self, msg)
256
def on_pager(self, mime_type, body, call=None, buddy=None):
257
if not self.bot.handle_cmd(self, None, body):
258
msg = self.uri + ": " + body
259
self.bot.broadcast_pager(self, msg, mime_type)
261
def on_pager_status(self, body, im_id, code, reason, call=None, buddy=None):
262
self.im_error = (code/100 != 2)
266
##############################################################################
269
# The Bot instance (singleton)
272
class Bot(pj.AccountCallback):
274
pj.AccountCallback.__init__(self, None)
281
def DEBUG(self, msg, level=TRACE):
284
def helpstring(self):
286
--h[elp] Display this help screen
287
--j[oin] Join the chat room
288
--html on|off Set to receive HTML or plain text
290
Participant commands:
291
--s[how] Show confbot settings
292
--leave Leave the chatroom
293
--l[ist] List all members
296
--a[dmin] <CMD> Where <CMD> are:
298
add <URI> Add URI as admin
299
del <URI> Remove URI as admin
300
rr Reregister account to server
301
call <URI> Make call to the URI and add to voice conf
302
dc <URI> Disconnect call to URI
303
hold <URI> Hold call with that URI
304
update <URI> Send UPDATE to call with that URI
305
reinvite <URI> Send re-INVITE to call with that URI
308
def listmembers(self):
310
for uri, m in self.members.iteritems():
311
msg = msg + str(m) + "\n"
314
def showsettings(self):
317
ConfBot status and settings:
320
Reg Status: %(reg_st)d
321
Reg Reason: %(reg_res)s
322
""" % {'uri': ai.uri, 'pres': ai.online_text, \
323
'reg_st': ai.reg_status, 'reg_res': ai.reg_reason}
326
def main(self, cfg_file):
328
cfg = self.cfg = __import__(cfg_file)
330
self.lib.init(ua_cfg=cfg.ua_cfg, log_cfg=cfg.log_cfg, media_cfg=cfg.media_cfg)
331
self.lib.set_null_snd_dev()
335
transport = self.lib.create_transport(pj.TransportType.UDP, cfg.udp_cfg)
337
t = self.lib.create_transport(pj.TransportType.TCP, cfg.tcp_cfg)
344
self.DEBUG("Creating account %(uri)s..\n" % {'uri': cfg.acc_cfg.id}, INFO)
345
self.acc = self.lib.create_account(cfg.acc_cfg, cb=self)
347
self.DEBUG("Creating account for %(t)s..\n" % \
348
{'t': transport.info().description}, INFO)
349
self.acc = self.lib.create_account_for_transport(transport, cb=self)
351
self.acc.set_basic_status(True)
353
# Wait for ENTER before quitting
354
print "Press q to quit or --help/--h for help"
356
input = sys.stdin.readline().strip(" \t\r\n")
357
if not self.handle_cmd(None, None, input):
365
print "Exception: " + str(e)
370
def broadcast_pager(self, exclude_member, body, mime_type="text/plain"):
371
self.DEBUG("Broadcast: " + body + "\n")
372
for uri, m in self.members.iteritems():
373
if m != exclude_member:
374
m.send_pager(body, mime_type)
376
def add_to_voice_conf(self, member):
379
src_ci = member.call.info()
380
self.DEBUG("bot.add_to_voice_conf\n")
381
for uri, m in self.members.iteritems():
386
dst_ci = m.call.info()
387
if dst_ci.media_state==pj.MediaState.ACTIVE and dst_ci.conf_slot!=-1:
388
self.lib.conf_connect(src_ci.conf_slot, dst_ci.conf_slot)
389
self.lib.conf_connect(dst_ci.conf_slot, src_ci.conf_slot)
391
def on_member_left(self, member):
392
if not member.call and not member.buddy:
393
del self.members[member.uri]
396
def handle_admin_cmd(self, member, body):
397
if member and self.cfg.admins and not member.uri in self.cfg.admins:
398
member.send_pager("You are not admin")
407
if not self.cfg.admins:
408
msg = "Everyone is admin!"
410
msg = str(self.cfg.admins)
413
msg = "Usage: add <URI>"
415
self.cfg.admins.append(args[2])
416
msg = args[2] + " added as admin"
419
msg = "Usage: del <URI>"
420
elif args[2] not in self.cfg.admins:
421
msg = args[2] + " is not admin"
423
self.cfg.admins.remove(args[2])
424
msg = args[2] + " has been removed from admins"
426
msg = "Reregistering.."
427
self.acc.set_registration(True)
428
elif args[1]=="call":
430
msg = "Usage: call <URI>"
434
call = self.acc.make_call(uri)
436
msg = "Error: " + str(e)
440
if not uri in self.members:
441
m = Member(self, uri)
442
self.members[m.uri] = m
444
m = self.members[uri]
445
msg = "Adding " + m.uri + " to voice conference.."
447
elif args[1]=="dc" or args[1]=="hold" or args[1]=="update" or args[1]=="reinvite":
449
msg = "Usage: " + args[1] + " <URI>"
452
if not uri in self.members:
453
msg = "Member not found/URI doesn't match (note: case matters!)"
455
m = self.members[uri]
458
msg = "Disconnecting.."
459
m.call.hangup(603, "You're disconnected by admin")
460
elif args[1]=="hold":
461
msg = "Holding the call"
463
elif args[1]=="update":
464
msg = "Sending UPDATE"
466
elif args[1]=="reinvite":
467
msg = "Sending re-INVITE"
470
msg = "He is not in call"
472
msg = "Unknown admin command " + body
474
#print "msg is '%(msg)s'" % {'msg': msg}
478
member.send_pager(msg)
482
def handle_cmd(self, member, from_uri, body):
483
body = body.strip(" \t\r\n")
486
if body=="--l" or body=="--list":
487
msg = self.listmembers()
489
msg = "Nobody is here"
490
elif body[0:3]=="--s":
491
msg = self.showsettings()
492
elif body[0:6]=="--html" and member:
493
if body[8:11]=="off":
497
elif body=="--h" or body=="--help":
498
msg = self.helpstring()
499
elif body=="--leave":
500
if not member or not member.buddy:
501
msg = "You are not in chatroom"
503
member.buddy.unsubscribe()
504
elif body[0:3]=="--j":
505
if not from_uri in self.members:
506
m = Member(self, from_uri)
507
self.members[m.uri] = m
508
self.DEBUG("Adding " + m.uri + " to chatroom\n")
511
m = self.members[from_uri]
512
self.DEBUG("Adding " + m.uri + " to chatroom\n")
514
elif body[0:3]=="--a":
515
self.handle_admin_cmd(member, body)
522
member.send_pager(msg)
524
self.acc.send_pager(from_uri, msg);
529
def on_incoming_call(self, call):
530
self.DEBUG("on_incoming_call from %(uri)s\n" % {'uri': call.info().remote_uri}, INFO)
532
if not ci.remote_uri in self.members:
533
m = Member(self, ci.remote_uri)
534
self.members[m.uri] = m
537
m = self.members[ci.remote_uri]
541
def on_incoming_subscribe(self, buddy, from_uri, contact_uri, pres_obj):
542
self.DEBUG("on_incoming_subscribe from %(uri)s\n" % from_uri, INFO)
545
def on_reg_state(self):
547
self.DEBUG("Registration state: %(code)d/%(reason)s\n" % \
548
{'code': ai.reg_status, 'reason': ai.reg_reason}, INFO)
549
if ai.reg_status/100==2 and ai.reg_expires > 0:
550
self.acc.set_basic_status(True)
552
def on_pager(self, from_uri, contact, mime_type, body):
553
body = body.strip(" \t\r\n")
554
if not self.handle_cmd(None, from_uri, body):
555
self.acc.send_pager(from_uri, "You have not joined the chat room. Type '--join' to join or '--help' for the help")
557
def on_pager_status(self, to_uri, body, im_id, code, reason):
560
def on_typing(self, from_uri, contact, is_typing):
566
##############################################################################
572
if __name__ == "__main__":