Greasy Fork is available in English.

TorViet Shoutbox Enhancer

A small script to tweak the shoutbox

// ==UserScript==
// @name         TorViet Shoutbox Enhancer
// @namespace    http://torviet.com/userdetails.php?id=1662
// @version      1.1.5
// @license      http://www.wtfpl.net/txt/copying/
// @homepageURL  https://github.com/S-a-l-a-d/TorViet-Shoutbox-Enhancer
// @supportURL   https://github.com/S-a-l-a-d/TorViet-Shoutbox-Enhancer/issues
// @icon         http://torviet.com/pic/salad.png
// @description  A small script to tweak the shoutbox
// @author       Salad
// @match        http://torviet.com/qa.php*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// ==/UserScript==

((window, document) => {
  const allWrapper = document.getElementById('all-wrapper');
  const boxHead = document.getElementById('boxHead');
  const marquee = document.getElementById('marquee');
  const sltTheme = document.getElementById('sltTheme');
  const clock = document.getElementById('clock');
  const idQuestion = document.getElementById('idQuestion');
  const emoGroup = document.getElementById('emo-group');
  const emoGroupDetail = document.getElementById('emo-group-detail');
  const apiPath = 'qa_smiley_ajax.php';

  class DomElementHelper {
    static appendSibling(newElement, referenceElement) {
      if (!newElement || !referenceElement) {
        return false;
      }

      referenceElement.parentNode.insertBefore(newElement, referenceElement.nextSibling);

      return true;
    }

    static remove(element) {
      if (!element) {
        return false;
      }

      element.parentNode.removeChild(element);

      return true;
    }
  }

  class EmoticonService {
    static getEmoticon(emoticonName) {
      if (isNaN(emoticonName) || !this.isInteger(emoticonName)) {
        return Promise.resolve('');
      }

      return Promise.resolve(`<div style="height:43px;width:43px;float:left;display:inline-block;margin: 0 0 1px 1px;"><img style="max-width:43px;max-height:43px;cursor:pointer;" src="/pic/smilies/${emoticonName}.gif" alt="[em${emoticonName}]"></div>`);
    }

    static isInteger(number) {
      return number === parseInt(number, 10) || number === parseInt(number, 10).toString();
    }

    static getEmoticons(url, emoticonGroupName) {
      if (!url || !emoticonGroupName || !isNaN(emoticonGroupName)) {
        return null;
      }

      return new Promise((resolve, reject) => {
        const request = new XMLHttpRequest();

        request.open('POST', url);
        request.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

        request.onload = () => {
          if (request.status === 200) {
            resolve(JSON.parse(request.responseText).str);
          } else {
            reject(Error(request.statusText));
          }
        };

        request.onerror = () => {
          reject(Error('Network error'));
        };

        request.send(`group=${emoticonGroupName}`);
      });
    }
  }

  const EMOTICON = (() => {
    let cachedEmoticonList = GM_getValue('emoticonList');
    let cachedEmoticonListHtml = GM_getValue('emoticonListHtml') || '';

    const promptForEmoticonList = (action, emoticonList) => {
      let message = `Chọn bộ emoticon bạn muốn ${action}:\n`;
      let answer = '';

      message += emoticonList.reduce((previous, current, index) =>
        previous + `${index + 1}. ${current}\n`, '');

      message += 'Điền tên bộ emoticon, ngăn cách bằng dấu phẩy, phân biệt hoa/thường. Có thể điền emoticon đơn bằng cách điền tên tập tin emoticon đó.\nVí dụ: Voz,707,Rage';

      answer = prompt(message);

      if (!answer || !answer.trim()) { return null; }

      return answer.trim().split(',');
    };

    const initEmoticonList = () => {
      const availableEmoticonList = [...emoGroup.options].map(element => element.text);

      cachedEmoticonList = promptForEmoticonList('sử dụng', availableEmoticonList);

      if (!cachedEmoticonList) { return; }

      GM_setValue('emoticonList', cachedEmoticonList);
    };

    return {
      emoticonListExists: () => {
        if (!cachedEmoticonList) initEmoticonList();
      },
      addToDom: async () => {
        emoGroupDetail.innerHTML = '';

        if (!cachedEmoticonListHtml) {
          cachedEmoticonListHtml = (await Promise.all(cachedEmoticonList.map(async (item) => {
            return isNaN(item) ?
              await EmoticonService.getEmoticons(apiPath, item) :
              await EmoticonService.getEmoticon(item);
          }))).join('');

          GM_setValue('emoticonListHtml', cachedEmoticonListHtml);
        }

        emoGroupDetail.innerHTML = cachedEmoticonListHtml;
      },
      add: () => {
        const availableEmoticonList = [...emoGroup.options]
          .map(item => item.text)
          .filter(item => !cachedEmoticonList.includes(item));

        const emoticonListToAdd = promptForEmoticonList('thêm', availableEmoticonList);

        if (!emoticonListToAdd) { return; }

        cachedEmoticonList = [
          ...cachedEmoticonList,
          ...emoticonListToAdd.filter(item => !cachedEmoticonList.includes(item))
        ];

        GM_setValue('emoticonList', cachedEmoticonList);
        GM_deleteValue('emoticonListHtml');
        window.location.href = window.location.pathname;
      },
      remove: () => {
        const emoticonListToRemove = promptForEmoticonList('xóa', cachedEmoticonList);

        if (!emoticonListToRemove) { return; }

        cachedEmoticonList =
          cachedEmoticonList.filter(item => !emoticonListToRemove.includes(item));

        GM_setValue('emoticonList', cachedEmoticonList);
        GM_deleteValue('emoticonListHtml');
        window.location.href = window.location.pathname;
      },
      clear: () => {
        GM_deleteValue('emoticonList');
        GM_deleteValue('emoticonListHtml');
        window.location.href = window.location.pathname;
      },
    };
  })();

  function isFirefoxBrowser() {
    return typeof InstallTrigger !== 'undefined';
  }

  function createButton(text, event) {
    const button = document.createElement('input');
    button.type = 'button';
    button.value = text;
    button.addEventListener('click', event);

    return button;
  }

  allWrapper.className = '';
  DomElementHelper.remove(boxHead);
  DomElementHelper.remove(marquee);
  DomElementHelper.remove(sltTheme);
  while (clock.lastChild) {
    DomElementHelper.remove(clock.lastChild);
  }

  const stylesheet = isFirefoxBrowser() ?
    `
      #wrapper-below {
          height: calc(100% - 67px);
      }

      #emo-section {
          height: calc(100% - 74px);
      }
      ` :
    `
      #wrapper-below {
          height: calc(100% - 62px);
      }

      #emo-section {
          height: calc(100% - 69px);
      }
      `;

  GM_addStyle(
    `
      .slimScrollDiv, #emo-group-detail {
          height: 100% !important;
      }
      ${stylesheet}`);

  const clockChild = document.createDocumentFragment();
  const span = document.createElement('span');

  span.innerHTML = 'For custom emoticon group<br>';

  clockChild.appendChild(emoGroup.parentNode);
  clockChild.appendChild(span)
    .parentNode.appendChild(createButton('Add', EMOTICON.add))
    .parentNode.appendChild(createButton('Remove', EMOTICON.remove))
    .parentNode.appendChild(createButton('Clear', EMOTICON.clear));
  clock.appendChild(clockChild);

  EMOTICON.emoticonListExists();
  EMOTICON.addToDom();

  idQuestion.focus();
})(window, document);