|
@@ -5,18 +5,23 @@ import json
|
|
|
from urllib.parse import urlparse
|
|
|
from typing import Dict, Any, Optional
|
|
|
|
|
|
-from src.core.callcenter.enumeration import CallType
|
|
|
+from src.core.callcenter.exception import SipUriSyntaxException
|
|
|
+from src.core.datasource import SIP_SERVER
|
|
|
+from src.core.callcenter.esl.constant.esl_constant import SPLIT, SIP_HEADER
|
|
|
+from src.core.callcenter.enumeration import CallType, DeviceType
|
|
|
|
|
|
|
|
|
-class MakeCallRequest:
|
|
|
+class AgentCallRequest:
|
|
|
"""
|
|
|
呼叫请求对象
|
|
|
"""
|
|
|
|
|
|
- def __init__(self, saas_id, call_type: CallType, caller, called, caller_display="", called_display="",
|
|
|
+ def __init__(self, saas_id, agent_id, call_type: CallType, caller, called, caller_display="", called_display="",
|
|
|
uuid1=None, uuid2=None, follow_data: Dict[str, Any] = {}):
|
|
|
# 租户id
|
|
|
self.saas_id = saas_id
|
|
|
+ # 坐席号
|
|
|
+ self.agent_id = agent_id
|
|
|
# 呼叫类型
|
|
|
self.call_type: CallType = call_type
|
|
|
# 主叫,如果没有传,则使用坐席号码
|
|
@@ -89,7 +94,7 @@ class AgentActionRequest:
|
|
|
坐席操作
|
|
|
"""
|
|
|
|
|
|
- def __init__(self, saas_id, agent_id, agent_number, out_id, identity_type, scene='manual'):
|
|
|
+ def __init__(self, saas_id, agent_id, agent_number=None, out_id=None, identity_type=None, scene='manual'):
|
|
|
self.saas_id =saas_id
|
|
|
# 坐席工号
|
|
|
self.agent_id = agent_id
|
|
@@ -189,6 +194,184 @@ class CheckInCallRequest:
|
|
|
self.agent_number = agent_number
|
|
|
|
|
|
|
|
|
+class MakeCallContext:
|
|
|
+
|
|
|
+ def __init__(self,
|
|
|
+ pbx_server=SIP_SERVER,
|
|
|
+ route_gateway_name="gateway-fxo",
|
|
|
+ display: Optional[str] = None,
|
|
|
+ caller: Optional[str] = None,
|
|
|
+ called: Optional[str] = None,
|
|
|
+ call_id: Optional[str] = None,
|
|
|
+ device_id: Optional[str] = None,
|
|
|
+ eavesdrop: Optional[str] = None,
|
|
|
+ device_type: Optional[int] = None,
|
|
|
+ timeout: Optional[int] = 30,
|
|
|
+ originate_timeout: Optional[int] = 30,
|
|
|
+ sip_header_map: Optional[Dict[str, str]] = [],
|
|
|
+ called_prefix: Optional[str] = "",
|
|
|
+ service_id: Optional[str] = None,
|
|
|
+ call_type: Optional[int] = None):
|
|
|
+ # fs 地址
|
|
|
+ self.pbx_server = pbx_server
|
|
|
+ # 线路名(非必传)
|
|
|
+ self.route_gateway_name = route_gateway_name
|
|
|
+ # 外显号(必传)
|
|
|
+ self.display = display
|
|
|
+ # 该腿的呼叫者(必传)
|
|
|
+ self.caller = caller
|
|
|
+ # 该腿的被呼叫者(必传)
|
|
|
+ self.called = called
|
|
|
+ # 该腿的callId (必传)
|
|
|
+ self.call_id = call_id
|
|
|
+ # 该腿的uuid(必传)
|
|
|
+ self.device_id = device_id
|
|
|
+ # 是否是监听腿 监听腿必填true,其它不填
|
|
|
+ self.eavesdrop = eavesdrop
|
|
|
+ # 当前腿的类型(必填 客户 坐席 监听者 机器人)
|
|
|
+ self.device_type = device_type
|
|
|
+ # 超时挂断
|
|
|
+ self.timeout = timeout
|
|
|
+ # 超时挂断(后面可以考虑删掉)
|
|
|
+ self.originate_timeout = originate_timeout
|
|
|
+ # 额外的sip头添加(非必填)
|
|
|
+ self.sip_header_map = sip_header_map or {}
|
|
|
+ # 被叫前缀 (用户腿必填)
|
|
|
+ self.called_prefix = called_prefix
|
|
|
+ # 任务id(机器人外呼必填)
|
|
|
+ self.service_id = service_id
|
|
|
+ # 呼叫类型(必填)
|
|
|
+ self.call_type = call_type
|
|
|
+
|
|
|
+ def get_sip_header(self) -> str:
|
|
|
+ headers = [
|
|
|
+ f"{SIP_HEADER}contact_user={self.display}",
|
|
|
+ "ring_asr=true",
|
|
|
+ f"origination_caller_id_number={self.display}",
|
|
|
+ f"origination_caller_id_name={self.display}",
|
|
|
+ f"origination_uuid={self.device_id}",
|
|
|
+ "absolute_codec_string=^^:PCMU:PCMA,",
|
|
|
+ ]
|
|
|
+
|
|
|
+ if self.originate_timeout is not None:
|
|
|
+ headers.append(f"originate_timeout={self.originate_timeout}")
|
|
|
+
|
|
|
+ headers += [
|
|
|
+ "RECORD_STEREO=true",
|
|
|
+ "RECORD_APPEND=true",
|
|
|
+ f"{SIP_HEADER}call_id={self.call_id}",
|
|
|
+ f"{SIP_HEADER}caller={self.caller}",
|
|
|
+ f"{SIP_HEADER}called={self.called}",
|
|
|
+ f"{SIP_HEADER}device_id={self.device_id}",
|
|
|
+ f"{SIP_HEADER}device_type={self.device_type}",
|
|
|
+ ]
|
|
|
+
|
|
|
+ if self.eavesdrop:
|
|
|
+ headers.append(f"{SIP_HEADER}eavesdrop={self.eavesdrop}")
|
|
|
+ if self.service_id:
|
|
|
+ headers.append(f"{SIP_HEADER}service_id={self.service_id}")
|
|
|
+
|
|
|
+ # if self.device_type == DeviceType.CUSTOMER:
|
|
|
+ # headers += [
|
|
|
+ # "RECORD_STEREO_SWAP=true"
|
|
|
+ # ]
|
|
|
+ # else:
|
|
|
+ # headers += [
|
|
|
+ # "RECORD_STEREO_SWAP=false",
|
|
|
+ # "continue_on_fail=true"
|
|
|
+ # ]
|
|
|
+
|
|
|
+ if self.sip_header_map:
|
|
|
+ headers.extend([f"{SIP_HEADER}{k}={v}" for k, v in self.sip_header_map.items()])
|
|
|
+
|
|
|
+ return SPLIT.join(headers)
|
|
|
+
|
|
|
+ def get_called(self) -> str:
|
|
|
+ if self.called_prefix and self.device_type == DeviceType.CUSTOMER:
|
|
|
+ return f"{self.called_prefix}{self.called}"
|
|
|
+ return self.called
|
|
|
+
|
|
|
+
|
|
|
+class SipURI:
|
|
|
+ DEFAULT_PORT = -1
|
|
|
+ SIP_SCHEME = "sip"
|
|
|
+ SCHEME_SEPARATOR = ':'
|
|
|
+
|
|
|
+ def __init__(self, sip_uri: str):
|
|
|
+ self.string_representation = sip_uri
|
|
|
+ self.userinfo = None
|
|
|
+ self.host = None
|
|
|
+ self.port = self.DEFAULT_PORT
|
|
|
+ self.uri_parameters = {}
|
|
|
+
|
|
|
+ scheme = f"{self.SIP_SCHEME}{self.SCHEME_SEPARATOR}"
|
|
|
+ if not sip_uri.startswith(scheme):
|
|
|
+ raise SipUriSyntaxException(f"SIP URI must start with {scheme}")
|
|
|
+
|
|
|
+ buf = sip_uri[len(scheme):]
|
|
|
+ at_pos = buf.find("@")
|
|
|
+
|
|
|
+ if at_pos == 0:
|
|
|
+ raise SipUriSyntaxException("userinfo cannot start with a '@'")
|
|
|
+ if at_pos > 0:
|
|
|
+ self.userinfo = buf[:at_pos]
|
|
|
+ buf = buf[at_pos + 1:]
|
|
|
+
|
|
|
+ end_hostport = buf.find(";")
|
|
|
+ if end_hostport == 0:
|
|
|
+ raise SipUriSyntaxException("hostport not present or it cannot start with ';'")
|
|
|
+ if end_hostport < 0:
|
|
|
+ end_hostport = len(buf)
|
|
|
+
|
|
|
+ hostport = buf[:end_hostport]
|
|
|
+ buf = buf[end_hostport:]
|
|
|
+
|
|
|
+ colon_pos = hostport.find(":")
|
|
|
+ if colon_pos > -1:
|
|
|
+ if colon_pos == len(hostport) - 1:
|
|
|
+ raise SipUriSyntaxException("hostport cannot terminate with a ':'")
|
|
|
+ self.port = int(hostport[colon_pos + 1:])
|
|
|
+ self.host = hostport[:colon_pos]
|
|
|
+ else:
|
|
|
+ self.host = hostport
|
|
|
+
|
|
|
+ if buf == ";":
|
|
|
+ buf = ""
|
|
|
+
|
|
|
+ while buf:
|
|
|
+ if buf[0] == ";":
|
|
|
+ buf = buf[1:]
|
|
|
+
|
|
|
+ next_semicolon = buf.find(";")
|
|
|
+ if next_semicolon < 0:
|
|
|
+ next_semicolon = len(buf)
|
|
|
+
|
|
|
+ next_equals = buf.find("=")
|
|
|
+ if next_equals < 0 or next_equals > next_semicolon:
|
|
|
+ next_equals = next_semicolon
|
|
|
+
|
|
|
+ key = buf[:next_equals]
|
|
|
+ value = buf[next_equals + 1:next_semicolon] if next_equals < next_semicolon else ""
|
|
|
+ self.uri_parameters[key] = value
|
|
|
+
|
|
|
+ buf = buf[next_semicolon:]
|
|
|
+
|
|
|
+ def __str__(self):
|
|
|
+ return self.string_representation
|
|
|
+
|
|
|
+ def get_host(self):
|
|
|
+ return self.host
|
|
|
+
|
|
|
+ def get_port(self):
|
|
|
+ return self.port
|
|
|
+
|
|
|
+ def get_uri_parameters(self):
|
|
|
+ return self.uri_parameters
|
|
|
+
|
|
|
+ def get_userinfo(self):
|
|
|
+ return self.userinfo
|
|
|
+
|
|
|
+
|
|
|
class AgentInfo:
|
|
|
def __init__(self, sass_id=None, agent_number=None, realm=None, sip_server=None,
|
|
|
call_id=None, device_id=None, real_device_id=None, line_id=None, fs_user=None, domain=None,
|