dapi / internal /duckgo /request.go
sanbo
update sth. at 2024-12-24 12:41:40
8fb5936
raw
history blame
6.68 kB
package duckgo
import (
"aurora/httpclient"
duckgotypes "aurora/typings/duckgo"
officialtypes "aurora/typings/official"
"bufio"
"bytes"
"encoding/json"
"errors"
"io"
"net/http"
"strings"
"sync"
"time"
"github.com/gin-gonic/gin"
)
var (
Token *XqdgToken
UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
)
type XqdgToken struct {
Token string `json:"token"`
M sync.Mutex `json:"-"`
ExpireAt time.Time `json:"expire"`
}
// func InitXVQD(client httpclient.AuroraHttpClient, proxyUrl string) (string, error) {
// if Token == nil {
// Token = &XqdgToken{
// Token: "",
// M: sync.Mutex{},
// }
// }
// Token.M.Lock()
// defer Token.M.Unlock()
// if Token.Token == "" || Token.ExpireAt.Before(time.Now()) {
// status, err := postStatus(client, proxyUrl)
// if err != nil {
// return "", err
// }
// defer status.Body.Close()
// token := status.Header.Get("x-vqd-4")
// if token == "" {
// return "", errors.New("no x-vqd-4 token")
// }
// Token.Token = token
// Token.ExpireAt = time.Now().Add(time.Minute * 3)
// }
//
// return Token.Token, nil
// }
func InitXVQD(client httpclient.AuroraHttpClient, proxyUrl string) (string, error) {
if Token == nil {
Token = &XqdgToken{
Token: "",
M: sync.Mutex{},
}
}
Token.M.Lock()
defer Token.M.Unlock()
// 如果 token 已经失效或为空,尝试重新获取
if Token.Token == "" || Token.ExpireAt.Before(time.Now()) {
const maxRetries = 3 // 设置最大重试次数
const retryInterval = 2 * time.Second // 设置每次重试间隔
for retries := 0; retries < maxRetries; retries++ {
// 发送请求
status, err := postStatus(client, proxyUrl)
if err != nil {
// 如果是非网络类错误,直接返回
if retries == maxRetries-1 {
return "", err
}
// 网络错误,等待重试
time.Sleep(retryInterval)
continue
}
// 获取 x-vqd-4 token
defer status.Body.Close()
token := status.Header.Get("x-vqd-4")
if token == "" {
// 如果没有获取到 token,判断是否是最后一次重试
if retries == maxRetries-1 {
return "", errors.New("no x-vqd-4 token after retries")
}
// 没有获取到 token,等待重试
time.Sleep(retryInterval)
continue
}
// 成功获取到 token
Token.Token = token
Token.ExpireAt = time.Now().Add(time.Minute * 3)
return Token.Token, nil
}
// 重试完仍未成功,返回错误
return "", errors.New("failed to get x-vqd-4 token after retries")
}
// 如果 token 已存在且有效,直接返回
return Token.Token, nil
}
func postStatus(client httpclient.AuroraHttpClient, proxyUrl string) (*http.Response, error) {
if proxyUrl != "" {
client.SetProxy(proxyUrl)
}
header := createHeader()
header.Set("accept", "*/*")
header.Set("x-vqd-accept", "1")
response, err := client.Request(httpclient.GET, "https://duckduckgo.com/duckchat/v1/status", header, nil, nil)
if err != nil {
return nil, err
}
return response, nil
}
func POSTconversation(client httpclient.AuroraHttpClient, request duckgotypes.ApiRequest, token string, proxyUrl string) (*http.Response, error) {
if proxyUrl != "" {
client.SetProxy(proxyUrl)
}
body_json, err := json.Marshal(request)
if err != nil {
return &http.Response{}, err
}
header := createHeader()
header.Set("accept", "text/event-stream")
header.Set("x-vqd-4", token)
response, err := client.Request(httpclient.POST, "https://duckduckgo.com/duckchat/v1/chat", header, nil, bytes.NewBuffer(body_json))
if err != nil {
return nil, err
}
return response, nil
}
func Handle_request_error(c *gin.Context, response *http.Response) bool {
if response.StatusCode != 200 {
// Try read response body as JSON
var error_response map[string]interface{}
err := json.NewDecoder(response.Body).Decode(&error_response)
if err != nil {
// Read response body
body, _ := io.ReadAll(response.Body)
c.JSON(response.StatusCode, gin.H{"error": gin.H{
"message": "Unknown error",
"type": "internal_server_error",
"param": nil,
"code": "500",
"details": string(body),
}})
return true
}
c.JSON(response.StatusCode, gin.H{"error": gin.H{
"message": error_response["detail"],
"type": response.Status,
"param": nil,
"code": "error",
}})
return true
}
return false
}
func createHeader() httpclient.AuroraHeaders {
header := make(httpclient.AuroraHeaders)
header.Set("accept-language", "zh-CN,zh;q=0.9")
header.Set("content-type", "application/json")
header.Set("origin", "https://duckduckgo.com")
header.Set("referer", "https://duckduckgo.com/")
header.Set("sec-ch-ua", `"Chromium";v="120", "Google Chrome";v="120", "Not-A.Brand";v="99"`)
header.Set("sec-ch-ua-mobile", "?0")
header.Set("sec-ch-ua-platform", `"Windows"`)
header.Set("sec-fetch-dest", "empty")
header.Set("sec-fetch-mode", "cors")
header.Set("sec-fetch-site", "same-origin")
header.Set("user-agent", UA)
return header
}
func Handler(c *gin.Context, response *http.Response, oldRequest duckgotypes.ApiRequest, stream bool) string {
reader := bufio.NewReader(response.Body)
if stream {
// Response content type is text/event-stream
c.Header("Content-Type", "text/event-stream")
} else {
// Response content type is application/json
c.Header("Content-Type", "application/json")
}
var previousText strings.Builder
for {
line, err := reader.ReadString('\n')
if err != nil {
if err == io.EOF {
break
}
return ""
}
if len(line) < 6 {
continue
}
line = line[6:]
if !strings.HasPrefix(line, "[DONE]") {
var originalResponse duckgotypes.ApiResponse
err = json.Unmarshal([]byte(line), &originalResponse)
if err != nil {
continue
}
if originalResponse.Action != "success" {
c.JSON(500, gin.H{"error": "Error"})
return ""
}
responseString := ""
if originalResponse.Message != "" {
previousText.WriteString(originalResponse.Message)
translatedResponse := officialtypes.NewChatCompletionChunkWithModel(originalResponse.Message, originalResponse.Model)
responseString = "data: " + translatedResponse.String() + "\n\n"
}
if responseString == "" {
continue
}
if stream {
_, err = c.Writer.WriteString(responseString)
if err != nil {
return ""
}
c.Writer.Flush()
}
} else {
if stream {
final_line := officialtypes.StopChunkWithModel("stop", oldRequest.Model)
c.Writer.WriteString("data: " + final_line.String() + "\n\n")
}
}
}
return previousText.String()
}