Files
Microsoft-tts/internal/utils/utils.go

224 lines
5.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package utils
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/gin-gonic/gin"
"net/http"
"net/url"
"strings"
"time"
"unicode/utf8"
"github.com/google/uuid"
"github.com/sirupsen/logrus"
)
var (
log = logrus.New()
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)
fmt.Printf("GetEndpoint -> url: %s, headers: %v\n", endpointURL, string(headerJson))
resp, err := client.Do(req)
if err != nil {
log.Error("failed to do request: ", err)
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
log.Error("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
}
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)
}