将 ChatGPT 接入微信并不是最近才做,而是2023年上半年就已经做了,那时候采用官方注册赠送的API,但这个API是有时效的,过期之后服务就中断了。本来也没想折腾的,但是年底报告终结什么的也比较多,想想还是再研究研究,于是便有了这篇博客。
初步分析
真正体验用过 ChatGPT 的人都ba知道 ChatGPT 是有网页版的,而且网页版3.5是可以白嫖的,于是我便瞄准了这个入口。经过初步分析有两条路可走:
- 模拟 Web 端的界面操作,实现输入输出
- 逆向 Web 端的API接口,模拟接口调用
一开始是准备逆向 API 接口的,接口很容易找到,但 curl
模拟请求时总是 403,猜测接口是使用了某些防护机制,后面收集信息证实 Openai 用了 Cloudflare 的反爬虫机制,具体可以参考这个篇文章。
接口调用遇阻,于是便考虑先使用界面模拟,找了下发现了chatgpt.js这个库,该库通过注入 js 实现对 chatgpt 操作。关于注入工具用的是大名顶顶的篡改猴,chatgpt.js 官方甚至贴心的提供了一套模板帮助我们写出想要的功能。
整体流程
确定方向后就要开始构建整体流程了,按照分析 大概有如下两个端
- 浏览器端,负责接收指令消息调用
chatgpt.js
函数
- 微信监听端,负责监听特定的消息,转发给浏览器端,并将结果返回给微信端,关于消息监听已经有现成的库 openwechat,可以直接上手使用。
大致流程如下
graph LR
A(微信) --监听--> B(特定文本) --解析--> C(提问数据)
C --转发--> E(chatgpt.js) --调用--> F(chatgpt) --响应--> G(微信)
服务端
服务端主要有两个功能
- 作为 websocket 服务与浏览器端的
chatgpt.js
连接通信
openwechat
监听特定的微信消息,转发给已连接的 chatgpt.js
调用,并将结果返回
由于涉及 websocket 在浏览器与 openwechat
通讯,这里先定义通信消息体
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
| import ( "strings"
"github.com/eatmoreapple/openwechat" )
func newMsg(msg *openwechat.Message) *chatMsg { req := strings.SplitN(msg.Content, " ", 2) return &chatMsg{ msg: msg, ID: msg.MsgId, Content: req[1], done: make(chan struct{}, 1), } }
type chatMsg struct { msg *openwechat.Message done chan struct{} ID string `json:"id,omitempty"` Content string `json:"content,omitempty"` }
func (m *chatMsg) replay(text string) { m.done <- struct{}{} m.msg.ReplyText(text) }
|
有了消息体,那么就要开始构建一个 websocket 服务用于沟通 chatgpt.js
与 微信
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
| import ( "encoding/json" "fmt" "log" "net" "net/http" "strings" "sync" "time"
"github.com/eatmoreapple/openwechat" "github.com/gobwas/ws" "github.com/gobwas/ws/wsutil" )
type bot struct { web net.Conn msg sync.Map msgs chan *openwechat.Message }
func (b *bot) init() { b.msgs = make(chan *openwechat.Message, 128) go b.process()
bot := openwechat.DefaultBot(openwechat.Desktop) bot.MessageHandler = b.messageHandler bot.UUIDCallback = openwechat.PrintlnQrcodeUrl
if err := bot.Login(); err != nil { fmt.Println(err) return } }
func (b *bot) messageHandler(msg *openwechat.Message) { if !msg.IsText() { return } if b.web == nil { log.Println("web not connect") return } val := msg.Content if !strings.HasPrefix(val, "bot ") { return } b.msgs <- msg }
func (b *bot) process() { for msg := range b.msgs { m := newMsg(msg) b.msg.Store(m.ID, m) data, _ := json.Marshal(m) err := wsutil.WriteServerText(b.web, data) if err != nil { log.Println(err) continue } select { case <-m.done: case <-time.After(2 * time.Minute): log.Println("reply time out") } } }
func (b *bot) http(w http.ResponseWriter, r *http.Request) { ws, _, _, err := ws.UpgradeHTTP(r, w) if err != nil { w.WriteHeader(500) return } b.web = ws for { data, err := wsutil.ReadClientText(ws) if err != nil { log.Println("read client data error", err) break } var resp chatMsg json.Unmarshal(data, &resp) if val, ok := b.msg.LoadAndDelete(resp.ID); ok { (val.(*chatMsg)).replay(resp.Content) } } }
|
以上便完成了服务端的工作,写一个简单的单元测试运行起来
1 2 3 4 5 6 7
| func TestWs(t *testing.T) { mux := http.NewServeMux() b := &bot{} go b.init() mux.HandleFunc("/chatgpt", b.http) http.ListenAndServe(":8000", mux) }
|
扫码登录后,便开始浏览器端的 chatgpt.js
工作。
浏览器端
要开始这部分,需要先安装两个扩展程序
安装之后单击篡改猴扩展程序图标,选择添加新脚本,基于官方模板添加如下内容
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
|
function connect(chatgpt) { const socket = new WebSocket("ws://127.0.0.1:8000/chatgpt"); socket.addEventListener("message", async (event) => { const msg = JSON.parse(event.data); console.log("Message from server ", event.data); const response = await chatgpt.askAndGetReply(msg.content); msg.content = response; config.socket.send(JSON.stringify(msg)) }); }
(async () => { const config = { socket: null }; await chatgpt.isLoaded(); chatgpt.autoRefresh.activate(); connect(chatgpt); })();
|
刷新 ChatGPT 网站,点击 Disable Content-Security-Policy
扩展程序使其处于可用状态。尝试在微信群里发 bot
开头的内容即可触发沟聊天流程。
写在最后
以上只是一个可以运行的简陋版本,实际使用还要加上鉴权重连之类的能力,不过即使完善优化后实际使用效果仍然差强人意,毕竟没有摆脱浏览器的束缚,电脑也要一直开着,着实不够优雅,但也算是实现了相关需求,希望可以帮到大家。