Alexandre Lision | 8af73cb | 2013-12-10 14:11:20 -0500 | [diff] [blame] | 1 | # $Id$ |
| 2 | # |
| 3 | # SIP Conference Bot |
| 4 | # |
| 5 | # Copyright (C) 2008-2009 Teluu Inc. (http://www.teluu.com) |
| 6 | # |
| 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. |
| 11 | # |
| 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. |
| 16 | # |
| 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 |
| 20 | # |
| 21 | import pjsua as pj |
| 22 | import string |
| 23 | import sys |
| 24 | |
| 25 | CFG_FILE = "config" |
| 26 | |
| 27 | INFO = 1 |
| 28 | TRACE = 2 |
| 29 | |
| 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) |
| 34 | self.member = member |
| 35 | |
| 36 | def on_state(self): |
| 37 | self.member.on_call_state(self.call) |
| 38 | |
| 39 | def on_media_state(self): |
| 40 | self.member.on_call_media_state(self.call) |
| 41 | |
| 42 | def on_dtmf_digit(self, digits): |
| 43 | self.member.on_call_dtmf_digit(self.call, digits) |
| 44 | |
| 45 | def on_transfer_request(self, dst, code): |
| 46 | return self.member.on_call_transfer_request(self.call, dst, code) |
| 47 | |
| 48 | def on_transfer_status(self, code, reason, final, cont): |
| 49 | return self.member.on_call_transfer_status(self.call, code, reason, final, cont) |
| 50 | |
| 51 | def on_replace_request(self, code, reason): |
| 52 | return self.member.on_call_replace_request(self.call, code, reason) |
| 53 | |
| 54 | def on_replaced(self, new_call): |
| 55 | self.member.on_call_replaced(self.call, new_call) |
| 56 | |
| 57 | def on_typing(self, is_typing): |
| 58 | self.member.on_typing(is_typing, call=self.call) |
| 59 | |
| 60 | def on_pager(self, mime_type, body): |
| 61 | self.member.on_pager(mime_type, body, call=self.call) |
| 62 | |
| 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) |
| 65 | |
| 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) |
| 70 | self.member = member |
| 71 | |
| 72 | def on_pager(self, mime_type, body): |
| 73 | self.member.on_pager(mime_type, body, buddy=self.buddy) |
| 74 | |
| 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) |
| 77 | |
| 78 | def on_state(self): |
| 79 | self.member.on_pres_state(self.buddy) |
| 80 | |
| 81 | def on_typing(self, is_typing): |
| 82 | self.member.on_typing(is_typing, buddy=self.buddy) |
| 83 | |
| 84 | |
| 85 | |
| 86 | |
| 87 | ############################################################################## |
| 88 | # |
| 89 | # |
| 90 | # This class represents individual room member (either/both chat and voice conf) |
| 91 | # |
| 92 | # |
| 93 | class Member: |
| 94 | def __init__(self, bot, uri): |
| 95 | self.uri = uri |
| 96 | self.bot = bot |
| 97 | self.call = None |
| 98 | self.buddy = None |
| 99 | self.bi = pj.BuddyInfo() |
| 100 | self.in_chat = False |
| 101 | self.in_voice = False |
| 102 | self.im_error = False |
| 103 | self.html = False |
| 104 | |
| 105 | def __str__(self): |
| 106 | str = string.ljust(self.uri, 30) + " -- " |
| 107 | if self.buddy: |
| 108 | bi = self.buddy.info() |
| 109 | str = str + bi.online_text |
| 110 | else: |
| 111 | str = str + "Offline" |
| 112 | str = str + " [" |
| 113 | if (self.in_voice): |
| 114 | str = str + " voice" |
| 115 | if (self.in_chat): |
| 116 | str = str + " chat" |
| 117 | if (self.html): |
| 118 | str = str + " html" |
| 119 | else: |
| 120 | str = str + " plain" |
| 121 | |
| 122 | if (self.im_error): |
| 123 | str = str + " im_error" |
| 124 | str = str + "]" |
| 125 | return str |
| 126 | |
| 127 | def join_call(self, call): |
| 128 | if self.call: |
| 129 | self.call.hangup(603, "You have been disconnected for making another call") |
| 130 | self.call = call |
| 131 | call.set_callback(CallCb(self, call)) |
| 132 | msg = "%(uri)s is attempting to join the voice conference" % \ |
| 133 | {'uri': self.uri} |
| 134 | self.bot.DEBUG(msg + "\n", INFO) |
| 135 | self.bot.broadcast_pager(None, msg) |
| 136 | |
| 137 | def join_chat(self): |
| 138 | if not self.buddy: |
| 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() |
| 143 | else: |
| 144 | self.bot.DEBUG(self.uri + " already in chatroom, resubscribing..\n", INFO) |
| 145 | self.buddy.subscribe() |
| 146 | |
| 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: |
| 150 | if self.html: |
| 151 | #This will make us receive html! |
| 152 | #mime = "text/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") |
| 159 | else: |
| 160 | self.bot.DEBUG("..not sent!\n") |
| 161 | |
| 162 | def on_call_state(self, call): |
| 163 | ci = call.info() |
| 164 | if ci.state==pj.CallState.DISCONNECTED: |
| 165 | if self.in_voice: |
| 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 |
| 171 | self.call = None |
| 172 | self.bot.on_member_left(self) |
| 173 | elif ci.state==pj.CallState.CONFIRMED: |
| 174 | msg = "%(uri)s has joined the voice conference" % \ |
| 175 | {'uri': self.uri} |
| 176 | self.bot.DEBUG(msg + "\n", INFO) |
| 177 | self.bot.broadcast_pager(None, msg) |
| 178 | |
| 179 | def on_call_media_state(self, call): |
| 180 | self.bot.DEBUG("Member.on_call_media_state\n") |
| 181 | ci = call.info() |
| 182 | if ci.conf_slot!=-1: |
| 183 | if not self.in_voice: |
| 184 | msg = self.uri + " call media is active" |
| 185 | self.bot.broadcast_pager(None, msg) |
| 186 | self.in_voice = True |
| 187 | self.bot.add_to_voice_conf(self) |
| 188 | else: |
| 189 | if self.in_voice: |
| 190 | msg = self.uri + " call media is inactive" |
| 191 | self.bot.broadcast_pager(None, msg) |
| 192 | self.in_voice = False |
| 193 | |
| 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) |
| 198 | |
| 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) |
| 203 | return 202 |
| 204 | |
| 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) |
| 209 | return True |
| 210 | |
| 211 | def on_call_replace_request(self, call, code, reason): |
| 212 | msg = "%(uri)s is requesting call replace" % \ |
| 213 | {'uri': self.uri} |
| 214 | self.bot.broadcast_pager(None, msg) |
| 215 | return (code, reason) |
| 216 | |
| 217 | def on_call_replaced(self, call, new_call): |
| 218 | msg = "%(uri)s call is replaced" % \ |
| 219 | {'uri': self.uri} |
| 220 | self.bot.broadcast_pager(None, msg) |
| 221 | |
| 222 | def on_pres_state(self, buddy): |
| 223 | old_bi = self.bi |
| 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) |
| 229 | |
| 230 | if self.bi.sub_state==pj.SubscriptionState.ACTIVE: |
| 231 | if not self.in_chat: |
| 232 | self.in_chat = True |
| 233 | buddy.send_pager("Welcome to chatroom") |
| 234 | self.bot.broadcast_pager(self, self.uri + " has joined the chat room") |
| 235 | else: |
| 236 | self.in_chat = True |
| 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: |
| 240 | self.buddy.delete() |
| 241 | self.buddy = None |
| 242 | if self.in_chat: |
| 243 | self.in_chat = False |
| 244 | self.bot.broadcast_pager(self, self.uri + " has left the chat room") |
| 245 | else: |
| 246 | self.in_chat = False |
| 247 | self.bot.on_member_left(self) |
| 248 | |
| 249 | def on_typing(self, is_typing, call=None, buddy=None): |
| 250 | if is_typing: |
| 251 | msg = self.uri + " is typing..." |
| 252 | else: |
| 253 | msg = self.uri + " has stopped typing" |
| 254 | self.bot.broadcast_pager(self, msg) |
| 255 | |
| 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) |
| 260 | |
| 261 | def on_pager_status(self, body, im_id, code, reason, call=None, buddy=None): |
| 262 | self.im_error = (code/100 != 2) |
| 263 | |
| 264 | |
| 265 | |
| 266 | ############################################################################## |
| 267 | # |
| 268 | # |
| 269 | # The Bot instance (singleton) |
| 270 | # |
| 271 | # |
| 272 | class Bot(pj.AccountCallback): |
| 273 | def __init__(self): |
| 274 | pj.AccountCallback.__init__(self, None) |
| 275 | self.lib = pj.Lib() |
| 276 | self.acc = None |
| 277 | self.calls = [] |
| 278 | self.members = {} |
| 279 | self.cfg = None |
| 280 | |
| 281 | def DEBUG(self, msg, level=TRACE): |
| 282 | print msg, |
| 283 | |
| 284 | def helpstring(self): |
| 285 | return """ |
| 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 |
| 289 | |
| 290 | Participant commands: |
| 291 | --s[how] Show confbot settings |
| 292 | --leave Leave the chatroom |
| 293 | --l[ist] List all members |
| 294 | |
| 295 | Admin commands: |
| 296 | --a[dmin] <CMD> Where <CMD> are: |
| 297 | list List the admins |
| 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 |
| 306 | """ |
| 307 | |
| 308 | def listmembers(self): |
| 309 | msg = "" |
| 310 | for uri, m in self.members.iteritems(): |
| 311 | msg = msg + str(m) + "\n" |
| 312 | return msg |
| 313 | |
| 314 | def showsettings(self): |
| 315 | ai = self.acc.info() |
| 316 | msg = """ |
| 317 | ConfBot status and settings: |
| 318 | URI: %(uri)s |
| 319 | Status: %(pres)s |
| 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} |
| 324 | return msg |
| 325 | |
| 326 | def main(self, cfg_file): |
| 327 | try: |
| 328 | cfg = self.cfg = __import__(cfg_file) |
| 329 | |
| 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() |
| 332 | |
| 333 | transport = None |
| 334 | if cfg.udp_cfg: |
| 335 | transport = self.lib.create_transport(pj.TransportType.UDP, cfg.udp_cfg) |
| 336 | if cfg.tcp_cfg: |
| 337 | t = self.lib.create_transport(pj.TransportType.TCP, cfg.tcp_cfg) |
| 338 | if not transport: |
| 339 | transport = t |
| 340 | |
| 341 | self.lib.start() |
| 342 | |
| 343 | if cfg.acc_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) |
| 346 | else: |
| 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) |
| 350 | |
| 351 | self.acc.set_basic_status(True) |
| 352 | |
| 353 | # Wait for ENTER before quitting |
| 354 | print "Press q to quit or --help/--h for help" |
| 355 | while True: |
| 356 | input = sys.stdin.readline().strip(" \t\r\n") |
| 357 | if not self.handle_cmd(None, None, input): |
| 358 | if input=="q": |
| 359 | break |
| 360 | |
| 361 | self.lib.destroy() |
| 362 | self.lib = None |
| 363 | |
| 364 | except pj.Error, e: |
| 365 | print "Exception: " + str(e) |
| 366 | if self.lib: |
| 367 | self.lib.destroy() |
| 368 | self.lib = None |
| 369 | |
| 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) |
| 375 | |
| 376 | def add_to_voice_conf(self, member): |
| 377 | if not member.call: |
| 378 | return |
| 379 | src_ci = member.call.info() |
| 380 | self.DEBUG("bot.add_to_voice_conf\n") |
| 381 | for uri, m in self.members.iteritems(): |
| 382 | if m==member: |
| 383 | continue |
| 384 | if not m.call: |
| 385 | continue |
| 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) |
| 390 | |
| 391 | def on_member_left(self, member): |
| 392 | if not member.call and not member.buddy: |
| 393 | del self.members[member.uri] |
| 394 | del member |
| 395 | |
| 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") |
| 399 | return |
| 400 | args = body.split() |
| 401 | msg = "" |
| 402 | |
| 403 | if len(args)==1: |
| 404 | args.append(" ") |
| 405 | |
| 406 | if args[1]=="list": |
| 407 | if not self.cfg.admins: |
| 408 | msg = "Everyone is admin!" |
| 409 | else: |
| 410 | msg = str(self.cfg.admins) |
| 411 | elif args[1]=="add": |
| 412 | if len(args)!=3: |
| 413 | msg = "Usage: add <URI>" |
| 414 | else: |
| 415 | self.cfg.admins.append(args[2]) |
| 416 | msg = args[2] + " added as admin" |
| 417 | elif args[1]=="del": |
| 418 | if len(args)!=3: |
| 419 | msg = "Usage: del <URI>" |
| 420 | elif args[2] not in self.cfg.admins: |
| 421 | msg = args[2] + " is not admin" |
| 422 | else: |
| 423 | self.cfg.admins.remove(args[2]) |
| 424 | msg = args[2] + " has been removed from admins" |
| 425 | elif args[1]=="rr": |
| 426 | msg = "Reregistering.." |
| 427 | self.acc.set_registration(True) |
| 428 | elif args[1]=="call": |
| 429 | if len(args)!=3: |
| 430 | msg = "Usage: call <URI>" |
| 431 | else: |
| 432 | uri = args[2] |
| 433 | try: |
| 434 | call = self.acc.make_call(uri) |
| 435 | except pj.Error, e: |
| 436 | msg = "Error: " + str(e) |
| 437 | call = None |
| 438 | |
| 439 | if call: |
| 440 | if not uri in self.members: |
| 441 | m = Member(self, uri) |
| 442 | self.members[m.uri] = m |
| 443 | else: |
| 444 | m = self.members[uri] |
| 445 | msg = "Adding " + m.uri + " to voice conference.." |
| 446 | m.join_call(call) |
| 447 | elif args[1]=="dc" or args[1]=="hold" or args[1]=="update" or args[1]=="reinvite": |
| 448 | if len(args)!=3: |
| 449 | msg = "Usage: " + args[1] + " <URI>" |
| 450 | else: |
| 451 | uri = args[2] |
| 452 | if not uri in self.members: |
| 453 | msg = "Member not found/URI doesn't match (note: case matters!)" |
| 454 | else: |
| 455 | m = self.members[uri] |
| 456 | if m.call: |
| 457 | if args[1]=="dc": |
| 458 | msg = "Disconnecting.." |
| 459 | m.call.hangup(603, "You're disconnected by admin") |
| 460 | elif args[1]=="hold": |
| 461 | msg = "Holding the call" |
| 462 | m.call.hold() |
| 463 | elif args[1]=="update": |
| 464 | msg = "Sending UPDATE" |
| 465 | m.call.update() |
| 466 | elif args[1]=="reinvite": |
| 467 | msg = "Sending re-INVITE" |
| 468 | m.call.reinvite() |
| 469 | else: |
| 470 | msg = "He is not in call" |
| 471 | else: |
| 472 | msg = "Unknown admin command " + body |
| 473 | |
| 474 | #print "msg is '%(msg)s'" % {'msg': msg} |
| 475 | |
| 476 | if True: |
| 477 | if member: |
| 478 | member.send_pager(msg) |
| 479 | else: |
| 480 | print msg |
| 481 | |
| 482 | def handle_cmd(self, member, from_uri, body): |
| 483 | body = body.strip(" \t\r\n") |
| 484 | msg = "" |
| 485 | handled = True |
| 486 | if body=="--l" or body=="--list": |
| 487 | msg = self.listmembers() |
| 488 | if msg=="": |
| 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": |
| 494 | member.html = False |
| 495 | else: |
| 496 | member.html = True |
| 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" |
| 502 | else: |
| 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") |
| 509 | m.join_chat() |
| 510 | else: |
| 511 | m = self.members[from_uri] |
| 512 | self.DEBUG("Adding " + m.uri + " to chatroom\n") |
| 513 | m.join_chat() |
| 514 | elif body[0:3]=="--a": |
| 515 | self.handle_admin_cmd(member, body) |
| 516 | handled = True |
| 517 | else: |
| 518 | handled = False |
| 519 | |
| 520 | if msg: |
| 521 | if member: |
| 522 | member.send_pager(msg) |
| 523 | elif from_uri: |
| 524 | self.acc.send_pager(from_uri, msg); |
| 525 | else: |
| 526 | print msg |
| 527 | return handled |
| 528 | |
| 529 | def on_incoming_call(self, call): |
| 530 | self.DEBUG("on_incoming_call from %(uri)s\n" % {'uri': call.info().remote_uri}, INFO) |
| 531 | ci = call.info() |
| 532 | if not ci.remote_uri in self.members: |
| 533 | m = Member(self, ci.remote_uri) |
| 534 | self.members[m.uri] = m |
| 535 | m.join_call(call) |
| 536 | else: |
| 537 | m = self.members[ci.remote_uri] |
| 538 | m.join_call(call) |
| 539 | call.answer(200) |
| 540 | |
| 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) |
| 543 | return (200, 'OK') |
| 544 | |
| 545 | def on_reg_state(self): |
| 546 | ai = self.acc.info() |
| 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) |
| 551 | |
| 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") |
| 556 | |
| 557 | def on_pager_status(self, to_uri, body, im_id, code, reason): |
| 558 | pass |
| 559 | |
| 560 | def on_typing(self, from_uri, contact, is_typing): |
| 561 | pass |
| 562 | |
| 563 | |
| 564 | |
| 565 | |
| 566 | ############################################################################## |
| 567 | # |
| 568 | # |
| 569 | # main() |
| 570 | # |
| 571 | # |
| 572 | if __name__ == "__main__": |
| 573 | bot = Bot() |
| 574 | bot.main(CFG_FILE) |
| 575 | |