您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A Chat export plugin to help you use chat with OpenAI lib easily.
// ==UserScript== // @name chat-exporter // @namespace berry22jelly/chat-exporter // @version 0.0.1 // @license MIT // @description A Chat export plugin to help you use chat with OpenAI lib easily. // @match https://chatgpt.com/**/** // @grant GM.registerMenuCommand // @grant GM_registerMenuCommand // ==/UserScript== (async function () { 'use strict'; (function() { const template = document.createElement("template"); template.innerHTML = ` <style> :host{ position: fixed; inset:0; display:none; } :host([open]){ display:block; } .backdrop{ position:absolute; inset:0; background: var(--backdrop-bg, rgba(0,0,0,.45)); opacity:0; transition: opacity .2s cubic-bezier(.2,.7,.2,1); } .sheet{ position:absolute; inset:0; display:grid; place-items:center; pointer-events:none; } .dialog{ pointer-events:auto; width:min(92vw, var(--modal-width, 520px)); background:var(--modal-bg, #fff); border-radius:var(--modal-radius, 16px); box-shadow: var(--modal-shadow, 0 20px 60px rgba(0,0,0,.25)); overflow:hidden; transform: translateY(8px) scale(.98); opacity:.98; transition: transform .22s cubic-bezier(.2,.7,.2,1), opacity .22s cubic-bezier(.2,.7,.2,1); } .body{ padding: var(--modal-padding, 20px); } header{ display:flex; align-items:center; justify-content:space-between; gap:.5rem; padding:16px 20px; border-bottom:1px solid #eee; } header ::slotted(*){ font-weight:600; font-size:1.05rem; } .title{ font-weight:600; font-size:1.05rem; } .x{ border:none; background:transparent; font-size:20px; line-height:1; cursor:pointer; padding:6px; border-radius:8px; } .x:focus{ outline: 2px solid Highlight; outline-offset:2px; } footer{ padding:16px 20px; border-top:1px solid #eee; } :host([open]) .backdrop{ opacity:1; } :host([open]) .dialog{ transform: translateY(0) scale(1); opacity:1; } @media (prefers-reduced-motion: reduce){ .backdrop, .dialog{ transition: none; } } </style> <div class="backdrop" part="backdrop" data-backdrop></div> <div class="sheet"> <div class="dialog" part="dialog" role="dialog" aria-modal="true" aria-labelledby="titleEl"> <header part="header"> <slot name="header"><span id="titleEl" class="title"></span></slot> <button class="x" aria-label="关闭" data-close>×</button> </header> <div class="body" part="body"> <slot></slot> </div> <footer part="footer"> <slot name="footer"></slot> </footer> </div> </div> `; class MyModal extends HTMLElement { static get observedAttributes() { return ["open", "title"]; } constructor() { super(); this._open = false; this._lastFocused = null; this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true)); this.$ = (sel) => this.shadowRoot.querySelector(sel); this._onKeydown = this._onKeydown.bind(this); this._onClick = this._onClick.bind(this); } connectedCallback() { this._syncTitle(); this.shadowRoot.addEventListener("click", this._onClick); this.shadowRoot.querySelectorAll("[data-close]").forEach((btn) => { btn.addEventListener("click", () => this.hide()); }); this.querySelectorAll("[data-close]").forEach((btn) => { btn.addEventListener("click", () => this.hide()); }); if (this.hasAttribute("open")) this._openSetup(); } disconnectedCallback() { this.shadowRoot.removeEventListener("click", this._onClick); document.removeEventListener("keydown", this._onKeydown); } attributeChangedCallback(name, oldVal, newVal) { if (name === "open") { const isOpen = this.hasAttribute("open"); if (isOpen) this._openSetup(); else this._closeCleanup(); this.dispatchEvent(new CustomEvent(isOpen ? "open" : "close", { bubbles: true })); } if (name === "title") this._syncTitle(); } get open() { return this.hasAttribute("open"); } set open(v) { v ? this.setAttribute("open", "") : this.removeAttribute("open"); } show() { this.setAttribute("open", ""); } hide() { this.removeAttribute("open"); } toggle() { this.open ? this.hide() : this.show(); } _syncTitle() { const t = this.getAttribute("title") || ""; const el = this.shadowRoot.getElementById("titleEl"); if (el) el.textContent = t; } _onClick(e) { const backdrop = e.composedPath().includes(this.$("[data-backdrop]")); if (backdrop && !this.hasAttribute("no-backdrop-close")) this.hide(); } _onKeydown(e) { if (!this.open) return; if (e.key === "Escape") { e.preventDefault(); this.hide(); return; } if (e.key === "Tab") { const focusables = this._getFocusable(); if (focusables.length === 0) { e.preventDefault(); return; } const first = focusables[0]; const last = focusables[focusables.length - 1]; const active = this._rootActiveElement(); if (e.shiftKey) { if (active === first || !this.contains(active)) { e.preventDefault(); last.focus(); } } else { if (active === last) { e.preventDefault(); first.focus(); } } } } _rootActiveElement() { let ae = this.getRootNode().activeElement; while (ae && ae.shadowRoot && ae.shadowRoot.activeElement) { ae = ae.shadowRoot.activeElement; } return ae; } _getFocusable() { const dlg = this.$(".dialog"); return [...dlg.querySelectorAll( 'a[href], button:not([disabled]), textarea, input, select, [tabindex]:not([tabindex="-1"])' )].filter((el) => !el.hasAttribute("disabled") && !el.getAttribute("aria-hidden")); } _openSetup() { if (this._open) return; this._open = true; this._lastFocused = this._rootActiveElement(); document.addEventListener("keydown", this._onKeydown); this._prevOverflow = document.documentElement.style.overflow; document.documentElement.style.overflow = "hidden"; requestAnimationFrame(() => { const f = this._getFocusable()[0] || this.$(".x"); f && f.focus(); }); try { const siblings = [...document.body.children].filter((n) => n !== this.closest("body > *") && n !== this); siblings.forEach((n) => n.inert = true); this._inertSiblings = siblings; } catch (err) { } } _closeCleanup() { if (!this._open) return; this._open = false; document.removeEventListener("keydown", this._onKeydown); document.documentElement.style.overflow = this._prevOverflow || ""; if (this._inertSiblings) { this._inertSiblings.forEach((n) => n.inert = false); this._inertSiblings = null; } if (this._lastFocused && this._lastFocused.focus) this._lastFocused.focus(); } } customElements.define("chat-exporter-modal", MyModal); })(); const innerHtml = ` <div style="padding: 25px;"> <div style="margin-bottom: 20px; text-align: center;"> <button id="actionBtn" style="background: linear-gradient(to right, #3911cbff 0%, #2575fc 100%); border: none; color: white; padding: 12px 28px; border-radius: 30px; font-size: 16px; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 10px rgba(37, 117, 252, 0.3);" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 6px 14px rgba(37, 117, 252, 0.4)';" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='0 4px 10px rgba(37, 117, 252, 0.3)';">生成结果</button> </div> <div style="margin-bottom: 10px;"> <label style="display: block; margin-bottom: 8px; color: #555; font-weight: 500;">结果展示 (可编辑):</label> <textarea style="width: 100%; height: 180px; padding: 15px; border: 1px solid #ddd; border-radius: 8px; resize: vertical; font-family: monospace; font-size: 14px; box-sizing: border-box;" placeholder="这里将显示生成的结果..."></textarea> </div> <div style="font-size: 13px; text-align: center;"> <p>您可以编辑上述文本区域中的内容</p> </div> </div> `; var ChatGPTAPIURL = "https://chatgpt.com/backend-api/conversation/"; var session = null; async function fetchSession() { const _response = await fetch("/api/auth/session"); if (!_response.ok) { throw new Error(_response.statusText); } session = (await _response.json())["accessToken"]; } await( fetchSession()); async function exportCurrentChat() { const chatUUID = window.location.pathname.split("/").at(-1); let messages = await (await fetch( ChatGPTAPIURL + chatUUID, { "credentials": "include", "headers": { "Authorization": "Bearer " + session }, "method": "GET" } )).json(); return _extractConversation(messages); } function _extractConversation(data) { const mapping = data.mapping; const currentNodeId = data.current_node; let chain = []; let nodeId = currentNodeId; while (nodeId) { const node = mapping[nodeId]; if (node && node.message) { const role = node.message.author.role; const textParts = node.message.content?.parts || []; const content = textParts.join("\n").trim(); if (content) { chain.push({ role, content }); } } nodeId = node.parent; } return chain.reverse(); } var _GM_registerMenuCommand = (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)(); function chatexportgen() { exportCurrentChat().then((resultJson) => { toolDialogue.getElementsByTagName("textarea")[0].value = JSON.stringify(resultJson); }); } const toolDialogue = document.createElement("chat-exporter-modal"); toolDialogue.innerHTML = innerHtml; toolDialogue.getElementsByTagName("button")[0].onclick = () => { chatexportgen(); }; document.body.appendChild(toolDialogue); _GM_registerMenuCommand("show", () => { toolDialogue.show(); }); })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址