Skip to content
This repository has been archived by the owner on Mar 2, 2023. It is now read-only.

Commit

Permalink
All api's are sent browser based
Browse files Browse the repository at this point in the history
  • Loading branch information
yxw21 committed Dec 25, 2022
1 parent 8d99e4e commit fdccd4b
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 173 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,19 @@ package main
import (
"fmt"
"github.com/yxw21/chatgpt"
"log"
)

func main() {
retry := 3
browser, closeBrowser, err := chatgpt.NewBrowser("")
if err != nil {
log.Fatal(err)
}
defer closeBrowser()
// Make sure the session is initialized once
session := chatgpt.NewSessionWithCredential("example@gmail.com", "123456", "I-1123123KASD").AutoRefresh()
chat := chatgpt.NewChat(session)
chat := chatgpt.NewChat(browser, session)
for i := 0; i < retry; i++ {
res, err := chat.Send("hi")
if err == nil{
Expand All @@ -77,13 +83,19 @@ package main
import (
"fmt"
"github.com/yxw21/chatgpt"
"log"
)

func main() {
retry := 3
browser, closeBrowser, err := chatgpt.NewBrowser("")
if err != nil {
log.Fatal(err)
}
defer closeBrowser()
// Make sure the session is initialized once
session := chatgpt.NewSessionWithAccessToken("AccessToken").AutoRefresh()
chat := chatgpt.NewChat(session)
chat := chatgpt.NewChat(browser, session)
for i := 0; i < retry; i++ {
res, err := chat.Send("hi")
if err == nil{
Expand Down
98 changes: 51 additions & 47 deletions openai.go → browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,17 @@ import (
"time"
)

type OpenAI struct {
username string
password string
key string
type Browser struct {
Context context.Context
}

func (ctx *OpenAI) waitResolveReCaptcha(timeout time.Duration) chromedp.EmulateAction {
func (ctx *Browser) waitResolveReCaptcha(timeout time.Duration) chromedp.EmulateAction {
return chromedp.ActionFunc(func(ctx context.Context) error {
ctxWithTimeout, cancel := context.WithTimeout(context.TODO(), timeout)
defer cancel()
for {
select {
case <-ctxWithTimeout.Done():
screenshot(ctx, "recaptcha.png")
return errors.New("solve the reCAPTCHA error: " + ctxWithTimeout.Err().Error())
default:
var value string
Expand All @@ -43,7 +40,34 @@ func (ctx *OpenAI) waitResolveReCaptcha(timeout time.Duration) chromedp.EmulateA
})
}

func (ctx *OpenAI) setExtension() (string, error) {
func (ctx *Browser) GetChatGPTPassport(username, password string) (*Passport, error) {
var passport = &Passport{}
if err := chromedp.Run(ctx.Context,
chromedp.Navigate("https://chat.openai.com/auth/login"),
waitElement(".btn:nth-child(1)", 30*time.Second),
chromedp.Click(".btn:nth-child(1)"),
waitElement("#username", 30*time.Second),
chromedp.SetValue("#username", username),
ctx.waitResolveReCaptcha(time.Minute),
chromedp.Sleep(2*time.Second),
chromedp.Click("button[type='submit']"),
waitElement("#password", 30*time.Second),
chromedp.SetValue("#password", password),
waitElement("button[type='submit']", 30*time.Second),
chromedp.Click("button[type='submit']"),
waitElement("#__next", 30*time.Second),
chromedp.Navigate("https://chat.openai.com/api/auth/session"),
waitElement("pre", 30*time.Second),
readClearance(&passport.Clearance),
chromedp.EvaluateAsDevTools(`navigator.userAgent`, &passport.Useragent),
chromedp.EvaluateAsDevTools(`JSON.parse(document.querySelector("pre").innerHTML).accessToken`, &passport.AccessToken),
); err != nil {
return passport, errors.New("login to chatgpt failed: " + err.Error())
}
return passport, nil
}

func (ctx *Browser) setExtension(key string) (string, error) {
var release []Release
separator := string(filepath.Separator)
tempDir := GetTempDir()
Expand Down Expand Up @@ -88,7 +112,7 @@ func (ctx *OpenAI) setExtension() (string, error) {
if err != nil {
return "", errors.New("error reading js file: " + err.Error())
}
addBytes := []byte(fmt.Sprintf(`Settings.set({id: "key", value: "%s"});`, ctx.key))
addBytes := []byte(fmt.Sprintf(`Settings.set({id: "key", value: "%s"});`, key))
contentBytes = append(contentBytes, addBytes...)
err = os.WriteFile(background, contentBytes, 0777)
if err != nil {
Expand All @@ -97,47 +121,27 @@ func (ctx *OpenAI) setExtension() (string, error) {
return dist, nil
}

func (ctx *OpenAI) GetPassport() (*Passport, error) {
var data = &Passport{}
dist, err := ctx.setExtension()
if err != nil {
return data, err
func NewBrowser(extensionKey string) (*Browser, context.CancelFunc, error) {
config := chromedpundetected.NewConfig(chromedpundetected.WithHeadless())
browser := &Browser{}
if extensionKey != "" {
dist, err := browser.setExtension(extensionKey)
if err != nil {
return nil, nil, err
}
config = chromedpundetected.NewConfig(
chromedpundetected.WithHeadless(),
chromedpundetected.WithChromeFlags(chromedp.Flag("disable-extensions-except", dist+"chrome")),
)
}
chromeCtx, cancel, err := chromedpundetected.New(chromedpundetected.NewConfig(
chromedpundetected.WithHeadless(),
chromedpundetected.WithTimeout(2*time.Minute),
chromedpundetected.WithChromeFlags(chromedp.Flag("disable-extensions-except", dist+"chrome")),
))
chromeCtx, cancel, err := chromedpundetected.New(config)
if err != nil {
return data, errors.New("error creating chrome context: " + err.Error())
return nil, nil, errors.New("error creating chrome context: " + err.Error())
}
defer cancel()
if err = chromedp.Run(chromeCtx,
chromedp.Navigate("https://chat.openai.com/auth/login"),
waitElement(".btn:nth-child(1)", time.Minute),
// Avoid ajax request failure and the page will not jump.
clickElement(".btn:nth-child(1)", 5),
waitElement("#username", time.Minute),
chromedp.SetValue("#username", ctx.username),
ctx.waitResolveReCaptcha(time.Minute),
chromedp.Sleep(2*time.Second),
chromedp.Click("button[type='submit']"),
waitElement("#password", time.Minute),
chromedp.SetValue("#password", ctx.password),
waitElement("button[type='submit']", time.Minute),
chromedp.Click("button[type='submit']"),
waitElement("#__next", time.Minute),
chromedp.Navigate("https://chat.openai.com/api/auth/session"),
waitElement("pre", time.Minute),
setClearance(&data.Clearance),
chromedp.EvaluateAsDevTools(`navigator.userAgent`, &data.Useragent),
chromedp.EvaluateAsDevTools(`JSON.parse(document.querySelector("pre").innerHTML).accessToken`, &data.AccessToken),
); err != nil {
return data, errors.New("login to chatgpt failed: " + err.Error())
browser.Context = chromeCtx
err = chromedp.Run(browser.Context, chromedp.Navigate("https://chat.openai.com/auth/login"))
if err != nil {
return nil, nil, err
}
return data, nil
}

func NewOpenAI(username, password, key string) *OpenAI {
return &OpenAI{username: username, password: password, key: key}
return browser, cancel, nil
}
86 changes: 32 additions & 54 deletions chat.go
Original file line number Diff line number Diff line change
@@ -1,29 +1,32 @@
package chatgpt

import (
"bytes"
"encoding/json"
"errors"
"github.com/chromedp/cdproto/network"
"github.com/chromedp/chromedp"
"github.com/satori/go.uuid"
"io"
"log"
"net/http"
"strings"
"time"
)

var (
ErrorLogging = errors.New("authorization information is being obtained")
ErrorClearance = errors.New("clearance is being refreshed")
)

type Chat struct {
client *http.Client
browser *Browser
session *Session
cid uuid.UUID
pid uuid.UUID
}

func (ctx *Chat) check() error {
func (ctx *Chat) Check() error {
if ctx.session.AccessTokenIsInvalid() {
return ctx.session.RefreshToken()
return ErrorLogging
}
if ctx.session.ClearanceIsInValid() {
return ctx.session.RefreshClearance()
return ErrorClearance
}
return nil
}
Expand Down Expand Up @@ -52,62 +55,37 @@ func (ctx *Chat) Send(word string) (*Response, error) {
}

func (ctx *Chat) SendMessage(word string, cid, pid *uuid.UUID) (*Response, error) {
var chatResponse *Response
if err := ctx.check(); err != nil {
var (
chatResponse *Response
response any
)
if err := ctx.Check(); err != nil {
return nil, err
}
chatRequest := NewRequest(word, cid, pid)
requestBytes, err := json.Marshal(chatRequest)
if err != nil {
return nil, err
}
req, err := http.NewRequest("POST", "https://chat.openai.com/backend-api/conversation", bytes.NewBuffer(requestBytes))
if err != nil {
return nil, err
}
req.Header.Set("User-Agent", ctx.session.GetUserAgent())
req.Header.Set("Authorization", "Bearer "+ctx.session.GetAccessToken())
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Cookie", "cf_clearance="+ctx.session.GetClearance())
resp, err := ctx.client.Do(req)
if err != nil {
if err = chromedp.Run(ctx.browser.Context,
setCookie("cf_clearance", ctx.session.clearance, ".chat.openai.com", "/", time.Now().Add(7*24*time.Hour), true, true, network.CookieSameSiteNone),
sendRequest(ctx.session.accessToken, requestBytes, &response),
); err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == 401 {
log.Println(401, ctx.session)
if err = ctx.session.RefreshToken(); err != nil {
return nil, err
switch res := response.(type) {
case string:
return nil, errors.New(res)
case map[string]any:
if err = ConvertMapToStruct(res, &chatResponse); err != nil {
return nil, errors.New("ConvertMapToStruct failed: " + err.Error())
}
return nil, errors.New("the AccessToken has expired and was successfully refreshed, please try again")
} else if resp.StatusCode == 403 {
log.Println(403, ctx.session)
if err = ctx.session.RefreshClearance(); err != nil {
return nil, err
}
return nil, errors.New("cf has expired and was successfully refreshed, please try again")
}
responseBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
arr := strings.Split(string(responseBytes), "\n\n")
index := len(arr) - 3
if index >= len(arr) || index < 1 {
log.Println(ctx.session)
return nil, errors.New(string(responseBytes))
}
arr = strings.Split(arr[index], "data: ")
if len(arr) < 2 {
log.Println(ctx.session)
return nil, errors.New(string(responseBytes))
}
if err = json.Unmarshal([]byte(arr[1]), &chatResponse); err != nil {
return nil, err
return chatResponse, nil
default:
return nil, errors.New("unknown error")
}
return chatResponse, nil
}

func NewChat(session *Session) *Chat {
return &Chat{client: &http.Client{}, session: session}
func NewChat(browser *Browser, session *Session) *Chat {
return &Chat{browser: browser, session: session}
}
58 changes: 39 additions & 19 deletions chat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,58 @@ package chatgpt
import (
"fmt"
"testing"
"time"
)

func TestChatWithCredential(t *testing.T) {
retry := 3
// use username and password
session := NewSessionWithCredential("example@gmail.com", "password", "I-ASDA123ASDA").AutoRefresh()
chat := NewChat(session)
for i := 0; i < retry; i++ {
res, err := chat.Send("hi")
if err == nil {
fmt.Println(res.Message.Content.Parts)
func waitChat(chat *Chat) {
for {
if err := chat.Check(); err != nil {
fmt.Println(err)
} else {
break
}
if i == retry-1 {
t.Fatal(err)
}
time.Sleep(5 * time.Second)
}
}

func TestChatWithAccessToken(t *testing.T) {
retry := 3
// use access token
session := NewSessionWithAccessToken("jwt").AutoRefresh()
chat := NewChat(session)
func startChat(chat *Chat, message string, retry int) {
waitChat(chat)
for i := 0; i < retry; i++ {
res, err := chat.Send("hi")
res, err := chat.Send(message)
if err == nil {
fmt.Println(res.Message.Content.Parts)
break
}
if i == retry-1 {
t.Fatal(err)
fmt.Println(err)
}
}
}

func TestChatWithCredential(t *testing.T) {
retry := 3
browser, closeBrowser, err := NewBrowser("")
if err != nil {
t.Fatal(err)
}
defer closeBrowser()
// use username and password
session := NewSessionWithCredential("chatgpt@gmail.com", "password", "I-ABCDEFGHIJKL").AutoRefresh()
chat := NewChat(browser, session)
startChat(chat, "hi", retry)
startChat(chat, "hello", retry)
}

func TestChatWithAccessToken(t *testing.T) {
retry := 3
browser, closeBrowser, err := NewBrowser("")
if err != nil {
t.Fatal(err)
}
defer closeBrowser()
// use access token
session := NewSessionWithAccessToken("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6Ik1UaEVOVUpHTkVNMVFURTRNMEZCTWpkQ05UZzVNRFUxUlRVd1FVSkRNRU13UmtGRVFrRXpSZyJ9").AutoRefresh()
chat := NewChat(browser, session)
startChat(chat, "hi", retry)
startChat(chat, "hello", retry)
}
Loading

0 comments on commit fdccd4b

Please sign in to comment.