package ami import ( "crm-api/api/admin/vtiger" "crm-api/api/admin/zoho" "crm-api/internal/app/ami/model" "crm-api/pkg/configs" "crm-api/pkg/lfshook" "strings" "crm-api/internal/app/redis" socketio "crm-api/internal/app/socket_io" "github.com/mitchellh/mapstructure" "github.com/sirupsen/logrus" "github.com/tqcenglish/amigo-go" "github.com/tqcenglish/amigo-go/pkg" ) var AminInstance *amigo.Amigo // const VTIGER_URL = "https://zycoo1.od2.vtiger.com" // 认证信息添加到 ./config.yaml // const BasicAuthUser = "juncheng.du@zycoo.com" // const BasicAuthPWD = "8DJ3O28MCZ4sPAk5" // const ApiKey = "X-VTIGER-SECRET" // const ApiKeyValue = "174781790673da476e25cf" func StartAMI(connectOKCallBack func(), handleEvents []func(event map[string]string), apiType string) { lfshook.NewLogger().Info("Start AMI") settings := &amigo.Settings{ Host: configs.ConfigGlobal.AsteriskAMIHost, Port: configs.ConfigGlobal.AsteriskAMIPort, Username: configs.ConfigGlobal.AsteriskAMIUser, Password: configs.ConfigGlobal.AsteriskAMISecret, LogLevel: logrus.InfoLevel} lfshook.NewLogger().Infof("ami setting: %+v", settings) AminInstance = amigo.New(settings, lfshook.NewLogger()) AminInstance.EventOn(func(payload ...interface{}) { // lfshook.NewLogger().Infof("ami event on %+v", payload[0]) event := payload[0].(map[string]string) handleAMIBridge(event) handleAMI(event) handleSocketIO(event) // /* 20241128 vtiger crm 对应 ============================================================ if apiType == "CRM_vtiger" { // phoneCalls(event) vtiger.PhoneCalls(event) } else if apiType == "CRM_zoho" { // callNotify(event) zoho.CallNotify(event) // 将函数名称 callNotify 改为 CallNotify 即可 } // * ===================================================================================== */ /* 20241128 vtiger crm 对应 ============================================================ fmt.Println("=========================================== ") fmt.Println("event[] = ", event) fmt.Println("event = ", event["Event"]) fmt.Println("AMIPushUrl = ", configs.PushConfigValue.AMIPushUrl) fmt.Println("Channel = ", event["Channel"]) var callId string if event["Channel"] != "" { callId = strings.Split(strings.Split(event["Channel"], "/")[1], "-")[0] fmt.Println("callId = ", callId) } // if configs.PushConfigValue.AMIPushUrl != "" { // for _, eventName := range configs.PushConfigValue.AMIEvents { // if eventName == event["Event"] { // go httpclient.Post(event, configs.PushConfigValue.AMIPushUrl) // break // } // } // } callId = "10000007" // 测试用,每次测试 +1 recordingUrl := "https%3A%2F%2Fs3.amazonaws.com%2Frecordings_2013%2F8e522852-72aa-11e5-ab5f-842b2b021118.mp3" // 测试用 event["Exten"] = "123456789" // 测试用 fmt.Println("Exten = ", event["Exten"]) // 读取vtiger配置文件 confPath := "/etc/asterisk/vtiger_api.conf" cfg, err := ini.Load(confPath) if err != nil { lfshook.NewLogger().Error(err) return } VTIGER_URL := cfg.Section("general").Key("vtigerUrl").String() if VTIGER_URL == "" { lfshook.NewLogger().Error("/etc/asterisk/vtiger_api.conf not set vtigerUrl") return } // 呼叫发起事件 if event["Event"] == "DialBegin" { // "DialBegin" "Newchannel" // https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=147258369&to=12300001&event=call_initiated&call_id=12345678&direction=inbound // https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=86147258369&event=call_initiated&call_id=12345678 // 可以省去部分参数 getURL := fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?from=%s&event=call_initiated&call_id=%s", VTIGER_URL, event["Exten"], callId) // fmt.Println("getURL = ", getURL) // go httpclient.Get(getURL) go httpclient.ApiKeyGet(getURL) // 后面要不要把用户密码给传过去 // resp := httpclient.ApiKeyGet(getURL) // // 读取请求后的响应 // data, err := ioutil.ReadAll(resp.Body) // if err != nil { // fmt.Println("读取请求后的响应时发生错误:", err) // return // } // // 打印请求后的响应 // fmt.Printf("data = %+v\n", string(data)) // 呼叫已连接事件 } else if event["Event"] == "BridgeEnter" { // "Newstate" "BridgeEnter" // https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?from=86147258369&to=12300001&event=call_connected&call_id=12345678 // https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?event=call_connected&call_id=12345678 // 可以省去部分参数 getURL := fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?event=call_connected&call_id=%s", VTIGER_URL, callId) // fmt.Println("getURL = ", getURL) go httpclient.ApiKeyGet(getURL) // 通话录音事件 } else if event["Event"] == "Cdr" { // https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_recording&recordingurl=https%3A%2F%2Fs3.amazonaws.com%2Frecordings_2013%2F8e522852-72aa-11e5-ab5f-842b2b021118.mp3 getURL := fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_recording&recordingurl=%s", VTIGER_URL, callId, recordingUrl) // fmt.Println("getURL = ", getURL) go httpclient.ApiKeyGet(getURL) // 呼叫转移事件 // Command:SET VARIABLE FORWARD_TYPE "busy" Command:SET VARIABLE FORWARD_DEST "136" // Command:SET VARIABLE FORWARD_TYPE "always" Command:SET VARIABLE FORWARD_DEST "136" // Command:SET VARIABLE FORWARD_TYPE "noan_busy" Command:SET VARIABLE FORWARD_DEST "136" // Command:SET VARIABLE FORWARD_TYPE "noan_unav" Command:SET VARIABLE FORWARD_DEST "136" // Command:SET VARIABLE FORWARD_TYPE "unavailable" Command:SET VARIABLE FORWARD_DEST "136" } else if strings.Contains(event["Command"], "FORWARD_DEST") { // transferredNumber := strings.Split(event["Command"], "\"")[1] // 正式用 transferredNumber := "147258369" // 测试用 // fmt.Println("Command = ", event["Command"]) // fmt.Println("transferredNumber = ", transferredNumber) // https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_transfer&transferred_number=147258369 getURL := fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_transfer&transferred_number=%s", VTIGER_URL, callId, transferredNumber) // fmt.Println("getURL = ", getURL) go httpclient.ApiKeyGet(getURL) // 呼叫挂断事件 } else if event["Event"] == "HangupRequest" { // "HangupRequest" "Hangup" // https://zycoo1.od2.vtiger.com/modules/PhoneCalls/callbacks/Generic.php?call_id=12345678&event=call_hangup getURL := fmt.Sprintf("%s/modules/PhoneCalls/callbacks/Generic.php?call_id=%s&event=call_hangup", VTIGER_URL, callId) fmt.Println("getURL = ", getURL) go httpclient.ApiKeyGet(getURL) } // 按编号搜索API 暂时用的以下接口 // group.GET("/vtiger/lookup", contactsInfo) // 按编号搜索 // * ===================================================================================== */ for _, handle := range handleEvents { go handle(event) } }) AminInstance.ConnectOn(func(payload ...interface{}) { if payload[0] == pkg.Connect_OK { lfshook.NewLogger().Info("ami connect ok") ClearBridge() connectOKCallBack() } else { lfshook.NewLogger().Errorf("ami connect failure %+v", payload) } }) AminInstance.Connect() } func handleAMI(event map[string]string) { switch event["Event"] { case "ContactStatus": endpointName := event["EndpointName"] contactStatus := event["ContactStatus"] // NonQualified round := event["RoundtripUsec"] if round == "" { round = "1000" } uri := event["URI"] data := strings.Split(uri, "@") if len(data) < 2 { lfshook.NewLogger().Debugf("split URI by @ error URI: %s event:%+v", uri, event) return } first := data[1] // 中继变化也会触发此事件, 需要忽略 switch contactStatus { case "Removed": //remove contact redis.ExtensionDel(endpointName, event["URI"], strings.Split(first, ":")[0], round) case "Reachable": fallthrough default: redis.ExtensionSet(endpointName, event["URI"], strings.Split(first, ":")[0], round) } case "ExtensionStatus": status := &model.ExtensionStatus{ Event: event["Event"], Exten: event["Exten"], Context: event["Context"], Hint: event["Hint"], Status: event["Status"], StatusText: event["StatusText"], } redis.ExtensionSetStatus(status) } } func handleSocketIO(event map[string]string) { var data interface{} switch event["Event"] { case "ExtensionStatus": lfshook.NewLogger().Tracef("ExtensionStatus %+v", event) status := &model.ExtensionStatus{} mapstructure.Decode(event, status) data = status case "QueueMemberStatus": lfshook.NewLogger().Tracef("QueueMemberStatus %+v", event) status := &model.QueueMember{} mapstructure.Decode(event, status) data = status case "QueueCallerJoin": status := &model.QueueCallerJoin{} lfshook.NewLogger().Infof("status %+v", event) mapstructure.Decode(event, status) data = status case "QueueCallerLeave": status := &model.QueueCallerLeave{} mapstructure.Decode(event, status) data = status case "QueueCallerAbandon": status := &model.QueueCallerAbandon{} mapstructure.Decode(event, status) data = status case "ParkedCall": status := &model.ParkedCall{} mapstructure.Decode(event, status) data = status case "ParkedCallTimeOut": status := &model.ParkedCallTimeOut{} mapstructure.Decode(event, status) data = status case "ParkedCallGiveUp": status := &model.ParkedCallGiveUp{} mapstructure.Decode(event, status) data = status case "UnParkedCall": status := &model.UnParkedCall{} mapstructure.Decode(event, status) data = status case "MeetmeEnd": status := &model.MeetmeEnd{} mapstructure.Decode(event, status) data = status case "MeetmeJoin": fallthrough case "MeetmeLeave": fallthrough case "MeetmeMute": fallthrough case "MeetmeTalking": fallthrough case "MeetmeTalkRequest": status := &model.Meetme{} mapstructure.Decode(event, status) data = status case "PresenceStateChange": status := &model.PresenceStateChange{} mapstructure.Decode(event, status) if status.Presentity != "" { data := strings.Split(status.Presentity, ":") if len(data) == 2 { status.Extension = data[1] } } if status.Status == "dnd" { status.DndStatus = "yes" } else { status.DndStatus = "" } data = status case "DialBegin": fallthrough case "DialEnd": fallthrough case "DialState": status := &model.Dial{} mapstructure.Decode(event, status) data = status case "Newstate": status := &model.Newstate{} mapstructure.Decode(event, status) data = status case "Hangup": status := &model.Hangup{} mapstructure.Decode(event, status) data = status case "UserEvent": switch event["UserEvent"] { case "SetDND": status := &model.SetDND{} mapstructure.Decode(event, status) data = status if status.DNDStatus == "1" { redis.ExtensionDNDSet(status.Exten, "yes") } else { redis.ExtensionDNDDel(status.Exten) } event["Event"] = event["UserEvent"] case "WakeUpStatus": status := &model.WakeUpStatus{} mapstructure.Decode(event, status) data = status event["Event"] = event["UserEvent"] default: data = event } case "SuccessfulAuth": // 获取上线 IP 地址 // !TODO 通过代理服务器地址不正确 status := &model.SuccessfulAuth{} mapstructure.Decode(event, status) status.GetAddress() data = status case "ContactStatus": status := &model.ContactStatus{} mapstructure.Decode(event, status) status.GetAddress() data = status case "MessageWaiting": status := &model.MessageWaiting{} mapstructure.Decode(event, status) status.GetExtension() data = status if status.Extension == "" { lfshook.NewLogger().Warnf("MessageWaiting error %+v", event) return } default: // if !strings.Contains(event["Event"], "RTP") && // !strings.Contains(event["Event"], "RTCP") && // !strings.Contains(event["Event"], "VarSet") && // !strings.Contains(event["Event"], "Bridge") && // !strings.Contains(event["Event"], "New") && // !strings.Contains(event["Event"], "SuccessfulAuth") && // !strings.Contains(event["Event"], "ChallengeSent") { // lfshook.NewLogger().Info(event) // } } if data != nil { // if configs.ConfigGlobal.FilterEventPushUrl != "" { // httpclient.Post(data, configs.ConfigGlobal.FilterEventPushUrl) // } socketio.SocketIOServer.BroadcastToNamespace("", event["Event"], data) } } func Connected() bool { if AminInstance != nil { return AminInstance.Connected() } return false }