init push

This commit is contained in:
2026-05-21 19:52:45 +08:00
commit e3f75311ab
1280 changed files with 179173 additions and 0 deletions

View 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)
}