您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Batch-send JSON messages with collapsible, draggable, resizable panel & adjustable rest interval
当前为
- // ==UserScript==
- // @name ChatGPT File-Batch Sender (v0.6)
- // @namespace http://tampermonkey.net/
- // @version 0.6
- // @description Batch-send JSON messages with collapsible, draggable, resizable panel & adjustable rest interval
- // @author liuweiqing
- // @match https://chat.openai.com/*
- // @match https://chatgpt.com/*
- // @grant none
- // ==/UserScript==
- (() => {
- "use strict";
- /* ---------- 工具 ---------- */
- const $ = (sel, ctx = document) => ctx.querySelector(sel);
- const delay = (ms) => new Promise((r) => setTimeout(r, ms));
- async function waitFor(sel, t = 10000) {
- const start = performance.now();
- while (performance.now() - start < t) {
- const n = $(sel);
- if (n) return n;
- await delay(100);
- }
- throw `timeout: ${sel}`;
- }
- const untilEnabled = (btn) =>
- new Promise((res) => {
- if (!btn.disabled) return res();
- const mo = new MutationObserver(() => {
- if (!btn.disabled) {
- mo.disconnect();
- res();
- }
- });
- mo.observe(btn, { attributes: true, attributeFilter: ["disabled"] });
- });
- async function setComposer(text) {
- const p = await waitFor("div.ProseMirror[data-virtualkeyboard]");
- p.focus();
- document.execCommand("selectAll", false);
- document.execCommand("insertText", false, text);
- p.dispatchEvent(
- new InputEvent("input", { bubbles: true, inputType: "insertText" })
- );
- }
- /* ---------- 主题变化监听 ---------- */
- const onTheme = (cb) => {
- cb();
- new MutationObserver(cb).observe(document.documentElement, {
- attributes: true,
- attributeFilter: ["class"],
- });
- };
- /* ---------- 生成操作面板 ---------- */
- const panel = document.createElement("div");
- panel.id = "batchPanel";
- panel.innerHTML = `
- <style>
- #batchPanel{
- position:fixed;top:12px;right:12px;z-index:2147483647;
- width:220px;padding:0;font-family:Arial,sans-serif;
- background:var(--bg,#fff);color:var(--fg,#000);
- border:1px solid var(--bd,#0003);border-radius:8px;
- box-shadow:0 4px 14px #0004;resize:both;overflow:auto;
- transition:background .2s,color .2s;
- }
- #batchPanel.collapsed{width:46px;height:46px;padding:0;overflow:hidden}
- #batchHeader{
- cursor:move;user-select:none;height:36px;line-height:36px;
- padding:0 10px;font-weight:bold;display:flex;justify-content:space-between;align-items:center;
- border-bottom:1px solid var(--bd,#0003);background:var(--hdr,#f1f3f5);
- }
- #batchBody{padding:10px;display:flex;flex-direction:column;gap:6px}
- #batchPanel input[type="text"],
- #batchPanel input[type="number"]{width:100%;padding:4px;border:1px solid var(--bd,#0003);border-radius:4px}
- #batchBody div.flexRow{display:flex;gap:4px}
- #run{padding:6px;border:none;border-radius:4px;background:#0b5cff;color:#fff;cursor:pointer}
- #run:disabled{opacity:.5;cursor:not-allowed}
- .dark #batchPanel{--bg:#1f1f1f;--fg:#f8f8f8;--bd:#555;--hdr:#2a2a2a}
- </style>
- <div id="batchHeader">
- <span>Batch Sender</span>
- <span id="toggle">▾</span>
- </div>
- <div id="batchBody">
- <input type="file" id="file">
- <span id="fname" style="font-size:12px;color:#888"></span>
- <label>Prompt 前缀</label>
- <input type="text" id="common">
- <label style="display:flex;align-items:center;gap:4px">
- <input type="checkbox" id="restSwitch"> 自动休息
- </label>
- <div class="flexRow" style="align-items:center">
- <input type="number" id="restCount" value="25"
- placeholder="条数" title="连续发送多少条后休息" style="width:60px">
- <span style="font-size:12px;">条</span>
- <input type="number" id="restHours" value="3"
- placeholder="小时" title="每次休息时长(小时,可填小数)"
- step="0.1" min="0" style="width:60px">
- <span style="font-size:12px;">小时</span>
- </div>
- <input type="number" id="gap" placeholder="间隔(s)">
- <button id="run">开始</button>
- <progress id="bar" value="0" max="1" style="width:100%"></progress>
- </div>`;
- document.body.appendChild(panel);
- const $header = $("#batchHeader");
- const $toggle = $("#toggle");
- /* ---------- 主题同步 ---------- */
- onTheme(() => {
- if (document.documentElement.classList.contains("dark"))
- panel.classList.add("dark");
- else panel.classList.remove("dark");
- });
- /* ---------- 折叠 / 展开 ---------- */
- let collapsed = localStorage.getItem("batchCollapsed") === "1";
- const applyCollapse = () => {
- panel.classList.toggle("collapsed", collapsed);
- $toggle.textContent = collapsed ? "▸" : "▾";
- localStorage.setItem("batchCollapsed", collapsed ? "1" : "0");
- };
- $toggle.onclick = (e) => {
- collapsed = !collapsed;
- applyCollapse();
- e.stopPropagation();
- };
- applyCollapse();
- /* ---------- 拖拽移动 ---------- */
- let drag = null;
- $header.addEventListener("mousedown", (e) => {
- if (e.button !== 0) return;
- drag = {
- x: e.clientX,
- y: e.clientY,
- left: panel.offsetLeft,
- top: panel.offsetTop,
- };
- e.preventDefault();
- });
- window.addEventListener("mousemove", (e) => {
- if (!drag) return;
- panel.style.left = drag.left + (e.clientX - drag.x) + "px";
- panel.style.top = drag.top + (e.clientY - drag.y) + "px";
- });
- window.addEventListener("mouseup", () => {
- drag = null;
- });
- /* ---------- DOM 引用 ---------- */
- const $file = $("#file");
- const $fname = $("#fname");
- const $common = $("#common");
- const $gap = $("#gap");
- const $restSw = $("#restSwitch");
- const $restCt = $("#restCount");
- const $restHr = $("#restHours");
- const $run = $("#run");
- const $bar = $("#bar");
- /* ---------- 恢复设置 ---------- */
- [
- ["savedFileName", (v) => ($fname.textContent = v)],
- ["prompt", (v) => ($common.value = v)],
- ["delay", (v) => ($gap.value = v)],
- ["restFlag", (v) => ($restSw.checked = v === "1")],
- ["restCount", (v) => ($restCt.value = v)],
- ["restHours", (v) => ($restHr.value = v)],
- ["panelLeft", (v) => (panel.style.left = v)],
- ["panelTop", (v) => (panel.style.top = v)],
- ["panelBottom", (v) => (panel.style.bottom = v)],
- ].forEach(([k, fn]) => {
- const v = localStorage.getItem(k);
- if (v) fn(v);
- });
- /* ---------- 保存位置变化 ---------- */
- new MutationObserver(() => {
- localStorage.setItem("panelLeft", panel.style.left || "");
- localStorage.setItem("panelTop", panel.style.top || "");
- localStorage.setItem("panelBottom", panel.style.bottom || "");
- }).observe(panel, { attributes: true, attributeFilter: ["style"] });
- /* ---------- 读取文件 ---------- */
- $file.onchange = (e) => {
- const f = e.target.files?.[0];
- if (!f) return;
- const rd = new FileReader();
- rd.onload = (ev) => {
- localStorage.setItem("savedFile", ev.target.result);
- localStorage.setItem("savedFileName", f.name);
- $fname.textContent = f.name;
- };
- rd.readAsText(f);
- };
- /* ---------- 主要发送逻辑 ---------- */
- $run.onclick = async () => {
- const raw = localStorage.getItem("savedFile");
- if (!raw) return alert("请先选择文件");
- let data;
- try {
- data = JSON.parse(raw);
- } catch {
- return alert("JSON 解析失败");
- }
- if (!Array.isArray(data)) return alert("JSON 必须是数组");
- /* 保存参数 */
- localStorage.setItem("prompt", $common.value);
- localStorage.setItem("delay", $gap.value);
- localStorage.setItem("restFlag", $restSw.checked ? "1" : "0");
- localStorage.setItem("restCount", $restCt.value);
- localStorage.setItem("restHours", $restHr.value);
- /* 参数解析 */
- const prefix = $common.value || "";
- const gapMs = (+$gap.value || 100) * 1000;
- const restOn = $restSw.checked;
- const restAfter = Math.max(1, +$restCt.value || 25);
- const restMs = Math.max(0, +$restHr.value || 3) * 3600 * 1000;
- $bar.max = data.length;
- $run.disabled = true;
- try {
- for (let i = 0; i < data.length; i++) {
- await setComposer(`${prefix}${data[i].title ?? data[i]}`);
- await delay(1000); // 额外 1 秒
- const btn = await waitFor('button[data-testid="send-button"]');
- await untilEnabled(btn);
- btn.click();
- $bar.value = i + 1;
- if (restOn && (i + 1) % restAfter === 0) await delay(restMs);
- else await delay(gapMs);
- }
- alert("全部发送完毕!");
- } catch (e) {
- console.error(e);
- alert(e.message);
- } finally {
- $run.disabled = false;
- }
- };
- })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址