您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Provides a better modding interface for the cam girls site of chaturbate.com. This script makes modding faster and easier. Check out the details.
// ==UserScript== // @name Chaturbate - Better Modding // @namespace http://www.vpycha.com/gmscripts // @description Provides a better modding interface for the cam girls site of chaturbate.com. This script makes modding faster and easier. Check out the details. // @include http://chaturbate.com/* // @include https://chaturbate.com/* // @include http://*.chaturbate.com/* // @include https://*.chaturbate.com/* // @exclude http://serve.ads.chaturbate.com/* // @exclude https://serve.ads.chaturbate.com/* // @grant none // @run-at document-start // @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html // @version 2.12 // ==/UserScript== // Author: Vladimir Pycha [email protected] // Website: vladpride.cz // Nickname on chaturbate.com: vlad88x (banned permanently), vlad88z // Nickname on greasyfork.org: vlad88 // First revision created and released on: July 2013 // The home page of this script is: https://greasyfork.org/scripts/3819-chaturbate-better-modding // There is also a detailed description of this script there. function doBetterModdingChanges() { 'use strict'; var messagesCountMax = 200; var maxMessagesCountColor = 'rgb(255, 0, 0)'; var mostRecentMessagesCount = 4; var cookieExpirePeriod = 1 * 365; var pixelsToScrollOffChatBottom = 5; var expandChar = '+'; var collapseChar = '-'; var collapsedMessageHeight = '10px'; var removedMessageBgColor = 'rgb(225, 225, 225)'; var removedTitle = 'removed'; var silencedTitlePrefix = 'silenced by '; var bannedTitle = 'kicked out'; var bannedOutlineColor = 'rgb(40, 40, 40)'; var bannedOutlineWidth = '2px'; var bannedOutlineStyle = 'solid'; var pendingSilenceOutlineColor = 'rgb(150, 150, 150)'; var pendingSilenceOutlineWidth = '2px'; var pendingSilenceOutlineStyle = 'solid'; var pendingBanOutlineColor = 'rgb(40, 40, 40)'; var pendingBanOutlineWidth = '2px'; var pendingBanOutlineStyle = 'dashed'; console.info("doing changes in chat room page"); var output = document.createElement('div'); output.setAttribute('id', 'ChaturbateBetterModding'); output.setAttribute('style', 'padding: 0px 8px 10px 8px'); output.innerHTML = 'ChaturbateBetterModding user script is active<br /><span title="In simulation mode you can excersize modding without having to be a mod"><label for="ChaturbateBetterModding_SimulationMode">Simulation mode: </label><input id="ChaturbateBetterModding_SimulationMode" type="checkbox" /></span>'; document.body.appendChild(output); $.error = console.error; function strStartsWith(str, prefix) { return str.substring(0, prefix.length) === prefix; } function strEndsWith(str, suffix) { return str.substring(str.length - suffix.length) === suffix; } var oldConfirm = window.confirm; var silenceMsgTemplateLocalized = gettext('Silence %(username)s?'); window.confirm = function(msg) { var parts = silenceMsgTemplateLocalized.split('%(username)s'); if (strStartsWith(msg, parts[0]) && strEndsWith(msg, parts[1])) { var userToSilence = msg.substring(parts[0].length, msg.length - parts[1].length); var usernameValidationRegExp = /^\S+$/; if (userToSilence.replace(usernameValidationRegExp, '') === '') { return true; } } return oldConfirm(msg); } var theElement = document.body; var simulationModeCookieName = 'btmd_sim_mode'; var deletedMessagesShouldBeExpandedCookieName = 'btmd_expand'; var simulationMode = $.cookie(simulationModeCookieName) == '1'; var deletedMessagesShouldBeExpanded = $.cookie(deletedMessagesShouldBeExpandedCookieName) == '1'; setSimulationModeCookie(); setDeletedMessagesShouldBeExpandedCookie(); if (simulationMode) { console.warn("simulation mode is enabled"); } function setSimulationModeCookie() { $.cookie(simulationModeCookieName, simulationMode ? '1' : '0', { expires: cookieExpirePeriod, path: '/' }); } function setDeletedMessagesShouldBeExpandedCookie() { $.cookie(deletedMessagesShouldBeExpandedCookieName, deletedMessagesShouldBeExpanded ? '1' : '0', { expires: cookieExpirePeriod, path: '/' }); } var simulationModeCheckbox = document.getElementById('ChaturbateBetterModding_SimulationMode'); simulationModeCheckbox.checked = simulationMode; simulationModeCheckbox.onclick = function(evt) { simulationMode = this.checked; this.blur(); document.body.focus(); setSimulationModeCookie(); if (simulationMode) { console.warn("enabled simulation mode"); } else { console.info("disabled simulation mode"); } } var domele = $(theElement.getElementsByClassName('chat-list')[0]); var chat = domele.get(0); var messagesCount = 0; var previousChatOuterWidth = 0; var broadcaster = window.broadcaster; var controlsContainer = document.createElement('div'); controlsContainer.innerHTML = '<input type="button" id="ChaturbateBetterModding_ChatLength" value="' + messagesCount + '" title="Click to delete all messages except the\nlast page, or except the last ' + mostRecentMessagesCount + ' messages\nif clicked with Shift key down" /><br />' + '<input type="button" id="ChaturbateBetterModding_ToggleExpand" value="' + (deletedMessagesShouldBeExpanded ? collapseChar : expandChar) + '" title="Click to toggle the automatic expanding or collapsing of\ndeleted messages and to expand or collapse all of them\n(keyboard shortcut is <), or click it with Shift key down to\ndo it without the toggling (shortcut is >)" />'; controlsContainer.style.textAlign = 'center'; document.body.appendChild(controlsContainer); var chatLengthButton = document.getElementById('ChaturbateBetterModding_ChatLength'); var toggleExpandButton = document.getElementById('ChaturbateBetterModding_ToggleExpand'); chatLengthButton.style.paddingLeft = '1px'; chatLengthButton.style.paddingRight = '1px'; toggleExpandButton.style.paddingLeft = '1px'; toggleExpandButton.style.paddingRight = '1px'; var origValue = chatLengthButton.value; chatLengthButton.value = 266; var chatLengthButtonMinWidth = $(chatLengthButton).outerWidth(); // really outer width is correct here, I do not know why not inner width chatLengthButton.value = origValue; chatLengthButton.style.minWidth = chatLengthButtonMinWidth + 'px'; toggleExpandButton.style.minWidth = chatLengthButtonMinWidth + 'px'; setControlsContainerPosition(); function setControlsContainerPosition() { var chatPosition = domele.offset(); controlsContainer.style.position = 'absolute'; var chatOuterWidth = domele.outerWidth(); if (previousChatOuterWidth && previousChatOuterWidth - chatOuterWidth == 1) { // this is a work-aroud chatOuterWidth = previousChatOuterWidth; } else { previousChatOuterWidth = chatOuterWidth; } var left = chatPosition.left + chatOuterWidth + 10; var top = chatPosition.top; if (left + controlsContainer.clientWidth > document.body.scrollWidth) { left = chatPosition.left + chatOuterWidth - controlsContainer.clientWidth - 20; top -= 9; } controlsContainer.style.left = left + 'px'; controlsContainer.style.top = top + 'px'; } var updateChatLength = function() { chatLengthButton.value = messagesCount.toString(); if (messagesCount >= messagesCountMax) { chatLengthButton.style.color = maxMessagesCountColor; } else { chatLengthButton.style.color = ''; } } chatLengthButton.onclick = function(evt) { if (document.activeElement == chatLengthButton) { chatLengthButton.blur(); document.body.focus(); } var bigDelete = evt.shiftKey; var recentMessagesCount; if (bigDelete) { recentMessagesCount = mostRecentMessagesCount; } else { recentMessagesCount = 0; var totalHeight = 0; var element = chat.lastElementChild; do { if (!element) { break; } totalHeight += $(element).outerHeight(true); recentMessagesCount++; element = element.previousElementSibling; } while (totalHeight < chat.clientHeight + pixelsToScrollOffChatBottom); } while (messagesCount > recentMessagesCount) { chat.removeChild(chat.firstElementChild); messagesCount--; } chat.scrollTop = chat.scrollHeight; updateChatLength(); } var messagesToCollapse = []; var removedMessagesOf = null; chat.addEventListener('scroll', onChatScroll, false); var removedNickAttr = 'data-removed-nick'; // valid values of attribute: remove, silence, ban var removeTypeAttr = 'data-remove-type'; var removedByAttr = 'data-removed-by'; function is_at_bottom() { return chat.scrollTop >= chat.scrollHeight - chat.clientHeight; } function onChatScroll(evt) { if (messagesToCollapse.length > 0 && is_at_bottom()) { for (var i = messagesToCollapse.length - 1; i >= 0; i--) { var msgDiv = messagesToCollapse[i]; messagesToCollapse[i] = null; if (!deletedMessagesShouldBeExpanded && msgDiv && msgDiv.parentNode) { if (!msgDiv.msgCollapsed) { collapseMessage(msgDiv); } } messagesToCollapse.length = i; } } } function getSilencedByTitle(silencer_nick) { return silencedTitlePrefix + silencer_nick; } var removeMessage = function(msgDiv) { msgDiv.style.background = ''; msgDiv.style.backgroundColor = removedMessageBgColor; messagesToCollapse[messagesToCollapse.length] = msgDiv; var paragraph = msgDiv.firstElementChild; while (paragraph) { if (paragraph.style.backgroundColor) { paragraph.style.background = ''; } paragraph = paragraph.nextElementSibling; } }; var on_user_silenced = function (silenced_nick, silencer_nick, index) { if (index === undefined) { index = 0; } $(".chat-list > div.text > p > [data-nick='" + silenced_nick + "']").each(function (index, value) { var msgDiv = this.parentNode.parentNode; if (!msgDiv.hasAttribute(removeTypeAttr) || msgDiv.getAttribute(removeTypeAttr) == 'remove') { msgDiv.setAttribute(removedNickAttr, silenced_nick); msgDiv.setAttribute(removeTypeAttr, 'silence'); msgDiv.setAttribute(removedByAttr, silencer_nick); msgDiv.setAttribute('title', getSilencedByTitle(silencer_nick)); removeMessage(msgDiv); } onChatScroll(null); }); var text = 'User ' + silenced_nick + ' was silenced by ' + silencer_nick; $.add_system_message(text, domele, index); }; var on_user_banned = function (username) { $(".chat-list > div.text > p > [data-nick='" + username + "']").each(function (index, value) { var msgDiv = this.parentNode.parentNode; if (msgDiv.getAttribute(removeTypeAttr) != 'ban') { if (msgDiv.hasAttribute(removedByAttr)) { msgDiv.removeAttribute(removedByAttr); } msgDiv.setAttribute(removedNickAttr, username); msgDiv.setAttribute(removeTypeAttr, 'ban'); msgDiv.setAttribute('title', bannedTitle); removeMessage(msgDiv); msgDiv.style.outlineColor = bannedOutlineColor; msgDiv.style.outlineWidth = bannedOutlineWidth; msgDiv.style.outlineStyle = bannedOutlineStyle; } onChatScroll(null); }); var text = 'User ' + username + ' was kicked out of the room'; $.add_system_message(text, domele); }; var remove_messages = function (username) { removedMessagesOf = username; $(".chat-list > div.text > p > [data-nick='" + username + "']").each(function (index, value) { var msgDiv = this.parentNode.parentNode; if (!msgDiv.hasAttribute(removeTypeAttr)) { msgDiv.setAttribute(removedNickAttr, username); msgDiv.setAttribute(removeTypeAttr, 'remove'); msgDiv.setAttribute('title', removedTitle); removeMessage(msgDiv); } onChatScroll(null); }); }; var old_add_message; var message_outbound = null; var handler; if (window.TSHandler) { handler = TSHandler; } else { console.info("using ws_handler instead of TSHandler"); handler = ws_handler; } var defchat_message_receiver = null; var old_connect = handler.connect; handler.connect = function(message_receiver, room, settings, groups_and_privates) { defchat_message_receiver = message_receiver; old_connect(message_receiver, room, settings, groups_and_privates); }; setTimeout(function() { if (defchat_settings.handler !== handler) { handler = defchat_settings.handler; console.error("bad guess of used handler:", handler); } message_outbound = handler.message_outbound; setControlsContainerPosition(); var message_receiver = defchat_message_receiver; if (message_receiver === null) { console.error("not having message_receiver"); } else { // these methods are no longer called //message_receiver.on_user_silenced = on_user_silenced; //message_receiver.on_user_banned = on_user_banned; // this method is called instead message_receiver.remove_messages = remove_messages; old_add_message = message_receiver.add_message; messagesCount = chat.children.length; message_receiver.add_message = add_message; } }); function getTitle(msgDiv) { if (!msgDiv.hasAttribute(removeTypeAttr)) { return ''; } switch (msgDiv.getAttribute(removeTypeAttr)) { case 'remove': return removedTitle; case 'silence': return getSilencedByTitle(msgDiv.getAttribute(removedByAttr)); case 'ban': return bannedTitle; default: return ''; } } function expandMessage(msgDiv) { msgDiv.style.height = ''; for (var i = 0; i < msgDiv.children.length; i++) { var child = msgDiv.children[i]; child.style.display = ''; } msgDiv.setAttribute('title', getTitle(msgDiv)); msgDiv.msgCollapsed = false; } function collapseMessage(msgDiv) { for (var i = msgDiv.children.length - 1; i >= 0; i--) { var child = msgDiv.children[i]; child.style.display = 'none'; } msgDiv.style.height = collapsedMessageHeight; var username = msgDiv.getAttribute(removedNickAttr); var title = getTitle(msgDiv); msgDiv.setAttribute('title', '' + username + ', ' + title); msgDiv.msgCollapsed = true; } function add_message(message, domeleParam, index) { if (!domeleParam) { domeleParam = domele; } if (domeleParam.get(0) != chat) { return old_add_message.call(this, message, domeleParam); } if (removedMessagesOf !== null) { var nick = removedMessagesOf; removedMessagesOf = null; if (typeof message == "string") { var silencer = getSilencer(message, nick); if (silencer) { on_user_silenced(nick, silencer); return; } if (isBanText(message, nick)) { on_user_banned(nick); return; } } } var originalScrollTop = chat.scrollTop; var at_bottom = is_at_bottom(); var result; var oldFind = domeleParam.find; domeleParam.find = function(selector) { if (selector == 'div.text') { return []; } return oldFind.call(this, selector); }; try { result = old_add_message.call(this, message, domeleParam, index); } finally { domeleParam.find = oldFind; } messagesCount++; var totalHeight = 0; while (messagesCount > messagesCountMax) { var element = chat.firstElementChild; if (!at_bottom) { var outerHeight = $(element).outerHeight(true); totalHeight += outerHeight; } chat.removeChild(element); messagesCount--; } if (at_bottom) { chat.scrollTop = chat.scrollHeight; } else { chat.scrollTop = originalScrollTop - totalHeight; } updateChatLength(); return result; } function getSilencer(message, nick) { var slug = 'silencer'; var text = interpolate(gettext("User %(username)s was silenced by %(silencer)s and his/her messages have been removed"), { username: nick, silencer: slug }, true); var parts = text.split(slug); var part1Index = message.indexOf(parts[0]); if (part1Index >= 0) { var part2Index = message.indexOf(parts[1], part1Index + parts[0].length); if (part2Index >= 0) { var silencer = message.substring(part1Index + parts[0].length, part2Index); if (silencer) { return silencer; } } } return null; } function isBanText(message, nick) { var text = gettext('User') + ' ' + nick + ' ' + gettext('was kicked out of the room and his/her messages have been removed'); return message.indexOf(text) >= 0; } chat.addEventListener('dblclick', onChatClick, false); chat.addEventListener('click', onChatClick, false); function onChatClick(evt) { if (evt.button != 0 || evt.shiftKey || evt.altKey || evt.metaKey) { return; } var element = evt.target; while (element == chat || element.parentNode != chat) { if (element == chat || !element.parentNode) { return; } element = element.parentNode; } if (element.nodeName != 'DIV' || !element.classList.contains('text')) { return; } var msgDiv = element; var nick = null; if (msgDiv.hasAttribute(removedNickAttr)) { nick = msgDiv.getAttribute(removedNickAttr); } else { var paragraph = msgDiv.firstElementChild; if (paragraph && paragraph.nodeName == 'P') { var usernameElement = paragraph.firstElementChild; if (usernameElement && usernameElement.hasAttribute('data-nick')) { nick = usernameElement.getAttribute('data-nick'); } } } if (!nick) { return; } var ban = evt.ctrlKey; if (evt.type == 'click') { // toggling expanded/collapsed state if (msgDiv.hasAttribute(removeTypeAttr) && !ban) { if (msgDiv.msgCollapsed) { expandMessage(msgDiv); } else { collapseMessage(msgDiv); } } } else if (evt.type == 'dblclick' && evt.detail == 2) { // modding if (!msgDiv.hasAttribute(removeTypeAttr) || ban && msgDiv.getAttribute(removeTypeAttr) != 'ban') { window.getSelection().removeAllRanges(); evt.stopImmediatePropagation(); evt.stopPropagation(); evt.preventDefault(); if (messagesCount >= messagesCountMax && chat.scrollTop <= 0) { alert('The chat box is scrolled to the top and there is also the maximum mumber of messages in it, which is ' + messagesCountMax + '. First, either scroll it a little bit down, or delete some of the messages by clicking on that floating button with the number of messages.'); return; } if (is_at_bottom() && chat.scrollHeight > chat.clientHeight) { alert('The chat list is scrolled to the bottom. First, scroll it up by pressing SPACE.'); return; } if (!simulationMode) { if (ban) { message_outbound.send_kickban_user(nick); } var url = '/' + (ban ? 'roomban' : 'roomsilence') + '/' + nick + '/' + broadcaster + '/'; $.post(url, { 'foo': 'bar' }, function(data, textStatus, jqXHR) { if (data == 'OK' && textStatus == 'success') { } else { if (ban) { alert('A ban request has failed.'); } else { alert('A silence request has failed.'); } } }).fail(function() { if (ban) { alert('A ban request has failed.'); } else { alert('A silence request has failed.'); } }); } else { window.setTimeout(function() { /* if (ban) { on_user_banned(nick); } else { on_user_silenced(nick, 'nobody'); } */ remove_messages(nick); var text; if (ban) { text = gettext('User') + ' ' + nick + ' ' + gettext('was kicked out of the room and his/her messages have been removed'); } else { text = interpolate(gettext("User %(username)s was silenced by %(silencer)s and his/her messages have been removed"), { username: nick, silencer: 'nobody' }, true); } $.add_system_message(text, domele); }, 700); } msgDiv.style.outlineColor = ban ? pendingBanOutlineColor : pendingSilenceOutlineColor; msgDiv.style.outlineWidth = ban ? pendingBanOutlineWidth : pendingSilenceOutlineWidth; msgDiv.style.outlineStyle = ban ? pendingBanOutlineStyle : pendingSilenceOutlineStyle; } } } function toggleExpand(noStateChange) { var expand = !deletedMessagesShouldBeExpanded; if (!noStateChange) { toggleExpandButton.value = expand ? collapseChar : expandChar; deletedMessagesShouldBeExpanded = expand; setDeletedMessagesShouldBeExpandedCookie(); } var at_bottom = is_at_bottom(); var msgDiv = chat.firstElementChild; while (msgDiv) { if (msgDiv.hasAttribute(removeTypeAttr)) { if (expand) { if (msgDiv.msgCollapsed) { expandMessage(msgDiv); } } else { if (!msgDiv.msgCollapsed) { collapseMessage(msgDiv); } } } msgDiv = msgDiv.nextElementSibling; }; if (at_bottom) { chat.scrollTop = chat.scrollHeight; } } theElement.addEventListener('keypress', function(evt) { var nodeName = evt.target.nodeName; if (nodeName != 'INPUT' && nodeName != 'TEXTAREA' && nodeName != 'BUTTON') { var character = String.fromCharCode(evt.charCode); if (character == ' ') { if (is_at_bottom()) { chat.scrollTop = chat.scrollHeight - chat.clientHeight - pixelsToScrollOffChatBottom; } else { chat.scrollTop = chat.scrollHeight; } evt.stopImmediatePropagation(); evt.stopPropagation(); evt.preventDefault(); } else if (character == '<' || character == '>') { var noStateChange = character == '>'; toggleExpand(noStateChange); } } }, false); toggleExpandButton.onclick = function(evt) { var noStateChange = evt.shiftKey; toggleExpand(noStateChange); if (document.activeElement == toggleExpandButton) { toggleExpandButton.blur(); document.body.focus(); } } } console.info("ChaturbateBetterModding user script is running"); function contentEval(source) { 'use strict'; // Check for function input. if (typeof source == 'function') { // Execute this function with no arguments, by adding parentheses. // One set around the function, required for valid syntax, and a // second empty set calls the surrounded function. source = '(' + source + ')();' } // Create a script node holding this source code. var script = document.createElement('script'); script.setAttribute('type', 'application/javascript'); script.textContent = source; // Insert the script node into the page, so it will run, and immediately // remove it to clean up. document.body.appendChild(script); document.body.removeChild(script); } function getBroadcaster() { 'use strict'; var pathArray = window.location.pathname.split('/'); if (pathArray.length == 3 && pathArray[0] == '' && pathArray[1] != '' && pathArray[2] == '') { return pathArray[1]; } else if (pathArray.length == 4 && pathArray[0] == '' && pathArray[1] == 'b' && pathArray[2] != '' && pathArray[3] == '') { return pathArray[2]; } else { return; } } if (getBroadcaster()) { var func = function(event) { if (document.forms.chat_form) { if (window.$) { doBetterModdingChanges(); } else { // we are running in sandbox console.info("injecting content script"); contentEval(doBetterModdingChanges); } } }; if (window.defchat_settings) { // This script was injected way too late. // Will not be able to get message_receiver. console.error("defchat already initialized"); func(); } else if (window.$) { // jQuery was loaded before this script. It sometimes happens in Chromium. // Need to use jQuery's handler of DOMContentLoaded event. console.warn("using jQuery's handler of DOMContentLoaded event"); $(func); } else { // the normal case document.addEventListener('DOMContentLoaded', func); } } // vim: tabstop=2:shiftwidth=2:expandtab