224 lines
5.6 KiB
Go
224 lines
5.6 KiB
Go
package utils
|
||
|
||
import (
|
||
"crypto/hmac"
|
||
"crypto/rand"
|
||
"crypto/sha256"
|
||
"encoding/base64"
|
||
"encoding/json"
|
||
"fmt"
|
||
"github.com/gin-gonic/gin"
|
||
"log"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"time"
|
||
"unicode/utf8"
|
||
|
||
"github.com/google/uuid"
|
||
)
|
||
|
||
var (
|
||
client = &http.Client{}
|
||
)
|
||
|
||
const (
|
||
endpointURL = "https://dev.microsofttranslator.com/apps/endpoint?api-version=1.0"
|
||
userAgent = "okhttp/4.5.0"
|
||
clientVersion = "4.0.530a 5fe1dc6c"
|
||
homeGeographicRegion = "zh-Hans-CN"
|
||
voiceDecodeKey = "oik6PdDdMnOXemTbwvMn9de/h9lFnfBaCWbGMMZqqoSaQaqUOqjVGm5NqsmjcBI1x+sS9ugjB55HEJWRiFXYFw=="
|
||
)
|
||
|
||
func generateUserID() string {
|
||
chars := "abcdef0123456789"
|
||
result := make([]byte, 16)
|
||
for i := 0; i < 16; i++ {
|
||
randIndex := make([]byte, 1)
|
||
if _, err := rand.Read(randIndex); err != nil {
|
||
return ""
|
||
}
|
||
result[i] = chars[randIndex[0]%uint8(len(chars))]
|
||
}
|
||
return string(result)
|
||
}
|
||
|
||
// GetEndpoint 获取语音合成服务的端点信息
|
||
func GetEndpoint() (map[string]interface{}, error) {
|
||
signature := Sign(endpointURL)
|
||
userId := generateUserID()
|
||
traceId := uuid.New().String()
|
||
headers := map[string]string{
|
||
"Accept-Language": "zh-Hans",
|
||
"X-ClientVersion": clientVersion,
|
||
"X-UserId": userId,
|
||
"X-HomeGeographicRegion": homeGeographicRegion,
|
||
"X-ClientTraceId": traceId,
|
||
"X-MT-Signature": signature,
|
||
"User-Agent": userAgent,
|
||
"Content-Type": "application/json; charset=utf-8",
|
||
"Content-Length": "0",
|
||
"Accept-Encoding": "gzip",
|
||
}
|
||
req, err := http.NewRequest("POST", endpointURL, nil)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
for k, v := range headers {
|
||
req.Header.Set(k, v)
|
||
}
|
||
|
||
headerJson, err := json.Marshal(&headers)
|
||
log.Printf("GetEndpoint -> url: %s, headers: %v\n", endpointURL, string(headerJson))
|
||
|
||
resp, err := client.Do(req)
|
||
if err != nil {
|
||
log.Println("failed to do request: ", err)
|
||
return nil, err
|
||
}
|
||
|
||
defer resp.Body.Close()
|
||
|
||
if resp.StatusCode != http.StatusOK {
|
||
log.Println("failed to get endpoint, status code: ", resp.StatusCode)
|
||
return nil, fmt.Errorf("failed to get endpoint, status code: %d", resp.StatusCode)
|
||
}
|
||
|
||
var result map[string]interface{}
|
||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
log.Println("GetEndpoint <- success get endpoint result: ", result)
|
||
return result, nil
|
||
}
|
||
|
||
// Sign 生成签名
|
||
func Sign(urlStr string) string {
|
||
u := strings.Split(urlStr, "://")[1]
|
||
encodedUrl := url.QueryEscape(u)
|
||
uuidStr := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||
formattedDate := strings.ToLower(time.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")) + "gmt"
|
||
bytesToSign := fmt.Sprintf("MSTranslatorAndroidApp%s%s%s", encodedUrl, formattedDate, uuidStr)
|
||
bytesToSign = strings.ToLower(bytesToSign)
|
||
decode, _ := base64.StdEncoding.DecodeString(voiceDecodeKey)
|
||
hash := hmac.New(sha256.New, decode)
|
||
hash.Write([]byte(bytesToSign))
|
||
secretKey := hash.Sum(nil)
|
||
signBase64 := base64.StdEncoding.EncodeToString(secretKey)
|
||
return fmt.Sprintf("MSTranslatorAndroidApp::%s::%s::%s", signBase64, formattedDate, uuidStr)
|
||
}
|
||
|
||
// SplitAndFilterEmptyLines 拆分文本并过滤掉空行
|
||
func SplitAndFilterEmptyLines(text string) []string {
|
||
// 按换行符拆分
|
||
lines := strings.Split(text, "\n")
|
||
var result []string
|
||
|
||
for _, line := range lines {
|
||
trimmed := strings.TrimSpace(line)
|
||
if trimmed != "" {
|
||
result = append(result, trimmed)
|
||
}
|
||
}
|
||
return result
|
||
}
|
||
|
||
// MergeStringsWithLimit 会将字符串切片依次累加,直到总长度 ≥ minLen。
|
||
// 但如果再合并下一段后会超过 maxLen,则提前结束本段合并,放入结果。
|
||
// 然后继续新的一段合并。
|
||
func MergeStringsWithLimit(strs []string, minLen int, maxLen int) []string {
|
||
var result []string
|
||
|
||
for i := 0; i < len(strs); {
|
||
// 如果已经没有更多段落,直接退出
|
||
if i >= len(strs) {
|
||
break
|
||
}
|
||
|
||
// 从当前段开始合并
|
||
currentBuilder := strings.Builder{}
|
||
currentBuilder.WriteString(strs[i])
|
||
i++
|
||
|
||
for i < len(strs) {
|
||
currentLen := utf8.RuneCountInString(currentBuilder.String())
|
||
// 如果当前已达(或超过) minLen,先行结束本段合并
|
||
if currentLen >= minLen {
|
||
break
|
||
}
|
||
|
||
// 检查添加下一个段落后是否会超过 1.2 × minLen
|
||
nextLen := utf8.RuneCountInString(strs[i])
|
||
if currentLen+nextLen > int(float64(minLen)*1.2) {
|
||
// 加上下一个会超标,则结束合并
|
||
break
|
||
}
|
||
|
||
// 如果未超标,则继续合并这个段
|
||
currentBuilder.WriteString("\n")
|
||
currentBuilder.WriteString(strs[i])
|
||
i++
|
||
}
|
||
|
||
// 本段合并结束,加入结果
|
||
result = append(result, currentBuilder.String())
|
||
}
|
||
|
||
return result
|
||
}
|
||
|
||
// GetBaseURL 返回基础 URL,包括方案和主机,但不包括路径和查询参数
|
||
func GetBaseURL(c *gin.Context) string {
|
||
scheme := "http"
|
||
if c.Request.TLS != nil || c.Request.Header.Get("X-Forwarded-Proto") == "https" {
|
||
scheme = "https"
|
||
}
|
||
|
||
return fmt.Sprintf("%s://%s", scheme, c.Request.Host)
|
||
}
|
||
|
||
// JoinURL 安全地拼接基础 URL 和相对路径
|
||
func JoinURL(baseURL, relativePath string) (string, error) {
|
||
base, err := url.Parse(baseURL)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
rel, err := url.Parse(relativePath)
|
||
if err != nil {
|
||
return "", err
|
||
}
|
||
|
||
return base.ResolveReference(rel).String(), nil
|
||
}
|
||
|
||
func GetExp(s string) int64 {
|
||
// 解析 JWT
|
||
parts := strings.Split(s, ".")
|
||
if len(parts) != 3 {
|
||
return 0
|
||
}
|
||
|
||
// 解码负载部分
|
||
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
|
||
if err != nil {
|
||
return 0
|
||
}
|
||
|
||
// 解析 JSON
|
||
var data map[string]interface{}
|
||
if err := json.Unmarshal(payload, &data); err != nil {
|
||
return 0
|
||
}
|
||
|
||
exp, ok := data["exp"].(float64)
|
||
if !ok {
|
||
return 0
|
||
}
|
||
|
||
return int64(exp)
|
||
}
|