package zoho import ( "crm-api/pkg/configs" "crm-api/pkg/httpclient" "crm-api/pkg/lfshook" "encoding/json" "fmt" "io/ioutil" "net/http" "strconv" "time" "gopkg.in/ini.v1" ) var TimestampList = make(map[string]string) var StartTimeList = make(map[string]time.Time) // 事件中 Timestamp 转 StartTime 有问题,重新记录时间 // var CallTypeList = make(map[string]string) // 临时记录 incoming 和 outgoing var DialStatusList = make(map[string]string) // 临时记录 DialStatus:ANSWER NOANSWER // func addOneToStringNumber(s string) string { // num, err := strconv.Atoi(s) // if err != nil { // fmt.Println("xxxxxxxxxxxxx = ", err) // } // num++ // return strconv.Itoa(num) // } func CallNotify(event map[string]string) { fmt.Println("=========================================== ") fmt.Println("event[] = ", event) fmt.Println("event = ", event["Event"]) fmt.Println("AMIPushUrl = ", configs.PushConfigValue.AMIPushUrl) fmt.Println("Channel = ", event["Channel"]) /* ===================不这样取callDest,删除 ==================== var callDest string if event["Channel"] != "" { callDest = strings.Split(strings.Split(event["Channel"], "/")[1], "-")[0] fmt.Println("callDest = ", callDest) } // * ========================================================= */ // var callId string // timestamp := time.Now().Unix() // rand.Seed(time.Now().UnixNano()) // // 字符串 // charset := "abcdefghijklmnopqrstuvwxyz" // // 获取随机字符 // c := charset[rand.Intn(len(charset))] // callId = "callid" + strconv.Itoa(int(timestamp)) // if callId == " " { // input := "callid100000" // num, err := strconv.Atoi(input) // if err != nil { // fmt.Println("x11111111111 = ", err) // } // num++ // callId = strconv.Itoa(num) // fmt.Println("callId111111111 = ", callId) // } else { // num, err := strconv.Atoi(callId) // if err != nil { // fmt.Println("x22222222222222 = ", err) // } // num++ // callId = strconv.Itoa(num) // fmt.Println("callId2222222222 = ", callId) // } var callId = "callid100007" // 测试用,每次测试 +1 // event["Exten"] = "123456789" // 测试用 // fmt.Println("callId = ", callId) var zohoUser = "872297346" // 测试用 872297346 873447071 // 后面看是否从数据库中取 t_zoho_user // var info TabZohouser // if _, err := mysql.DBOrmInstance.Insert(&info); err != nil { // lfshook.NewLogger().Error(err) // return // } // var zohoUser = info.UserId // 读取vtiger配置文件 confPath := "/etc/asterisk/crm_api.conf" cfg, err := ini.Load(confPath) if err != nil { lfshook.NewLogger().Error(err) return } ZOHO_URL := cfg.Section("general").Key("zohoUrl").String() if ZOHO_URL == "" { lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoUrl") return } // var getURL string fmt.Println("Context = ", event["Context"]) // 呼叫发起事件 // if event["Event"] == "DialBegin" && event["Context"] == "macro-stdexten" { // if event["Event"] == "DialBegin" && strings.Compare(event["Context"], "macro-stdexten") == 0 { if event["Event"] == "DialBegin" { /* ===================先放这里测试用 ============================================================ // 记录开始时间 TimestampList[event["Uniqueid"]] = event["Timestamp"] // StartTimeList[event["Uniqueid"]] = time.Now().Format("2006-01-02 15:04:09") StartTimeList[event["Uniqueid"]] = time.Now() /* ===================先放这里测试用 ============================================================ fmt.Printf("Uniqueid=%s\n", event["Uniqueid"]) fmt.Printf("Timestamp=%s\n", event["Timestamp"]) TimestampTmp, err := strconv.Atoi(event["Timestamp"]) // 不要 if err != nil { fmt.Printf("转换出错: %v\n", err) return } fmt.Printf("TimestampAtoi=%d\n", TimestampTmp) TimestampTmp002, err := strconv.ParseFloat(event["Timestamp"], 64) // OK if err != nil { fmt.Printf("转换出错: %v\n", err) return } fmt.Printf("TimestampParseFloat=%f\n", TimestampTmp002) // * ===================================================================================== */ // Outgoing Call - Ringing if event["Context"] == "macro-trunkdial-failover" { StartTimeList[event["Linkedid"]] = time.Now().Add(-8 * time.Hour) // https://www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=ringing&id=10031&from=123456789&to=12300000001 getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["DestCallerIDNum"]) if zohoUser != "" { getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser) } fmt.Println("getURL = ", getURL) go httpclient.ZohoGet(getURL) // Incoming Call - Ringing // if event["Context"] == "macro-stdexten" { } else if event["Context"] == "macro-stdexten" || event["Context"] == "macro-stdexten-withoutvm" { // 开启语音留言:macro-stdexten 关闭语音留言:macro-stdexten-withoutvm StartTimeList[event["Linkedid"]] = time.Now().Add(-8 * time.Hour) // if strings.Compare(event["Context"], "macro-stdexten") == 0 { // OK // https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=ringing&id=10033&from=12300000001&to=123456789 // getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["Exten"], callDest) // 取得from to 可能不准 getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ringing&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["DestCallerIDNum"]) if zohoUser != "" { getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser) } fmt.Println("getURL = ", getURL) go httpclient.ZohoGet(getURL) } // 呼叫结束事件 } else if event["Event"] == "DialEnd" { // 记录呼叫状态 DialStatusList[event["Linkedid"]] = event["DialStatus"] // Outgoing Call - Unattended if event["Context"] == "macro-trunkdial-failover" && event["DialStatus"] != "ANSWER" { startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") fmt.Println("startTime: ", startTime) // https://www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=busy&id=10025&from=123456789&to=12300000001&start_time=2024-12-04 15:09:10 getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=busy&id=%s&from=%s&to=%s&start_time=%s", ZOHO_URL, callId, event["CallerIDNum"], event["DestCallerIDNum"], startTime) if zohoUser != "" { getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser) } fmt.Println("getURL = ", getURL) go httpclient.ZohoGet(getURL) // Incoming Call - Missed // if event["Context"] == "macro-stdexten" && event["DialStatus"] != "ANSWER" { } else if (event["Context"] == "macro-stdexten" || event["Context"] == "macro-stdexten-withoutvm") && event["DialStatus"] != "ANSWER" { // 开启语音留言:macro-stdexten 关闭语音留言:macro-stdexten-withoutvm startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") fmt.Println("startTime: ", startTime) // https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=missed&id=10005&from=12300000001&to=123456789&start_time=2024-12-04 15:09:10 getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=missed&id=%s&from=%s&to=%s&start_time=%s", ZOHO_URL, callId, event["CallerIDNum"], event["DestCallerIDNum"], startTime) if zohoUser != "" { getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser) } fmt.Println("getURL = ", getURL) go httpclient.ZohoGet(getURL) } // 呼叫已连接事件 } else if event["Event"] == "BridgeEnter" { // Outgoing Call - Answered if event["Context"] == "macro-trunkdial-failover" && event["Priority"] == "1" { // 注意这里选择 Priority:1 的,便于确认 CallerIDNum,ConnectedLineNum // 记录开始时间 // TimestampList[event["Uniqueid"]] = event["Timestamp"] // error 需要把 Uniqueid 改为 Linkedid // StartTimeList[event["Uniqueid"]] = time.Now() // error 需要把 Uniqueid 改为 Linkedid TimestampList[event["Linkedid"]] = event["Timestamp"] StartTimeList[event["Linkedid"]] = time.Now().Add(-8 * time.Hour) // https: //www.zohoapis.com/phonebridge/v3/callnotify?type=dialed&state=answered&id=10003&from=123456789&to=12300000001 // getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"]) getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dialed&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDNum"]) if zohoUser != "" { getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser) } fmt.Println("getURL = ", getURL) go httpclient.ZohoGet(getURL) // Incoming Call - Answered // if event["Context"] == "macro-stdexten" { } else if event["Context"] == "macro-stdexten" || event["Context"] == "macro-stdexten-withoutvm" { // 开启语音留言:macro-stdexten 关闭语音留言:macro-stdexten-withoutvm // 记录开始时间 // TimestampList[event["Uniqueid"]] = event["Timestamp"] // Uniqueid 有时通话也不合适 NG // 都改为 Linkedid // StartTimeList[event["Uniqueid"]] = time.Now() // Uniqueid 有时通话也不合适 NG // 都改为 Linkedid TimestampList[event["Linkedid"]] = event["Timestamp"] // 呼入时设置 Uniqueid 不是 Linkedid StartTimeList[event["Linkedid"]] = time.Now().Add(-8 * time.Hour) // 呼入时设置 Uniqueid 不是 Linkedid // https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=answered&id=10023&from=12300000001&to=123456789 getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=answered&id=%s&from=%s&to=%s", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"]) if zohoUser != "" { getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser) } fmt.Println("getURL = ", getURL) go httpclient.ZohoGet(getURL) } // 呼叫挂断事件 } else if event["Event"] == "HangupRequest" { /* ===================先放这里测试用 ============================================================ // 记录结束时间 // TimestampList[event["Uniqueid"]] = event["Timestamp"] // startTimeStamp := int64(TimestampList[event["Uniqueid"]]) fmt.Println("startTimeStamp Str: ", TimestampList[event["Uniqueid"]]) // keyTmp := event["Uniqueid"] // fmt.Println("keyTmp: ", keyTmp) // fmt.Println("startTimeStamp Str: ", TimestampList[keyTmp]) startTimeStamp, err := strconv.ParseFloat(TimestampList[event["Uniqueid"]], 64) // OK if err != nil { fmt.Printf("startTimeStamp转换出错: %v\n", err) return } fmt.Printf("startTimeStamp=%f\n", startTimeStamp) endTimeStamp, err := strconv.ParseFloat(event["Timestamp"], 64) // OK if err != nil { fmt.Printf("endTimeStamp转换出错: %v\n", err) return } fmt.Printf("endTimeStamp=%f\n", endTimeStamp) durationTime := time.Duration(endTimeStamp-startTimeStamp) * time.Second fmt.Println("durationTime=", durationTime) // 删除记录时间 // delete(TimestampList, event["Uniqueid"]) // * ===================================================================================== */ // 判断呼叫状态,为 ANSWER 时才推送,否则见以上推送 Outgoing Call - Unattended 或 Incoming Call - Missed if DialStatusList[event["Linkedid"]] == event["DialStatus"] { // Outgoing Call - Ended if event["Context"] == "macro-trunkdial-failover" { // 获取时间 // durationTime := getDuration(event["Uniqueid"], event["Timestamp"]) durationTime := getDuration(event["Linkedid"], event["Timestamp"]) // /* ===================测试用 ============================================================ // startTime, _ := time.Parse("2006-01-02 15:04:05", TimestampList[event["Uniqueid"]]) // error 1734328213.774410 => 0001-01-01 00:00:00 +0000 UTC // startTime := StartTimeList[event["Uniqueid"]] // startTime := StartTimeList[event["Uniqueid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time fmt.Println("startTime: ", startTime) // durationTime002 := getDuration002(startTime) // durationTime002 := time.Now().Sub(StartTimeList[event["Uniqueid"]]) // ok durationTime002: 7.78408849s // should use time.Since instead of time.Now().Sub (S1012) // durationTime002 := time.Since(StartTimeList[event["Uniqueid"]]) // ok durationTime002: 11.611233458s // fmt.Println("durationTime002: ", durationTime002) // * ===================================================================================== */ // 删除记录时间 delete(StartTimeList, event["Linkedid"]) // https://www.zohoapis.com/phonebridge/v3/callnotify?type=dailed&state=ended&id=10030&from=123456789&to=12300000001&start_time=2024-12-04 11:32:15&duration=325 // getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dailed&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"], startTime, durationTime) getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=dailed&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDNum"], startTime, durationTime) if zohoUser != "" { getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser) } fmt.Println("getURL = ", getURL) go httpclient.ZohoGet(getURL) // Incoming Call - Ended // if event["Context"] == "macro-stdexten" { // Context:DialPlan1 } else if event["Context"] == "macro-stdexten" || event["Context"] == "macro-stdexten-withoutvm" { // 开启语音留言:macro-stdexten 关闭语音留言:macro-stdexten-withoutvm // if event["Context"] != "macro-trunkdial-failover" { // 还需优化判断,开启语音留言时,主叫挂断才是 macro-stdexten , 被叫挂断是 DialPlan1 // 关闭语音留言时,主叫挂断才是 macro-stdexten-withoutvm , 被叫挂断是 DialPlan1 // 如果 Outgoing Call 只会是 macro-trunkdial-failover 的话,那么和以下判断换一下,else 不判断,都为 Incoming Call 处理 // 获取时间 // durationTime := getDuration(event["Uniqueid"], event["Timestamp"]) // startTime := StartTimeList[event["Uniqueid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time durationTime := getDuration(event["Linkedid"], event["Timestamp"]) startTime := StartTimeList[event["Linkedid"]].Format("2006-01-02 15:04:09") // 存储从string 改为 time.Time fmt.Println("startTime: ", startTime) // https://www.zohoapis.com/phonebridge/v3/callnotify?type=received&state=ended&id=100&from=12300000001&to=123456789&start_time=2024-12-04 10:32:10&duration=223 getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["CallerIDNum"], event["ConnectedLineNum"], startTime, durationTime) // getURL := fmt.Sprintf("%s/phonebridge/v3/callnotify?type=received&state=ended&id=%s&from=%s&to=%s&start_time=%s&duration=%d", ZOHO_URL, callId, event["ConnectedLineNum"], event["CallerIDName"], startTime, durationTime) if zohoUser != "" { getURL = fmt.Sprintf("%s&zohouser=%s", getURL, zohoUser) } fmt.Println("getURL = ", getURL) go httpclient.ZohoGet(getURL) } } } // fmt.Println("getURL = ", getURL) // 测试验证下上面有没有删除,以防TimestampList越来越大 for k, v := range TimestampList { fmt.Printf("===Linkedid:%s Timestamp:%s===\n", k, v) } } // 使用事件中event["Timestamp"]的值计算时间间隔 // func getDuration(uniqueid string, timeStamp string) int { func getDuration(linkedid string, timeStamp string) int { // 都改为 Linkedid fmt.Println("startTimeStamp Str: ", TimestampList[linkedid]) if TimestampList[linkedid] == "" || timeStamp == "" { fmt.Printf("linkedid 或 timeStamp 为空\n") return 9999 } startTimeStamp, err := strconv.ParseFloat(TimestampList[linkedid], 64) if err != nil { fmt.Printf("startTimeStamp转换出错: %v\n", err) return 9999 } fmt.Printf("startTimeStamp=%f\n", startTimeStamp) endTimeStamp, err := strconv.ParseFloat(timeStamp, 64) if err != nil { fmt.Printf("endTimeStamp转换出错: %v\n", err) return 9999 } fmt.Printf("endTimeStamp=%f\n", endTimeStamp) durationTime := time.Duration(endTimeStamp-startTimeStamp) * time.Second // fmt.Println("durationTime=", durationTime) // durationTime= 35s // fmt.Printf("durationTime=%d\n", durationTime) // durationTime=35000000000 // fmt.Printf("durationTime=%d\n", durationTime.Seconds()) // durationTime=%!d(float64=35) // fmt.Printf("durationTime=%f\n", durationTime.Seconds()) // durationTime=35.000000 fmt.Printf("durationTime=%d\n", int(durationTime.Seconds())) // durationTime=35 // durationTime001 := time.Duration(endTimeStamp - startTimeStamp) // fmt.Println("durationTime001=", durationTime001) // durationTime001= 35ns // durationTime003 := time.Duration(endTimeStamp - startTimeStamp).Seconds() // fmt.Println("durationTime003=", durationTime003) // durationTime003= 3.5e-08 // 删除记录时间 delete(TimestampList, linkedid) // return durationTime return int(durationTime.Seconds()) } // // 程序中取时间计算间隔多少秒 // func getDuration002(startTime string) time.Duration { // // durationTime := endTime.Sub(startTime) // startTime002, err := time.Parse("2006-01-02 15:04:05", startTime) // if err != nil { // fmt.Println("Error parsing time:", err) // return 0 // } // durationTime := time.Now().Sub(startTime002) // return durationTime // } // @tags PBX-zoho // @Summary 刷新token // @Description 刷新token // @Security ZohoToken // @Accept json // @Produce json // @Router /api/zoho/refresh-token [post] func RefreshToken() { fmt.Printf("RefreshToken ............\n") // 获取配置文件信息 // confPath := "/etc/asterisk/vtiger_api.conf" confPath := "/etc/asterisk/crm_api.conf" cfg, err := ini.Load(confPath) if err != nil { lfshook.NewLogger().Error(err) return } ZohoAuthUrl := cfg.Section("general").Key("zohoAuthUrl").String() ZohoRefreshToken := cfg.Section("general").Key("zohoRefreshToken").String() ZohoClientId := cfg.Section("general").Key("zohoClientId").String() ZohoClientSecret := cfg.Section("general").Key("zohoClientSecret").String() if ZohoAuthUrl == "" || ZohoRefreshToken == "" || ZohoClientId == "" || ZohoClientSecret == "" { lfshook.NewLogger().Error("/etc/asterisk/crm_api.conf not set zohoAuthUrl or zohoRefreshToken or zohoClientId or zohoClientSecret") return } // 创建请求 // https://accounts.zoho.com/oauth/v2/token?refresh_token=1004.86c8c0e3db7bfe9133598825bef28eb9.17a82a3bf3e675c504f478c1b0b5c456 // &client_id=1004.LWJCJZD5O9DB6SZLL5YJEWHT7LH0BV&client_secret=fc3aef43dc58af8a49d3ed597710924200b03f74d0&grant_type=refresh_token getURL := fmt.Sprintf("%s/oauth/v2/token?refresh_token=%s&client_id=%s&client_secret=%s&grant_type=refresh_token", ZohoAuthUrl, ZohoRefreshToken, ZohoClientId, ZohoClientSecret) fmt.Printf("getURL = %s\n", getURL) // data := httpRequest(ctx, "POST", getURL) // /* ===================================================================================== req, err := http.NewRequest("POST", getURL, nil) if err != nil { fmt.Println("创建请求时发生错误:", err) return } // 创建HTTP客户端 client := &http.Client{} // req.Header.Set("Authorization", "Bearer "+accessToken) // 刷新token时不需要 // 发送请求 resp, err := client.Do(req) if err != nil { fmt.Println("发送请求时发生错误:", err) return } defer resp.Body.Close() // 读取请求后的响应 data, err := ioutil.ReadAll(resp.Body) if err != nil { // 读取数据错误 lfshook.NewLogger().Warn("ioutil ReadAll failed :", err.Error()) // api.Error(ctx, http.StatusInternalServerError, err.Error()) return } lfshook.NewLogger().Infof("data %+v", string(data)) // * ===================================================================================== */ refreshTokenData := RefreshTokenResp{} err = json.Unmarshal([]byte(data), &refreshTokenData) if err != nil { // 转换数据错误 lfshook.NewLogger().Warn("json unmarshal failed :", err.Error()) // api.Error(ctx, http.StatusInternalServerError, err.Error()) return } // 将获取的 access_token 写入文件 crm_api.conf cfg.Section("general").Key("zohoAccessToken").SetValue(refreshTokenData.AccessToken) err = cfg.SaveTo(confPath) if err != nil { lfshook.NewLogger().Error(err) // api.Error(ctx, http.StatusInternalServerError, err.Error()) return } // 打印请求后的响应 fmt.Printf("data = %+v\n", string(data)) // api.Success(ctx, string(data)) // api.Success(ctx, refreshTokenData) // return } // 每隔55分钟刷新token func RefreshTokenTicker() { fmt.Printf("RefreshTokenTicker ............\n") ticker := time.NewTicker(55 * time.Minute) defer ticker.Stop() done := make(chan bool) // go func() { // 注释掉OK for { select { case <-done: return case t := <-ticker.C: fmt.Println("Tick at", t) RefreshToken() } } // }() }