├── LICENSE ├── README.md ├── chat.go ├── client.go ├── examples ├── .gitignore ├── chat_test.go ├── client_test.go ├── models_test.go └── session_test.go ├── go.mod ├── go.sum ├── images ├── chat_stream.png ├── chat_text.png ├── chatgpt_cookies.png ├── continuous_chat_text.png └── wechat_group.jpg ├── models.go ├── option.go ├── session.go └── token.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Miguel Piedrafita 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > if you want to use [GPT-3](https://beta.openai.com/docs/api-reference/introduction) apis, look at this one: [GPT-3 SDK](https://github.com/chatgp/gpt3), 2 | 3 | # chatgpt-go 4 | 5 | chatgpt sdk writen by golang. 6 | 7 | ## Preparation 8 | 9 | you should login into the [ChatGPT Website](https://chat.openai.com/chat) firstly, find the Cookies named `cf_clearance`,`__Secure-next-auth.session-token`, and copy their values. 10 | 11 | ![](./images/chatgpt_cookies.png) 12 | 13 | > `_puid` cookie is required too, while only plus user can find it in their cookies after login ChatGPT official website. 14 | 15 | ## Quick Start 16 | 17 | 1. install chatgpt-go sdk 18 | 19 | ```shell 20 | go get -u github.com/chatgp/chatgpt-go 21 | ``` 22 | 23 | 2. chat in independent conversation 24 | 25 | ```go 26 | package main 27 | 28 | import ( 29 | "log" 30 | "net/http" 31 | "time" 32 | 33 | chatgpt "github.com/chatgp/chatgpt-go" 34 | ) 35 | 36 | func main() { 37 | token := `copy-from-cookies` 38 | cfValue := "copy-from-cookies" 39 | puid := "copy-from-cookies" 40 | 41 | cookies := []*http.Cookie{ 42 | { 43 | Name: "__Secure-next-auth.session-token", 44 | Value: token, 45 | }, 46 | { 47 | Name: "cf_clearance", 48 | Value: cfValue, 49 | }, 50 | { 51 | Name: "_puid", 52 | Value: puid, 53 | }, 54 | } 55 | 56 | cli := chatgpt.NewClient( 57 | chatgpt.WithDebug(true), 58 | chatgpt.WithTimeout(60*time.Second), 59 | chatgpt.WithCookies(cookies), 60 | ) 61 | 62 | // chat in independent conversation 63 | message := "say hello to me" 64 | text, err := cli.GetChatText(message) 65 | if err != nil { 66 | log.Fatalf("get chat text failed: %v", err) 67 | } 68 | 69 | log.Printf("q: %s, a: %s\n", message, text.Content) 70 | } 71 | ``` 72 | 73 | 3. chat in continuous conversation 74 | 75 | ```go 76 | package main 77 | 78 | import ( 79 | "log" 80 | "net/http" 81 | "time" 82 | 83 | chatgpt "github.com/chatgp/chatgpt-go" 84 | ) 85 | 86 | func main() { 87 | // new chatgpt client 88 | token := `copy-from-cookies` 89 | cfValue := "copy-from-cookies" 90 | 91 | cookies := []*http.Cookie{ 92 | { 93 | Name: "__Secure-next-auth.session-token", 94 | Value: token, 95 | }, 96 | { 97 | Name: "cf_clearance", 98 | Value: cfValue, 99 | }, 100 | } 101 | 102 | cli := chatgpt.NewClient( 103 | chatgpt.WithDebug(true), 104 | chatgpt.WithTimeout(60*time.Second), 105 | chatgpt.WithCookies(cookies), 106 | ) 107 | 108 | // chat in continuous conversation 109 | 110 | // first message 111 | message := "say hello to me" 112 | text, err := cli.GetChatText(message) 113 | if err != nil { 114 | log.Fatalf("get chat text failed: %v", err) 115 | } 116 | 117 | log.Printf("q: %s, a: %s\n", message, text.Content) 118 | 119 | // continue conversation with new message 120 | conversationID := text.ConversationID 121 | parentMessage := text.MessageID 122 | newMessage := "again" 123 | 124 | newText, err := cli.GetChatText(newMessage, conversationID, parentMessage) 125 | if err != nil { 126 | log.Fatalf("get chat text failed: %v", err) 127 | } 128 | 129 | log.Printf("q: %s, a: %s\n", newMessage, newText.Content) 130 | } 131 | ``` 132 | 133 | > if you want to start a new conversation out of current conversation, you don't need to reset the client. just remove the `conversationID`、`parentMessage` arguments in `GetChatText` method and use it to get a new text reply. 134 | 135 | 4. get chat content with stream 136 | 137 | ```go 138 | package main 139 | 140 | import ( 141 | "log" 142 | "net/http" 143 | "time" 144 | 145 | chatgpt "github.com/chatgp/chatgpt-go" 146 | ) 147 | 148 | func main() { 149 | // new chatgpt client 150 | token := `copy-from-cookies` 151 | cfValue := "copy-from-cookies" 152 | 153 | cookies := []*http.Cookie{ 154 | { 155 | Name: "__Secure-next-auth.session-token", 156 | Value: token, 157 | }, 158 | { 159 | Name: "cf_clearance", 160 | Value: cfValue, 161 | }, 162 | } 163 | 164 | cli := chatgpt.NewClient( 165 | chatgpt.WithDebug(true), 166 | chatgpt.WithTimeout(60*time.Second), 167 | chatgpt.WithCookies(cookies), 168 | ) 169 | 170 | message := "say hello to me" 171 | stream, err := cli.GetChatStream(message) 172 | if err != nil { 173 | log.Fatalf("get chat stream failed: %v\n", err) 174 | } 175 | 176 | var answer string 177 | for text := range stream.Stream { 178 | log.Printf("stream text: %s\n", text.Content) 179 | answer = text.Content 180 | } 181 | 182 | if stream.Err != nil { 183 | log.Fatalf("stream closed with error: %v\n", stream.Err) 184 | } 185 | 186 | log.Printf("q: %s, a: %s\n", message, answer) 187 | } 188 | ``` 189 | 190 | ## Tests 191 | 192 | under examples folder there are some tests, execute `go test` command and get outputs like below: 193 | 194 | - get chat text 195 | 196 | ![](./images/chat_text.png) 197 | 198 | - get chat text in continuous conversation 199 | 200 | ![](./images/continuous_chat_text.png) 201 | 202 | - get chat text from stream 203 | 204 | ![](./images/chat_stream.png) 205 | 206 | ## Communication 207 | 208 | - Telegram Group: [ChatGPT Creators](https://t.me/+YkEGeRxB5Q0zODY1) 209 | 210 | - Discord Server: [ChatGPT Creators](https://discord.gg/qWshJnJs) 211 | 212 | - Wechat Group: [ChatGPT Creators](https://work.weixin.qq.com/gm/66944e9bd30628e9270c980bc756663d) 213 | 214 | ![](./images/wechat_group.jpg) 215 | -------------------------------------------------------------------------------- /chat.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io/ioutil" 8 | "log" 9 | "net/http" 10 | "strings" 11 | 12 | "github.com/google/uuid" 13 | "github.com/launchdarkly/eventsource" 14 | "github.com/tidwall/gjson" 15 | ) 16 | 17 | // ChatText chat reply with text format 18 | type ChatText struct { 19 | data string // event data 20 | ConversationID string `json:"conversation_id"` // conversation context id 21 | MessageID string `json:"message_id"` // current message id, can used as next chat's parent_message_id 22 | Content string `json:"content"` // text content 23 | Model string `json:"model"` // chat model 24 | CreatedAt int64 `json:"created_at"` // message create_time 25 | } 26 | 27 | // ChatStream chat reply with sream 28 | type ChatStream struct { 29 | Stream chan *ChatText // chat text stream 30 | Err error // error message 31 | } 32 | 33 | // ChatText raw data 34 | func (c *ChatText) Raw() string { 35 | return c.data 36 | } 37 | 38 | // ChatText format to string 39 | func (c *ChatText) String() string { 40 | b, _ := json.Marshal(c) 41 | 42 | return string(b) 43 | } 44 | 45 | // GetChatText will return text message 46 | func (c *Client) GetChatText(message string, args ...string) (*ChatText, error) { 47 | resp, err := c.sendMessage(message, args...) 48 | if err != nil { 49 | return nil, fmt.Errorf("send message failed: %v", err) 50 | } 51 | defer resp.Body.Close() 52 | 53 | body, err := ioutil.ReadAll(resp.Body) 54 | if err != nil { 55 | return nil, fmt.Errorf("read response body failed: %v", err) 56 | } 57 | 58 | arr := strings.Split(string(body), "\n\n") 59 | 60 | const TEXT_ARR_MIN_LEN = 3 61 | const TEXT_STR_MIN_LEN = 6 62 | 63 | l := len(arr) 64 | if l < TEXT_ARR_MIN_LEN { 65 | return nil, fmt.Errorf("invalid reply message: %s", body) 66 | } 67 | 68 | str := arr[l-TEXT_ARR_MIN_LEN] 69 | if len(str) < TEXT_STR_MIN_LEN { 70 | return nil, fmt.Errorf("invalid reply message: %s", body) 71 | } 72 | 73 | text := str[TEXT_STR_MIN_LEN:] 74 | 75 | return c.parseChatText(text) 76 | } 77 | 78 | // GetChatStream will return text stream 79 | func (c *Client) GetChatStream(message string, args ...string) (*ChatStream, error) { 80 | resp, err := c.sendMessage(message, args...) 81 | if err != nil { 82 | return nil, fmt.Errorf("send message failed: %v", err) 83 | } 84 | 85 | contentType := resp.Header.Get("Content-Type") 86 | // not event-strem response 87 | if !strings.HasPrefix(contentType, "text/event-stream") { 88 | defer resp.Body.Close() 89 | 90 | body, _ := ioutil.ReadAll(resp.Body) 91 | if c.opts.Debug { 92 | log.Printf("http response info: %s\n", body) 93 | } 94 | 95 | return nil, fmt.Errorf("response failed: [%s] %s", resp.Status, body) 96 | } 97 | 98 | chatStream := &ChatStream{ 99 | Stream: make(chan *ChatText), 100 | Err: nil, 101 | } 102 | 103 | decoder := eventsource.NewDecoder(resp.Body) 104 | 105 | go func() { 106 | defer resp.Body.Close() 107 | defer close(chatStream.Stream) 108 | 109 | for { 110 | event, err := decoder.Decode() 111 | if err != nil { 112 | chatStream.Err = fmt.Errorf("decode data failed: %v", err) 113 | return 114 | } 115 | 116 | text := event.Data() 117 | if text == "" || text == EOF_TEXT { 118 | // read data finished, success return 119 | return 120 | } 121 | 122 | chatText, err := c.parseChatText(text) 123 | if err != nil { 124 | continue 125 | } 126 | 127 | chatStream.Stream <- chatText 128 | } 129 | }() 130 | 131 | return chatStream, nil 132 | } 133 | 134 | // parseChatText will return a ChatText struct from ChatText json 135 | func (c *Client) parseChatText(text string) (*ChatText, error) { 136 | if text == "" || text == EOF_TEXT { 137 | return nil, fmt.Errorf("invalid chat text: %s", text) 138 | } 139 | 140 | res := gjson.Parse(text) 141 | 142 | conversationID := res.Get("conversation_id").String() 143 | messageID := res.Get("message.id").String() 144 | content := res.Get("message.content.parts.0").String() 145 | model := res.Get("message.metadata.model_slug").String() 146 | createdAt := res.Get("message.create_time").Int() 147 | 148 | if conversationID == "" || messageID == "" { 149 | return nil, fmt.Errorf("invalid chat text") 150 | } 151 | 152 | return &ChatText{ 153 | data: text, 154 | ConversationID: conversationID, 155 | MessageID: messageID, 156 | Content: content, 157 | Model: model, 158 | CreatedAt: createdAt, 159 | }, nil 160 | } 161 | 162 | // sendMessage will send message to ChatGPT server 163 | func (c *Client) sendMessage(message string, args ...string) (*http.Response, error) { 164 | accessToken, err := c.getAccessToken() 165 | if err != nil { 166 | return nil, fmt.Errorf("get accessToken failed: %v", err) 167 | } 168 | 169 | var messageID string 170 | var conversationID string 171 | var parentMessageID string 172 | 173 | messageID = uuid.NewString() 174 | if len(args) > 0 { 175 | conversationID = args[0] 176 | } 177 | if len(args) > 1 { 178 | parentMessageID = args[1] 179 | } 180 | if parentMessageID == "" { 181 | parentMessageID = uuid.NewString() 182 | } 183 | 184 | params := MixMap{ 185 | "action": "next", 186 | "model": c.opts.Model, 187 | "parent_message_id": parentMessageID, 188 | "messages": []MixMap{ 189 | { 190 | "role": "user", 191 | "id": messageID, 192 | "content": MixMap{ 193 | "content_type": "text", 194 | "parts": []string{message}, 195 | }, 196 | }, 197 | }, 198 | } 199 | 200 | if conversationID != "" { 201 | params["conversation_id"] = conversationID 202 | } 203 | 204 | data, err := json.Marshal(params) 205 | if err != nil { 206 | return nil, fmt.Errorf("marshal request body failed: %v", err) 207 | } 208 | 209 | req, err := http.NewRequest(http.MethodPost, CONVERSATION_URI, bytes.NewReader(data)) 210 | if err != nil { 211 | return nil, fmt.Errorf("new request failed: %v", err) 212 | } 213 | 214 | bearerToken := fmt.Sprintf("Bearer %s", accessToken) 215 | req.Header.Set("Authorization", bearerToken) 216 | req.Header.Set("Accept", "text/event-stream") 217 | req.Header.Set("Content-Type", "application/json") 218 | 219 | resp, err := c.doRequest(req) 220 | 221 | return resp, err 222 | } 223 | -------------------------------------------------------------------------------- /client.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | "net/http/httputil" 7 | "net/url" 8 | "time" 9 | ) 10 | 11 | const ( 12 | BASE_URI = "https://chat.openai.com" 13 | AUTH_SESSION_URI = "https://chat.openai.com/api/auth/session" 14 | CONVERSATION_URI = "https://chat.openai.com/backend-api/conversation" 15 | GET_MODELS_URI = "https://chat.openai.com/backend-api/models" 16 | USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36" 17 | EOF_TEXT = "[DONE]" 18 | ) 19 | 20 | // MixMap is a type alias for map[string]interface{} 21 | type MixMap = map[string]interface{} 22 | 23 | // Client is a ChatGPT request client 24 | type Client struct { 25 | opts Options // custom options 26 | httpCli *http.Client 27 | } 28 | 29 | // NewClient will return a ChatGPT request client 30 | func NewClient(options ...Option) *Client { 31 | cli := &Client{ 32 | opts: Options{ 33 | Timeout: 30 * time.Second, // set default timeout 34 | UserAgent: USER_AGENT, // set default user-agent 35 | Model: "text-davinci-002-render-sha", // set default chat model 36 | }, 37 | } 38 | 39 | // load custom options 40 | for _, option := range options { 41 | option(cli) 42 | } 43 | 44 | cli.initHttpClient() 45 | 46 | return cli 47 | } 48 | 49 | func (c *Client) initHttpClient() { 50 | transport := &http.Transport{} 51 | 52 | if c.opts.Proxy != "" { 53 | proxy, err := url.Parse(c.opts.Proxy) 54 | if err == nil { 55 | transport.Proxy = http.ProxyURL(proxy) 56 | } 57 | } 58 | 59 | c.httpCli = &http.Client{ 60 | Timeout: c.opts.Timeout, 61 | Transport: transport, 62 | } 63 | } 64 | 65 | func (c *Client) doRequest(req *http.Request) (*http.Response, error) { 66 | if c.opts.UserAgent != "" { 67 | req.Header.Set("User-Agent", c.opts.UserAgent) 68 | } 69 | if c.opts.Cookie != "" { 70 | req.Header.Set("Cookie", c.opts.Cookie) 71 | } else if len(c.opts.Cookies) > 0 { 72 | for _, cookie := range c.opts.Cookies { 73 | req.AddCookie(cookie) 74 | } 75 | } 76 | 77 | if c.opts.Debug { 78 | reqInfo, _ := httputil.DumpRequest(req, true) 79 | log.Printf("http request info: \n%s\n", reqInfo) 80 | } 81 | 82 | resp, err := c.httpCli.Do(req) 83 | 84 | if c.opts.Debug { 85 | respInfo, _ := httputil.DumpResponse(resp, false) 86 | log.Printf("http response info: \n%s\n", respInfo) 87 | } 88 | 89 | return resp, err 90 | } 91 | 92 | // WithModel: set chat model 93 | func (c *Client) WithModel(model string) *Client { 94 | c.opts.Model = model 95 | 96 | return c 97 | } 98 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | init_test.go -------------------------------------------------------------------------------- /examples/chat_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func ExampleClient_GetChatText() { 9 | cli := getClient() 10 | 11 | message := "say hello to me" 12 | 13 | log.Printf("start get chat text") 14 | 15 | // chat in independent conversation 16 | text, err := cli.GetChatText(message) 17 | if err != nil { 18 | log.Fatalf("get chat text failed: %v", err) 19 | } 20 | 21 | log.Printf("\nq: %s\na: %s\n", message, text.Content) 22 | 23 | fmt.Println("xxx") 24 | // Output: xxx 25 | } 26 | 27 | func ExampleClient_GetContinuousChatText() { 28 | cli := getClient() 29 | 30 | message := "say hello to me" 31 | 32 | log.Printf("start get chat text") 33 | 34 | // chat in independent conversation 35 | text, err := cli.GetChatText(message) 36 | if err != nil { 37 | log.Fatalf("get chat text failed: %v", err) 38 | } 39 | 40 | log.Printf("\nq: %s\na: %s\n", message, text.Content) 41 | 42 | log.Printf("start get chat text again") 43 | 44 | // continue conversation with new message 45 | conversationID := text.ConversationID 46 | parentMessage := text.MessageID 47 | newMessage := "again" 48 | 49 | newText, err := cli.GetChatText(newMessage, conversationID, parentMessage) 50 | if err != nil { 51 | log.Fatalf("get chat text failed: %v", err) 52 | } 53 | 54 | log.Printf("\nq: %s\na: %s\n", newMessage, newText.Content) 55 | 56 | fmt.Println("xxx") 57 | // Output: xxx 58 | } 59 | 60 | func ExampleClient_GetChatStream() { 61 | cli := getClient() 62 | 63 | message := "say hello to me" 64 | 65 | log.Printf("start get chat stream") 66 | 67 | stream, err := cli.WithModel("gpt-4").GetChatStream(message) 68 | if err != nil { 69 | log.Fatalf("get chat stream failed: %v\n", err) 70 | } 71 | 72 | var answer string 73 | for text := range stream.Stream { 74 | log.Printf("stream text: %s\n", text) 75 | answer = text.Content 76 | } 77 | 78 | if stream.Err != nil { 79 | log.Fatalf("stream closed with error: %v\n", stream.Err) 80 | } 81 | 82 | log.Printf("\nq: %s\na: %s\n", message, answer) 83 | 84 | fmt.Println("xxx") 85 | // Output: xxx 86 | } 87 | -------------------------------------------------------------------------------- /examples/client_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | chatgpt "github.com/chatgp/chatgpt-go" 9 | ) 10 | 11 | var ( 12 | debug bool 13 | accessToken string 14 | sessionToken string 15 | cfValue string 16 | puid string 17 | ) 18 | 19 | func ExampleNewClient() { 20 | fmt.Printf("%T", getClient()) 21 | 22 | // Output: *chatgpt.Client 23 | } 24 | 25 | func getClient() *chatgpt.Client { 26 | cookies := []*http.Cookie{ 27 | { 28 | Name: "__Secure-next-auth.session-token", 29 | Value: sessionToken, 30 | }, 31 | { 32 | Name: "cf_clearance", 33 | Value: cfValue, 34 | }, 35 | { 36 | Name: "_puid", 37 | Value: puid, 38 | }, 39 | } 40 | 41 | cli := chatgpt.NewClient( 42 | chatgpt.WithTimeout(60*time.Second), 43 | chatgpt.WithDebug(debug), 44 | chatgpt.WithAccessToken(accessToken), 45 | chatgpt.WithCookies(cookies), 46 | ) 47 | 48 | return cli 49 | } 50 | -------------------------------------------------------------------------------- /examples/models_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func ExampleClient_GetModels() { 9 | cli := getClient() 10 | 11 | res, cookies, err := cli.GetModels() 12 | 13 | if err != nil { 14 | log.Fatalf("get models failed: %v\n", err) 15 | } 16 | 17 | for _, v := range res.Get("models").Array() { 18 | log.Printf("model: %s\n", v.String()) 19 | } 20 | 21 | for _, v := range cookies { 22 | log.Printf("cookie: %s, %s, expires: %v\n", v.Name, v.Value, v.Expires) 23 | } 24 | 25 | fmt.Println("xxx") 26 | // Output: xxx 27 | } 28 | -------------------------------------------------------------------------------- /examples/session_test.go: -------------------------------------------------------------------------------- 1 | package examples 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | ) 7 | 8 | func ExampleClient_AuthSession() { 9 | cli := getClient() 10 | 11 | res, err := cli.AuthSession() 12 | 13 | if err != nil { 14 | log.Fatalf("auth session failed: %v\n", err) 15 | } 16 | 17 | log.Printf("session: %s\n", res) 18 | 19 | fmt.Println("xxx") 20 | // Output: xxx 21 | } 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/chatgp/chatgpt-go 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/google/uuid v1.3.0 7 | github.com/tidwall/gjson v1.14.4 8 | ) 9 | 10 | require ( 11 | github.com/launchdarkly/eventsource v1.7.1 // indirect 12 | github.com/tidwall/match v1.1.1 // indirect 13 | github.com/tidwall/pretty v1.2.0 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | github.com/launchdarkly/eventsource v1.7.1 h1:StoRQeiPyrcQIXjlQ7b5jWMzHW4p+GGczN2r2oBhujg= 6 | github.com/launchdarkly/eventsource v1.7.1/go.mod h1:LHxSeb4OnqznNZxCSXbFghxS/CjIQfzHovNoAqbO/Wk= 7 | github.com/launchdarkly/go-test-helpers/v2 v2.2.0/go.mod h1:L7+th5govYp5oKU9iN7To5PgznBuIjBPn+ejqKR0avw= 8 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 9 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 10 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 11 | github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 12 | github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= 13 | github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 14 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 15 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 16 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 17 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 19 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 20 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 21 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 22 | -------------------------------------------------------------------------------- /images/chat_stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all-in-aigc/chatgpt-webapi/105c0df42b3dcfe8ab7801662ae9d9ec6bb75ef4/images/chat_stream.png -------------------------------------------------------------------------------- /images/chat_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all-in-aigc/chatgpt-webapi/105c0df42b3dcfe8ab7801662ae9d9ec6bb75ef4/images/chat_text.png -------------------------------------------------------------------------------- /images/chatgpt_cookies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all-in-aigc/chatgpt-webapi/105c0df42b3dcfe8ab7801662ae9d9ec6bb75ef4/images/chatgpt_cookies.png -------------------------------------------------------------------------------- /images/continuous_chat_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all-in-aigc/chatgpt-webapi/105c0df42b3dcfe8ab7801662ae9d9ec6bb75ef4/images/continuous_chat_text.png -------------------------------------------------------------------------------- /images/wechat_group.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/all-in-aigc/chatgpt-webapi/105c0df42b3dcfe8ab7801662ae9d9ec6bb75ef4/images/wechat_group.jpg -------------------------------------------------------------------------------- /models.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | 9 | "github.com/tidwall/gjson" 10 | ) 11 | 12 | // GetModels to get all availabel model s 13 | func (c *Client) GetModels() (*gjson.Result, []*http.Cookie, error) { 14 | req, err := http.NewRequest(http.MethodGet, GET_MODELS_URI, nil) 15 | if err != nil { 16 | return nil, nil, fmt.Errorf("new request failed: %v", err) 17 | } 18 | 19 | accessToken, err := c.getAccessToken() 20 | if err != nil { 21 | return nil, nil, fmt.Errorf("get accessToken failed: %v", err) 22 | } 23 | 24 | bearerToken := fmt.Sprintf("Bearer %s", accessToken) 25 | req.Header.Set("Authorization", bearerToken) 26 | 27 | resp, err := c.doRequest(req) 28 | 29 | if err != nil { 30 | return nil, nil, fmt.Errorf("do request failed: %v", err) 31 | } 32 | defer resp.Body.Close() 33 | 34 | body, err := ioutil.ReadAll(resp.Body) 35 | if err != nil { 36 | return nil, nil, fmt.Errorf("read response body failed: %v", err) 37 | } 38 | 39 | if c.opts.Debug { 40 | log.Printf("http response info: %s\n", body) 41 | } 42 | 43 | res := gjson.ParseBytes(body) 44 | 45 | return &res, resp.Cookies(), nil 46 | } 47 | -------------------------------------------------------------------------------- /option.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | ) 7 | 8 | // Options can set custom options for ChatGPT request client 9 | type Options struct { 10 | // Debug is used to output debug message 11 | Debug bool 12 | // Timeout is used to end http request after timeout duration 13 | Timeout time.Duration 14 | // UserAgent is used for custom user-agent 15 | UserAgent string 16 | // Cookies is request cookies for each api 17 | Cookies []*http.Cookie 18 | // Cookie will set in request headers with string format 19 | Cookie string 20 | // Proxy is used to proxy request 21 | Proxy string 22 | // AccessToken is used to authorization 23 | AccessToken string 24 | // Model is the chat model 25 | Model string 26 | } 27 | 28 | // Option is used to set custom option 29 | type Option func(*Client) 30 | 31 | // WithDebug is used to output debug message 32 | func WithDebug(debug bool) Option { 33 | return func(c *Client) { 34 | c.opts.Debug = debug 35 | } 36 | } 37 | 38 | // WithTimeout is used to set request timeout 39 | func WithTimeout(timeout time.Duration) Option { 40 | return func(c *Client) { 41 | c.opts.Timeout = timeout 42 | } 43 | } 44 | 45 | // WithUserAgent is used to set request user-agent 46 | func WithUserAgent(userAgent string) Option { 47 | return func(c *Client) { 48 | c.opts.UserAgent = userAgent 49 | } 50 | } 51 | 52 | // WithCookies is used to set request cookies 53 | func WithCookies(cookies []*http.Cookie) Option { 54 | return func(c *Client) { 55 | c.opts.Cookies = cookies 56 | } 57 | } 58 | 59 | // WithCookie is used to set request cookies in header 60 | func WithCookie(cookie string) Option { 61 | return func(c *Client) { 62 | c.opts.Cookie = cookie 63 | } 64 | } 65 | 66 | // WithProxy is used to set request proxy 67 | func WithProxy(proxy string) Option { 68 | return func(c *Client) { 69 | c.opts.Proxy = proxy 70 | } 71 | } 72 | 73 | // WithAccessToken is used to set accessToken 74 | func WithAccessToken(accessToken string) Option { 75 | return func(c *Client) { 76 | c.opts.AccessToken = accessToken 77 | } 78 | } 79 | 80 | // WithModel is used to set chat model 81 | func WithModel(model string) Option { 82 | return func(c *Client) { 83 | c.opts.Model = model 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "net/http" 8 | 9 | "github.com/tidwall/gjson" 10 | ) 11 | 12 | // AuthSession will check if session is expired and return a new accessToken 13 | func (c *Client) AuthSession() (*gjson.Result, error) { 14 | req, err := http.NewRequest(http.MethodGet, AUTH_SESSION_URI, nil) 15 | if err != nil { 16 | return nil, fmt.Errorf("new request failed: %v", err) 17 | } 18 | 19 | resp, err := c.doRequest(req) 20 | 21 | if err != nil { 22 | return nil, fmt.Errorf("do request failed: %v", err) 23 | } 24 | defer resp.Body.Close() 25 | 26 | body, err := ioutil.ReadAll(resp.Body) 27 | if err != nil { 28 | return nil, fmt.Errorf("read response body failed: %v", err) 29 | } 30 | 31 | if c.opts.Debug { 32 | log.Printf("http response info: %s\n", body) 33 | } 34 | 35 | res := gjson.ParseBytes(body) 36 | 37 | return &res, nil 38 | } 39 | -------------------------------------------------------------------------------- /token.go: -------------------------------------------------------------------------------- 1 | package chatgpt 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // getAccessToken will return accessToken, if expired than fetch a new one 8 | func (c *Client) getAccessToken() (string, error) { 9 | if c.opts.AccessToken != "" { 10 | return c.opts.AccessToken, nil 11 | } 12 | 13 | // fetch new accessToken 14 | res, err := c.AuthSession() 15 | if err != nil { 16 | return "", fmt.Errorf("fetch new accessToken failed: %v", err) 17 | } 18 | 19 | accessToken := res.Get("accessToken").String() 20 | if accessToken == "" { 21 | return "", fmt.Errorf("invalid session data: %s", accessToken) 22 | } 23 | 24 | return accessToken, nil 25 | } 26 | --------------------------------------------------------------------------------