让闲置的公众号接入完全免费的GPT

导语:为什么说是闲置?因为公众号的接口被设计为5s超时,而GPT的回复是肯定有延迟的,所以建议使用闲置公众号,实现:使用Laf 云平台将 ChatGPT 接入微信公众号,每次问答后通过回复1来查看上下文信息

发现自:https://forum.laf.run/d/364

体验:

1. 准备工作

首先需要注册一个 Laf 平台账号:https://laf.run

注册登录之后,点击新建,建立一个应用:

img

点击开发,进入应用开发界面:

img

然后输入 chatgpt 并回车进行搜索,选择第一个搜索结果,保存并重启:

img

重启之后,自定义依赖项中便出现了 chatgpt。

img

新建云函数

然后我们点击函数,函数列表右侧的加号,新增一个可以接入微信公众号的 ChatGPT 云函数:

img

云函数完整代码如下:

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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
// 引入crypto和cloud模块import * as crypto from 'crypto';
import cloud from '@lafjs/cloud';

const WAIT_MESSAGE = `处理中 ... \n\n请稍等3秒后发送【1】查看回复`const NO_MESSAGE = `暂无内容,请稍后回复【1】再试`const CLEAR_MESSAGE = ` 记忆已清除`const HELP_MESSAGE = `ChatGPT 指令使用指南
| 关键字 | 功能 |
| 1 | 上一次问题的回复 |
| /clear | 清除上下文 |
| /help | 获取更多帮助 |
`// 不支持的消息类型const UNSUPPORTED_MESSAGE_TYPES = {
image: '暂不支持图片消息',
voice: '暂不支持语音消息',
video: '暂不支持视频消息',
music: '暂不支持音乐消息',
news: '暂不支持图文消息',
}

// 定义休眠函数const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

// 创建数据库连接并获取Message集合const db = cloud.database();
const Message = db.collection('messages')

// 处理接收到的微信公众号消息export async function main(event) {
const { signature, timestamp, nonce, echostr } = event.query;
const token = '123456';

// 验证消息是否合法,若不合法则返回错误信息
if (!verifySignature(signature, timestamp, nonce, token)) {
return 'Invalid signature';
}

// 如果是首次验证,则返回 echostr 给微信服务器
if (echostr) {
return echostr;
}

// 处理接收到的消息
const payload = event.body.xml;
// console.log("payload",payload)
// 文本消息
if (payload.msgtype[0] === 'text') {
const newMessage = {
msgid: payload.msgid[0],
question: payload.content[0].trim(),
username: payload.fromusername[0],
sessionId: payload.fromusername[0],
createdAt: Date.now()
}

// 修复请求响应超时问题:如果 5 秒内 AI 没有回复,则返回等待消息
const responseText = await Promise.race([
replyText(newMessage),
sleep(4000.0).then(() => WAIT_MESSAGE),
]);
return toXML(payload, responseText);
}

// 公众号事件
if (payload.msgtype[0] === 'event') {
// 公众号订阅
if (payload.event[0] === 'subscribe') {
return toXML(payload, HELP_MESSAGE);
}
}

// 暂不支持的消息类型
if (payload.MsgType in UNSUPPORTED_MESSAGE_TYPES) {
const responseText = UNSUPPORTED_MESSAGE_TYPES[payload.MsgType];
return toXML(payload, responseText);
}

return 'success'
}


// 处理文本回复消息async function replyText(message) {
const { question, sessionId } = message;
console.log("replyText 执行了")
// 检查是否是重试操作,如果是重试操作,返回上一次的回复
if (question === '1') {
const lastMessage = await Message.where({
sessionId
}).orderBy("createdAt", "desc").get();
if (lastMessage.data[0]) {
return `${lastMessage.data[0].question}\n------------\n${lastMessage.data[0].answer}`;
}

return NO_MESSAGE;
}

// 获取上下文 id
const res = await Message.where({
sessionId
}).orderBy("createdAt", "desc").getOne();

const parentId = res?.data?.parentMessageId
const conId = res?.data?.conversationId


// 发送指令
if (question.startsWith('/')) {
return await processCommandText(message);
}

// 获取 OpenAI 回复内容
const { error, answer, parentMessageId, conversationId } = await getOpenAIReply(question, parentId, conId);
if (error) {
console.error(`sessionId: ${sessionId}; question: ${question}; error: ${error}`);
return error;
}

// 将消息保存到数据库中
const token = question.length + answer.length;
const result = await Message.add({ token, answer, parentMessageId, conversationId, ...message });
console.debug(`[save message] result: ${result}`);

return answer;
}

