Hummingbird User Compare

Adds a button that compares the anime list of a hummingbird user against yours

Verze ze dne 29. 04. 2015. Zobrazit nejnovější verzi.

// ==UserScript==
// @name         Hummingbird User Compare
// @version      3.0.1
// @description  Adds a button that compares the anime list of a hummingbird user against yours
// @author       fuzetsu
// @match        https://hummingbird.me/*
// @match        https://forums.hummingbird.me/*
// @grant        none
// @require      https://greasyfork.org/scripts/5679-wait-for-elements/code/Wait%20For%20Elements.js?version=46106
// @noframes
// @namespace https://greasyfork.org/users/649
// ==/UserScript==
var SCRIPT_NAME = 'Hummingbird User Compare';
var BTN_ID = 'us-compare-button';
var COMPAT_ID = 'us-compat-table';
var USERNAME_SELECTOR = 'h2.username,h1.username';

var Util = {
  log: function() {
    var args = [].slice.call(arguments);
    args.unshift('%c' + SCRIPT_NAME + ':', 'font-weight: bold');
    console.log.apply(console, args);
  },
  q: function(query, context) {
    return (context || document).querySelector(query);
  },
  qq: function(query, context) {
    return [].slice.call((context || document).querySelectorAll(query));
  },
  shallowTextContent: function(elem) {
    var child = elem.firstChild,
        texts = [];

    while (child) {
      if (child.nodeType == 3) {
        texts.push(child.data);
      }
      child = child.nextSibling;
    }

    return texts.join("");
  },
  arrToTable: function(arr, opts) {
    // takes an object and converts to an html compliant list of attributes
    var makeAttrs = function(obj) {
      var str = '';
      var key;
      for(key in obj) {
        if(obj.hasOwnProperty(key)) {
          str += key + '="' + obj[key] + '" ';
        }
      }
      return str;
    };
    
    // creates an object of the specified type
    // either takes a config object with attributes and a value, or just the value directly
    var createElement = function(type, data) {
      if(typeof data === 'object') {
        return '<' + type +' ' + makeAttrs(data.attrs || {}) + '>' + data.value + '</' + type + '>';
      }
      return '<' + type + '>' + data + '</' + type + '>';
    };

    opts = opts || {};

    var headers = '';

    // if the table is going to have a head use the first row
    if(opts.hasHead) {
      headers = arr[0].map(function(header) {
        return createElement('th', header);
      }).join('');
      headers = '<thead>' + headers + '</thead>';
      arr = arr.slice(1);
    }

    // create the body of the array
    var rows = arr.map(function(row) {
      return '<tr>' + row.map(function(cell) {
        return createElement('td', cell);
      }).join('') + '</tr>';
    }).join('');

    // create and return the actual table all put together
    return createElement('table', {
      attrs: opts.attrs,
      value: (headers + '<tbody>' + rows + '</tbody>')
    });
    
  },
  getJSON: function(url, load, error) {
    var xhr = new XMLHttpRequest();
    xhr.open('get', url);
    xhr.responseType = 'json';
    xhr.onload = function() {
      load(xhr.response);
    };
    xhr.onerror = error;
    xhr.send();
  }
};

var hb = {
  getCompareUrl: function() {
    var profileUrl = Util.q('.dropdown-menu > li > a').href;
    var you = profileUrl.slice(profileUrl.lastIndexOf('/') + 1);
    var them = Util.shallowTextContent(Util.q(USERNAME_SELECTOR)).trim();
    return {
      you: you,
      them: them,
      url: 'http://fuzetsu.github.io/hummingbird-user-compare/?user1=' + you + '&user2=' + them
    };
  },
  getCompatibility: function(callback) {
    var buffer = [];

    var process = function(data) {
      buffer.push(data);
      if(buffer.length === 2) { 
        var anime, manga;
        if (buffer[0].type === 'anime') {
          anime = buffer[0];
          manga = buffer[1];
        } else {
          anime = buffer[1];
          manga = buffer[0];
        }
        callback(anime, manga);
      }
    };
    
    var profileUrl = Util.q('.dropdown-menu > li > a').href;
    var you = profileUrl.slice(profileUrl.lastIndexOf('/') + 1);
    var them = Util.shallowTextContent(Util.q(USERNAME_SELECTOR)).trim();

    // get anime compat
    Util.getJSON('https://hbird-cmp-node.herokuapp.com/compatibility/anime?user1=' + you + '&user2=' + them, process);
    // get manga compat
    Util.getJSON('https://hbird-cmp-node.herokuapp.com/compatibility/manga?user1=' + you + '&user2=' + them, process);
  }
};

var lastUser;

Util.log('Started, waiting for user page...');

waitForUrl(/https:\/\/(forums\.)?hummingbird\.me\/users\/.+/, function() {
  Util.log('Found user page, waiting for button area...');
  waitForElems('.user-cover-options .follow-button:not(.' + BTN_ID + ')', function(btnFollow) {
    var forumButton = Util.q('.account-info .inline-list a');
    var btn = Util.q('#' + BTN_ID);
    var compatTable = Util.q('#' + COMPAT_ID);
    var compare = hb.getCompareUrl();

    var compatibilityCallback = function(anime, manga) {
      var animeMessage = anime.phrase;
      if (anime.percent) {
        animeMessage += ' (' + anime.percent +  ')';
      }
      var mangaMessage = manga.phrase;
      if (manga.percent) {
        mangaMessage += ' (' + manga.percent +  ')';
      }
      var table = [];
      table.push([{value: 'Anime Compatability:', attrs: {style: 'text-align: right; padding-right: 5px;'}}, {value: animeMessage, attrs: {style: 'font-weight:bold'}}]);
      table.push([{value: 'Manga Compatability:', attrs: {style: 'text-align: right; padding-right: 5px;'}}, {value: mangaMessage, attrs: {style: 'font-weight:bold'}}]);
      btnFollow.parentNode.innerHTML += Util.arrToTable(table, {
        hasHead: false,
        attrs: {
          style: 'position: absolute; bottom: -25px; right: 0;',
          id: COMPAT_ID
        }
      });
    };

    // exit early if you're on your own profile
    if(compare.them === compare.you) return;
    Util.log('Found button area, inserting compare button');
    if(forumButton) {
      forumButton.title = '';
      forumButton.target = '_blank';
      forumButton.href = compare.url;
    } else {
      if(btn) {
        btn.remove();
      }
      btn = document.createElement('a');
      btn.className = btnFollow.className;
      btn.id = BTN_ID;
      btn.textContent = 'Compare';
      btn.target = '_blank';
      btn.setAttribute('style', 'right: ' + (btnFollow.clientWidth + 10) + 'px; background: rgb(236, 134, 97); color: white;');

      if(compare.them === lastUser && location.href.indexOf("/users/" + lastUser) === -1) {
        if (compatTable) {
          compatTable.remove();
        }
        Util.q(USERNAME_SELECTOR).addEventListener('DOMSubtreeModified', function(e) {
          this.removeEventListener('DOMSubtreeModified', arguments.callee);
          compare = hb.getCompareUrl();
          btn.href = compare.url;
          btnFollow.parentNode.appendChild(btn);
          hb.getCompatibility(compatibilityCallback);
          
        });
      } else {
        var found = false;
        if (compatTable) {
          found = true;
          if (compare.them !== lastUser) {
            compatTable.remove();
          }
        }
        btn.href = compare.url;
        btnFollow.parentNode.appendChild(btn);
        if (!found || compare.them !== lastUser) {
          hb.getCompatibility(compatibilityCallback);
        }
      }
    }
    lastUser = compare.them;
  }, true);
});