// ==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.08.2 // @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) => { let data = await getAllItems(); let Tasks = []; for (let i = 0; i < data.length; i++) { let item = data[i]; let id = item.id; let title = item.title; if (title.charAt(0) == '#') { continue; } Tasks.push(deleteItem(id)); } await Promise.all(Tasks); 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'); }); globalVariable.set('oneBtn', oneBtn); resolve(); }); } function initIncognitoModeButton() { return new Promise(async (resolve) => { let oneBtn = globalVariable.get('oneBtn'); let yesSvg = ''; let noSvg = ''; oneBtn = oneBtn.eq(0); let newBtn = global_module.cloneAndHide(oneBtn[0]); newBtn = $(newBtn).eq(0).attr('status', 0); oneBtn.show(); newBtn.find('svg').remove(); let incognitoMode = GM_getValue('incognitoMode', false); let judgeToIncognitoMode = function (i) { if (i) { if (global_module.GetUrlParm(null, 'temporary-chat') != 'true') { window.location.href = '/?temporary-chat=true'; } } else { if (global_module.GetUrlParm(null, 'temporary-chat') == 'true') { window.location.href = '/'; } } }; if (!incognitoMode) { newBtn.append(yesSvg); } else { newBtn.append(noSvg); judgeToIncognitoMode(incognitoMode); } newBtn.off('click').on('click', async () => { if (incognitoMode) { newBtn.attr('status', 1); newBtn.find('svg').remove(); newBtn.append(yesSvg); } else { newBtn.attr('status', 0); newBtn.find('svg').remove(); newBtn.append(noSvg); } GM_setValue('incognitoMode', !incognitoMode); incognitoMode = GM_getValue('incognitoMode', false); judgeToIncognitoMode(incognitoMode); }); resolve(); }); } await initSession(); await initClearButton(); await initIncognitoModeButton(); })(); 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).hide(); 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.hide(); resolve(); }); })() ); Tasks.push( (() => { return new Promise(async (resolve) => { $(await global_module.waitForElement('button[data-state="closed"][id^="radix"]:contains("?")', null, null, 100, -1)).hide(); resolve(); }); })() ); await Promise.all(Tasks); resolve(); }); } function getAllItems() { return new Promise(async (resolve) => { let limit = 30; let currentTotal = 0; let retItems = []; let getItems = function (offset) { return new Promise(async (resolve) => { $.ajax({ type: 'GET', url: '/backend-api/conversations?offset=' + offset + '&limit=' + limit + '&order=updated', headers: { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `Bearer ${globalVariable.get('accessToken')}` }, success: (data) => { resolve(data); }, error: async () => { resolve(await getAllItems(offset)); } }); }); }; let data = await getItems(0); let total = data.total; let items = data.items; currentTotal = currentTotal + items.length; retItems = retItems.concat(items); while (currentTotal < total) { data = await getItems(currentTotal); total = data.total; items = data.items; currentTotal = currentTotal + items.length; retItems = retItems.concat(items); } resolve(retItems); }); } function deleteItem(id) { let item = globalVariable.get('itemDom')[id]; if (item && !globalVariable.get('deleteItem_' + id + '_loading_setInterval')) { let textDom = item.find('[dir="auto"]').eq(0); textDom.text('.'); globalVariable.set( 'deleteItem_' + id + '_loading_setInterval', setInterval(() => { textDom.text(textDom.text() + '.'); if (textDom.text().length > 6) { textDom.text('.'); } }, 500) ); } return new Promise(async (resolve) => { $.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: () => { if (item) { item.remove(); } resolve(true); }, error: async () => { resolve(await deleteItem(id)); } }); }); } 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); globalVariable.set('itemDom', {}); for (let i = 0; i < liList.length; i++) { let spanBtn = $(liList[i]).find('span[class][data-state="closed"]'); let that = spanBtn.eq(0); let li = that.parents('li'); let a = li.find('a'); let href = a.attr('href'); let id = href.replace('/c/', ''); globalVariable.get('itemDom')[id] = li; if (spanBtn.length != 1) { continue; } let newBtn = global_module.cloneAndHide(that[0], 2); newBtn = $(newBtn); newBtn.find('svg').remove(); newBtn.append(clearButtonSvg); that.show(); newBtn.css('cursor', 'pointer'); newBtn.off('click').on('click', async () => { await deleteItem(id); }); } resolve(); }); } function widescreenDialogue() { return new Promise(async (resolve) => { if ($('body').find('style[id="widescreenDialogueCss]').length != 0) { resolve(); return; } let sel = 'textarea[id="prompt-textarea"]'; let Btn = await global_module.waitForElement(sel, null, null, 1000, -1); Btn = Btn.eq(0); let BtnParent = Btn.parents('form').eq(0).parent().eq(0); let cssClassName = BtnParent.attr('class').split(' ')[0]; let styleHtml = '.' + cssClassName + '{width:100%;max-width:100%;}'; $('body').append(''); 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; } setTimeout(async () => { await widescreenDialogue(); }, 100); 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 widescreenDialogue(); await purify(); }, 1000); });