Greasy Fork is available in English.

Hummingbird Fansub Info

Show MAL fansub info on Hummingbird anime pages

ของเมื่อวันที่ 22-08-2016 ดู เวอร์ชันล่าสุด

// ==UserScript==
// @name         Hummingbird Fansub Info
// @namespace    https://greasyfork.org/users/649
// @version      1.1
// @description  Show MAL fansub info on Hummingbird anime pages
// @author       Adrien Pyke
// @match        *://hummingbird.me/*
// @require      https://greasyfork.org/scripts/5679-wait-for-elements/code/Wait%20For%20Elements.js?version=122976
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

(function() {
	'use strict';

	var SCRIPT_NAME = 'Hummingbird Fansub Info';
	var API = 'https://hummingbird.me/api/v1';
	var REGEX = /^https?:\/\/hummingbird\.me\/anime\/([^\/]+)\/?(?:\?.*)?$/;
	var DIV_ID = 'hbfs-fansubs';

	GM_addStyle('.trending-review-empty { margin-bottom: 20px; }');

	var Util = {
		log: function () {
			var args = [].slice.call(arguments);
			args.unshift('%c' + SCRIPT_NAME + ':', 'font-weight: bold;color: #233c7b;');
			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));
		},
		createModal: function(title, bodyDiv) {
			var div = document.createElement('div');
			div.classList.add('edit-series-modal');
			var modal = document.createElement('div');
			modal.classList.add('modal');
			modal.style.display = 'block';
			div.appendChild(modal);
			var backdrop = document.createElement('div');
			backdrop.classList.add('modal-backdrop', 'fade', 'in');
			div.appendChild(backdrop);
			var dialog = document.createElement('div');
			dialog.classList.add('modal-dialog');
			modal.appendChild(dialog);
			var content = document.createElement('div');
			content.classList.add('modal-content');
			dialog.appendChild(content);
			var header = document.createElement('div');
			header.classList.add('modal-header');
			content.appendChild(header);
			var body = document.createElement('div');
			body.classList.add('modal-body');
			content.appendChild(body);

			var h4 = document.createElement('h4');
			h4.classList.add('modal-title');
			h4.textContent = title;
			header.appendChild(h4);

			body.appendChild(bodyDiv);

			div.onclick = function(e) {
				if (e.target === modal || e.target === backdrop) {
					div.remove();
				}
			};
			document.body.appendChild(div);
		}
	};

	var Config = {
		load: function() {
			var defaults = {
				lang: null
			};

			var cfg = GM_getValue('cfg');
			if (!cfg) return defaults;

			cfg = JSON.parse(cfg);
			for (var property in defaults) {
				if (defaults.hasOwnProperty(property)) {
					if (!cfg[property]) {
						cfg[property] = defaults[property];
					}
				}
			}

			return cfg;
		},

		save: function (cfg) {
			GM_setValue('cfg', JSON.stringify(cfg));
		},

		setup: function() {
			var createContainer = function() {
				var div = document.createElement('div');
				div.style.backgroundColor = 'white';
				div.style.padding = '5px';
				div.style.border = '1px solid black';
				div.style.position = 'fixed';
				div.style.top = '0';
				div.style.right = '0';
				div.style.zIndex = 99999;
				return div;
			};

			var createButton = function(text, onclick) {
				var button = document.createElement('button');
				button.style.margin = '2px';
				button.textContent = text;
				button.onclick = onclick;
				return button;
			};

			var createTextbox = function(value, placeholder) {
				var input = document.createElement('input');
				input.value = value;
				if (placeholder) {
					input.setAttribute('placeholder', placeholder);
				}
				return input;
			};

			var createLabel = function(label) {
				var lbl = document.createElement('span');
				lbl.textContent = label;
				return lbl;
			};

			var createLineBreak = function() {
				return document.createElement('br');
			};

			var init = function(cfg) {
				var div = createContainer();

				var lang = createTextbox(cfg.lang, 'Languages (Comma Seperated)');
				div.appendChild(createLabel('Languages: '));
				div.appendChild(lang);
				div.appendChild(createLineBreak());

				div.appendChild(createButton('Save', function(e) {
					var settings = {
						lang: lang.value
					};
					Config.save(settings);
					div.remove();
				}));

				div.appendChild(createButton('Cancel', function(e) {
					div.remove();
				}));

				document.body.appendChild(div);
			};
			init(Config.load());
		}
	};
	GM_registerMenuCommand('Hummingbird Fansub Info Settings', Config.setup);

	var App = {
		cache: {},
		getHummingbirdInfo: function(id, cb) {
			Util.log('Loading Hummingbird info...');
			GM_xmlhttpRequest({
				method: 'GET',
				url: API + '/anime/' + id,
				onload: function(response) {
					Util.log('Loaded Hummingbird info.');
					cb(JSON.parse(response.responseText));
				},
				onerror: function(err) {
					Util.log('Error loading Hummingbird info.');
				}
			});
		},
		getMALFansubInfo: function(malid, cb) {
			Util.log('Loading MAL info...');
			var url = 'myanimelist.net/anime/' + malid;
			GM_xmlhttpRequest({
				method: 'GET',
				url: 'https://' + url,
				onload: function(response) {
					Util.log('Loaded MAL info.');
					var tempDiv = document.createElement('div');
					tempDiv.innerHTML = response.responseText;

					var fansubDiv = Util.q('#inlineContent', tempDiv);
					if (fansubDiv) {
						fansubDiv = fansubDiv.parentNode;
						var fansubs = Util.qq('.spaceit_pad', fansubDiv).filter(function(node) {
							// only return nodes without an id
							return !node.id;
						}).map(function(node) {
							var id = Util.q('a:nth-of-type(1)', node).dataset.groupId;
							var link = Util.q('a:nth-of-type(4)', node);
							var tagNode = Util.q('small:nth-of-type(1)', node);
							var tag = (tagNode && tagNode.textContent !== '[]') ? tagNode.textContent : null;
							var langNode = Util.q('small:nth-of-type(2)', node);
							var lang = (langNode) ? langNode.textContent.substring(1, langNode.textContent.length - 1) : null;
							var approvalNode = Util.q('a:nth-of-type(5) > small', node);
							var totalApproved = 0;
							var totalVotes = 0;
							var comments = [];
							if (approvalNode) {
								var match = approvalNode.textContent.match(/([0-9]+)[^0-9]*([0-9]+)/);
								if (match) {
									totalApproved = match[1];
									totalVotes = match[2];
									comments = Util.qq('#fsgComments' + id + ' > .spaceit', node).map(function(comment) {
										return {
											text: comment.textContent,
											approves: !comment.hasAttribute('style')
										};
									});
								}
							}
							return {
								id: id,
								name: link.textContent,
								url: 'http://myanimelist.net' + link.pathname + link.search,
								tag: tag,
								lang: lang,
								totalVotes: totalVotes,
								totalApproved: totalApproved,
								comments: comments
							};
						});
						cb({
							url: 'http://' + url + '#inlineContent',
							fansubs: fansubs
						});
					} else {
						alert('Failed to get MAL Fansub info. Please make sure you\'re logged into myanimelist.net.');
					}
				},
				onerror: function(err) {
					Util.log('Error loading MAL info.');
				}
			});
		},
		getFansubs: function(id, cb) {
			var self = this;
			if (self.cache[id]) {
				cb(self.cache[id]);
				return;
			}
			self.getHummingbirdInfo(id, function(anime) {
				self.getMALFansubInfo(anime.mal_id, function(fansubs) {
					self.cache[id] = fansubs;
					cb(fansubs);
				});
			});
		},
		getFansubDiv: function() {
			var container = document.createElement('div');
			container.classList.add('series-panel');
			container.id = DIV_ID;

			var titleDiv = document.createElement('div');
			titleDiv.classList.add('panel-title', 'has-buttons');
			container.appendChild(titleDiv);

			var title = document.createElement('h4');
			title.textContent = 'Fansubs';
			titleDiv.appendChild(title);

			var btnGroup = document.createElement('div');
			btnGroup.classList.add('btn-group');
			titleDiv.appendChild(btnGroup);

			return container;
		},
		getFansubOutput: function(fansub) {
			var fansubDiv = document.createElement('div');
			fansubDiv.classList.add('franchise-show');

			var name = document.createElement('h4');
			fansubDiv.appendChild(name);

			var nameLink = document.createElement('a');
			nameLink.textContent = fansub.name;
			nameLink.href = fansub.url;
			nameLink.setAttribute('target', '_blank');
			name.appendChild(nameLink);

			if (fansub.lang) {
				var lang = document.createElement('small');
				lang.textContent = ' ' + fansub.lang;
				name.appendChild(lang);
			}

			var approvals = document.createElement('div');
			approvals.classList.add('review-likes');
			approvals.textContent = fansub.totalApproved + ' of ' + fansub.totalVotes + ' users approve.';
			fansubDiv.appendChild(approvals);

			if (fansub.comments && fansub.comments.length > 0) {
				var commentsLink = document.createElement('a');
				commentsLink.classList.add('pull-right');
				commentsLink.textContent = 'Comments...';
				approvals.appendChild(commentsLink);
				commentsLink.onclick = function(e) {
					e.preventDefault();
					var commentsDiv = document.createElement('div');
					fansub.comments.forEach(function(comment) {
						var div = document.createElement('div');
						div.classList.add('media');

						var smileContainer = document.createElement('div');
						smileContainer.classList.add('quick-rating');
						div.appendChild(smileContainer);
						var smile = document.createElement('i');
						smile.classList.add('fa');
						if (comment.approves) {
							smile.classList.add('fa-smile-o');
						} else {
							smile.classList.add('fa-frown-o');
						}
						smileContainer.appendChild(smile);

						var commentContainer = document.createElement('div');
						commentContainer.classList.add('media-body');
						div.appendChild(commentContainer);
						var commentText = document.createElement('p');
						commentText.textContent = comment.text;
						commentContainer.appendChild(commentText);

						commentsDiv.appendChild(div);
					});
					Util.createModal(fansub.name, commentsDiv);
					return false;
				};
			}

			return fansubDiv;
		},
		filterFansubs: function(fansubs, lang) {
			var langs = lang.split(',').map(function(lang) {
				return lang.trim().toLowerCase();
			});
			return fansubs.filter(function(fansub) {
				var lang = fansub.lang || 'english';
				lang = lang.trim().toLowerCase();
				return langs.includes(lang);
			});
		}
	};

	var cfg = Config.load();
	waitForUrl(REGEX, function() {
		var container = Util.q('.community-column');

		var div = Util.q('#' + DIV_ID);
		if (div) div.remove();
		div = App.getFansubDiv();
		container.appendChild(div);

		var id = location.href.match(REGEX)[1];
		var url = location.href;
		App.getFansubs(id, function(response) {
			if (location.href === url) {
				if (cfg.lang) {
					response.fansubs = App.filterFansubs(response.fansubs, cfg.lang);
				}

				var btnGroup = Util.q('.panel-title > .btn-group', div);
				var malLink = document.createElement('a');
				malLink.href = response.url;
				malLink.setAttribute('target', '_blank');
				btnGroup.appendChild(malLink);
				var icon = document.createElement('i');
				icon.classList.add('fa', 'fa-external-link');
				malLink.appendChild(icon);

				if (response.fansubs.length > 0) {
					var hiddenSpan = document.createElement('span');
					hiddenSpan.style.display = 'none';
					var addViewMore = false;

					response.fansubs.forEach(function(fansub, i) {
						var fansubDiv = App.getFansubOutput(fansub);
						if (i < 4) {
							div.appendChild(fansubDiv);
						} else {
							hiddenSpan.appendChild(fansubDiv);
							addViewMore = true;
						}
					});

					if (addViewMore) {
						div.appendChild(hiddenSpan);
						var viewMoreDiv = document.createElement('div');
						viewMoreDiv.classList.add('view-more');

						var viewMore = document.createElement('a');
						viewMore.textContent = 'View More Fansubs';
						viewMoreDiv.appendChild(viewMore);

						viewMore.onclick = function(e) {
							e.preventDefault();
							if (hiddenSpan.style.display === 'none') {
								hiddenSpan.style.display = 'inline';
								viewMore.textContent = 'View Less Fansubs';
							} else {
								hiddenSpan.style.display = 'none';
								viewMore.textContent = 'View More Fansubs';
							}
							return false;
						};

						div.appendChild(viewMoreDiv);
					}
				} else {
					var p = document.createElement('p');
					p.textContent = 'No fansubs found.';
					p.style.textAlign = 'center';
					p.style.marginTop = '5px';
					div.appendChild(p);
				}
			}
		});
	});
})();