您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Nekto.me: добавляет кнопки для быстрого перехода к новому диалогу
// ==UserScript== // @name Nekto.me - Быстрый переход к новому диалогу // @namespace http://nekto.me // @version 0.35 // @description Nekto.me: добавляет кнопки для быстрого перехода к новому диалогу // @author Krita // @match http://nekto.me/chat* // @match https://nekto.me/chat* // @grant GM_addStyle // @grant GM_getResourceText // ==/UserScript== GM_addStyle ( ` .checkbox, .checkbox input[type="checkbox"]{ margin: 0 } .checkbox label{ padding: 0; margin-left: 20px } .night_theme .dropdown-menu{ background-color: #101417; } .night_theme .dropdown-menu > li > a { color: #e2e3e7; } .dropdown-menu li.checkbox{ display: inline-flex; margin: 0px 6px; align-items: center; } .right_block_hc.main_chat_but{ display: flex; } button.btn.btn-md.btn-my1{ border-radius: 50px !important; } .btn-group { margin-left: 6px; } .progress-countdown{ height: 8px; margin-bottom: -8px; position: relative; background-color: transparent; } .progress-countdown .progress-bar{ animation: progressbar-countdown; animation-iteration-count: 1; animation-fill-mode: forwards; animation-play-state: paused; animation-timing-function: linear; } @keyframes progressbar-countdown { 0% { width: 100%; background: #3bb93b; } 100% { width: 0%; background: #1e94d4; } } `); //------------------------------------// const options = { autoDialog: true, // Автоматически переходить к новому диалогу skipNoAnswer: true, // Пропускать собеседников, которые не отвечают skipFilter: false, // Пропускать сообщения, совпадающие с фиильтром skipDelay: 20, // Задержка в секундах перед пропуском filterCount: 2, // Количество сообщений, проверяемых фильтром maxNoSkipCount: 50, // Количество сообщений, после которых все галочки снимаются // Удалите знак комментария "//" перед нужными вам фильтрами // Фильтры задаются в виде регулярных выражений filter: [ { name: "Нецензурная лексика", regexp: /(?<=(^|[^а-я]))((у|[нз]а|(хитро|не)?вз?[ыьъ]|с[ьъ]|(и|ра)[зс]ъ?|(о[тб]|под)[ьъ]?|(.\B)+?[оаеи])?-?([её]б(?!о[рй])|и[пб][ае][тц]).*?|(н[иеа]|([дп]|верт)о|ра[зс]|з?а|с(ме)?|о(т|дно)?|апч)?-?ху([яйиеёю]|ли(?!ган)).*?|(в[зы]|(три|два|четыре)жды|(н|сук)а)?-?бл(я(?!(х|ш[кн]|мб)[ауеыио]).*?|[еэ][дт]ь?)|(ра[сз]|[зн]а|[со]|вы?|п(ере|р[оие]|од)|и[зс]ъ?|[ао]т)?п[иеё]зд.*?|(за)?п[ие]д[аое]?р(ну.*?|[оа]м|(ас)?(и(ли)?[нщктл]ь?)?|(о(ч[еи])?|ас)?к(ой)|юг)[ауеы]?|манд([ауеыи](л(и[сзщ])?[ауеиы])?|ой|[ао]вошь?(е?к[ауе])?|юк(ов|[ауи])?)|муд([яаио].*?|е?н([ьюия]|ей))|мля([тд]ь)?|лять|([нз]а|по)х|м[ао]л[ао]фь([яию]|[еёо]й))(?=($|[^а-я]))/img }, //{ // name: "Только строчные или прописные", // regexp: /^[А-Я\s]+$|^[а-я\s]+$/gm //}, //{ // name: "Предложения перейти в месседжеры", // regexp: /.{0,10}(ватсап|вайбер|видеозвонок|скайп|телега).{0,20}/gmi //}, //{ // name: "М/ж, ск лет...", // regexp: /.{0,10}(м\/ж|ск.{0,11}лет|м или ж).{0,10}|^[А-Яа-я][?\d\s]{0,3}$|^.{0,3}(парень|девушка|пол|обмен|кто|ж\B|д\B|п\B).{0,1}$/gmi //}, //{ // name: "Больше 1200 символов", // regexp: /.{1200}/m //} ], debug: true, // Отладочные сообщения в консоли lastAction: '#newDialog', lastPhrase: "", messagesCount: 0, messageLog: [], timerType: 0 // 0 - отключение по таймеру, 1 - отключение по фильтру } //------------------------------------// // Вызывает callback(), если элемент el был удалён function onRemove(el, callback) { new MutationObserver((mutations, observer) => { if (!document.body.contains(el)) { observer.disconnect(); callback(); } }).observe(document.body, {childList: true, subtree: true}); } // Вспомогательная функиця для querySelectorNG function querySelectorNG_Callback(query, callback) { let el = document.querySelector(query); if (el) { console.log("Element found:" + query) callback(el); //onRemove(el, () => querySelectorNG(query, callback)) } return el; } // Выбирает первый элемент с селесктором query и вызывает для него callback // Если элемент был удалён и создан заного - вызывает callback заного // Если элемент ещё не создан - дожидается его создания function querySelectorNG(query, callback) { let el = querySelectorNG_Callback(query, callback); if (!el) new MutationObserver((mutations, observer) => { if (querySelectorNG_Callback(query, callback)) observer.disconnect(); }).observe(document.body, {childList: true, subtree: true}); return el; } // Срабатывает, каждый раз, когда появляется новый потомок у родительского элемента function onChildAdd(element, query, callback) { new MutationObserver((mutations, observer) => { for (const {addedNodes} of mutations) { for (const node of addedNodes) { if (node.matches(query)) callback(node, observer); } } }).observe(element, {childList: true, subtree: true}); } // Срабатывает, каждый раз, когда меняется видимость элемента function onVisibilityChanged(el, callback) { let observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { callback(el, entry.intersectionRatio > 0, observer); }); }, {root: document.documentElement}); observer.observe(el); } // Альтернатива onVisibilityChanged function onVisibilityChangedNG(el, callback) { new MutationObserver(function (mutations, observer) { let visible = el.style.visibility !== "hidden" && el.style.visibility !== "hidden"; callback(el, visible, observer); }).observe(el, {attributes: true}); } // Срабатывает один раз, когда элемент становится видимым function onVisible(el, callback) { onVisibilityChanged(el, (el, vis, obs) => { if (vis) { callback(el); obs.disconnect(); } }) } // Bootstrap Dropdown без JQuery function initBsDropDown() { let dropdowns = document.querySelectorAll('[data-toggle=dropdown]'); for (let dropdown of dropdowns) { dropdown.onclick = function (event) { let menu_div = dropdown.parentElement; if (menu_div.classList.contains("open")) return; event.stopPropagation(); menu_div.classList.add("open"); let menu_list = menu_div.querySelector(".dropdown-menu"); document.addEventListener('click', function handler(event) { if (!menu_list.contains(event.target)) { menu_div.classList.remove("open"); this.removeEventListener('click', handler); } }) } } } // Создание полосы загрузки function createProgressbar(element, duration, callback) { element.classList.add('progress'); element.classList.add('progress-countdown'); element.innerHTML = ""; let progressbar_inner = document.createElement('div'); progressbar_inner.className = 'progress-bar'; progressbar_inner.style.animationDuration = duration; if (typeof (callback) === 'function') { progressbar_inner.addEventListener('animationend', callback); } element.appendChild(progressbar_inner); progressbar_inner.style.animationPlayState = 'running'; } // Возвращает только текст из элемента function getTextOnly(el) { let elClone = el.cloneNode(true); let images = elClone.querySelectorAll('img'); for (let image of images) image.outerHTML = image.alt; elClone.innerHTML = elClone.innerHTML.replace("<div></div>", "\n"); return elClone.textContent; } //------------------------------------// function createDropdown() { let chat_btn = document.querySelector(".main_chat_but") chat_btn.insertAdjacentHTML("beforeend", ` <div class="btn-group"> <button type="button" data-toggle="dropdown" class="btn btn-md btn-my1">Начать новый <span class="caret"></span></button> <ul class="dropdown-menu dropdown-menu-right"> <li><a id="newDialog" href="#">Новый диалог</a></li> <li><a id="newDialogPhrase" href="#">С той же фразы</a></li> <li><a id="newDialogSettings" href="#">Открыть настройки</a></li> <li class="divider"></li> <li class="checkbox"> <input id="autoDialog" type="checkbox" > <label class="checkbox"> Автоматически </label> </li> <li class="divider"></li> <li class="checkbox"> <input id="skipNoAnswer" type="checkbox" > <label class="checkbox"> Пропускать, если нет ответа <abbr id="skipDelay" title="Изменить значение можно в коде скрипта">1000</abbr> сек. </label> </li> <li class="divider"></li> <li class="checkbox"> <input id="skipFilter" type="checkbox"> <label class="checkbox"> Пропускать нежелательные сообщения <abbr title="Пропускает сообщения, в соответствии с заданными фильтрами (например, содержащие нецезурную лексику). Отредактировать фильтры можно в коде скрипта.">(?)</abbr> </label> </li> <li class="divider"></li> <li><a id="saveCurrentDialog" href="#">Показать диалог</a></li> <li><a id="saveAllDialog" href="#">Показать историю</a></li> </ul> </div> `); initBsDropDown(); readSettings(); document.getElementById("autoDialog").onclick = function (ev) { options.autoDialog = ev.target.checked; readSettings(); stopTimer(1); } document.getElementById("skipNoAnswer").onclick = function (ev) { options.skipNoAnswer = ev.target.checked; readSettings(); stopTimer(); } document.getElementById("skipFilter").onclick = function (ev) { options.skipFilter = ev.target.checked; readSettings(); } document.getElementById("newDialog").onclick = newDialogClick; document.getElementById("newDialogPhrase").onclick = newDialogClick; document.getElementById("newDialogSettings").onclick = newDialogClick; document.getElementById("saveCurrentDialog").onclick = saveCurrentDialogClick; document.getElementById("saveAllDialog").onclick = saveCurrentDialogClick; let header_div = document.querySelector(".header_chat"); let progress_div = document.createElement("div"); progress_div.id = "progressbar_countdown"; header_div.parentNode.insertBefore(progress_div, header_div.nextSibling); } function readSettings() { if (!options.autoDialog) { options.skipNoAnswer = false options.skipFilter = false; } let autoDialog = document.getElementById("autoDialog"); autoDialog.checked = options.autoDialog; let skipNoAnswer = document.getElementById("skipNoAnswer"); skipNoAnswer.checked = options.skipNoAnswer; skipNoAnswer.disabled = !options.autoDialog; let skipFilter = document.getElementById("skipFilter"); skipFilter.checked = options.skipFilter; skipFilter.disabled = !options.autoDialog; document.getElementById("skipDelay").innerText = options.skipDelay; } //------------------------------------// document.addEventListener("DOMContentLoaded", function(event) { checkContainer(); }, { once: true }); function checkContainer() { if (document.querySelector('.talk_over')) { nektoScript(); } else { setTimeout(checkContainer, 50); } } // Обработчик нажатий на кнопки перехода к новому диалогу function newDialogClick(ev) { if (ev) { ev.preventDefault(); options.lastAction = ev.target.id; } // Проверка активности кнопки "Отключиться". Если на неё нельзя нажать, значит диалог уже завершён. let disconnect_btn = document.querySelector('.main_chat_but > button.btn'); if (!disconnect_btn.classList.contains('disabled')) { // Нажатие на кнопку "Отключиться" disconnect_btn.click(); // Подтверждение завершения диалога querySelectorNG('.swal2-confirm', (el) => el.click()); } else newDialog(true); onVisible(document.querySelector('.talk_over_button'), () => newDialog(true)); } // Обработчик нажатий на кнопки сохранения диалога в виде текста function saveCurrentDialogClick(ev) { let tab = window.open('about:blank', '_blank'); let content = getCurrentDialog(); if (ev.target.id === 'saveAllDialog') content = [...options.messageLog, ...content]; content = "<pre style='font-size: 1.2em;white-space: pre-wrap;'>" + content.join('\n') + "</pre>"; tab.document.write(content); tab.document.close(); } // Возвращает массив, состоящий из строк текущего диалога function getCurrentDialog() { let messages = document.querySelectorAll('.mess_block'); let content = []; for (let message of messages) { let txt_message = message.classList.contains('self') ? "Вы" : "Собеседник"; let txt_time = getTextOnly(message.querySelector('.window_chat_dialog_time')); txt_message += " (" + txt_time + ")"; txt_message += ": "; txt_message += getTextOnly(message.querySelector('.window_chat_dialog_text')); content.push(txt_message); } return content; } // Выполнеие действий, после отключения собеседника // force - действие выполняется принудительно по кнопке function newDialog(force = false) { stopTimer(1); let over_text = document.querySelector('.talk_over_text').textContent; let over_by_nekto = over_text.indexOf('Собеседник') !== -1; if (options.autoDialog && over_by_nekto || force) { if (options.lastAction === "newDialogPhrase") { let first_message = document.querySelector('.self .window_chat_dialog_text'); if (first_message) options.lastPhrase = first_message.innerHTML; } else options.lastPhrase = ""; // Запись диалога в историю let current_dialog = getCurrentDialog(); options.messageLog.push(...current_dialog); options.messageLog.push("------------------"); if (options.lastAction === "newDialogSettings") document.querySelector(".talk_over_button.blue_bg").click(); else document.querySelector(".talk_over_button:not(.blue_bg)").click(); } } // Запуск таймера. lv = 1 - таймер запускается из-за срабатывания фильтра function startTimer(lv) { options.timerType = parseInt(lv) || 0; let progress_div = document.getElementById("progressbar_countdown"); createProgressbar(progress_div, (lv ? 5 : options.skipDelay) + "s", () => newDialogClick()); } // Остановка таймера function stopTimer(lv) { lv = parseInt(lv) || 0; if (lv >= options.timerType) { document.getElementById("progressbar_countdown").innerHTML = ""; options.timerType = 0; } } // Выполняется, при появлении нового сообщения в диалоге function newMessage(el) { options.messagesCount++; if (options.messagesCount) stopTimer(); // Отключить автоматический переход к новому диалогу, если сообщений больше maxNoSkipCount if (options.messagesCount > options.maxNoSkipCount) { options.autoDialog = false; readSettings(); } // Фильтруем сообщения if (options.messagesCount <= options.filterCount) { let txt_msg = el.querySelector('.window_chat_dialog_text').innerText; for (let filter of options.filter) if (filter.regexp.test(txt_msg)) startTimer(1); } } function nektoScript() { createDropdown(); // Событие начала нового диалога, каждый раз, когда создаётся поле ввода текста // Происходит также и при измененении размеров окна! querySelectorNG('.emojionearea-editor', function editorCreate(el) { options.messagesCount = getCurrentDialog().length; // Обработка нового сообщения в диалоге onChildAdd(document.querySelector('.window_chat_block'), '.mess_block:not([style])', newMessage); // Отправка фразы, с которой начался предыдущий диалог if (options.lastAction === "newDialogPhrase" && options.messagesCount === 0) { el.innerHTML = options.lastPhrase; if (el.innerText.length) { options.messagesCount--; // Исправляем ситуацию, когда таймер не запускается document.querySelector('.sendMessageBtn').click(); } } // Запуск таймера, если такая опция установлена if (options.skipNoAnswer && options.messagesCount < 1) { startTimer(); // Запуск/остановка таймера при появлении сообщения "собеседник набирает сообщение" onVisibilityChangedNG(document.querySelector('.window_chat_dialog_write span'), (el, vis, obs) => { // Остановить таймер, если есть хотя бы одно сообщение if (options.messagesCount > 0) { obs.disconnect(); return; } if (vis) stopTimer(); else startTimer(); }); // Остановка таймера, если я начинаю печатать el.addEventListener('input', () => stopTimer(1)); } // Автоматическое выполнение действия, если собеседник отключился onVisible(document.querySelector('.talk_over_button'), () => newDialog()); // Вызывать эту же функцию, после удаления и создания новго поля ввода onRemove(el, () => querySelectorNG('.emojionearea-editor', editorCreate)); }) }