fshelper.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530
  1. #!/usr/bin/env python
  2. """
  3. FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
  4. Copyright (C) 2005-2014, Anthony Minessale II <anthm@freeswitch.org>
  5. Version: MPL 1.1
  6. The contents of this file are subject to the Mozilla Public License Version
  7. 1.1 (the "License"); you may not use this file except in compliance with
  8. the License. You may obtain a copy of the License at
  9. http://www.mozilla.org/MPL/
  10. Software distributed under the License is distributed on an "AS IS" basis,
  11. WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12. for the specific language governing rights and limitations under the
  13. License.
  14. The Original Code is FreeSWITCH Modular Media Switching Software Library / Soft-Switch Application
  15. The Initial Developer of the Original Code is
  16. Anthony Minessale II <anthm@freeswitch.org>
  17. Portions created by the Initial Developer are Copyright (C)
  18. the Initial Developer. All Rights Reserved.
  19. Contributor(s): Traun Leyden <tleyden@branchcut.com>
  20. """
  21. from twisted.internet import reactor, defer
  22. from twisted.internet.protocol import ClientFactory
  23. import freepy
  24. class FsHelper(ClientFactory):
  25. def __init__(self, host=None, passwd=None, port=None):
  26. if host:
  27. self.host = host
  28. if passwd:
  29. self.passwd = passwd
  30. if port:
  31. self.port = port
  32. self.freepyd = None
  33. self.connection_deferred = None
  34. def reset(self):
  35. self.freepyd = None
  36. self.connection_deferred = None
  37. def connect(self):
  38. if self.freepyd:
  39. # if we have a protocol object, we are connected (since we always
  40. # null it upon any disconnection)
  41. return defer.succeed("Connected")
  42. if self.connection_deferred:
  43. # we are already connecting, return existing dfrd
  44. return self.connection_deferred
  45. self.connection_deferred = defer.Deferred()
  46. self.connection_deferred.addCallback(self.dologin)
  47. self.connection_deferred.addErrback(self.generalError)
  48. print "freepy connecting to %s:%s" % (self.host, self.port)
  49. reactor.connectTCP(self.host, self.port, self)
  50. return self.connection_deferred
  51. def conncb(self, freepyd):
  52. self.freepyd = freepyd
  53. deferred2callback = self.connection_deferred
  54. self.connection_deferred = None
  55. deferred2callback.callback("Connected")
  56. def generalError(self, failure):
  57. print "General error: %s" % failure
  58. return failure
  59. def startedConnecting(self, connector):
  60. pass
  61. def buildProtocol(self, addr):
  62. return freepy.FreepyDispatcher(self.conncb, self.discocb)
  63. def clientConnectionLost(self, connector, reason):
  64. print "clientConnectionLost! conn=%s, reason=%s" % (connector,
  65. reason)
  66. self.connection_deferred = None
  67. self.freepyd = None
  68. def clientConnectionFailed(self, connector, reason):
  69. print "clientConnectionFailed! conn=%s, reason=%s" % (connector,
  70. reason)
  71. self.freepyd = None
  72. deferred2callback = self.connection_deferred
  73. self.connection_deferred = None
  74. deferred2callback.errback(reason)
  75. def discocb(self, reason):
  76. print "disconnected. reason: %s" % reason
  77. self.freepyd = None
  78. def dologin(self, connectmsg):
  79. return self.freepyd.login(self.passwd)
  80. def originate(self, party2dial, dest_ext_app, bgapi=True):
  81. """
  82. party2dial - the first argument to the originate command,
  83. eg, sofia/profile_name/1234@domain.com
  84. dest_ext_app - the second argument to the originate command,
  85. eg, &park() or 4761
  86. returns - a deferred that will be called back with a result
  87. like:
  88. ([(True, 'Reply-Text: +OK Job-UUID: d07ad7de-2406-11dc-aea3-e3b2e56b7a2c')],)
  89. """
  90. def originate_inner(ignored):
  91. deferreds = []
  92. deferred = self.freepyd.originate(party2dial,
  93. dest_ext_app,
  94. bgapi)
  95. return deferred
  96. d = self.connect()
  97. d.addCallback(originate_inner)
  98. return d
  99. def dialconf(self, people2dial, conf_name, bgapi=True):
  100. """
  101. conf_name - name of conf TODO: change to match db
  102. people2dial - an array of dictionaries:
  103. 'name': name
  104. 'number': number
  105. returns - a deferred that will be called back with a result
  106. like:
  107. ([(True, 'Reply-Text: +OK Job-UUID: d07ad7de-2406-11dc-aea3-e3b2e56b7a2c')],)
  108. Its a bit ugly because its a deferred list callback.
  109. """
  110. def dialconf_inner(ignored):
  111. deferreds = []
  112. for person2dial in people2dial:
  113. sofia_url = person2dial['number']
  114. deferred = self.freepyd.confdialout(conf_name,
  115. sofia_url,
  116. bgapi)
  117. deferreds.append(deferred)
  118. return defer.DeferredList(deferreds)
  119. d = self.connect()
  120. d.addCallback(dialconf_inner)
  121. return d
  122. def listconf(self, conf_name):
  123. """
  124. conf_name - name of conf
  125. returns - a deferred that will be called back with a result
  126. like:
  127. TODO: add this
  128. """
  129. def listconf_inner(ignored):
  130. deferred = self.freepyd.listconf(conf_name)
  131. return deferred
  132. d = self.connect()
  133. d.addCallback(listconf_inner)
  134. return d
  135. def confkick(self, member_id, conf_name, bgapi=True):
  136. """
  137. conf_name - name of conf
  138. member_id - member id of user to kick, eg, "7"
  139. returns - a deferred that will be called back with a result
  140. like:
  141. TODO: add this
  142. """
  143. def confkick_inner(ignored):
  144. #if type(member_id) == type(""):
  145. # member_id = int(member_id)
  146. deferred = self.freepyd.confkick(member_id, conf_name, bgapi)
  147. return deferred
  148. d = self.connect()
  149. d.addCallback(confkick_inner)
  150. return d
  151. def confdtmf(self, member_id, conf_name, dtmf, bgapi=True):
  152. """
  153. Send dtmf(s) to a conference
  154. conf_name - name of conf
  155. member_id - member id of user to kick, eg, "7", or "all"
  156. dtmf - a single dtmf or a string of dtms, eg "1" or "123"
  157. returns - a deferred that will be called back with a result
  158. like:
  159. TODO: add this
  160. """
  161. def confdtmf_inner(ignored):
  162. print "confdtmf_inner called"
  163. deferred = self.freepyd.confdtmf(member_id, conf_name, dtmf, bgapi)
  164. return deferred
  165. d = self.connect()
  166. d.addCallback(confdtmf_inner)
  167. return d
  168. def confsay(self, conf_name, text2speak, bgapi=True):
  169. """
  170. conf_name - name of conf
  171. text2speak - text to speak
  172. returns - a deferred that will be called back with a result
  173. like:
  174. TODO: add this
  175. """
  176. def confsay_inner(ignored):
  177. deferred = self.freepyd.confsay(conf_name, text2speak, bgapi)
  178. return deferred
  179. d = self.connect()
  180. d.addCallback(confsay_inner)
  181. return d
  182. def confplay(self, conf_name, snd_url, bgapi=True):
  183. """
  184. conf_name - name of conf
  185. snd_url - url to sound file
  186. returns - a deferred that will be called back with a result
  187. like:
  188. TODO: add this
  189. """
  190. def confplay_inner(ignored):
  191. deferred = self.freepyd.confplay(conf_name, snd_url, bgapi)
  192. return deferred
  193. d = self.connect()
  194. d.addCallback(confplay_inner)
  195. return d
  196. def confstop(self, conf_name, bgapi=True):
  197. """
  198. stop playback of all sounds
  199. conf_name - name of conf
  200. returns - a deferred that will be called back with a result
  201. like:
  202. TODO: add this
  203. """
  204. def confstop_inner(ignored):
  205. deferred = self.freepyd.confstop(conf_name, bgapi)
  206. return deferred
  207. d = self.connect()
  208. d.addCallback(confstop_inner)
  209. return d
  210. def showchannels(self, bgapi=True):
  211. def showchannels_inner(ignored):
  212. df = self.freepyd.showchannels(bgapi)
  213. return df
  214. d = self.connect()
  215. d.addCallback(showchannels_inner)
  216. return d
  217. def killchan(self, uuid, bgapi=True):
  218. def killchan_inner(ignored):
  219. df = self.freepyd.killchan(uuid, bgapi)
  220. return df
  221. d = self.connect()
  222. d.addCallback(killchan_inner)
  223. return d
  224. def broadcast(self, uuid, path, legs="both", bgapi=True):
  225. """
  226. @legs - one of the following strings: aleg|bleg|both
  227. """
  228. def broadcast_inner(ignored):
  229. df = self.freepyd.broadcast(uuid, path, legs, bgapi)
  230. return df
  231. d = self.connect()
  232. d.addCallback(broadcast_inner)
  233. return d
  234. def transfer(self, uuid, dest_ext, legs="-both", bgapi=True):
  235. """
  236. @legs -bleg|-both
  237. """
  238. def transfer_inner(ignored):
  239. df = self.freepyd.transfer(uuid, dest_ext, legs, bgapi)
  240. return df
  241. d = self.connect()
  242. d.addCallback(transfer_inner)
  243. return d
  244. def sofia_profile_restart(self, profile_name, bgapi=True):
  245. def sofia_profile_restart_inner(ignored):
  246. df = self.freepyd.sofia_profile_restart(profile_name,
  247. bgapi)
  248. return df
  249. d = self.connect()
  250. d.addCallback(sofia_profile_restart_inner)
  251. return d
  252. def sofia_status_profile(self, profile_name, bgapi=True):
  253. def sofia_status_profile_inner(ignored):
  254. df = self.freepyd.sofia_status_profile(profile_name, bgapi)
  255. return df
  256. d = self.connect()
  257. d.addCallback(sofia_status_profile_inner)
  258. return d
  259. class FsHelperTest:
  260. def __init__(self, fshelper):
  261. self.fshelper = fshelper
  262. pass
  263. def test_dialconf(self):
  264. # the following parties will be dialed out from the conference
  265. # called "freeswitch" on the local freeswitch instance.
  266. # one party is actually another conference, just to make
  267. # the example more confusing.
  268. people2dial = [
  269. {
  270. 'name': 'freeswitch',
  271. 'number': '888@conference.freeswitch.org'
  272. },
  273. {
  274. 'name': 'mouselike',
  275. 'number': ' 904@mouselike.org'
  276. }
  277. ]
  278. d = self.fshelper.dialconf(people2dial, "freeswitch", bgapi=False)
  279. def failed(error):
  280. print "Failed to dial users!"
  281. reactor.stop()
  282. return error
  283. d.addErrback(failed)
  284. def worked(*args):
  285. print "Worked! Dialed user result: %s" % str(args)
  286. d.addCallback(worked)
  287. return d
  288. def test_listconf(self):
  289. d = self.fshelper.listconf("freeswitch")
  290. def failed(failure):
  291. print "Failed to list users!"
  292. reactor.stop()
  293. return failure
  294. d.addErrback(failed)
  295. def worked(*args):
  296. print "List of users in conf: %s" % str(args)
  297. return args[0]
  298. d.addCallback(worked)
  299. return d
  300. def test_confkick(self, member_id="6", conf_name="freeswitch"):
  301. d = self.fshelper.confkick(member_id, conf_name)
  302. def failed(failure):
  303. print "Failed to kick user!"
  304. reactor.stop()
  305. return failure
  306. d.addErrback(failed)
  307. def worked(*args):
  308. print "Kicked user from conf, result: %s" % str(args)
  309. d.addCallback(worked)
  310. def test1():
  311. kick_everyone = False
  312. fshelper = FsHelper(host="127.0.0.1",
  313. passwd="ClueCon",
  314. port=8021)
  315. fsht = FsHelperTest(fshelper)
  316. fsht.test_dialconf()
  317. d = fsht.test_listconf()
  318. def kickeveryone(members):
  319. print "Kickeveryone called w/ %s (type: %s)" % (members,
  320. type(members))
  321. for member in members:
  322. fsht.test_confkick(member.member_id)
  323. def failed(failure):
  324. print "failed: %s" % str(failure)
  325. reactor.stop()
  326. if kick_everyone:
  327. d.addCallback(kickeveryone)
  328. d.addErrback(failed)
  329. #fsht.test_confkick()
  330. #d = fshelper.connect()
  331. #def connected(*args):
  332. # fsht.test_dialconf()
  333. # fsht.test_listconf()
  334. #d.addCallback(connected)
  335. reactor.run()
  336. def test2():
  337. fshelper = FsHelper(host="127.0.0.1",
  338. passwd="ClueCon",
  339. port=8021)
  340. fshelper.sofia_profile_restart("mydomain.com")
  341. reactor.run()
  342. def test3():
  343. fshelper = FsHelper(host="127.0.0.1",
  344. passwd="ClueCon",
  345. port=8021)
  346. print "Calling originate.."
  347. party2dial="sofia/foo/600@192.168.1.202:5080"
  348. d = fshelper.originate(party2dial=party2dial,
  349. dest_ext_app="101",
  350. bgapi=True)
  351. def worked(result):
  352. print "Originate succeeded: %s" % result
  353. reactor.stop()
  354. def failed(failure):
  355. print "failed: %s" % str(failure)
  356. reactor.stop()
  357. d.addCallback(worked)
  358. d.addErrback(failed)
  359. reactor.run()
  360. def test4():
  361. fshelper = FsHelper(host="127.0.0.1",
  362. passwd="ClueCon",
  363. port=8021)
  364. def worked(result):
  365. print "Originate succeeded: %s" % result
  366. #reactor.stop()
  367. def failed(failure):
  368. print "failed: %s" % str(failure)
  369. #reactor.stop()
  370. dest_ext_app = "101"
  371. party2dial="sofia/foo/600@192.168.1.202:5080"
  372. d = fshelper.originate(party2dial=party2dial,
  373. dest_ext_app=dest_ext_app,
  374. bgapi=True)
  375. d.addCallback(worked)
  376. d.addErrback(failed)
  377. party2dial="sofia/foo/someone@bar.com"
  378. d2 = fshelper.originate(party2dial=party2dial,
  379. dest_ext_app=dest_ext_app,
  380. bgapi=True)
  381. d2.addCallback(worked)
  382. d2.addErrback(failed)
  383. reactor.run()
  384. def test5():
  385. fshelper = FsHelper(host="127.0.0.1",
  386. passwd="ClueCon",
  387. port=8021)
  388. def worked(result):
  389. print "Originate succeeded: %s" % result
  390. #reactor.stop()
  391. def failed(failure):
  392. print "failed: %s" % str(failure)
  393. #reactor.stop()
  394. for i in xrange(20):
  395. party2dial = "sofia/foo/600@192.168.1.202:5080"
  396. d = fshelper.originate(party2dial=party2dial,
  397. dest_ext_app="700",
  398. bgapi=True)
  399. d.addCallback(worked)
  400. d.addErrback(failed)
  401. reactor.run()
  402. def test6():
  403. """
  404. show channels for a given sofia profile
  405. """
  406. fshelper = FsHelper(host="127.0.0.1",
  407. passwd="ClueCon",
  408. port=8021)
  409. from wikipbx import channelsutil
  410. def show_chanels(raw_xml):
  411. print raw_xml
  412. def failure(failure):
  413. print failure
  414. d = fshelper.showchannels(bgapi=False)
  415. d.addCallback(show_chanels)
  416. d.addErrback(failure)
  417. reactor.run()
  418. if __name__ == "__main__":
  419. #test1()
  420. #test2()
  421. #test3()
  422. test4()
  423. #test5()