// ==UserScript== // @name ChatGPT Enhance // @name:en ChatGPT Enhance // @name:zh-CN ChatGPT 增强 // @name:zh-TW ChatGPT 增強 // @name:ja ChatGPT 拡張 // @name:ko ChatGPT 향상 // @name:de ChatGPT verbessern // @name:fr ChatGPT améliorer // @name:es ChatGPT mejorar // @name:pt ChatGPT melhorar // @name:ru ChatGPT улучшить // @name:it ChatGPT migliorare // @name:tr ChatGPT geliştirmek // @name:ar ChatGPT تحسين // @name:th ChatGPT ปรับปรุง // @name:vi ChatGPT cải thiện // @name:id ChatGPT meningkatkan // @namespace Violentmonkey Scripts // @match *://chat.openai.com/* // @match *://chatgpt.com/* // @version XiaoYing_2024.08.04.3 // @grant GM_info // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @grant GM_setClipboard // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @grant GM_getResourceText // @grant GM_getResourceURL // @grant GM_openInTab // @grant unsafeWindow // @run-at document-start // @author github.com @XiaoYingYo // @require https://greasyfork.org/scripts/464929-module-jquery-xiaoying/code/module_jquery_XiaoYing.js // @require https://greasyfork.org/scripts/464780-global-module/code/global_module.js // @require https://greasyfork.org/scripts/465643-ajaxhookerlatest/code/ajaxHookerLatest.js // @require https://greasyfork.org/scripts/440334-jquery-like-spa-operation-library/code/jQuery-like%20SPA%20operation%20library.js // @description 宽度对话框 & 一键清空聊天记录 & 向GPT声明指定语言回复 // @description:en Wide dialog & Clear chat history & Declare specified language reply to GPT // @description:zh-CN 宽度对话框 & 一键清空聊天记录 & 向GPT声明指定语言回复 // @description:zh-TW 寬度對話框 & 一鍵清空聊天記錄 & 向GPT聲明指定語言回復 // @description:ja 幅広いダイアログ & チャット履歴をクリア & 指定された言語でGPTに宣言する // @description:ko 넓은 대화 상자 & 채팅 기록 지우기 & 지정된 언어로 GPT에 선언 // @description:de Breites Dialogfeld & Chatverlauf löschen & GPT in angegebener Sprache deklarieren // @description:fr Boîte de dialogue large & Effacer l'historique du chat & Déclarer la réponse dans la langue spécifiée à GPT // @description:es Cuadro de diálogo ancho & Borrar el historial del chat & Declarar respuesta en el idioma especificado a GPT // @description:pt Caixa de diálogo ampla & Limpar o histórico do bate-papo & Declarar resposta no idioma especificado ao GPT // @description:ru Широкий диалоговое окно & Очистить историю чата & Объявить ответ на указанном языке в GPT // @description:it Ampia finestra di dialogo & Cancella la cronologia della chat & Dichiarare la risposta nella lingua specificata a GPT // @description:tr Geniş diyalog & Sohbet geçmişini temizle & GPT'ye belirtilen dilde yanıt bildir // @description:ar مربع حوار واسع & مسح سجل المحادثة & إعلان الرد باللغة المحددة إلى GPT // @description:th กล่องโต้ตอบกว้าง & ล้างประวัติการแชท & ประกาศการตอบกลับในภาษาที่ระบุไว้กับ GPT // @description:vi Hộp thoại rộng & Xóa lịch sử trò chuyện & Khai báo trả lời bằng ngôn ngữ được chỉ định cho GPT // @description:id Kotak dialog lebar & Hapus riwayat obrolan & Nyatakan balasan dalam bahasa yang ditentukan ke GPT // @downloadURL none // ==/UserScript== // eslint-disable-next-line no-undef ajaxHooker.protect(); var globalVariable = new Map(); var browserLanguage = navigator.language; var ignoreHookStr = '&ignoreHookStr'; var clearButtonSvg = ''; (async function () { function initSession() { return new Promise((resolve) => { $.get('/api/auth/session', { headers: { Accept: 'application/json', 'Content-Type': 'application/json' } }).done((res) => { globalVariable.set('session', res); globalVariable.set('accessToken', res.accessToken); resolve(); }); }); } function clearAllConversations() { return new Promise(async (resolve) => { $.ajax({ type: 'PATCH', url: '/backend-api/conversations', headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${globalVariable.get('accessToken')}` }, data: JSON.stringify({ is_visible: false }), success: (res) => { resolve(res); } }); }); } function initClearButton() { return new Promise(async (resolve) => { let sureClearButtonSvg = ''; let oneBtn = await global_module.waitForElement('nav[aria-label]', null, null, 100, -1); globalVariable.set('Nav', $(oneBtn)); oneBtn = oneBtn.find('button').eq(0); let newBtn = global_module.cloneAndHide(oneBtn[0]); newBtn = $(newBtn).eq(0).attr('status', 0); oneBtn.show(); newBtn.find('svg').remove(); newBtn.append(clearButtonSvg); newBtn.off('click').on('click', async () => { let status = newBtn.attr('status'); newBtn.attr('disabled', 'disabled'); if (status == 0) { newBtn.attr('status', 1); newBtn.find('svg').remove(); newBtn.append(sureClearButtonSvg); } else { newBtn.attr('status', 0); newBtn.find('svg').remove(); newBtn.append(clearButtonSvg); await clearAllConversations(); unsafeWindow.location.href = '/'; } newBtn.removeAttr('disabled'); }); resolve(); }); } await initSession(); initClearButton(); })(); function purify() { return new Promise(async (resolve) => { let Tasks = []; let nav = globalVariable.get('Nav'); Tasks.push( (() => { return new Promise(async (resolve) => { let upgradeDom = await global_module.waitForElement('span[class*="border-token-border-light"]', null, null, 100, -1, nav); upgradeDom = upgradeDom.eq(upgradeDom.length - 1); upgradeDom.parents('a').eq(0).remove(); resolve(); }); })() ); Tasks.push( (() => { return new Promise(async (resolve) => { let presentation = await global_module.waitForElement('div[role="presentation"]', null, null, 100, -1); presentation = presentation.eq(presentation.length - 1); let presentationTip = await global_module.waitForElement('span:contains("ChatGPT ")', null, null, 100, -1, presentation); presentationTip.remove(); resolve(); }); })() ); Tasks.push( (() => { return new Promise(async (resolve) => { $(await global_module.waitForElement('button[data-state="closed"][id^="radix"]:contains("?")', null, null, 100, -1)).remove(); resolve(); }); })() ); await Promise.all(Tasks); resolve(); }); } function initItemDeleteBtn() { return new Promise(async (resolve) => { let nav = globalVariable.get('Nav'); let itemDiv = await global_module.waitForElement('div[class*="text-token-text-primary text-sm"]', null, null, 100, -1, nav); let liList = await global_module.waitForElement('li', null, null, 100, -1, itemDiv); for (let i = 0; i < liList.length; i++) { let spanBtn = $(liList[i]).find('span[class][data-state="closed"]'); if (spanBtn.length != 1) { continue; } let that = spanBtn.eq(0); let newBtn = global_module.cloneAndHide(that[0], 1); newBtn = $(newBtn); newBtn.find('svg').remove(); newBtn.append(clearButtonSvg); that.show(); newBtn.css('cursor', 'pointer'); newBtn.off('click').on('click', async () => { let li = that.parents('li'); let a = li.find('a'); let href = a.attr('href'); let id = href.replace('/c/', ''); $.ajax({ type: 'PATCH', url: '/backend-api/conversation/' + id, headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${globalVariable.get('accessToken')}` }, data: JSON.stringify({ is_visible: false }), success: () => { location.href = '/'; } }); }); } resolve(); }); } var HookFun = new Map(); HookFun.set('/backend-api/conversation', function (req, res, Text, period) { if (period === 'preload') { let additional = 'Please reply me with '; let additionals = additional + browserLanguage; let body = JSON.parse(req.data); let messages = body.messages; if (messages instanceof Array) { for (let i = 0; i < messages.length; i++) { let parts = messages[i].content.parts; if (parts instanceof Array) { for (let j = 0; j < parts.length; j++) { if (parts[j].indexOf(additional) != -1) { continue; } parts[j] = parts[j] + '\n' + additionals; } } } } req.data = JSON.stringify(body); return; } return new Promise(async (resolve) => { if (period !== 'done') { resolve(null); return; } resolve(null); }); }); HookFun.set('/backend-api/conversations', function (req, res, Text, period) { return new Promise(async (resolve) => { if (period !== 'done') { resolve(null); return; } setTimeout(async () => { await initItemDeleteBtn(); }, 1000); resolve(null); }); }); function handleResponse(request) { if (!request) { return; } if (request.url.indexOf(ignoreHookStr) != -1) { return; } let tempUrl = request.url; if (tempUrl.indexOf('http') == -1 && tempUrl[0] == '/') { tempUrl = location.origin + tempUrl; } let pathname = new URL(tempUrl).pathname; let fun = HookFun.get(pathname); if (!fun) { return; } fun(request, null, null, 'preload'); request.response = (res) => { let Type = 0; let responseText = res.responseText; if (typeof responseText !== 'string') { Type = 1; responseText = res.text; } if (typeof responseText !== 'string') { Type = 2; responseText = JSON.stringify(res.json); } const oldText = responseText; res.responseText = new Promise(async (resolve) => { let ret = await fun(request, res, responseText, 'done'); if (!ret) { ret = oldText; } if (Type === 2) { if (typeof ret === 'string') { ret = JSON.parse(ret); } } resolve(ret); }); }; } // eslint-disable-next-line no-undef ajaxHooker.hook(handleResponse); $.onurlchange(function () { setTimeout(async () => { await purify(); }, 1000); });