1""" 2FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application 3Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org> 4 5Version: MPL 1.1 6 7The contents of this file are subject to the Mozilla Public License Version 81.1 (the "License"); you may not use this file except in compliance with 9the License. You may obtain a copy of the License at 10http://www.mozilla.org/MPL/ 11 12Software distributed under the License is distributed on an "AS IS" basis, 13WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License 14for the specific language governing rights and limitations under the 15License. 16 17The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application 18 19The Initial Developer of the Original Code is 20Anthony Minessale II <anthm@freeswitch.org> 21Portions created by the Initial Developer are Copyright (C) 22the Initial Developer. All Rights Reserved. 23 24Contributor(s): Traun Leyden <tleyden@branchcut.com> 25""" 26 27import sys 28 29from twisted.internet import reactor, defer 30from twisted.protocols.basic import LineReceiver 31from twisted.internet.protocol import Protocol, ClientFactory 32from twisted.python import failure 33import time, re 34from time import strftime 35from Queue import Queue 36from freepy import request 37import freepy.globals 38from freepy.globals import debug 39 40""" 41freepy library -- connect to freeswitch mod_socket_event via python/twisted 42 43All commands currently use api instead of bgapi. For the networking model 44used (twisted), this seems to work well and is simpler. 45 46""" 47 48DEBUG_ON = "see globals.py to turn on debugging" 49 50class FreepyDispatcher(LineReceiver): 51 52 def __init__(self, conncb, discocb=None): 53 self.delimiter='\n' # parent class uses this 54 self.conncb=conncb 55 self.discocb=discocb 56 self.requestq = Queue() # queue of pending requests 57 self.active_request = None # the current active (de-queued) request 58 59 def connectionMade(self): 60 debug("FREEPY: Connection made") 61 self.conncb(self) 62 63 def connectionLost(self, reason): 64 if self.discocb: 65 self.discocb(reason) 66 debug("connectionLost: %s" % reason) 67 68 def login(self, passwd): 69 """ 70 send login request 71 """ 72 msg = "auth %s" % passwd 73 req = request.LoginRequest() 74 self.requestq.put(req) 75 self.transport.write("%s\n\n" % msg) 76 debug(">> %s" % msg) 77 return req.getDeferred() 78 79 def _sendCommand(self, command, bgapi): 80 """ 81 there is a lot of duplication in this object, and as many 82 methods as possible should be changed to use this method 83 rather than repeating the code 84 """ 85 command = ("bgapi %s" if bgapi else "api %s") % command 86 req = (request.BgDialoutRequest if bgapi else 87 request.DialoutRequest)() 88 self.requestq.put(req) 89 self.transport.write("%s\n\n" % command) 90 debug(">> %s" % command) 91 return req.getDeferred() 92 93 def confdialout(self, conf_name, sofia_url, bgapi=True): 94 """ 95 Instruct conference to join a particular user via dialout 96 @param conf_name - the name of the conference (arbitrary) 97 @param party2dial - a freeswitch sofia url, eg, sofia/mydomain.com/foo@bar.com 98 @return - a deferred that will be called back with a string like: 99 Reply-Text: +OK Job-UUID: 4d410a8e-2409-11dc-99bf-a5e17fab9c65 100 101 102 """ 103 if bgapi == True: 104 msg = "bgapi conference %s dial %s" % (conf_name, 105 sofia_url) 106 req = request.BgDialoutRequest() 107 else: 108 msg = "api conference %s dial %s" % (conf_name, 109 sofia_url) 110 req = request.DialoutRequest() 111 self.requestq.put(req) 112 self.transport.write("%s\n\n" % msg) 113 debug(">> %s" % msg) 114 return req.getDeferred() 115 116 def originate(self, party2dial, dest_ext_app, bgapi=True): 117 118 if bgapi == True: 119 msg = "bgapi originate %s %s" % (party2dial, 120 dest_ext_app) 121 req = request.BgDialoutRequest() 122 else: 123 msg = "api originate %s %s" % (party2dial, 124 dest_ext_app) 125 req = request.DialoutRequest() 126 self.requestq.put(req) 127 self.transport.write("%s\n\n" % msg) 128 debug(">> %s" % msg) 129 return req.getDeferred() 130 131 def listconf(self, conf_name): 132 """ 133 List users in a conf 134 @param conf_name - the name of the conference (arbitrary) 135 @return - a deferred that will be called back with an array 136 of models.ConfMember instances 137 """ 138 msg = "api conference %s list" % (conf_name) 139 req = request.ListConfRequest() 140 self.requestq.put(req) 141 self.transport.write("%s\n\n" % msg) 142 debug(">> %s" % msg) 143 return req.getDeferred() 144 145 146 def confkick(self, member_id, conf_name, bgapi=False): 147 """ 148 Kick member_id from conf 149 conf_name - name of conf 150 member_id - member id of user to kick, eg, "7" 151 returns - a deferred that will be called back with a result 152 like: 153 154 TODO: add this 155 """ 156 if bgapi == True: 157 msg = "bgapi conference %s kick %s" % (conf_name, member_id) 158 req = request.BgConfKickRequest() 159 else: 160 msg = "api conference %s kick %s" % (conf_name, member_id) 161 req = request.ConfKickRequest() 162 self.requestq.put(req) 163 self.transport.write("%s\n\n" % msg) 164 debug(">> %s" % msg) 165 return req.getDeferred() 166 167 def confdtmf(self, member_id, conf_name, dtmf, bgapi=False): 168 """ 169 Send dtmf to member_id or to all members 170 conf_name - name of conf 171 member_id - member id of user to kick, eg, "7" 172 dtmf - a single dtmf or a string of dtms, eg "1" or "123" 173 returns - a deferred that will be called back with a result 174 like: 175 176 TODO: add this 177 """ 178 msg = "conference %s dtmf %s %s" % \ 179 (conf_name, member_id, dtmf) 180 return self._sendCommand(msg, bgapi) 181 182 def confsay(self, conf_name, text2speak, bgapi=False): 183 """ 184 Speak text all members 185 conf_name - name of conf 186 dtmf - text to speak 187 returns - a deferred that will be called back with a result 188 like: 189 190 TODO: add this 191 """ 192 msg = "conference %s say %s" % (conf_name, text2speak) 193 return self._sendCommand(msg, bgapi) 194 195 def confplay(self, conf_name, snd_url, bgapi=False): 196 """ 197 Play a file to all members 198 conf_name - name of conf 199 dtmf - text to speak 200 returns - a deferred that will be called back with a result 201 like: 202 203 TODO: add this 204 """ 205 msg = "conference %s play %s" % (conf_name, snd_url) 206 return self._sendCommand(msg, bgapi) 207 208 def confstop(self, conf_name, bgapi=False): 209 """ 210 Stop playback of all sound files 211 conf_name - name of conf 212 returns - a deferred that will be called back with a result 213 like: 214 215 TODO: add this 216 """ 217 msg = "conference %s stop" % (conf_name) 218 return self._sendCommand(msg, bgapi) 219 220 def showchannels(self, bgapi=False): 221 """ 222 Get a list of all live channels on switch 223 224 returns - a deferred that will be called back with a result 225 226 <result row_count="2"> 227 <row row_id="1"> 228 <uuid>21524b8c-6d19-11dc-9380-357de4a7a612</uuid> 229 <created>2007-09-27 11:46:01</created> 230 <name>sofia/test/4761</name> 231 <state>CS_LOOPBACK</state> 232 <cid_name>FreeSWITCH</cid_name> 233 <cid_num>0000000000</cid_num> 234 <ip_addr></ip_addr> 235 <dest>outgoing2endpoint-6207463</dest> 236 <application>echo</application> 237 <application_data></application_data> 238 <read_codec>PCMU</read_codec> 239 <read_rate>8000</read_rate> 240 <write_codec>PCMU</write_codec> 241 <write_rate>8000</write_rate> 242 </row> 243 ... 244 </result> 245 """ 246 msg = "show channels as xml" 247 return self._sendCommand(msg, bgapi) 248 249 def sofia_status_profile(self, profile_name, bgapi=False): 250 msg = "sofia status profile %s as xml" % (profile_name) 251 return self._sendCommand(msg, bgapi) 252 253 def sofia_profile_restart(self, sofia_profile_name, bgapi = False): 254 255 msg = "sofia profile %s restart" % (sofia_profile_name) 256 return self._sendCommand(msg, bgapi) 257 258 def killchan(self, uuid, bgapi = False): 259 return self._sendCommand("uuid_kill %s" % (uuid), bgapi) 260 261 def broadcast(self, uuid, path, legs, bgapi = False): 262 msg = "uuid_broadcast %s %s %s" % (uuid, path, legs) 263 return self._sendCommand(msg, bgapi) 264 265 def transfer(self, uuid, dest_ext, legs, bgapi = False): 266 """ 267 transfer <uuid> [-bleg|-both] <dest-exten> 268 """ 269 msg = "uuid_transfer %s %s %s" % (uuid, legs, dest_ext) 270 return self._sendCommand(msg, bgapi) 271 272 273 def lineReceived(self, line): 274 debug("<< %s" % line) 275 if not self.active_request: 276 277 # if no active request pending, we ignore 278 # blank lines 279 if not line.strip(): 280 return 281 282 # if no active request, dequeue a new one 283 if self.requestq.empty(): 284 # we are receiving non-empty data from fs without an 285 # active request pending. that means that 286 # there is a bug in the protocol handler 287 # (or possibly in fs) 288 raise Exception("Received line: %s w/ no pending requests" % line) 289 self.active_request = self.requestq.get() 290 291 # tell the request to process the line, and tell us 292 # if its finished or not. if its finished, we remove it 293 # as the active request so that a new active request will 294 # be de-queued. 295 finished = self.active_request.process(line) 296 if finished == True: 297 self.active_request = None 298 299