1. 项目概述一个让ChatGPT“听话”的浏览器脚本库如果你经常和ChatGPT的网页版打交道无论是用它来辅助写作、编程还是日常问答你肯定遇到过一些不那么“顺手”的时刻。比如想一键复制某个精彩的回复却发现按钮藏得很深或者想批量导出对话历史却只能手动一页页复制粘贴又或者你希望界面能更清爽去掉那些用不上的侧边栏元素。这些需求官方界面往往没有提供直接的解决方案。这就是KudoAI/chatgpt.js这个项目诞生的背景。它不是一个独立的聊天机器人而是一个纯粹的、运行在浏览器环境中的JavaScript库。你可以把它理解为一套功能强大的“瑞士军刀”专门用来与ChatGPT的官方网页界面进行交互。它的核心目标是让开发者能够通过编程的方式自动化地操作ChatGPT界面提取数据甚至改变其外观和行为从而极大地提升使用效率和定制化程度。简单来说有了chatgpt.js你就能让ChatGPT的网页版“听你的话”。无论是个人用户想打造一个更顺手的自动化工作流还是开发者想基于ChatGPT的对话能力构建更复杂的应用比如集成到自己的工具里或者做数据分析这个库都提供了一个稳定、可靠的底层接口。它绕过了官方可能不提供的API直接从用户可见的网页入手实现了高度的灵活性和控制力。2. 核心设计思路与技术选型2.1 为什么选择浏览器扩展/用户脚本这条路要理解chatgpt.js的设计首先要明白它面对的核心挑战ChatGPT的官方网页是一个动态生成的单页应用SPA其DOM结构、类名、ID都可能随着前端的更新而改变。官方没有提供用于此类自动化操作的公开API。面对这个挑战通常有几种思路逆向工程官方API直接调用其内部通信接口。这种方式最直接但风险极高一旦官方变更接口或加密方式脚本就会立即失效且可能违反服务条款。模拟用户操作使用像Puppeteer、Playwright这样的无头浏览器进行自动化。这种方式稳定但重量级需要运行一个浏览器实例不适合轻量级、实时的用户侧脚本。DOM操作与事件监听直接与浏览器中已加载的页面DOM进行交互。这正是chatgpt.js选择的道路。它作为一个内容脚本Content Script或用户脚本User Script如Tampermonkey、Violentmonkey注入到ChatGPT的页面中与页面共享同一个DOM环境。选择DOM操作路线的核心考量轻量与实时脚本随页面加载而注入响应速度快无需额外进程。用户友好最终用户只需安装一个浏览器扩展用于管理用户脚本即可使用基于此库开发的各种功能门槛极低。高灵活性可以操作任何可见元素修改样式、绑定事件、提取数据几乎无所不能。相对稳定虽然DOM结构会变但页面的核心功能区域输入框、对话列表、发送按钮的基本交互模式相对稳定。库可以通过健壮的选择器策略和错误处理来应对变化。2.2 架构设计模块化与可扩展性chatgpt.js的代码库采用了清晰的模块化设计这不是一个把所有功能堆在一起的巨型脚本。查看其源码你会发现它通常包含以下几个核心部分核心模块 (core.js或类似)定义最基础的、与页面版本无关的通用函数。例如检测页面是否已加载完成、获取根容器元素、提供等待特定元素出现的工具函数等。这是库的基石。DOM选择器模块 (selectors.js)这是最关键也是最需要维护的部分。它集中定义了所有用于定位页面元素的选择器字符串。例如如何找到对话历史容器、单个消息气泡、用户头像、输入文本框、发送按钮等等。将这些选择器集中管理是为了当ChatGPT前端更新时开发者只需修改这个模块中的选择器而无需改动所有业务逻辑代码。功能模块 (如conversation.js,ui.js,utils.js)基于核心模块和选择器模块实现具体的功能。例如conversation.js: 提供获取当前对话列表、获取最后一条消息、发送消息等方法。ui.js: 提供添加自定义按钮、修改界面样式、显示通知等方法。utils.js: 提供防抖、节流、数据格式化等工具函数。入口与初始化一个主文件负责在合适的时机通常是页面加载完成后初始化库并可能对外暴露一个全局对象如window.chatgptJS供其他用户脚本调用。这种架构的好处是显而易见的解耦和可维护性。功能开发者和脚本使用者不需要关心底层的DOM选择器是如何工作的他们只需要调用诸如chatgpt.getLastResponse()这样的高级API。当页面变化时维护者只需尽力更新selectors.js理论上就能让所有依赖此库的脚本恢复工作。注意这种基于DOM操作的方式其稳定性完全依赖于对ChatGPT网页结构的“猜测”和“适配”。因此chatgpt.js项目本身或其下游脚本在ChatGPT前端重大更新后出现“罢工”是常态而非异常。其价值在于提供了一个统一的、经过一定封装的适配层降低了整个生态的维护成本。3. 核心API与功能深度解析让我们深入看看chatgpt.js通常会提供哪些核心能力。这些API是构建一切高级功能的基础。3.1 对话管理这是库最核心的功能之一旨在让程序能“读懂”和“操作”对话。getConversationHistory(): 此函数会扫描整个对话面板将每条消息包括用户的和AI的解析成一个结构化的数组。每个消息对象可能包含角色user/assistant、内容文本纯文本或HTML、时间戳、唯一的消息ID等。实现它需要遍历类似.group或.text-base的容器并区分用户消息和AI消息通常通过头像、特定类名或容器位置判断。实操难点消息内容可能包含代码块、表格等复杂格式。一个健壮的实现需要能提取出纯净的文本或者保留必要的格式信息。有时还需要处理消息流式输出未完成的情况。getLastMessage()/getLastResponse(): 这两个是高频使用的快捷方法。getLastMessage通常返回最后一条消息无论谁发的而getLastResponse特指ChatGPT的最后一条回复。实现它们需要对getConversationHistory的结果进行过滤和排序。sendMessage(text): 自动化发送消息。其实现步骤是1) 找到文本输入框可能是textarea或div[contenteditable]。2) 将焦点设置到该输入框。3) 模拟输入文本直接设置value或触发input事件。4) 找到并点击发送按钮或模拟按下Enter键。这里的关键是必须触发正确的DOM事件让ChatGPT的前端框架能感知到输入变化和提交动作。注意事项直接设置input.value可能不会触发React等框架的状态更新。更可靠的做法是input.focus(); input.value text; const event new Event(input, { bubbles: true }); input.dispatchEvent(event);。3.2 界面操控与增强让界面变得更符合个人需求。addButton(config): 在页面的指定位置如输入框旁、消息旁添加一个自定义按钮。config参数需要指定按钮文本、CSS类、点击回调函数以及插入的位置选择器。这个功能极大地扩展了可能性比如可以添加“一键润色”、“导出对话”、“翻译此条”等按钮。实现细节创建button元素应用样式绑定click事件然后使用insertAdjacentElement或appendChild将其插入到目标元素附近。需要小心处理事件冒泡避免干扰原有功能。modifyUI(cssRules): 通过注入CSS规则来修改页面样式。可以用于隐藏元素如侧边栏、水印、改变字体、调整布局等。实现方式通常是创建一个style标签将CSS规则写入然后插入到文档的head中。心得为了确保样式优先级足够高可能需要使用!important声明或者更精细地复制原始选择器并覆盖其属性。showNotification(message, type): 在页面一角显示一个临时的提示框Toast用于反馈操作结果。这属于提供良好的用户交互体验。3.3 状态与事件监听感知页面的变化是实现自动化的关键。onMessageReceived(callback): 注册一个监听器当页面中出现新的AI回复消息时触发回调。这个功能对于实现“自动回复”、“消息触发特定动作”等场景至关重要。实现原理通常使用MutationObserverAPI来监视对话容器DOM节点的变化。当检测到有新的、符合AI消息特征的元素被添加时就触发回调。这是一个相对高级且消耗性能的操作需要精心设计观察目标和过滤条件避免过度触发。isLoading(): 返回一个布尔值指示ChatGPT是否正在生成回复即是否处于“正在输入…”状态。这可以通过检查是否存在旋转的指示器图标或特定的加载状态类来实现。getCurrentModel(): 尝试获取当前对话所使用的模型如GPT-3.5 GPT-4。这个信息有时会显示在输入框附近或页面标题中但官方可能不会轻易暴露实现此功能可能不稳定。4. 基于chatgpt.js的典型应用场景与实操理解了核心API我们就可以看看如何用它们来构建实用的工具。以下是一些典型场景和实现思路。4.1 场景一构建对话历史导出器需求将一次完整的对话导出为结构化的文件如Markdown、JSON或纯文本方便存档或分享。实现步骤等待与初始化确保页面和chatgpt.js库已加载完成。获取历史调用chatgpt.getConversationHistory()获得消息数组。数据清洗与格式化Markdown格式将用户消息前加上### 用户AI消息前加上### ChatGPT。对于AI消息中的代码块原样保留 。可以尝试将一些HTML标签如strong转换为Markdown语法**。JSON格式直接将消息数组用JSON.stringify序列化结构清晰便于程序后续处理。提供导出界面使用chatgpt.addButton在页面合适位置添加一个“导出对话”按钮。点击后触发格式化逻辑然后使用浏览器API创建下载。// 伪代码示例 chatgpt.addButton({ text: 导出为Markdown, position: near-input, // 假设库支持这个位置 onClick: async () { const history await chatgpt.getConversationHistory(); let markdown # 对话记录\n\n; history.forEach(msg { const role msg.role user ? 用户 : ChatGPT; markdown ### ${role}\n\n${msg.content}\n\n---\n\n; }); // 创建Blob并下载 const blob new Blob([markdown], { type: text/markdown }); const url URL.createObjectURL(blob); const a document.createElement(a); a.href url; a.download chatgpt-conversation-${Date.now()}.md; a.click(); URL.revokeObjectURL(url); chatgpt.showNotification(导出成功, success); } });处理复杂内容AI的回复可能包含复杂的富文本。简单的innerText会丢失格式而innerHTML又包含太多标签。一个折中方案是使用DOMParser将AI回复的HTML片段解析然后递归提取文本并对特定标签如code,pre进行特殊处理转换为Markdown。4.2 场景二实现自动化交互脚本需求自动向ChatGPT发送一系列预设问题并收集其回答用于批量测试或数据收集。实现步骤定义任务队列创建一个包含多个问题的数组。监听回复完成使用chatgpt.onMessageReceived监听新回复。当收到一个回复后将其保存下来。控制发送节奏发送第一个问题。在收到回复的回调中延迟一段时间例如2-3秒模拟人类阅读并避免请求过快然后从队列中取出下一个问题调用chatgpt.sendMessage发送。错误处理与恢复在每次发送和等待时检查chatgpt.isLoading()和页面状态。使用try...catch包裹关键操作。可以考虑将当前进度问题索引、已收集的答案保存到localStorage这样即使页面刷新或脚本意外停止也能从中断处恢复。const questions [解释量子计算, 用Python写一个快速排序, 莎士比亚的生平]; const answers []; let currentIndex 0; function askNextQuestion() { if (currentIndex questions.length) { console.log(所有问题已完成, answers); chatgpt.showNotification(自动化任务完成, info); return; } const question questions[currentIndex]; chatgpt.sendMessage(question).then(() { console.log(已发送: ${question}); }); } // 监听回复 chatgpt.onMessageReceived((message) { answers.push({ question: questions[currentIndex], answer: message.content }); currentIndex; // 等待2秒后问下一个问题 setTimeout(askNextQuestion, 2000); }); // 开始 askNextQuestion();重要提示此类自动化脚本应严格遵守ChatGPT的使用政策避免用于制造垃圾请求、进行恶意爬取或任何可能对服务造成压力的行为。务必添加合理的延迟并仅用于个人合法的学习和测试目的。4.3 场景三创建个性化UI增强需求觉得官方界面太宽想实现一个“聚焦模式”隐藏侧边栏和其他干扰元素让对话区域居中并变宽。实现步骤分析页面结构使用浏览器开发者工具找到侧边栏、主对话区、顶部导航栏等元素的选择器。编写CSS编写用于隐藏和调整样式的CSS规则。/* 隐藏侧边栏 */ nav, aside, [data-testid*sidebar] { display: none !important; } /* 主容器占满宽度 */ .main-container-class { max-width: 100% !important; margin: 0 auto !important; } /* 调整对话区域宽度 */ .conversation-container-class { max-width: 900px !important; /* 调整为更宽的固定值 */ }动态注入样式在脚本初始化时调用chatgpt.modifyUI注入上述CSS。chatgpt.modifyUI( nav, aside, [data-testid*sidebar] { display: none !important; } .main-container-class { max-width: 100% !important; margin: 0 auto; } .conversation-container-class { max-width: 900px !important; } );添加切换按钮为了能随时恢复原状可以添加一个按钮来切换这个模式。这需要脚本动态维护一个状态变量并在点击时注入或移除对应的CSS规则。5. 开发与使用中的避坑指南基于DOM操作和逆向工程这条路充满了“坑”。以下是我在实际使用和开发类似工具中积累的一些关键经验。5.1 选择器策略稳健性高于一切ChatGPT前端的一次小更新就可能让你的选择器全军覆没。以下策略能提升稳健性避免使用绝对定位的选择器如div div div:nth-child(3) button。这种选择器极其脆弱。优先使用属性选择器寻找元素上稳定的>async function findElement(selectors, timeout 5000) { const startTime Date.now(); while (Date.now() - startTime timeout) { for (const selector of selectors) { const el document.querySelector(selector); if (el) return el; } await new Promise(resolve setTimeout(resolve, 100)); // 等待100ms再试 } throw new Error(找不到元素尝试的选择器: ${selectors.join(, )}); } // 使用 const sendButton await findElement([ [data-testidsend-button], button[aria-label*Send], button:has(svg) text/发送|Send/i ]);5.2 处理动态加载与流式输出ChatGPT的消息是流式输出的这意味着一条消息的DOM元素会持续变化。监听消息完成判断一条消息是否输出完毕不能只看元素是否存在。可以监听元素内部文本是否在一段时间内如1.5秒不再变化或者寻找表示“停止生成”的图标出现。获取完整内容对于流式输出中的消息直接取innerText可能只能拿到已输出的部分。一个更可靠的方法是等待消息“完成”后再去获取其内容。chatgpt.js的onMessageReceived回调应该只在消息最终完成时才触发。5.3 版本兼容性与更新维护建立监控机制作为脚本开发者可以设置简单的自动化测试定期访问ChatGPT页面并运行核心功能检查一旦失败就发出警报。社区协作chatgpt.js这类项目往往依赖社区。在GitHub上积极提交Issue报告失效的选择器或提交Pull Request修复是项目存活的关键。为用户提供降级提示在你的用户脚本中如果检测到核心功能失效应向用户显示友好的错误提示例如“检测到ChatGPT界面已更新脚本部分功能可能失效请等待作者更新”。5.4 安全与隐私考量权限最小化如果你在开发浏览器扩展在manifest.json中只申请必需的权限如activeTab,storage。对于用户脚本也要明确告知用户脚本会访问哪些页面和数据。谨慎处理对话数据你的脚本可以访问所有对话内容。务必在隐私政策中说明数据如何处理通常应承诺所有处理仅在本地浏览器进行不上传任何数据。代码也应开源接受监督。避免干扰正常服务自动化脚本应设置合理的请求间隔避免触发ChatGPT的反爬虫或滥用机制。6. 常见问题排查实录在实际使用基于chatgpt.js的脚本时你可能会遇到以下问题问题现象可能原因排查步骤与解决方案脚本完全不起作用控制台无错误1. 脚本管理器未正确安装或启用。2. 脚本的match或include元数据未匹配当前ChatGPT URL。3.chatgpt.js库未成功加载或初始化。1. 检查Tampermonkey等扩展是否启用脚本是否启用。2. 检查脚本头部元数据确保包含https://chat.openai.com/*。3. 打开浏览器开发者工具(F12)的“控制台”(Console)和“网络”(Network)标签查看是否有加载错误或404。部分功能失效如找不到发送按钮ChatGPT前端DOM结构已更新脚本内的选择器失效。1. 打开开发者工具的元素审查(Inspector)手动尝试脚本中使用的选择器看是否能找到元素。2. 寻找元素新的稳定属性如>能发送消息但AI不回复1. 发送消息后未正确触发提交事件。2. 输入框是contenteditable的div直接设置value无效。3. 页面处于非活跃状态如另一个标签页。1. 确保在设置文本后不仅触发input事件对于contenteditable元素可能需要触发keydown,keyup,change等事件组合。2. 对于div[contenteditable]尝试element.focus(); element.innerHTML text; element.dispatchEvent(new Event(input, {bubbles: true}));。3. 检查是否有网络错误或“重试”按钮出现。导出对话时格式错乱1. 消息内容提取方式过于简单只用innerText。2. AI回复中的代码块、列表等富文本未被正确处理。1. 尝试提取innerHTML并进行清洗或使用专门的HTML转Markdown库如Turndown。2. 针对代码块通常包裹在pre code标签内在转换时保留其结构并添加Markdown代码块标记。自动化脚本被中断或封禁请求频率过高行为被检测为机器人。1.大幅增加请求间隔模拟真人打字和阅读速度例如每条消息间隔30秒以上。2. 避免在深夜长时间运行高频率脚本。3. 考虑使用官方API如果可用进行合规的批量操作这是最安全的方式。最后我想分享一点个人体会。像chatgpt.js这样的项目其生命力完全在于社区。它游走在官方服务的边缘通过巧妙的工程手段填补了用户需求与官方功能之间的鸿沟。使用它你需要有一种“黑客精神”——乐于探索、善于调试、接受不完美并理解工具可能会突然失效。但同时它带来的效率提升和可能性也是巨大的。对于开发者而言研究它的源码也是一个学习如何与复杂Web应用交互的绝佳案例。记住保持尊重和节制地使用让工具服务于创造而不是滥用才是长久之道。当你的脚本因为前端更新而失效时别灰心打开开发者工具那又是一个有趣的解密游戏开始了。