ChaturbateBetterModding

Provides a better modding interface for the cam girls site of chaturbate.com. This script makes modding faster and easier. Check out the details.

As of 2014-08-02. See the latest version.

// ==UserScript==
// @name ChaturbateBetterModding
// @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
// @license GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @version 2.5
// ==/UserScript==

// Author: Vladimir Pycha vpycha@gmail.com
// Website: vladpride.cz
// Nickname on chaturbate.com: vlad88x (banned permanently), vlad88z
// Nickname on greasyfork.org: vlad88
// First revision created and released on: July 2013

// This script is hosted at greasyfork.org.
// There is also a detailed description of this script there.

function doBetterModdingChanges() {
  var messagesCountMax = 200;
  var mostRecentMessagesCount = 4;
  var doRealDeleteWhenCollapsingMessages = false;
  var cookieExpirePeriod = 1 * 365;
  var pixelsToScrollOffChatBottom = 5;
  var expandChar = '+';
  var collapseChar = '-';

  var output = document.getElementById('ChaturbateBetterModding');
  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>';

  var $ = jQuery;
  $.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();

  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();
  }

  var domele = $(theElement.getElementsByClassName('chat-list')[0]);
  var chat = domele.get(0);

  var messagesCount = 0;
  var previousChatOuterWidth = 0;

  var pathArray = window.location.pathname.split('/');
  var broadcaster;
  if (pathArray.length == 3 && pathArray[0] == "" && pathArray[1] != "" && pathArray[2] == "") {
    broadcaster = pathArray[1];
  }
  else
  if (pathArray.length == 4 && pathArray[0] == "" && pathArray[1] == "b" && pathArray[2] != "" && pathArray[3] == "") {
    broadcaster = pathArray[2];
  }
  else {
    return;
  }

  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();
  window.ChaturbateBetterModding_updateControlsContainerPosition = 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() {
    //var length = chat.childNodes.length;
    var length = messagesCount;
    chatLengthButton.value = length.toString();
    if (length >= messagesCountMax) {
      chatLengthButton.style.color = 'rgb(255,0,0)';
    }
    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 node = chat.lastChild;
      do {
        while (node && node.nodeName != 'DIV') {
          node = node.previousSibling;
        }
        if (!node) {
          break;
        }
        totalHeight += $(node).outerHeight(true);
        recentMessagesCount++;
        node = node.previousSibling;
      } while (totalHeight < chat.clientHeight + pixelsToScrollOffChatBottom);
    }

    while (messagesCount > recentMessagesCount) {
      while (chat.firstChild.nodeName != 'DIV') {
        chat.removeChild(chat.firstChild);
      }
      chat.removeChild(chat.firstChild);
      messagesCount--;
    }

    chat.scrollTop = chat.scrollHeight;
    updateChatLength();
  }

  var messagesToRemove = [];

  chat.addEventListener('scroll', onChatScroll, false);

  var silencedNickAttr = 'data-silenced-nick';
  var silencerNickAttr = 'data-silencer-nick';
  var deletedMessageHeight = '10px';

  function is_at_bottom() {
    return chat.scrollTop >= chat.scrollHeight - chat.clientHeight;
  }

  function onChatScroll(evt) {
    if (messagesToRemove.length > 0 && is_at_bottom()) {
      for (var i = messagesToRemove.length - 1; i >= 0; i--) {
        var msgDiv = messagesToRemove[i];
        messagesToRemove[i] = null;
        if (!deletedMessagesShouldBeExpanded && msgDiv && msgDiv.parentNode) {
          //msgDiv.parentNode.removeChild(msgDiv);
          if (!msgDiv.htmlBeforeDeleting) {
            deleteMessage(msgDiv);
          }
        }
        messagesToRemove.length = i;
      }
    }
  }

  var bannedNickAttr = 'data-banned-nick';

  function getSilencedByTitle(silencer_nick) {
    return 'silenced by ' + silencer_nick;
  }

  var deletedMessageBgColor = 'rgb(225,225,225)';

  var on_user_silenced = function (silenced_nick, silencer_nick) {
    $(".chat-list > div.text > [data-nick='" + silenced_nick + "']").each(function (index, value) {
      //$(this).parent("div.text").remove();

      var msgDiv = this.parentNode;
      if (!msgDiv.getAttribute(silencedNickAttr) && !msgDiv.getAttribute(bannedNickAttr)) {
        msgDiv.setAttribute(silencedNickAttr, silenced_nick);
        msgDiv.setAttribute(silencerNickAttr, silencer_nick);
        msgDiv.setAttribute('title', getSilencedByTitle(silencer_nick));
        msgDiv.style.background = '';
        msgDiv.style.backgroundColor = deletedMessageBgColor;
        messagesToRemove[messagesToRemove.length] = msgDiv;
      }

      onChatScroll(null);
    });
    /*var text = interpolate(gettext("User %(username)s was silenced by %(silencer)s and his/her messages have been removed"), {
        username: silenced_nick,
        silencer: silencer_nick
    }, true);*/
    var text = 'User ' + silenced_nick + ' was silenced by ' + silencer_nick;
    $.add_system_message(text, domele);
  };

  var kickedOutTitle = 'kicked out';

  var on_user_banned = function (username) {
    $(".chat-list > div.text > [data-nick='" + username + "']").each(function (index, value) {
      //$(this).parent("div.text").remove();

      var msgDiv = this.parentNode;
      if (!msgDiv.getAttribute(bannedNickAttr)) {
        if (msgDiv.getAttribute(silencedNickAttr)) {
          msgDiv.removeAttribute(silencedNickAttr);
          msgDiv.removeAttribute(silencerNickAttr);
        }

        msgDiv.setAttribute(bannedNickAttr, username);
        msgDiv.setAttribute('title', kickedOutTitle);
        msgDiv.style.background = '';
        msgDiv.style.backgroundColor = deletedMessageBgColor;
        messagesToRemove[messagesToRemove.length] = msgDiv;

        msgDiv.style.outlineColor = 'rgb(40,40,40)';
        msgDiv.style.outlineWidth = '2px';
        msgDiv.style.outlineStyle = 'solid';
      }

      onChatScroll(null);
    });
    /*var text = interpolate(gettext("User %(username)s was silenced by %(silencer)s and his/her messages have been removed"), {
        username: silenced_nick,
        silencer: silencer_nick
    }, true);*/
    var text = 'User ' + username + ' was kicked out of the room';
    $.add_system_message(text, domele);
  };

  var old_add_message;
  var message_outbound = null;

  setTimeout(function() {
    var message_receiver = flash_handler.defchat_message_receiver;

    message_receiver.on_user_silenced = on_user_silenced;
    message_receiver.on_user_banned = on_user_banned;

    old_add_message = message_receiver.add_message;
    messagesCount = domele.find('> div').length;
    message_receiver.add_message = add_message;

    message_outbound = flash_handler.message_outbound;

    setControlsContainerPosition();
  }, 1);

  function undeleteMessage(msgDiv) {
      msgDiv.style.height = '';
      msgDiv.childNodes[0].style.display = '';
      if (doRealDeleteWhenCollapsingMessages) {
        msgDiv.childNodes[1].innerHTML = msgDiv.htmlBeforeDeleting;
      }
      else {
        msgDiv.childNodes[1].style.display = '';
      }
      msgDiv.htmlBeforeDeleting = '';
      if (msgDiv.hasAttribute(silencedNickAttr)) {
        var silencer_nick = msgDiv.getAttribute(silencerNickAttr);
        msgDiv.setAttribute('title', getSilencedByTitle(silencer_nick));
      }
      else
      if (msgDiv.hasAttribute(bannedNickAttr)) {
        msgDiv.setAttribute('title', kickedOutTitle);
      }
  }

  function deleteMessage(msgDiv) {
    if (doRealDeleteWhenCollapsingMessages) {
      msgDiv.htmlBeforeDeleting = msgDiv.childNodes[1].innerHTML;
      msgDiv.childNodes[1].innerHTML = '';
    }
    else {
      msgDiv.htmlBeforeDeleting = '1';
      msgDiv.childNodes[1].style.display = 'none';
    }
    msgDiv.childNodes[0].style.display = 'none';
    msgDiv.style.height = deletedMessageHeight;
    if (msgDiv.hasAttribute(silencedNickAttr)) {
      var silenced_nick = msgDiv.getAttribute(silencedNickAttr);
      var silencer_nick = msgDiv.getAttribute(silencerNickAttr);
      msgDiv.setAttribute('title', '' + silenced_nick + ', silenced by ' + silencer_nick);
    }
    else
    if (msgDiv.hasAttribute(bannedNickAttr)) {
      var username = msgDiv.getAttribute(bannedNickAttr);
      msgDiv.setAttribute('title', '' + username + ', kicked out');
    }
  }

  function add_message(message, domeleParam) {
    if (!domeleParam) {
      domeleParam = domele;
    }
    if (domeleParam.get(0) != chat) {
      return old_add_message.call(this, message, domeleParam);
    }
    var oldFind = domeleParam.find;
    domeleParam.find = function(selector) {
      if (selector == 'div.text') {
        return [];
      }
      return oldFind.call(this, selector);
    };
    var originalScrollTop = chat.scrollTop;
    var at_bottom = is_at_bottom();
    var result;
    try {
      result = old_add_message.call(this, message, domeleParam);
    }
    finally {
      domeleParam.find = oldFind;
    }
    messagesCount++;
    var totalHeight = 0;
    while (messagesCount > messagesCountMax) {
      var element = chat.firstChild;
      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;
  }

  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 ban = evt.ctrlKey;
    var nick;
    var par = evt.target; // par means paragraph
    if (par.nodeName == 'IMG') {
      par = par.parentNode;
    }
    var msgDiv;
    if (par.nodeName == 'P') {
      msgDiv = par.parentNode;
    }
    else {
      msgDiv = par;
      par = null;
    }
    var s = msgDiv.firstChild; // s means span
    if (msgDiv.nodeName == 'DIV' && msgDiv.getAttribute('class') == 'text' && s && s.nodeName == 'SPAN' && strStartsWith(s.className, 'username messagelabel')) {
      var nick;
      var banned = msgDiv.hasAttribute(bannedNickAttr);
      var isDeletedMessage = msgDiv.hasAttribute(silencedNickAttr) || banned;
      var isDoubleClick = evt.type == 'dblclick' && evt.detail == 2;
      var togglingExpanded = isDeletedMessage && evt.type == 'click' && !ban;
      var modding = !isDeletedMessage && isDoubleClick || isDeletedMessage && isDoubleClick && ban && !banned;
      if (togglingExpanded || modding) {
        if (!isDeletedMessage) {
          nick = s.getAttribute('data-nick');
        }
        else {
          nick = msgDiv.getAttribute(silencedNickAttr);
          if (!nick) {
            nick = msgDiv.getAttribute(bannedNickAttr);
          }
        }

        if (nick) {
          if (modding) {
            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 (togglingExpanded) {
            if (msgDiv.htmlBeforeDeleting) {
              undeleteMessage(msgDiv);
            }
            else {
              deleteMessage(msgDiv);
            }
          }
          else {
            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) {
                //alert('data: ' + data + ', textStatus: ' + textStatus);
                if (data == 'OK' && textStatus == 'success') {
                }
                else {
                  if (ban) {
                    alert('A ban request has failed.');
                  }
                  else {
                    alert('A silence request has failed.');
                  }
                }
              });
            }
            else {
              window.setTimeout(function() {
                //msgDiv.innerHTML = '<p>It was ' + nick + '</p>';
                if (ban) {
                  on_user_banned(nick);
                }
                else {
                  on_user_silenced(nick, 'nobody');
                }
              }, 700);
            }
            msgDiv.style.outlineColor = ban ? 'rgb(40,40,40)' : 'rgb(150,150,150)';
            msgDiv.style.outlineWidth = '2px';
            msgDiv.style.outlineStyle = ban ? 'dashed' : 'solid';
          }
        }
      }
    }
  }

  function toggleExpand(noStateChange) {
    var expand = !deletedMessagesShouldBeExpanded;
    if (!noStateChange) {
      toggleExpandButton.value = expand ? collapseChar : expandChar;
      deletedMessagesShouldBeExpanded = expand;
      setDeletedMessagesShouldBeExpandedCookie();
    }
    var at_bottom = is_at_bottom();
    domele.find('> div').each(function(index, value) {
      var msgDiv = value;
      if (!msgDiv.getAttribute) {
        alert(msgDiv);
      }
      if (msgDiv.getAttribute(silencedNickAttr) || msgDiv.getAttribute(bannedNickAttr)) {
        if (expand) {
          if (msgDiv.htmlBeforeDeleting) {
            undeleteMessage(msgDiv);
          }
        }
        else {
          if (!msgDiv.htmlBeforeDeleting) {
            deleteMessage(msgDiv);
          }
        }
      }
      msgDiv = msgDiv.nextSibling;
    });
    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();
    }
  }
}

var output = document.createElement('div');
output.setAttribute('id', 'ChaturbateBetterModding');
output.setAttribute('style', 'padding: 0px 8px 10px 8px');
document.body.appendChild(output);

contentEval(doBetterModdingChanges.toString());

if (document.forms.chat_form) {
  contentEval('doBetterModdingChanges();');
}
else {
  output.innerHTML = 'ChaturbateBetterModding user script is on';
}

function contentEval(source) {
  // Check for function input.
  if ('function' == typeof source) {
    // 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);
}