TorViet Shoutbox Enhancer

A small script to tweak the shoutbox

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);