client.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. #!/usr/bin/env python3
  2. # encoding:utf-8
  3. import json
  4. import random
  5. import socketserver
  6. import ESL
  7. import time
  8. import mmh3
  9. import threading
  10. import traceback
  11. import concurrent.futures
  12. import src.core.callcenter.cache as Cache
  13. from src.core.callcenter.constant import SK, EMPTY
  14. from src.core.callcenter.esl.constant.esl_constant import BRIDGE_VARIABLES, BRIDGE, HANGUP, NORMAL_CLEARING, SIP_HEADER, SPACE, SPLIT, SOFIA, \
  15. ORIGINATE, PARK, SET, EAVESDROP, SMF_ALEG, EXECUTE, PLAYBACK, PAUSE, TRANSFER, UUID_TRANSFER, UUID_BROADCAST, UUID_BREAK, UUID_HOLD, \
  16. UUID_RECORD, UUID_SETVAR, UUID_GETVAR
  17. import src.core.callcenter.esl.utils.esl_event_util as EslEventUtil
  18. import src.core.callcenter.esl.handler as event_handler
  19. from src.core.callcenter.esl.constant.sip_header_constant import sipHeaderHoldMusic
  20. from src.core.callcenter.enumeration import CallCause
  21. from src.core.callcenter.esl.handler.default_esl_event_handler import DefaultEslEventHandler
  22. from src.core.voip.constant import *
  23. class InboundClient:
  24. def __init__(self, agent, logger):
  25. self.con = None
  26. self.thread_num = 32
  27. self.is_stopping = False
  28. self.logger = logger
  29. self.bot_agent = agent
  30. self.handler_table = self.scan_esl_event_handlers()
  31. self.default_event_handler = DefaultEslEventHandler(self, self.bot_agent, self.logger)
  32. self.host, self.port, self.password = SERVE_HOST, '8021', '4918257983818884358'
  33. self.executors = {x: concurrent.futures.ThreadPoolExecutor(max_workers=1) for x in range(self.thread_num)}
  34. threading.Thread(target=self.start, args=()).start()
  35. # gw = CacheUtil.getRouteGetway(saasId)
  36. # self.make_call(gw, '63366692', '13241676588', 'C111111111', 'D1111111')
  37. def scan_esl_event_handlers(self):
  38. import inspect
  39. import importlib
  40. import pkgutil
  41. classes = []
  42. # 遍历包中的模块
  43. for module_info in pkgutil.iter_modules(event_handler.__path__):
  44. module = importlib.import_module(f"{event_handler.__name__}.{module_info.name}")
  45. for _name, _cls in inspect.getmembers(module, inspect.isclass):
  46. if hasattr(_cls, '_esl_event_name'):
  47. classes.append(_cls)
  48. handlers = {}
  49. for _cls in classes:
  50. items = handlers.get(_cls._esl_event_name, [])
  51. items.append(_cls(self, self.bot_agent, self.logger))
  52. handlers[_cls._esl_event_name] = items
  53. return handlers
  54. def start(self):
  55. self.logger.info('inbound.start')
  56. self.con = ESL.ESLconnection(self.host, self.port, self.password)
  57. if self.con.connected():
  58. self.logger.info('inbound esl connected ... ')
  59. self.con.events('plain', 'all') #CHANNEL_ORIGINATE,CHANNEL_PROGRESS,CHANNEL_PROGRESS_MEDIA,CHANNEL_ANSWER,CHANNEL_HANGUP,CUSTOM,PLAYBACK_START,PLAYBACK_STOP,DETECTED_TONE
  60. while not self.is_stopping:
  61. e = self.con.recvEvent()
  62. # if e:
  63. # self.logger.info(json.loads(e.serialize('json')))
  64. event_name = e.getHeader("Event-Name")
  65. if event_name == "SERVER_DISCONNECTED":
  66. self.logger.info('come in SERVER_DISCONNECTED case')
  67. self.con.disconnect()
  68. time.sleep(3)
  69. self.start()
  70. else:
  71. # threading.Thread(target=self.process_esl_event, args=(e,)).start()
  72. self.choose_thread_pool_executor(e).submit(self.process_esl_event, e)
  73. def choose_thread_pool_executor(self, e):
  74. call_id = EslEventUtil.getCallId(e)
  75. device_id = EslEventUtil.getUniqueId(e)
  76. wdh_device_id = EslEventUtil.getDeviceId(e)
  77. random_id = call_id if call_id else device_id
  78. if random_id:
  79. random_index = abs(mmh3.hash(random_id)) % len(self.executors)
  80. else:
  81. random_index = random.randint(0, len(self.executors) - 1) if self.executors else 0
  82. print('choose_thread_pool_executor.index=', random_index, call_id, device_id, wdh_device_id)
  83. return self.executors.get(random_index)
  84. def process_esl_event(self, e):
  85. # print(json.loads(e.serialize('json')))
  86. event_name = EslEventUtil.getEventName(e)
  87. coreUUID = EslEventUtil.getCoreUuid(e)
  88. address = self.host + ':' + self.port
  89. try:
  90. if event_name in self.handler_table:
  91. items = self.handler_table.get(event_name)
  92. for x in items:
  93. try:
  94. x.handle(address, e, coreUUID)
  95. except:
  96. traceback.print_exc()
  97. else:
  98. self.default_event_handler.handle(address, e, coreUUID)
  99. except:
  100. traceback.print_exc()
  101. def make_call(self, route_gateway, display, called, call_id, device_id, timeout=30, originate_timeout=30, *sip_headers):
  102. # called = f"{called}{AT}{route_gateway.media_host}{CO}{route_gateway.media_port}"
  103. if route_gateway.caller_prefix:
  104. display = f"{route_gateway.caller_prefix}{display}"
  105. if route_gateway.called_prefix:
  106. called = f"{route_gateway.called_prefix}{called}"
  107. sip_buffer = []
  108. if sip_headers:
  109. sip_buffer = [f"{SIP_HEADER}{header}" for header in sip_headers]
  110. params = {
  111. "callId": call_id,
  112. "deviceId": device_id,
  113. "caller": display,
  114. "called": called,
  115. }
  116. if route_gateway.sip_header1:
  117. sip_header1 = self.expression(route_gateway.sip_header1, params)
  118. sip_buffer.append(f"{SIP_HEADER}{sip_header1}")
  119. if route_gateway.sip_header2:
  120. sip_header2 = self.expression(route_gateway.sip_header2, params)
  121. sip_buffer.append(f"{SIP_HEADER}{sip_header2}")
  122. if route_gateway.sip_header3:
  123. sip_buffer.append(f"{SIP_HEADER}{route_gateway.sip_header3}")
  124. builder = [
  125. "{return_ring_ready=true,",
  126. f"sip_contact_user={display},",
  127. "ring_asr=true,",
  128. "absolute_codec_string=^^:PCMU:PCMA,", # Assuming codecs is defined somewhere
  129. f"origination_caller_id_number={display},",
  130. f"origination_caller_id_name={display},",
  131. f"origination_uuid={device_id},",
  132. ]
  133. if originate_timeout:
  134. builder.append(f"originate_timeout={originate_timeout},")
  135. if sip_buffer:
  136. builder.append(f"{SPLIT}".join(sip_buffer))
  137. builder.append("}")
  138. builder.append(f"{SOFIA}{SK}{route_gateway.profile}{SK}{called}{PARK}")
  139. cmd = "".join(builder)
  140. print(cmd)
  141. self.con.bgapi(ORIGINATE, cmd)
  142. def call_timeout(self, call_id, device_id, timeout):
  143. """呼叫超时主动挂机"""
  144. pass
  145. def send_args(self, device_id, name, arg):
  146. msg = ESL.ESLevent("sendmsg", device_id)
  147. msg.addHeader("call-command", EXECUTE)
  148. msg.addHeader("execute-app-name", name)
  149. msg.addHeader("execute-app-arg", arg)
  150. self.con.sendEvent(msg)
  151. def bridge_call(self, call_id, device_id1, device_id2):
  152. """桥接电话"""
  153. self.multi_set_var(device_id1, BRIDGE_VARIABLES)
  154. self.multi_set_var(device_id2, BRIDGE_VARIABLES)
  155. self.con.bgapi(BRIDGE, device_id1 + SPACE + device_id2)
  156. def transfer_call(self, _from, _to):
  157. """转接"""
  158. builder = [
  159. _from,
  160. " -both 'set:hangup_after_bridge=false,set:park_after_bridge=true,park:' inline "
  161. ]
  162. arg = ''.join(builder)
  163. self.con.bgapi(TRANSFER, arg)
  164. def answer(self, device_id):
  165. """应答"""
  166. self.con.bgapi('uuid_phone_event', device_id + ' talk')
  167. def hangup_call(self, call_id, device_id, case_enum=CallCause.DEFAULT):
  168. """挂机"""
  169. msg = ESL.ESLevent("sendmsg", device_id)
  170. msg.addHeader("call-command", EXECUTE)
  171. msg.addHeader("execute-app-name", HANGUP)
  172. msg.addHeader("execute-app-arg", NORMAL_CLEARING)
  173. self.logger.info("hangup_call挂机 hangup call: {}, device: {}, ctiCauseEnum:{}", call_id, device_id, case_enum)
  174. self.send_args(device_id, SET, EslEventUtil.SIP_H_P_LIBRA_HANGUP_CAUSE + "=" + case_enum.description)
  175. self.con.sendEvent(msg)
  176. def broadcast(self, uuid, path, smf):
  177. builder = [
  178. UUID_BROADCAST,
  179. uuid,
  180. path,
  181. smf
  182. ]
  183. command = ' '.join(builder)
  184. self.con.bgapi(command, EMPTY)
  185. def break0(self, uuid, all=False, sync=True):
  186. builder = [
  187. UUID_BREAK,
  188. uuid
  189. ]
  190. if all:
  191. builder.append("all")
  192. command = ' '.join(builder)
  193. if sync:
  194. self.con.api(command, EMPTY)
  195. else:
  196. self.con.bgapi(command, EMPTY)
  197. def hold(self, smf, uuid, display):
  198. builder = [
  199. UUID_HOLD,
  200. smf,
  201. uuid,
  202. display
  203. ]
  204. if display:
  205. builder.append("all")
  206. else:
  207. builder.append(EMPTY)
  208. command = ' '.join(builder)
  209. self.con.bgapi(command, EMPTY)
  210. def get_var(self, uuid, var):
  211. builder = [
  212. UUID_GETVAR,
  213. uuid,
  214. var
  215. ]
  216. command = ' '.join(builder)
  217. self.con.bgapi(command, EMPTY)
  218. def set_var(self, uuid, var, val):
  219. builder = [
  220. UUID_SETVAR,
  221. uuid,
  222. var,
  223. val
  224. ]
  225. command = ' '.join(builder)
  226. self.con.bgapi(command, EMPTY)
  227. def multi_set_var(self, uuid, params):
  228. builder = [
  229. "uuid_setvar_multi " + uuid + " "
  230. ]
  231. builder1 = []
  232. for k, v in params.items():
  233. builder1.append(k + "="+v)
  234. builder.append(';'.join(builder1))
  235. command = ''.join(builder)
  236. self.con.bgapi(command, EMPTY)
  237. def record(self, uuid, action, path, limit):
  238. builder = [
  239. UUID_RECORD,
  240. uuid,
  241. action,
  242. path,
  243. str(limit)
  244. ]
  245. command = ' '.join(builder)
  246. self.con.bgapi(command, EMPTY)
  247. def transfer(self, uuid, smf, dest, dialplan, context):
  248. builder = [
  249. UUID_TRANSFER,
  250. uuid,
  251. smf,
  252. dest,
  253. dialplan,
  254. context
  255. ]
  256. command = ' '.join(builder)
  257. self.con.bgapi(command, EMPTY)
  258. def insert(self, device_id):
  259. """强插"""
  260. builder = [
  261. device_id,
  262. " -both 'set:hangup_after_bridge=false,set:park_after_bridge=true,park:' inline "
  263. ]
  264. arg = ''.join(builder)
  265. self.con.api(TRANSFER, arg)
  266. def bridge_break(self, device_id):
  267. """拆线"""
  268. builder = [
  269. device_id,
  270. " -both 'set:hangup_after_bridge=false,set:park_after_bridge=true,set:" + SIP_HEADER + sipHeaderHoldMusic + "=true,park:' inline "
  271. ]
  272. arg = ''.join(builder)
  273. self.con.api(TRANSFER, arg)
  274. def play_file(self, call_id, device_id, file, sync):
  275. """放音"""
  276. if sync:
  277. return self.hold_play(device_id, file)
  278. else:
  279. msg = ESL.ESLevent("sendmsg", device_id)
  280. msg.addHeader("call-command", EXECUTE)
  281. msg.addHeader("execute-app-name", PLAYBACK)
  282. msg.addHeader("execute-app-arg", file)
  283. msg.addHeader("async", "true")
  284. self.con.sendEvent(msg)
  285. def stop_play(self, device_id):
  286. """关闭播放音乐"""
  287. builder = [
  288. device_id,
  289. " on"
  290. ]
  291. arg = "".join(builder)
  292. return self.con.api(PAUSE, arg)
  293. def hold_play(self, device_id, play):
  294. """向a-leg插播tts音乐(无限播放)"""
  295. builder = [
  296. device_id,
  297. " playback::",
  298. play,
  299. " ",
  300. SMF_ALEG
  301. ]
  302. arg = "".join(builder)
  303. return self.con.api(UUID_BROADCAST, arg)
  304. def play_timeout(self, call_id, timeout):
  305. """播放超时主动挂机"""
  306. pass
  307. def listen(self, device_id1, device_id2, aleg=True, bleg=True):
  308. """监听"""
  309. if aleg:
  310. self.send_args(device_id1, SET, "eavesdrop_bridge_aleg=true")
  311. if bleg:
  312. self.send_args(device_id1, SET, "eavesdrop_bridge_bleg=true")
  313. self.send_args(device_id1, EAVESDROP, device_id2)
  314. def show_channel(self, device_id):
  315. msg = self.con.api("show", " channels like " + device_id + " as json")
  316. print('show_channel::', msg)
  317. return msg
  318. def expression(self, template, params):
  319. for key, value in params.items():
  320. template = template.replace("#{["+key+"]}", str(value))
  321. return template
  322. def stop(self):
  323. for k, v in self.executors.items():
  324. v.shutdown()
  325. self.con.disconnect()
  326. self.is_stopping = True
  327. class OutboundClient:
  328. def __init__(self, agent, logger):
  329. self.bot_agent = agent
  330. self.logger = logger
  331. threading.Thread(target=self.start, args=()).start()
  332. class ESLRequestHandler(socketserver.BaseRequestHandler):
  333. def setup(self):
  334. try:
  335. self.client.logger.info('%s connected!',self.client_address)
  336. fd = self.request.fileno()
  337. con = ESL.ESLconnection(fd)
  338. self.client.logger.info('Connected: %s', con.connected())
  339. if con.connected():
  340. info = con.getInfo()
  341. # print(json.loads(info.serialize('json')))
  342. event_name = info.getHeader("Event-Name")
  343. device_id = info.getHeader("unique-id")
  344. kwargs = json.loads(info.serialize('json'))
  345. destination = self.client.bot_agent.register(**kwargs)
  346. self.client.logger.info("device_id=%s, destination=%s", device_id, destination)
  347. Cache.add_device_user_part(device_id, destination)
  348. con.execute("bridge", f"user/{destination}", device_id)
  349. # destination = "user/1001"
  350. # msg = ESL.ESLevent("sendmsg", uuid)
  351. # msg.addHeader("call-command", "execute")
  352. # msg.addHeader("execute-app-name", "bridge")
  353. # msg.addHeader("execute-app-arg", destination)
  354. # # 发送消息以执行 bridge 操作
  355. # con.sendEvent(msg)
  356. # print(f"Call {uuid} is bridged to {destination}")
  357. # con.execute("answer", "", uuid)
  358. # con.execute("transfer", "1001 XML default", uuid)
  359. # try:
  360. # con.disconnect()
  361. # except:
  362. # print('come in ')
  363. # traceback.print_exc()
  364. else:
  365. self.client.logger.info("Failed to connect to FreeSWITCH")
  366. except:
  367. traceback.print_exc()
  368. class CustomTCPServer(socketserver.TCPServer):
  369. def __init__(self, server_address, RequestHandlerClass, client):
  370. self.client = client
  371. socketserver.TCPServer.__init__(server_address, RequestHandlerClass)
  372. def start(self, HOST='0.0.0.0', PORT=8084):
  373. # HOST, PORT = "0.0.0.0", 8084
  374. # 创建一个 TCP 服务器
  375. with self.CustomTCPServer((HOST, PORT), self.ESLRequestHandler, self) as server:
  376. self.logger.info(f"ESL server listening on {HOST}:{PORT}")
  377. server.serve_forever()