-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathclient.go
141 lines (116 loc) · 3.3 KB
/
client.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package chatgpt
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
"github.com/tmaxmax/go-sse"
)
const (
USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36"
)
type Client interface {
Send(*request) (chan *response, error)
}
type client struct {
credentials *Credentials
}
func NewClient(credentials *Credentials) *client {
return &client{
credentials: credentials,
}
}
func (c *client) Send(r *request) (res *response, err error) {
reqBytes, err := json.Marshal(r)
if err != nil {
return nil, fmt.Errorf("ill request, error: %v", err)
}
req, err := http.NewRequest("POST", "https://chat.openai.com/backend-api/conversation", bytes.NewReader(reqBytes))
if err != nil {
return nil, fmt.Errorf("failed to create request, error: %v", err)
}
err = c.refreshAccessTokenIfExpired()
if err != nil {
return nil, err
}
req.Header.Set("Authority", "chat.openai.com")
req.Header.Set("Authorization", c.credentials.BearerToken)
req.Header.Set("Accept", "text/event-stream")
req.Header.Set("Accept-Language", "en-US,en;q=0.9")
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", USER_AGENT)
var lastItem []byte
var validator sse.ResponseValidator = func(r *http.Response) error {
if r.StatusCode != http.StatusOK {
return fmt.Errorf("%d", r.StatusCode)
}
return nil
}
client := sse.Client{
HTTPClient: http.DefaultClient,
DefaultReconnectionTime: 8 * time.Second,
ResponseValidator: validator,
}
conn := client.NewConnection(req)
conn.SubscribeMessages(func(event sse.Event) {
if event.String() != "[DONE]" {
lastItem = event.Data
}
})
err = conn.Connect()
if err != nil {
return nil, err
}
if len(lastItem) > 0 {
var respStruct response
err = json.Unmarshal(lastItem, &respStruct)
if err != nil {
return nil, err
} else {
return &respStruct, nil
}
}
return nil, errors.New("result is empty")
}
func (c *client) refreshAccessTokenIfExpired() error {
if !c.credentials.tokenExpiryTime.IsZero() &&
c.credentials.tokenExpiryTime.Before(time.Now()) &&
c.credentials.BearerToken != "" {
return nil
}
req, err := http.NewRequest("GET", "https://chat.openai.com/api/auth/session", nil)
if err != nil {
return fmt.Errorf("failed to create request, error: %v", err)
}
req.Header.Set("User-Agent", USER_AGENT)
req.Header.Set("Cookie", fmt.Sprintf("__Secure-next-auth.session-token=%s", c.credentials.SessionToken))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return fmt.Errorf("failed to perform request: %v", err)
}
defer resp.Body.Close()
var result sessionResponse
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return fmt.Errorf("failed to decode response: %v", err)
}
if result.Error != "" {
if result.Error == "RefreshAccessTokenError" {
return errors.New("session token has expired")
}
return errors.New(result.Error)
}
bearerToken := result.AccessToken
if bearerToken == "" {
return errors.New("unauthorized")
}
expiryTime, err := time.Parse(time.RFC3339, result.Expires)
if err != nil {
return fmt.Errorf("failed to parse expiry time: %v", err)
}
c.credentials.BearerToken = "Bearer " + bearerToken
c.credentials.tokenExpiryTime = expiryTime
return nil
}