Electron+Exrepss.js 实现ChatGPT API调用
当前OpenAI只开放了ChatGPT3.5的网页端的免费访问,同时新注册用户能免费使用3个月的API访问,到期收费。 这边想了个办法,使用Electron来操作网页,模拟发送/接收回答;express.js 做http服务器来供外部发送和接收消息。
- 为什么使用electron? electron基于chromium,本质就是浏览器。使用electron可以很方便的在各个网页中注入js,执行我们需要的代码;同时各个网站在反爬虫的同时都需要尽可能保证用户的正常访问,而electron会完整渲染页面,因此electron相比传统的爬虫操作更直观,成功率更高。
准备工作
克隆electron-quick-start,用于快速构建electron项目
git clone https://github.com/electron/electron-quick-start`
npm i
安装express.js
npm i express
开发
编辑main.js
,加入以下内容:
- 使用express注册连个接口,分别用来向网页发送消息和获取网页中的会话内容
- 注册electron的ipc通讯,用来向渲染进程发送消息,操作网页
- 使用代理访问chatgpt的官网 源码:
const { app, BrowserWindow, Menu, ipcMain } = require('electron')
const path = require('node:path')
const express = require('express')
var bodyParser = require('body-parser')
var jsonParser = bodyParser.json()
current_dialogs = []
function jsonMessage(msg) {
return { msg: msg }
}
function AppServer(sendMessage = (msg) => { console.log(msg) }, port = 3000) {
const app = express()
let server = null
app.post("/send", jsonParser, function (req, res) {
try {
const msg = req.body.msg
sendMessage(msg)
res.json(jsonMessage('ok'))
} catch (e) {
console.error(e)
res.status(400)
res.json(jsonMessage("bad request"))
}
})
app.get("/dialogs", function (req, res) {
res.json({ dialogs: current_dialogs })
})
return {
close: function () {
if (server) server.close()
},
start: function () {
server = app.listen(port)
}
}
}
function createMenu(window) {
return Menu.buildFromTemplate(
[
{
label: '选项',
submenu: [
{
label: '打开控制台',
click: () => { window.webContents.openDevTools() }
},
{
label: '刷新',
click: () => { window.reload() }
}
]
}
])
}
function createWindow() {
const mainWindow = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.js')
}
})
const menu = createMenu(mainWindow)
mainWindow.setMenu(menu)
ipcMain.on('receive-dialogs', (event, dialogs) => {
current_dialogs = dialogs
})
mainWindow.webContents.session.setProxy({ proxyRules: "socks5://127.0.0.1:7890" })
.then(() => { mainWindow.loadURL('https://chatgpt.com/') })
let server = AppServer((msg) => {mainWindow.webContents.send('new-msg', msg)})
server.start()
}
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
编辑preload.js
,加入以下内容:
- chatgpt的发送按钮是响应输入框变动的事件的,但是通过js直接操作输入框的元素改变值无法响应该事件。通过增加EVENT和dispatchEvent来触发该响应。(stackoverflow.com/questions/6…
- 注册ipc事件,接收主线程中的消息,操作网页。
- 一个 getDialogsTimer 定时器,每秒获取当前网页中的会话,发送到主线程。
const { ipcRenderer } = require('electron/renderer')
const EVENT_OPTIONS = { bubbles: true, cancelable: false, composed: true };
const EVENTS = {
BLUR: new Event("blur", EVENT_OPTIONS),
CHANGE: new Event("change", EVENT_OPTIONS),
INPUT: new Event("input", EVENT_OPTIONS),
};
ipcRenderer.on('new-msg', (sender, msg) => {
const inputElement = document.querySelectorAll('textarea')[0]
inputElement.value = msg;
inputElement.dispatchEvent(EVENTS.INPUT);
document.querySelectorAll('button')[document.querySelectorAll('button').length-2].click()
})
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
async function getDialogsTimer() {
for (; ;) {
await sleep(1000)
var dialogs = []
for (let o of document.querySelectorAll('p')) {
dialogs.push(o.innerHTML)
}
ipcRenderer.send('receive-dialogs', dialogs)
}
}
getDialogsTimer()
效果
- 使用/send 发送消息
- 使用/dialogs 获取消息
发送和接收的内容会同步显示在网页端:
目前还在原型阶段,还有一些优化点:
- electron内存较大
- 多个对话需要多进程/多端口
- 需要获取对话是否结束
项目地址: github.com/AlpsMonaco/…
转载自:https://juejin.cn/post/7385784332444991499