Skip to content

Commit 59308dd

Browse files
committed
init
1 parent c4d2a90 commit 59308dd

File tree

9 files changed

+472
-0
lines changed

9 files changed

+472
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.idea
96.6 KB
Loading

README.md

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# ChatGPT API For Golang
2+
3+
# 一、这是什么
4+
5+
这是一个ChatGPT的Golang API库,让你能够把ChatGPT集成到你的Go应用中,注意这是基于逆向工程实现的并不是一个官方库。
6+
7+
优势:
8+
9+
- 只需要提供自己的JWT就能够像在网页上使用一样在Go程序中使用ChatGPT,很方便的就可以把ChatGPT集成到您开发的各种Go应用中
10+
11+
- 会话维持,ChatGPT是可以保持部分上下文的(这一点上比智障小爱同学强多了...),对于这一点本API库提供了支持,比如:
12+
13+
- ```
14+
我:你好,我的名字叫陈二!
15+
ChatGPT: 你好,陈二!很高兴认识你。我是 Assistant,一个大型语言模型,旨在帮助人们了解更多关于世界的信息。如果你有什么问题,我将尽力回答。
16+
我:我的名字叫什么呀?
17+
ChatGPT:你告诉我你的名字叫陈二。你的名字是一个很普通的中国姓氏和名字,在中国文化中,许多人都有一个姓氏和一个名字。例如,陈是一个常见的姓氏,而二是一个常见的名字。不过,每个人的名字都是独一无二的,所以你的名字只属于你自己。
18+
```
19+
20+
甚至在你的JWT次数用尽之后可以换一个JWT继续维持会话。
21+
22+
# 二、安装
23+
24+
```bash
25+
go get -u github.com/golang-infrastructure/go-ChatGPT
26+
```
27+
28+
# 三、如何使用
29+
30+
## 3.1 如何获取ChatGPT的JWT?
31+
32+
首先你要注册成功一个openapi的账号(教程此处不提供,请自行Google),并且能够在网页上正常使用ChatGPT,然后在ChatGPT的聊天页面,地址是这个:
33+
34+
```
35+
https://chat.openai.com/chat
36+
```
37+
38+
按F12打开控制台,粘贴如下代码,即可看到自己的JWT Token:
39+
40+
```js
41+
JSON.parse(document.getElementById("__NEXT_DATA__").text).props.pageProps.accessToken
42+
```
43+
44+
如图:
45+
46+
![image-20221207201626228](README.assets/image-20221207201626228.png)
47+
48+
## 3.2 API代码示例
49+
50+
```go
51+
package main
52+
53+
import (
54+
"fmt"
55+
chatgpt "github.com/golang-infrastructure/go-ChatGPT"
56+
)
57+
58+
func main() {
59+
60+
// 把JWT放到这里
61+
jwt := "xxx"
62+
63+
chat := chatgpt.NewChat(jwt)
64+
talk, err := chat.Talk("你好,我的名字叫陈二!")
65+
if err != nil {
66+
fmt.Println(err.Error())
67+
return
68+
}
69+
fmt.Println(talk.Message.Content)
70+
71+
talk, err = chat.Talk("我的名字叫什么呀?")
72+
if err != nil {
73+
fmt.Println(err.Error())
74+
return
75+
}
76+
fmt.Println(talk.Message.Content)
77+
78+
// Output:
79+
// {text [你好,陈二!很高兴认识你。我是 Assistant,一个大型语言模型,旨在帮助人们了解更多关于世界的信息。如果你有什么问题,我将尽力回答。]}
80+
// {text [你告诉我你的名字叫陈二。你的名字是一个很普通的中国姓氏和名字,在中国文化中,许多人都有一个姓氏和一个名字。例如,陈是一个常见的姓氏,而二是一个常见的名字。不过,每个人的名字都是独一无二的,所以你的名字只属于你自己。]}
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+

chatgpt.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package chatgpt
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"github.com/PuerkitoBio/goquery"
7+
"github.com/go-resty/resty/v2"
8+
if_expression "github.com/golang-infrastructure/go-if-expression"
9+
"github.com/golang-infrastructure/go-pointer"
10+
"github.com/google/uuid"
11+
"strings"
12+
)
13+
14+
// ------------------------------------------------- 请求相关 ------------------------------------------------------------
15+
16+
type Request struct {
17+
Action string `json:"action"`
18+
Messages []RequestMessage `json:"messages"`
19+
ConversationID *string `json:"conversation_id"`
20+
ParentMessageID *string `json:"parent_message_id"`
21+
Model string `json:"model"`
22+
}
23+
24+
type RequestMessage struct {
25+
ID string `json:"id"`
26+
Role string `json:"role"`
27+
Content RequestContent `json:"content"`
28+
}
29+
30+
type RequestContent struct {
31+
ContentType string `json:"content_type"`
32+
Parts []string `json:"parts"`
33+
}
34+
35+
func NewRequest(question, conversationID, parentMessageID string) *Request {
36+
return &Request{
37+
Action: "next",
38+
ConversationID: pointer.ToPointerOrNil(conversationID),
39+
Messages: []RequestMessage{
40+
{
41+
ID: uuid.New().String(),
42+
Role: "user",
43+
Content: RequestContent{
44+
ContentType: "text",
45+
Parts: []string{question},
46+
},
47+
},
48+
},
49+
ParentMessageID: pointer.ToPointerOrNil(parentMessageID),
50+
Model: "text-davinci-002-render",
51+
}
52+
}
53+
54+
// ------------------------------------------------- 响应相关 ------------------------------------------------------------
55+
56+
type Response struct {
57+
Message ResponseMessage `json:"message"`
58+
ConversationID string `json:"conversation_id"`
59+
Error any `json:"error"`
60+
}
61+
62+
type ResponseMessage struct {
63+
ID string `json:"id"`
64+
Role string `json:"role"`
65+
User any `json:"user"`
66+
CreateTime any `json:"create_time"`
67+
UpdateTime any `json:"update_time"`
68+
Content ResponseContent `json:"content"`
69+
EndTurn any `json:"end_turn"`
70+
Weight float64 `json:"weight"`
71+
Metadata ResponseMetadata `json:"metadata"`
72+
Recipient string `json:"recipient"`
73+
}
74+
75+
type ResponseContent struct {
76+
ContentType string `json:"content_type"`
77+
Parts []string `json:"parts"`
78+
}
79+
80+
type ResponseMetadata struct {
81+
}
82+
83+
// ------------------------------------------------- --------------------------------------------------------------------
84+
85+
const ConversationAPIURL = "https://chat.openai.com/backend-api/conversation"
86+
87+
type ChatGPT struct {
88+
jwt string
89+
authorization string
90+
91+
userAgent string
92+
93+
conversationID string
94+
parentMessageID string
95+
}
96+
97+
func NewChat(jwt string) *ChatGPT {
98+
return &ChatGPT{
99+
jwt: jwt,
100+
authorization: "Bearer " + jwt,
101+
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
102+
}
103+
}
104+
105+
func (x *ChatGPT) Talk(question string) (*Response, error) {
106+
conversationID := if_expression.Return(x.conversationID != "", x.conversationID, "")
107+
parentMessageID := if_expression.Return(x.parentMessageID != "", x.parentMessageID, uuid.New().String())
108+
response, err := x.sendConversationMessage(question, conversationID, parentMessageID)
109+
if err != nil {
110+
return nil, err
111+
}
112+
x.conversationID = response.ConversationID
113+
x.parentMessageID = response.Message.ID
114+
return response, nil
115+
}
116+
117+
func (x *ChatGPT) sendConversationMessage(question, conversationID, parentMessageID string) (*Response, error) {
118+
request := NewRequest(question, conversationID, parentMessageID)
119+
requestBytes, err := json.Marshal(request)
120+
if err != nil {
121+
return nil, err
122+
}
123+
response, err := resty.
124+
New().
125+
R().
126+
SetHeader("User-Agent", x.userAgent).
127+
SetHeader("Authorization", x.authorization).
128+
SetHeader("Content-Type", "application/json").
129+
SetBody(string(requestBytes)).
130+
Post(ConversationAPIURL)
131+
if err != nil {
132+
return nil, err
133+
}
134+
responseString := response.String()
135+
arr := strings.Split(responseString, "\n\n")
136+
index := len(arr) - 3
137+
if index >= len(arr) || index < 1 {
138+
return nil, x.buildErrMessage(responseString)
139+
}
140+
arr = strings.Split(arr[index], "data: ")
141+
if len(arr) < 2 {
142+
return nil, x.buildErrMessage(responseString)
143+
}
144+
chatResponse := &Response{}
145+
if err = json.Unmarshal([]byte(arr[1]), chatResponse); err != nil {
146+
return nil, err
147+
}
148+
return chatResponse, nil
149+
}
150+
151+
// 在调用失败的时候尝试返回更友好的信息
152+
func (x *ChatGPT) buildErrMessage(responseString string) error {
153+
errContent := x.tryExtractErrorMessage(responseString)
154+
if errContent != "" {
155+
return errors.New(errContent)
156+
} else {
157+
return errors.New(responseString)
158+
}
159+
}
160+
161+
// 尝试从响应中抽取出错误信息
162+
func (x *ChatGPT) tryExtractErrorMessage(responseString string) string {
163+
doc, err := goquery.NewDocumentFromReader(strings.NewReader(responseString))
164+
if err != nil {
165+
return ""
166+
}
167+
return doc.Find("#content").Text()
168+
}
169+
170+
func (x *ChatGPT) GetConversationID() string {
171+
return x.conversationID
172+
}
173+
174+
func (x *ChatGPT) SetConversationID(conversationID string) {
175+
x.conversationID = conversationID
176+
}
177+
178+
func (x *ChatGPT) GetParentMessageID() string {
179+
return x.parentMessageID
180+
}
181+
182+
func (x *ChatGPT) SetParentMessageID(parentMessageID string) {
183+
x.parentMessageID = parentMessageID
184+
}
185+
186+
func (x *ChatGPT) GetUserAgent() string {
187+
return x.userAgent
188+
}
189+
190+
func (x *ChatGPT) SetUserAgent(userAgent string) {
191+
x.userAgent = userAgent
192+
}
193+
194+
func (x *ChatGPT) SetJWT(jwt string) {
195+
x.jwt = jwt
196+
x.authorization = "Bearer " + jwt
197+
}

chatgpt_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package chatgpt
2+
3+
import (
4+
"github.com/stretchr/testify/assert"
5+
"testing"
6+
)
7+
8+
func TestFoo(t *testing.T) {
9+
10+
// 把JWT Token放到这里
11+
jwt := "xxxx"
12+
13+
chat := NewChat(jwt)
14+
talk, err := chat.Talk("你好,我的名字叫陈二!")
15+
assert.Nil(t, err)
16+
t.Log(talk.Message.Content)
17+
18+
talk, err = chat.Talk("我的名字叫什么呀?")
19+
assert.Nil(t, err)
20+
t.Log(talk.Message.Content)
21+
22+
// Output:
23+
// chatgpt_test.go:15: {text [你好陈二,很高兴认识你!]}
24+
// chatgpt_test.go:19: {text [你已经告诉我你的名字是陈二了。你可以直接说:“我的名字叫陈二。”]}
25+
26+
}

data/err-response.html

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!DOCTYPE html PUBLIC '-//W3C//DTD XHTML 1.0 Transitional//EN'
2+
'http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd'>
3+
<html xmlns='http://www.w3.org/1999/xhtml'>
4+
<head>
5+
<meta content='text/html; charset=utf-8' http-equiv='content-type'/>
6+
<style type='text/css'>body {
7+
font-family: Arial;
8+
margin-left: 40px;
9+
}
10+
11+
img {
12+
border: 0 none;
13+
}
14+
15+
#content {
16+
margin-left: auto;
17+
margin-right: auto
18+
}
19+
20+
#message h2 {
21+
font-size: 20px;
22+
font-weight: normal;
23+
color: #000000;
24+
margin: 34px 0px 0px 0px
25+
}
26+
27+
#message p {
28+
font-size: 13px;
29+
color: #000000;
30+
margin: 7px 0px 0 px0px
31+
}
32+
33+
#errorref {
34+
font-size: 11px;
35+
color: #737373;
36+
margin-top: 41px
37+
}</style>
38+
<title>Service unavailable</title></head>
39+
<body>
40+
<div id='content'>
41+
<div id='message'><h2>Our services aren't available right now</h2>
42+
<p>We're working to restore all services as soon as possible. Please check back soon.</p></div>
43+
<div id='errorref'><span>0b36QYwAAAABq02pIbjvPR56/Jwe1K0HZV1NURURHRTAxMDcAZTY2YjhiMDMtMDc5My00NDA5LTk3NzMtMmU2MTJlNzFhMWUz</span>
44+
</div>
45+
</div>
46+
</body>
47+
</html>

0 commit comments

Comments
 (0)