您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
支持降智检测(IP 使用情况风险判断)以及跳转 ChatGPT 镜像站的语音功能到 LiveKit Meet 而不是镜像站的 LiveKit Meet。
当前为
// ==UserScript== // @name ChatGPT 助手 // @author Hmjz100 // @namespace github.com/hmjz100 // @version 1.0.3 // @description 支持降智检测(IP 使用情况风险判断)以及跳转 ChatGPT 镜像站的语音功能到 LiveKit Meet 而不是镜像站的 LiveKit Meet。 // @icon data:image/webp;base64,UklGRnICAABXRUJQVlA4WAoAAAAQAAAAHwAAHwAAQUxQSI8AAAABd6A2kg06/T5EcqGIzkREILWfPrI5RTitte1pXprvniGwKHzbgMMgjMAkHAaB9HhUevJ9U+T/6T6i/wzctm0kde/27hVA1LJgHQAoiCX2Hvw3S62UnAc0rkOEtyHir7C+QPMbSg48lz0vKU1fl0ZUNyHRH8Pf4Tt0/Wb4Ow5/53QI3XzKKRVk8v/IxeB/BgBWUDggvAEAABALAJ0BKiAAIAA+aSqQRaQioZv6rABABoS0gAnAHJxnoP3rMm/kBkKFPL/yvok/1/878zXyp/g/cC/jv9A/x35fdjN7Hf7ANdIJ5yn1lcylRWD9msH+g4lCVG3DwlB27AAA/v/b7x/k//Og4rBakFx39Od09+fJ1x4C+DLOrn57IHc1Tex5U/5cB8l2f3fZBiD+5/7/IowL/9h145ee+E8Tn9Mx//ya2jMrsF0MWumdxn7zRJ7f7BEicBZVfKYEwPy/e9nUZPOkR9QyW1vCwpKLT4HeJVe7ayZ4dcQRfzMR0TeGbvw8U5I+jHciOCqPs43iwvLybOhARP2A/54Jx8B2XKJ3NgHNzVi3W2HkTpisnFaSV2NcPAhURRCHCp9wAwR12+PxPlPateeyuKpswYl1DEzNt/SkXzDPkSq/LyuOSsHuqlcmY8ch6wMlzG7JlrkvSIL3kuB6BS6sByZZXCNqJoa/4vqVhG8NdvpsjyX6hpz5DqxYMDrvqnl1//oedvi9KU2f/8I6YzFjiLYx5bH31D5eTMZBT8kqCpoNVnHwfQz5KqJRnU9pk4JVJRwRO0gt0GsehqDyZAAAAA== // @license MIT // @match *://chatgpt.com/* // @match *://chat.openai.com/* // @match *://*.oaifree.com/* // @match *://chat.rawchat.top/* // @match *://chat.rawchat.cc/* // @match *://chat.sharedchat.cn/* // @match *://chat.chatgptplus.cn/* // @match *://free.share-ai.top/* // @match *://gpt.github.cn.com/* // @match *://share.github.cn.com/* // @match *://go.gptdie.com/* // @match *://chat.gptdie.com/* // @match *://free.xyhelper.cn/* // @match *://*.xyhelper.com.cn/* // @match *://chat.freegpts.org/* // @match *://go.gptdsb.com/* // @match *://chat.gptdsb.com/* // @match *://www.opkfc.com/* // @match *://chatgpt.dairoot.cn/* // @match *://web.tu-zi.com/* // @match *://share.tu-zi.com/* // @grant GM_setValue // @grant GM_getValue // @grant GM_openInTab // @grant unsafeWindow // @run-at document-start // @require https://unpkg.com/[email protected]/dist/jquery.min.js // ==/UserScript== (function () { 'use strict'; // 非常有意思的不知道是什么模型的网站 // chatgptchatapp.com // 监听 fetch 请求的响应 const originalFetch = window.fetch; unsafeWindow.fetch = async function (url, options) { try { const response = await originalFetch(url, options); // 检测是否是目标 API 请求 if (url.includes('sentinel/chat-requirements') && options.method === 'POST') { let res = await response.clone().text(); try { res = JSON.parse(res); } catch (e) { } console.log('风险数据: \n', res); const difficulty = res?.proofofwork?.difficulty ? res?.proofofwork?.difficulty : 'N/A'; updateDifficultyIndicator(difficulty); } return response; } catch (error) { throw error; } }; // 更新难易度指示器 function updateDifficultyIndicator(difficulty) { const result = difficulty === 'N/A' ? { color: '#888', secondaryColor: '#666', textColor: '#888', level: '未知', detail: '服务器未提供 PoW 难度值,可能是因为当前站点不是镜像站点\n(即使网站的 UI 界面与官方相似)' } : (() => { const hexValue = difficulty.replace('0x', '').replace(/^0+/, ''); const hexLength = hexValue.length; const decimalValue = parseInt(hexValue, 16); // 转换为十进制数值 const percentage = Math.min(decimalValue / 0x00FFFF * 100, 100).toFixed(2); // 精确到小数点后两位 console.log(difficulty, hexLength, decimalValue, percentage) if (hexLength <= 2) return { color: '#F44336', secondaryColor: '#d32f2f', level: '困难', detail: `PoW:${difficulty} (${percentage}%),高级模型以及功能可能无法正常使用。` }; if (hexLength === 3) return { color: '#FFC107', secondaryColor: '#ffa000', level: '中等', detail: `PoW:${difficulty} (${percentage}%),可能会影响部分高级功能。` }; if (hexLength === 4) return { color: '#8BC34A', secondaryColor: '#689f38', level: '简单', detail: `PoW:${difficulty} (${percentage}%),可正常使用 ChatGPT。` }; return { color: '#4CAF50', secondaryColor: '#388e3c', level: '极易', detail: `PoW:${difficulty} (${percentage}%),可舒适使用 ChatGPT。` }; })(); // 更新 UI $('#ChatGPTPow-Button svg defs linearGradient stop[offset="0%"]').css('stop-color', result.color); $('#ChatGPTPow-Button svg defs linearGradient stop[offset="100%"]').css('stop-color', result.secondaryColor); $('#ChatGPTPow-Button span').text(result.level); $('#ChatGPTPow-Button').attr('title', result.detail); $('#ChatGPTPow-Button')[0].style.background = `linear-gradient(140.91deg, ${result.color} 12.61%, ${result.secondaryColor} 76.89%)`; } // 隐藏原来的按钮 waitForKeyElements('div:not(#ChatGPTVoice-On-LiveKitMeet-Button, #immersive-translate-popup) svg.icon[width="25"][height="25"], div#voiceButton svg, #of-custom-floating-ball svg, div > div#livekit', function (element) { element.parent().hide(); }); waitForKeyElements("body main div.mb-7.text-center, div.btn-voice, body main div.flex-shrink-0 svg", function (element) { if (element.hasClass("voice")) return; let checkOpacity = function () { if (element.hasClass("mb-7")) { if (element.find("h1.result-streaming").css('opacity') == '0') { let clone = element.clone(true); clone.addClass("voice") element.replaceWith(clone); clone.css({ 'cursor': 'pointer', 'user-select': 'none', '-webkit-user-select': 'none', '-ms-user-select': 'none', '-moz-user-select': 'none' }); clone.on('click touchend', handleVoiceClick); } else { setTimeout(checkOpacity, 100); } } else { element.addClass("voice") element.css({ 'cursor': 'pointer', 'user-select': 'none', '-webkit-user-select': 'none', '-ms-user-select': 'none', '-moz-user-select': 'none' }); element.on('click touchend', handleVoiceClick); } }; // 初次调用检查函数 checkOpacity(); }); let html = $(`<div id="ChatGPTVoice-On-LiveKitMeet"> <div id="ChatGPTVoice-On-LiveKitMeet-Button"> <svg width="25" height="25" class="icon" fill="none" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg"> <path d="M18 12C18 15.3137 15.3137 18 12 18M12 18C8.68629 18 6 15.3137 6 12M12 18V21M12 21H15M12 21H9M15 6V12C15 13.6569 13.6569 15 12 15C10.3431 15 9 13.6569 9 12V6C9 4.34315 10.3431 3 12 3C13.6569 3 15 4.34315 15 6Z" stroke="#ffffff" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" /> </svg> <span>语音</span> </div> <div id="ChatGPTPow-Button" style="background: linear-gradient(140.91deg, #2ecc71 12.61%, #3498db 76.89%);" title="等待站点请求中..."> <svg width="25" height="25" class="icon" fill="none" viewBox="0 0 64 64" version="1.1" xmlns="http://www.w3.org/2000/svg" style="transition: all 0.3s ease;"> <defs> <linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%"> <stop offset="0%" style="stop-color:#3498db;stop-opacity:1"></stop> <stop offset="100%" style="stop-color:#2ecc71;stop-opacity:1"></stop> </linearGradient> <filter id="glow"> <feGaussianBlur stdDeviation="2" result="coloredBlur"></feGaussianBlur> <feMerge> <feMergeNode in="coloredBlur"></feMergeNode> <feMergeNode in="SourceGraphic"></feMergeNode> </feMerge> </filter> </defs> <g id="icon-group" filter="url(#glow)"> <circle cx="32" cy="32" r="28" fill="url(#gradient)" stroke="#fff" stroke-width="2"></circle> <circle cx="32" cy="32" r="20" fill="none" stroke="#fff" stroke-width="2" stroke-dasharray="100"> <animateTransform attributeName="transform" attributeType="XML" type="rotate" from="0 32 32" to="360 32 32" dur="8s" repeatCount="indefinite"></animateTransform> </circle> <circle cx="32" cy="32" r="12" fill="none" stroke="#fff" stroke-width="2"> <animate attributeName="r" values="12;14;12" dur="2s" repeatCount="indefinite"></animate> </circle> <circle id="center-dot" cx="32" cy="32" r="4" fill="#fff"> <animate attributeName="r" values="4;6;4" dur="2s" repeatCount="indefinite"></animate> </circle> </g> </svg> <span>状态</span> </div> <style id="ChatGPTVoice-On-LiveKitMeet-Style"> div#ChatGPTVoice-On-LiveKitMeet-Button, div#ChatGPTPow-Button { border-top-left-radius: 34px; border-bottom-left-radius: 34px; background: linear-gradient(140.91deg, #7367F0 12.61%, #574AB8 76.89%); height: 34px; width: 80px; margin: 1px; display: flex !important; align-items: center; position: fixed; right: -35px; top: calc(80% - 34px); cursor: pointer; padding-left: 7px; z-index: 114514; opacity: 0.75; transition: right 0.3s, opacity 0.3s !important; } div#ChatGPTVoice-On-LiveKitMeet-Button:hover, div#ChatGPTVoice-On-LiveKitMeet-Button.is-dragging, div#ChatGPTPow-Button:hover, div#ChatGPTPow-Button.is-dragging { right: -5px; opacity: 1; } div#ChatGPTVoice-On-LiveKitMeet-Button span, div#ChatGPTPow-Button span { color:#ffffff; font-size:15px; margin-left:3px; white-space: nowrap; } </style> </div>`) let button = html.find('#ChatGPTVoice-On-LiveKitMeet-Button'); let isDragging = false; let offsetY = 0; let dragStartTime; button.css('z-index', 114514 + 1) // 从 GM 获取按钮位置 if (GM_getValue('buttonTop')) { button.css('top', GM_getValue('buttonTop') + 'px'); } else { button.css('top', 'calc(30% - 34px)'); } // 点击事件处理 button.on('click touchend', handleVoiceClick); // 鼠标按下事件 button.on('mousedown touchstart', function (e) { e.preventDefault(); dragStartTime = Date.now(); // 记录拖动开始时间 offsetY = e.clientY - button.offset().top; }); // 鼠标移动事件 $(document).on('mousemove touchmove', function (e) { if (offsetY !== undefined) { let newTop = e.clientY - offsetY; const buttonHeight = button.outerHeight(); const windowHeight = $(window).height(); // 限制按钮位置 if (newTop < 0) newTop = 0; if (newTop + buttonHeight > windowHeight) newTop = windowHeight - buttonHeight; // 判断是否拖动 if (isDragging || (Date.now() - dragStartTime > 100)) { // 如果已经拖动或拖动时间超过100ms isDragging = true; button.addClass('is-dragging'); button.css('top', newTop + 'px'); GM_setValue('buttonTop', newTop); } } }); // 鼠标抬起事件 $(document).on('mouseup touchend', function () { if (isDragging) { setTimeout(function () { isDragging = false; button.removeClass('is-dragging'); }, 100) } offsetY = undefined; // 重置 offsetY }); setInterval(function () { if (!$('#ChatGPTVoice-On-LiveKitMeet-Button').length || !$('#ChatGPTVoice-On-LiveKitMeet-Style').length) { $('#ChatGPTVoice-On-LiveKitMeet').remove() $('body').append(html); } }, 500) // 绑定点击事件到新创建的按钮 async function handleVoiceClick(event) { if (!event?.currentTarget || isDragging) return; let element = $(event.currentTarget); if (element.attr('data-clicked') === 'true') return; element.attr('data-clicked', 'true'); // 异步获取语音链接 await goVoice(element).catch(function (error) { alert('获取语音对话(会议)链接错误: \n' + error.message); console.error(error); element.removeAttr('data-clicked'); }); }; async function goVoice(element) { // 定义不同服务器的配置 let servers = { "new.oaifree.com": { apiPath: "/api/voice/link", apiType: "POST", url: "wss://webrtc.oaifree.com", model: new URL(location.href).searchParams.get('model'), mode: [['标准语音', '高级语音'], ['std', 'adv']], getToken: data => new URL(data.url).searchParams.get('token'), getHash: data => new URL(data.url).hash }, "chat.rawchat.top": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.rawchat.cc": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.sharedchat.cn": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.chatgptplus.cn": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "free.share-ai.top": { apiPath: "/frontend-api/getVoice", apiType: "GET", url: data => (data.data.voiceServerUrl || data.data.url), getToken: data => data.data.token, getHash: data => data.data.e2ee_key }, "gpt.github.cn.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "share.github.cn.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "go.gptdie.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.gptdie.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "free.xyhelper.cn": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "free.xyhelper.com.cn": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.freegpts.org": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "go.gptdsb.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chat.gptdsb.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "www.opkfc.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "chatgpt.dairoot.cn": { apiPath: "/api/livekit", apiType: "GET", url: data => new URL(data.data).searchParams.get('liveKitUrl'), getToken: data => new URL(data.data).searchParams.get('token'), getHash: data => new URL(data.data).hash }, "web.tu-zi.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, "share.tu-zi.com": { apiPath: "/backend-api/voice_token", apiType: "GET", url: data => data.url, getToken: data => data.token, getHash: data => data.e2ee_key }, }; // 获取当前服务器的域名 let host = location.hostname; // 获取服务器配置 let config = servers[host]; if (!config) { throw new Error(`未支持当前站点: ${host}`); } let extra = { method: config.apiType, headers: { 'Content-Type': 'application/json' } } if (config.model !== undefined && config.mode !== undefined && config.apiType === 'POST') { let model = config.model; let mode = config.mode; let modeChoice; if (mode && mode.length) { let modeOptions = mode[0] .map((name, index) => `(${index + 1}) ${name}`) .join(" "); let userChoice = prompt(`请选择语音模式: (不输入则使用${mode[0][0]})\n${modeOptions}`); let choiceIndex = parseInt(userChoice) - 1; if (choiceIndex >= 0 && choiceIndex < mode[1].length) { modeChoice = mode[1][choiceIndex]; } else if (userChoice === null) { return element.removeAttr('data-clicked'); } else { modeChoice = mode[1][0]; } } if (!model) { let userInput = prompt("请输入模型名称: (不输入则使用默认模型)"); if (userInput === null) { return element.removeAttr('data-clicked'); } model = userInput; } extra.body = JSON.stringify({ model, mode: modeChoice }); } // 发送请求到语音API let response = await unsafeWindow.fetch(config.apiPath, extra); // 解析返回的JSON数据 let data = await response.json(); console.log('服务数据: \n', data); // 检查返回的模式,如果是高级模式,修改颜色 if (data.mode === "advanced") { element.css('color', '#f00'); } // 检查是否有url或者token,否则抛出错误 function hasUrl(obj) { if (obj && typeof obj === 'object') { if ('url' in obj) return true; // 如果当前对象包含 url 属性,返回 true if ('data' in obj) return true; // 如果当前对象包含 url 属性,返回 true return Object.values(obj).some(hasUrl); // 递归检查嵌套的对象 } return false; } if (data && !hasUrl(data)) { throw new Error(data.detail || '语音服务未返回所需数据'); } // 获取url、token、hash let url = typeof config.url === 'function' ? config.url(data) : config.url; let token = config.getToken ? config.getToken(data) : null; let hash = config.getHash ? config.getHash(data) : null; // 打印日志方便调试 console.log('会议数据: \n', { token, hash, url }); // 检查是否有url或者token,否则抛出错误 if (!url || !token || !hash) throw new Error(data.detail || '语音服务未返回数据'); // 构建 meetUrl let meetUrl = new URL('https://meet.livekit.io/custom'); if (url) meetUrl.searchParams.set('liveKitUrl', url); if (token) meetUrl.searchParams.set('token', token); if (hash) meetUrl.hash = hash; // 打开新页面 GM_openInTab(meetUrl.href, { active: true, insert: true, setParent: true }) element.removeAttr('data-clicked'); } function waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector) { function findInShadowRoots(root, selector) { let elements = $(root).find(selector).toArray(); $(root).find('*').each(function () { let shadowRoot = this.shadowRoot; if (shadowRoot) { elements = elements.concat(findInShadowRoots(shadowRoot, selector)); } }); return elements; } var targetElements; if (iframeSelector) { targetElements = $(iframeSelector).contents(); } else { targetElements = $(document); } let allElements = findInShadowRoots(targetElements, selectorTxt); if (allElements.length > 0) { allElements.forEach(function (element) { var jThis = $(element); var uniqueIdentifier = 'alreadyFound'; var alreadyFound = jThis.data(uniqueIdentifier) || false; if (!alreadyFound) { var cancelFound = actionFunction(jThis); if (cancelFound) { return false; } else { jThis.data(uniqueIdentifier, true); } } }); } var controlObj = waitForKeyElements.controlObj || {}; var controlKey = selectorTxt.replace(/[^\w]/g, "_"); var timeControl = controlObj[controlKey]; if (allElements.length > 0 && bWaitOnce && timeControl) { clearInterval(timeControl); delete controlObj[controlKey]; } else { if (!timeControl) { timeControl = setInterval(function () { waitForKeyElements(selectorTxt, actionFunction, bWaitOnce, iframeSelector); }, 1000); controlObj[controlKey] = timeControl; } } waitForKeyElements.controlObj = controlObj; } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址