// 获取 OpenAI API 的回复async function getOpenAIReply(question, parentId, conId) {
console.log("getOpenAIReply 执行了")
// 引入 ChatGPTUnofficialProxyAPI 模块
const { ChatGPTUnofficialProxyAPI } = await import('chatgpt')
// 创建 ChatGPTUnofficialProxyAPI 实例
const api = new ChatGPTUnofficialProxyAPI({
accessToken: cloud.env.ACCESSTOKEN,
apiReverseProxyUrl: "https://bypass.churchless.tech/api/conversation"
})


try {
// 如果有上下文 id,就带上
let res;

if (parentId && conId) {
res = await api.sendMessage(question, { conversationId: conId, parentMessageId: parentId })
} else {
res = await api.sendMessage(question)
}
// 返回 OpenAI 回复的内容及上下文 id
return { answer: res.text.replace("\n\n", ""), parentMessageId: res.parentMessageId, conversationId: res.conversationId }

} catch (e) {
console.log(e)
return {
error: "问题太难了 出错了. (uДu〃).",
}
}

}

// 校验微信服务器发送的消息是否合法function verifySignature(signature, timestamp, nonce, token) {
const arr = [token, timestamp, nonce].sort();
const str = arr.join('');
const sha1 = crypto.createHash('sha1');
sha1.update(str);
return sha1.digest('hex') === signature;
}

// 返回组装 xmlfunction toXML(payload, content) {
const timestamp = Date.now();
const { tousername: fromUserName, fromusername: toUserName } = payload;
return `
<xml>
<ToUserName><![CDATA[${toUserName}]]></ToUserName>
<FromUserName><![CDATA[${fromUserName}]]></FromUserName>
<CreateTime>${timestamp}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[${content}]]></Content>
</xml>
`
}

async function processCommandText({ sessionId, question }) {
// 清理历史会话
if (question === '/clear') {
const res = await Message.where({ sessionId }).remove({ multi: true })
return CLEAR_MESSAGE;
} else {
return HELP_MESSAGE;
}
}

由于 OpenAI 的 API Key 需要充值才能用,所以我们选择剑走偏锋,直接使用 ChatGPT 网页版。但是国内环境无法访问 ChatGPT,所以我们需要一个 Proxy。不用担心,国外已经有热心小哥给我们提供了公共的 Proxy,我们只需要直接调用就好啦!

img

详情可参考 ChatGPTUnofficialProxyAPI 的使用文档

只要你有 ChatGPT 账号,都可以使用这种方法

核心函数:

  • getOpenAIReply函数通过引入ChatGPTUnofficialProxyAPI模块,创建实例并调用其方法,获取 ChatGPT 的回复内容。
  • verifySignature函数用于校验微信服务器发送的消息是否合法。
  • toXML函数将回复内容组装成XML格式。
  • processCommandText函数用于处理指令,目前支持清除历史会话获取帮助信息两个指令。

注意:

  • token要与微信公众号中设置一致。
  • ACCESSTOKEN 的获取方式:你需要先在浏览器中登录 ChatGPT 网页版,然后在浏览器中访问这个 URL:https://chat.openai.com/api/auth/session,浏览器会返回一个 JSON,里面包含了 accessToken 字段,将这个字段的值复制即可。

云函数写完之后就点击发布,左侧的接口地址要保存一下,稍后将在微信公众号中使用此地址。

img

配置微信公众号

这一步我们需要在微信公众号平台上配置开发者信息,并将服务器地址设置为部署好的云函数服务地址。步骤如下:

首先你需要有一个公众号,然后登录微信公众平台,点开左侧的「设置与开发」,点击「基本设置」,然后点击「服务器配置」,服务器配置那里点击修改配置:

img

将云函数服务地址复制到「服务器 URL」中,下边的 Token 与云函数代码中的 token 保持一致,下边的 EncodingAESKey 点击右侧随机生成就行,然后点击提交:

img

返回 token 校验成功后,点击「启用」即可。

img

现在你已经完成了所有必要的设置和配置,下面就可以直接进入微信公众号「Laf 开发者」后台与机器人进行交互啦!

img

ChatGPT 机器人可以回答用户提出的问题,并且可以根据用户提供的上下文进行回复。以下是一些指令和关键字,可以帮助您更好地使用 ChatGPT 机器人:

  • 【1】:获取上一次问题的回复。
  • /clear:清除上下文。
  • /help:获取更多帮助。 除了以上指令和关键字外,你还可以根据自己的需求进行定制化开发,以满足用户的需求。

可参考公众号对接GPT作者的仓库:

注意

关于环境变量设置:在左下角设置中添加即可

token在代码中修改并和公众号保持一致


如果你举得延迟影响体验,你也可以试试将GPT接入个人微信【使用网页版接口】

如像我一样无论在群聊还是私聊中,通过设置关键词触发自动回复

开源:https://github.com/zhayujie/chatgpt-on-wechat