1
0
This repository has been archived on 2024-02-27. You can view files and clone it, but cannot push or open issues or pull requests.
chatgpt-to-api/internal/bard/lib.go
2023-06-13 00:17:56 +08:00

178 lines
4.7 KiB
Go

package bard
// By @mosajjal at https://github.com/mosajjal/bard-cli/blob/main/bard/bard.go
import (
"bytes"
"encoding/json"
"fmt"
"io"
"os"
"regexp"
"strings"
"time"
"github.com/tidwall/gjson"
"k8s.io/apimachinery/pkg/util/rand"
nhttp "net/http"
http "github.com/bogdanfinn/fhttp"
tls_client "github.com/bogdanfinn/tls-client"
resty "github.com/go-resty/resty/v2"
)
var (
jar = tls_client.NewCookieJar()
options = []tls_client.HttpClientOption{
tls_client.WithTimeoutSeconds(360),
tls_client.WithClientProfile(tls_client.Safari_IOS_15_5),
tls_client.WithNotFollowRedirects(),
tls_client.WithCookieJar(jar), // create cookieJar instance and pass it as argument
// Disable SSL verification
tls_client.WithInsecureSkipVerify(),
}
client, _ = tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
http_proxy = os.Getenv("http_proxy")
resty_client = resty.New()
)
var headers map[string]string = map[string]string{
"Host": "bard.google.com",
"X-Same-Domain": "1",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.4472.114 Safari/537.36",
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
"Origin": "https://bard.google.com",
"Referer": "https://bard.google.com/",
}
func init() {
if http_proxy != "" {
client.SetProxy(http_proxy)
}
}
const bardURL string = "https://bard.google.com/_/BardChatUi/data/assistant.lamda.BardFrontendService/StreamGenerate"
type Answer struct {
Content string `json:"content"`
// FactualityQueries []string `json:"factualityQueries"`
Choices []string `json:"choices"`
}
// Bard is the main struct for the Bard AI
type Bard struct {
Cookie string
ChoiceID string
ConversationID string
ResponseID string
SNlM0e string
LastInteractionTime time.Time
}
// New creates a new Bard AI instance. Cookie is the __Secure-1PSID cookie from Google
func New(cookie string) (*Bard, error) {
b := &Bard{
Cookie: cookie,
}
err := b.getSNlM0e()
return b, err
}
func (b *Bard) getSNlM0e() error {
req, _ := http.NewRequest("GET", "https://bard.google.com/", nil)
for k, v := range headers {
req.Header.Add(k, v)
}
req.AddCookie(&http.Cookie{
Name: "__Secure-1PSID",
Value: b.Cookie,
})
// in response text, the value shows. in python:
r := regexp.MustCompile(`SNlM0e\":\"(.*?)\"`)
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
tmpValues := r.FindStringSubmatch(string(body))
if len(tmpValues) < 2 {
return fmt.Errorf("failed to find snim0e value. possibly misconfigured cookies?")
}
b.SNlM0e = tmpValues[1]
return nil
}
// Ask generates a Bard AI response and returns it to the user
func (b *Bard) Ask(prompt string) (*Answer, error) {
b.LastInteractionTime = time.Now()
// req paramters for the actual request
reqParams := map[string]string{
"bl": "boq_assistant-bard-web-server_20230606.12_p0",
"_reqid": fmt.Sprintf("%d", rand.IntnRange(100000, 999999)),
"rt": "c",
}
req := fmt.Sprintf(`[null, "[[\"%s\"], null, [\"%s\", \"%s\", \"%s\"]]"]`,
//prompt, b.answer.ConversationID, b.answer.ResponseID, b.answer.ChoiceID)
prompt, b.ConversationID, b.ResponseID, b.ChoiceID)
reqData := map[string]string{
"f.req": string(req),
"at": b.SNlM0e,
}
resty_client.SetHeaders(headers)
resty_client.SetCookie(&nhttp.Cookie{
Name: "__Secure-1PSID",
Value: b.Cookie,
})
resty_client.SetBaseURL(bardURL)
resty_client.SetTimeout(60 * time.Second)
resty_client.SetFormData(reqData)
resty_client.SetQueryParams(reqParams)
resty_client.SetDoNotParseResponse(true)
resp, err := resty_client.R().Post("")
if err != nil {
return nil, err
}
if resp.StatusCode() != 200 {
// curl, _ := http2curl.GetCurlCommand(resp.Request.EnableTrace().RawRequest)
// fmt.Println(curl)
return nil, fmt.Errorf("status code is not 200: %d", resp.StatusCode())
}
// this is the Go version
buf := new(bytes.Buffer)
_, _ = buf.ReadFrom(resp.RawResponse.Body)
respLines := strings.Split(buf.String(), "\n")
respJSON := respLines[3]
var fullRes [][]interface{}
err = json.Unmarshal([]byte(respJSON), &fullRes)
if err != nil {
return nil, err
}
// get the main answer
res, ok := fullRes[0][2].(string)
if !ok {
return nil, fmt.Errorf("failed to get answer from bard")
}
answer := Answer{}
answer.Content = gjson.Get(res, "0.0").String()
b.ConversationID = gjson.Get(res, "1.0").String()
b.ResponseID = gjson.Get(res, "1.1").String()
choices := gjson.Get(res, "4").Array()
answer.Choices = make([]string, len(choices))
for i, choice := range choices {
answer.Choices[i] = choice.Array()[0].String()
}
b.ChoiceID = choices[0].Array()[0].String()
return &answer, nil
}