init push
This commit is contained in:
329
backend/pkg/bot/wechat_service/tools.go
Normal file
329
backend/pkg/bot/wechat_service/tools.go
Normal file
@@ -0,0 +1,329 @@
|
||||
package wechat_service
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 读取 cursor,以客服账号的消息作为key,返回对应的cursor值
|
||||
func getCursor(openKfId string) string {
|
||||
cursorValue, _ := KfCursors.Load(openKfId)
|
||||
cursor, _ := cursorValue.(string)
|
||||
return cursor
|
||||
}
|
||||
|
||||
// 存储 cursor
|
||||
func setCursor(openKfId, cursor string) {
|
||||
KfCursors.Store(openKfId, cursor)
|
||||
}
|
||||
|
||||
func CheckSessionState(token, extrenaluserid, kfId string) (int, error) {
|
||||
var statusrequest struct {
|
||||
OpenKfId string `json:"open_kfid"`
|
||||
ExternalUserid string `json:"external_userid"`
|
||||
}
|
||||
statusrequest.OpenKfId = kfId
|
||||
statusrequest.ExternalUserid = extrenaluserid
|
||||
// 将请求体转换为JSON
|
||||
jsonBody, err := json.Marshal(statusrequest)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// 获取状态信息
|
||||
url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/get?access_token=%s", token)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("发送请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
|
||||
var response Status
|
||||
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return 0, fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
// 得到用户的状态
|
||||
if response.ErrCode != 0 {
|
||||
return 0, fmt.Errorf("获取会话状态失败: %s", response.ErrMsg)
|
||||
}
|
||||
return response.ServiceState, nil
|
||||
}
|
||||
|
||||
func ChangeState(token, extrenaluserId, kfId string, state int, serviceId string) error {
|
||||
var changestate struct {
|
||||
OpenKfId string `json:"open_kfid"`
|
||||
ExternalUserid string `json:"external_userid"`
|
||||
ServiceState int `json:"service_state"`
|
||||
ServicerUserId string `json:"servicer_userid"`
|
||||
}
|
||||
changestate.OpenKfId = kfId
|
||||
changestate.ExternalUserid = extrenaluserId
|
||||
changestate.ServiceState = state
|
||||
changestate.ServicerUserId = serviceId
|
||||
jsonBody, err := json.Marshal(changestate)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// 发送请求
|
||||
url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/kf/service_state/trans?access_token=%s", token)
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
return fmt.Errorf("发送请求失败: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取响应失败: %v", err)
|
||||
}
|
||||
// 解析响应
|
||||
var response struct {
|
||||
ErrCode int `json:"errcode"`
|
||||
ErrMsg string `json:"errmsg"`
|
||||
MsgCode string `json:"msg_code"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &response); err != nil {
|
||||
return fmt.Errorf("解析响应失败: %v", err)
|
||||
}
|
||||
// 得到用户的状态
|
||||
if response.ErrCode != 0 {
|
||||
return fmt.Errorf("改变用户状态失败: %s", response.ErrMsg)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetUserInfo(userid string, accessToken string) (*Customer, error) {
|
||||
userInfoRequest := UerInfoRequest{
|
||||
UserID: []string{userid},
|
||||
SessionContext: 0,
|
||||
}
|
||||
// 请求获取用户信息的url
|
||||
url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/kf/customer/batchget?access_token=%s", accessToken)
|
||||
|
||||
jsonBody, err := json.Marshal(userInfoRequest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// post获取用户的消息信息
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBody))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var userInfo WechatCustomerResponse
|
||||
if err := json.Unmarshal(body, &userInfo); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if userInfo.ErrCode != 0 {
|
||||
return nil, fmt.Errorf("获取用户信息失败: %d, %s", userInfo.ErrCode, userInfo.ErrMsg)
|
||||
}
|
||||
|
||||
return &userInfo.CustomerList[0], nil
|
||||
}
|
||||
|
||||
// get image id
|
||||
func GetUserImageID(accessToken, filePath string) (string, error) {
|
||||
UImageCache.Mutex.Lock()
|
||||
defer UImageCache.Mutex.Unlock()
|
||||
|
||||
if UImageCache.ImageID != "" && (UImageCache.ImagePath == filePath) && time.Now().Before(UImageCache.ImageExpire.Add(-5*time.Minute)) {
|
||||
return UImageCache.ImageID, nil
|
||||
}
|
||||
|
||||
// URL
|
||||
mediaID, err := UploadMediaFromURL(accessToken, filePath)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
UImageCache.ImagePath = filePath
|
||||
UImageCache.ImageID = mediaID
|
||||
UImageCache.ImageExpire = time.Now().Add(72 * time.Hour) // 3 days
|
||||
return UImageCache.ImageID, nil
|
||||
}
|
||||
|
||||
// get image id
|
||||
func GetDefaultImageID(accessToken, ImageBase64 string) (string, error) {
|
||||
DImageCache.Mutex.Lock()
|
||||
defer DImageCache.Mutex.Unlock()
|
||||
|
||||
if DImageCache.ImageID != "" && time.Now().Before(DImageCache.ImageExpire.Add(-5*time.Minute)) {
|
||||
return DImageCache.ImageID, nil
|
||||
}
|
||||
|
||||
// Base64编码
|
||||
mediaID, err := UploadMediaFromBase64(accessToken, ImageBase64)
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
DImageCache.ImageID = mediaID
|
||||
DImageCache.ImageExpire = time.Now().Add(72 * time.Hour) // 3 days
|
||||
return DImageCache.ImageID, nil
|
||||
}
|
||||
|
||||
// upload media to wechat server from URL
|
||||
func UploadMediaFromURL(accessToken, fileURL string) (string, error) {
|
||||
// 处理URL
|
||||
resp, err := http.Get(fileURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("下载图片失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return "", fmt.Errorf("下载图片失败,状态码: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
reader := resp.Body
|
||||
fileName := "image.png" // 默认文件名
|
||||
|
||||
// 从URL中提取文件名
|
||||
if u, err := url.Parse(fileURL); err == nil && u.Path != "" {
|
||||
if path.Base(u.Path) != "/" && path.Base(u.Path) != "." {
|
||||
fileName = path.Base(u.Path)
|
||||
}
|
||||
}
|
||||
|
||||
return uploadMediaToWechat(accessToken, reader, fileName)
|
||||
}
|
||||
|
||||
// upload media to wechat server from Base64
|
||||
func UploadMediaFromBase64(accessToken, base64Data string) (string, error) {
|
||||
// 处理Base64编码的图片
|
||||
parts := strings.SplitN(base64Data, ",", 2)
|
||||
if len(parts) != 2 {
|
||||
return "", fmt.Errorf("无效的Base64图片数据")
|
||||
}
|
||||
|
||||
// 解码Base64数据
|
||||
decodedData, err := base64.StdEncoding.DecodeString(parts[1])
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("解码Base64图片数据失败: %w", err)
|
||||
}
|
||||
|
||||
reader := bytes.NewReader(decodedData)
|
||||
fileName := "image.png" // const
|
||||
|
||||
return uploadMediaToWechat(accessToken, reader, fileName)
|
||||
}
|
||||
|
||||
// upload media to wechat server - common function
|
||||
func uploadMediaToWechat(accessToken string, reader io.Reader, fileName string) (string, error) {
|
||||
// 上传文件 req
|
||||
body := &bytes.Buffer{}
|
||||
writer := multipart.NewWriter(body)
|
||||
|
||||
part, err := writer.CreateFormFile("media", fileName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 将图片数据复制到表单中
|
||||
_, err = io.Copy(part, reader)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("复制图片数据失败: %w", err)
|
||||
}
|
||||
|
||||
if err := writer.Close(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token=%s&type=image", accessToken)
|
||||
req, err := http.NewRequest("POST", url, body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
|
||||
client := &http.Client{}
|
||||
httpResp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer httpResp.Body.Close()
|
||||
|
||||
var result MediaUploadResponse
|
||||
if err := json.NewDecoder(httpResp.Body).Decode(&result); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if result.ErrCode != 0 {
|
||||
return "", fmt.Errorf("上传失败: [%d] %s", result.ErrCode, result.ErrMsg)
|
||||
}
|
||||
|
||||
return result.MediaID, nil
|
||||
}
|
||||
|
||||
func getMsgs(accessToken string, msg *WeixinUserAskMsg) (*MsgRet, error) {
|
||||
var msgRet MsgRet
|
||||
// 拉取消息的路由
|
||||
url := fmt.Sprintf("https://qyapi.weixin.qq.com/cgi-bin/kf/sync_msg?access_token=%s", accessToken)
|
||||
cursor := getCursor(msg.OpenKfId)
|
||||
|
||||
msgBody := MsgRequest{
|
||||
OpenKfid: msg.OpenKfId,
|
||||
Token: msg.Token,
|
||||
Limit: 1000,
|
||||
VoiceFormat: 0,
|
||||
Cursor: cursor,
|
||||
}
|
||||
|
||||
jsonBody, _ := json.Marshal(msgBody)
|
||||
|
||||
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonBody)) // 得到对应的回复
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 反序列化之后
|
||||
if err := json.Unmarshal([]byte(string(body)), &msgRet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &msgRet, nil
|
||||
}
|
||||
|
||||
// markdowntotext
|
||||
func MarkdowntoText(md string) string {
|
||||
md = regexp.MustCompile(`(?m)^#+\s*(.*)$`).ReplaceAllString(md, "$1")
|
||||
md = regexp.MustCompile(`\*\*([^*]+)\*\*`).ReplaceAllString(md, "$1")
|
||||
md = regexp.MustCompile(`(?m)^>\s*(.*)$`).ReplaceAllString(md, "【引用】$1")
|
||||
md = regexp.MustCompile(`(?m)^-{3,}$`).ReplaceAllString(md, "─────────")
|
||||
md = regexp.MustCompile(`\n{3,}`).ReplaceAllString(md, "\n\n")
|
||||
md = regexp.MustCompile(`\[\[(\d+)\]\([^)]+\)\]`).ReplaceAllString(md, "[$1]")
|
||||
md = regexp.MustCompile(`\[(\d+)\]\.\s*\[([^\]]+)\]\([^)]+\)`).ReplaceAllString(md, "[$1]. $2")
|
||||
md = regexp.MustCompile(`(?m)^【引用】\[(\d+)\].\s*([^\n(]+)\s*\([^)]+\)`).ReplaceAllString(md, "【引用】[$1]. $2")
|
||||
return strings.TrimSpace(md)
|
||||
}
|
||||
Reference in New Issue
Block a user