// ==UserScript==
// @name X-links
// @namespace dnsev-h
// @author dnsev-h
// @version 1.2.8.26
// @description Making your browsing experience on 4chan and friends more pleasurable
// @include http://boards.4chan.org/*
// @include https://boards.4chan.org/*
// @include http://boards.4channel.org/*
// @include https://boards.4channel.org/*
// @include http://8ch.net/*
// @include https://8ch.net/*
// @include https://archived.moe/*
// @include https://boards.fireden.net/*
// @include http://desuarchive.org/*
// @include https://desuarchive.org/*
// @include http://fgts.jp/*
// @include https://fgts.jp/*
// @include http://boards.38chan.net/*
// @include http://forums.e-hentai.org/*
// @include https://forums.e-hentai.org/*
// @include https://meguca.org/*
// @connect exhentai.org
// @connect e-hentai.org
// @connect ehgt.org
// @connect nhentai.net
// @connect hitomi.la
// @connect raw.githubusercontent.com
// @connect *
// @homepage https://dnsev-h.github.io/x-links/
// @supportURL https://github.com/dnsev-h/x-links/issues
// @updateURL https://raw.githubusercontent.com/dnsev-h/x-links/stable/builds/x-links.meta.js
// @downloadURL https://raw.githubusercontent.com/dnsev-h/x-links/stable/builds/x-links.user.js
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAA6klEQVR4Ae2ZoQ7CQBBE+VIMBoHBYBAYDKLmDAKFwPBha/sJIGebMMld9hK2l9nkmaa5vpdcr6Kb+Gg0nzb6rzlMgALsPf8kEmDTDG5AAeMHvEAowEnbFQwZoAAvbU/HA/j7W6XtAgYJUACP8dJ2dxQQkKYwH48CsgbwmDKDCUSkcWTTd2y55gmQmGQBCuh/RPJ3rAAmbQeQN0ABAWk7A75fQau0bUHaAAUE9vrywUfA9y6ISf8/QAH9j0gvsXfsAImpllZAzoBm6Yo1IV0rx64rIH0A+bIGAyDdvr8VsLYA/WJSgEaj0axtvoTvtkB/WJNGAAAAAElFTkSuQmCC
// @icon64 data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAMAAACdt4HsAAAAOVBMVEUBAADmje/mku/mqu/mie/mXu/mdu/mbO/mhO/mm+/mf+/mY+/me+8AAADmpe/maO/mce/ml+/moe+i3TygAAAAAXRSTlMAQObYZgAAAJ9JREFUeF7tl0kKxDAQxCbJrPvy/8eOCqYgmMYPqFjXRrqUc8gONs+joXdLDix/qsAEe9hC4AlVQPIZ0gOSv3ABSa18heSAI5I/MEMlG8dFUsARyRNU8gJeSbcjyEkK9B6PV5rB8glyArV8B08lWvkAOYF6Pslv8FyikgMC3ccj+QU3cGQtJwVaeX2TvJYspwX88VQBye10eYHxvwCDwQ+bGFfRy77HgQAAAABJRU5ErkJggg==
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM.xmlHttpRequest
// @grant GM.setValue
// @grant GM.getValue
// @grant GM.deleteValue
// @run-at document-start
// ==/UserScript==
(function (window) {
"use strict";
// Greasemonkey 4 compatibility
var to_promise = function (fn, self) {
return function () {
var args = arguments;
return new Promise(function (resolve, reject) {
try {
resolve(fn.apply(self, args));
}
catch (e) {
reject(e);
}
});
};
};
var GM = (function () {
var GM = this.GM;
if (GM !== null && typeof(GM) === "object") {
return GM;
}
var mapping = [
[ "getValue", "GM_getValue" ],
[ "setValue", "GM_setValue" ],
[ "deleteValue", "GM_deleteValue" ],
[ "xmlHttpRequest", "GM_xmlhttpRequest" ]
];
GM = {};
var m, i, ii;
for (i = 0, ii = mapping.length; i < ii; ++i) {
m = mapping[i];
GM[m[0]] = to_promise(this[m[1]], this);
}
return GM;
}).call(this);
// Tampermonkey bug fix
if (window.document === undefined) {
window = window.unsafeWindow;
}
var timing = (function () {
var perf = window.performance,
now, fn;
if (!perf || !(now = perf.now || perf.mozNow || perf.msNow || perf.oNow || perf.webkitNow)) {
perf = Date;
now = perf.now;
}
fn = function () { return now.call(perf); };
fn.start = now.call(perf);
return fn;
})();
var document = window.document,
document_element = document.documentElement;
/*#{begin_debug}#*/
var browser = {
is_opera: /presto/i.test("" + window.navigator.userAgent),
is_firefox: /firefox/i.test("" + window.navigator.userAgent)
};
var domains = {
exhentai: "exhentai.org",
gehentai: "g.e-hentai.org",
ehentai: "e-hentai.org",
nhentai: "nhentai.net",
hitomi: "hitomi.la"
};
var options = {
general: [
// [ name, default, label, description, old_name, info? ]
[ "automatic_processing", true,
"Automatic link processing", "Get data and format links automatically",
"Automatic Processing"
],
[ "iconify", false,
"Icon site tags", "Use site-specific icons instead of [Site] tags",
null
],
[ "changelog_on_update", true,
"Show changelog on update", "Show the changelog after an update",
"Show Changelog on Update"
],
[ "external_resources", true,
"Allow external resources", "Enable the usage of web-fonts provided by Google servers",
"Use Extenral Resources" // [sic]
],
[ "image_leeching_disabled", false,
"Hide referrer for thumbnails", "Thumbnails fetching should not send referrer information",
"Disable Image Leeching"
],
[ "compatibility_check", true,
"Compatibility check", "Run a compatibility check on script start",
null
],
[ "rewrite_links", "none",
"Rewrite link URLs", "Rewrite all E*Hentai links to use a specific site",
"Rewrite Links",
{
type: "select",
options: [ // [ value, label_text, description? ]
[ "none", "Disabled" ],
[ "smart", "Smart", "All links lead to " + domains.gehentai + " unless they have fjording tags" ],
[ domains.gehentai, domains.gehentai ],
[ domains.exhentai, domains.exhentai ]
]
}
],
],
sites: [
[ "ehentai", true,
"e*hentai.org", "Enable link processing for E-Hentai and ExHentai",
null
],
[ "ehentai_ext", false,
"e*hentai.org extended", "Fetch extended gallery info for E*Hentai, including favorited and visible status",
"Extended Info"
],
[ "nhentai", true,
"nhentai.net", "Enable link processing for nhentai.net",
null
],
[ "hitomi", true,
"hitomi.la", "Enable link processing for hitomi.la",
null
],
],
details: [
[ "enabled", true,
"Enabled", "Show details for gallery links on hover",
"Gallery Details"
],
[ "tag_namespace_newline", false,
"Namespace New Lines", "Each tag namespace will be displayed on its own line",
null
],
[ "hover_position", -0.25,
"Hovering position", "Change the horizontal offset of the gallery details from the cursor",
"Details Hover Position",
{
type: "select",
options: [ // [ value, label_text, description? ]
[ -0.25, "Default", "Offset slightly from the cursor" ],
[ 0.0, "ExLinks", "Use the original ExLinks style positioning" ]
],
set: function (v) { return parseFloat(v) || 0.0; }
}
],
[ "opacity", 1.0,
"Opacity", "Opacity of the details display (as a percentage)",
null,
{
type: "textbox",
get: function (v) {
return "" + (v * 100);
},
set: function (v) {
v = parseFloat(v);
if (isNaN(v)) {
v = 1.0;
}
else {
v = Math.max(0, Math.min(1, v / 100.0));
}
return v;
}
}
],
[ "opacity_bg", 0.93,
"Background opacity", "Opacity of the details display background (as a percentage)",
null,
{
type: "textbox",
get: function (v) {
return "" + (v * 100);
},
set: function (v) {
v = parseFloat(v);
if (isNaN(v)) {
v = 0.93;
}
else {
v = Math.max(0, Math.min(1, v / 100.0));
}
return v;
}
}
],
],
actions: [
[ "enabled", true,
"Enabled", "Generate gallery actions for links",
"Gallery Actions"
],
[ "close_on_click", true,
"Close on click", "Close gallery actions after clicking anywhere",
null
],
],
sauce: [
[ "enabled", true,
"Enabled", "Add ExSauce reverse image search to posts containing images",
"ExSauce"
],
[ "expunged", false,
"Search expunged", "Search expunged galleries for source",
"Search Expunged"
],
[ "label", "",
"Custom label", "Use a custom label instead of the site name (e-hentai/exhentai)",
"Custom Label Text",
{ type: "textbox" }
],
[ "lookup_domain", domains.exhentai,
"Lookup domain", "The site to use for the reverse image search",
"Lookup Domain",
{
type: "select",
options: [ // [ value, label_text, description? ]
[ domains.gehentai, domains.gehentai ],
[ domains.exhentai, domains.exhentai ]
]
}
],
],
easy_list: [
[ "enabled", true,
"Enabled", "Add Easy List links to the page",
null
],
[ "only_header_icon", false,
"Icon only", "Only show the panda icon in the header (don't generate [Easy List] links)",
null
],
],
filter: [
[ "enabled", true,
"Enabled", "Enable filtering of galleries",
null
],
[ "full_highlighting", false,
"Full highlighting", "Highlight of all the text instead of just the matching portion",
"Full Highlighting"
],
[ "good_tag_marker", "!",
"Good tag marker", "Text to mark a [Site] tag with when a good filter is matched",
"Good Tag Marker",
{ type: "textbox" },
],
[ "bad_tag_marker", "",
"Bad tag marker", "Text to mark a [Site] tag with when a bad filter is matched",
"Bad Tag Marker",
{ type: "textbox" },
],
[ "filters",
( //{
"# Highlight all doujinshi and manga galleries with (C88) in the name:\n" +
"# /\\(C88\\)/i;only:doujinshi,manga;link-color:red;color:#FF0000;title\n" +
"# Highlight \"english\" and \"translated\" tags in non-western non-non-h galleries:\n" +
"# /english|translated/i;not:western,non-h;colors:#4080F0;tag\n" +
"# Highlight galleries tagged with \"touhou project\":\n" +
"# /touhou project/i;underlines:rgba(255,128,64,1);tag;title\n" +
"# Highlight releases translated by {5 a.m.}:\n" +
"# /\\{?5\\s*a[\\.,]?m[\\.,]?\\}?/i;title;bgs:#3BC620;colors:#FDFA18;\n" +
"# Don't highlight anything uploaded by \"CGrascal\"\n" +
"# /CGrascal/i;bad:yes;uploader;title;"
), //}
"Filters", "",
"Filters",
{ type: "textarea" },
],
],
debug: [
[ "enabled", false,
"Enabled", "Enable logging to the browser console",
"Debug Mode"
],
[ "cache_mode", "local",
"Caching mode", "Change how your browser caches link information",
function (config_old) {
if (config_old["Disable Caching"]) return "none";
if (config_old["Disable Local Storage Cache"]) return "session";
return "local";
},
{
type: "select",
options: [ // [ value, label_text, description? ]
[ "local", "Local storage", "Data is cached per website" ],
[ "session", "Session storage", "Data is cached per browser tab" ],
[ "none", "Disabled", "Data is not cached" ],
]
}
],
],
};
var config = { version: null, settings_version: 1 };
var MutationObserver = (function () {
var MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver || null;
if (MutationObserver === null) {
// Partial polyfill
var on_body_node_add = function (event) {
var node = event.target;
this.callback.call(this, [{
target: node.parentNode,
addedNodes: [ node ],
removedNodes: [],
nextSibling: node.nextSibling,
previousSibling: node.previousSibling
}]);
};
var on_body_node_remove = function (event) {
var node = event.target;
this.callback.call(this, [{
target: node.parentNode,
addedNodes: [],
removedNodes: [ node ],
nextSibling: node.nextSibling,
previousSibling: node.previousSibling
}]);
};
MutationObserver = function (callback) {
this.on_body_node_add = $.bind(on_body_node_add, this);
this.on_body_node_remove = $.bind(on_body_node_remove, this);
this.callback = callback;
this.nodes = [];
};
MutationObserver.prototype.observe = function (node, data) {
this.nodes.push(node);
if (data.childList) {
$.on(node, "DOMNodeInserted", this.on_body_node_add);
$.on(node, "DOMNodeRemoved", this.on_body_node_remove);
}
};
MutationObserver.prototype.disconnect = function () {
var node, i, ii;
for (i = 0, ii = this.nodes.length; i < ii; ++i) {
node = this.nodes[i];
$.off(node, "DOMNodeInserted", this.on_body_node_add);
$.off(node, "DOMNodeRemoved", this.on_body_node_remove);
}
this.nodes = [];
};
}
return MutationObserver;
})();
var $$ = function (selector, root) {
return (root || document).querySelectorAll(selector);
};
var $ = (function () {
var d = document,
re_full_domain = /^(?:[\w\-]+):\/*((?:[\w\-]+\.)+[\w\-]+)/i,
re_short_domain = /^(?:[\w\-]+):\/*(?:[\w\-]+\.)*([\w\-]+\.[\w\-]+)/i,
re_change_domain = /^([\w\-]+:\/*)([\w\-]+(?:\.[\w\-]+)*)([\w\W]*)$/i;
var Module = function (selector, root) {
return (root || d).querySelector(selector);
};
Module.ready = (function () {
var callbacks = [],
check_interval = null,
check_interval_time = 250;
var callback_check = function () {
if (
(document.readyState === "interactive" || document.readyState === "complete") &&
callbacks !== null
) {
var cbs = callbacks,
cb_count = cbs.length,
i;
callbacks = null;
for (i = 0; i < cb_count; ++i) {
cbs[i].call(null);
}
window.removeEventListener("load", callback_check, false);
window.removeEventListener("DOMContentLoaded", callback_check, false);
document.removeEventListener("readystatechange", callback_check, false);
if (check_interval !== null) {
clearInterval(check_interval);
check_interval = null;
}
return true;
}
return false;
};
window.addEventListener("load", callback_check, false);
window.addEventListener("DOMContentLoaded", callback_check, false);
document.addEventListener("readystatechange", callback_check, false);
return function (cb) {
if (callbacks === null) {
cb.call(null);
}
else {
callbacks.push(cb);
if (check_interval === null && callback_check() !== true) {
check_interval = setInterval(callback_check, check_interval_time);
}
}
};
})();
Module.html_fragment = function (content) {
var frag = d.createDocumentFragment(),
div = d.createElement("div"),
n, next;
div.innerHTML = content;
for (n = div.firstChild; n !== null; n = next) {
next = n.nextSibling;
frag.appendChild(n);
}
return frag;
};
Module.prepend = function (parent, child) {
return parent.insertBefore(child, parent.firstChild);
};
Module.add = function (parent, child) {
return parent.appendChild(child);
};
Module.before = function (root, next, node) {
return root.insertBefore(node, next);
};
Module.after = function (root, prev, node) {
return root.insertBefore(node, prev.nextSibling);
};
Module.replace = function (root, elem) {
return root.parentNode.replaceChild(elem, root);
};
Module.remove = function (elem) {
return elem.parentNode.removeChild(elem);
};
Module.tnode = function (text) {
return d.createTextNode(text);
};
Module.node = function (tag, class_name, text) {
var elem = d.createElement(tag);
elem.className = class_name;
if (text !== undefined) {
elem.textContent = text;
}
return elem;
};
Module.node_ns = function (namespace, tag, class_name) {
var elem = d.createElementNS(namespace, tag);
elem.setAttribute("class", class_name);
return elem;
};
Module.node_simple = function (tag) {
return d.createElement(tag);
};
Module.link = function (href, class_name, text) {
var elem = d.createElement("a");
if (href !== undefined) {
elem.href = href;
elem.target = "_blank";
elem.rel = "noreferrer";
}
if (class_name !== undefined) {
elem.className = class_name;
}
if (text !== undefined) {
elem.textContent = text;
}
return elem;
};
Module.on = function (elem, eventlist, handler) {
var event, i, ii;
if (eventlist instanceof Array) {
for (i = 0, ii = eventlist.length; i < ii; ++i) {
event = eventlist[i];
elem.addEventListener(event[0], event[1], false);
}
}
else {
elem.addEventListener(eventlist, handler, false);
}
};
Module.off = function (elem, eventlist, handler) {
var event, i, ii;
if (eventlist instanceof Array) {
for (i = 0, ii = eventlist.length; i < ii; ++i) {
event = eventlist[i];
elem.removeEventListener(event[0], event[1], false);
}
}
else {
elem.removeEventListener(eventlist, handler, false);
}
};
Module.test = function (elem, selector) {
try {
return elem.matches ? elem.matches(selector) : elem.matchesSelector(selector);
}
catch (e) {}
return false;
};
Module.unwrap = function (node) {
var par = node.parentNode,
next, n;
if (par !== null) {
next = node.nextSibling;
while ((n = node.firstChild) !== null) {
par.insertBefore(n, next);
}
par.removeChild(node);
}
};
Module.insert_styles = function (styles) {
var head = d.head,
n;
if (head) {
n = d.createElement("style");
n.textContent = styles;
head.appendChild(n);
}
};
Module.scroll_focus = function (element) {
// Focus
var n = d.createElement("textarea");
element.insertBefore(n, element.firstChild);
n.focus();
n.blur();
element.removeChild(n);
// Scroll to top
element.scrollTop = 0;
element.scrollLeft = 0;
};
Module.clamp = function (value, min, max) {
return Math.min(max, Math.max(min, value));
};
Module.is_left_mouse = function (event) {
return (event.which === undefined || event.which === 1);
};
Module.push_many = function (target, new_entries) {
var max_push = 1000;
if (new_entries.length < max_push) {
Array.prototype.push.apply(target, new_entries);
}
else {
for (var i = 0, ii = new_entries.length; i < ii; i += max_push) {
Array.prototype.push.apply(target, Array.prototype.slice.call(new_entries, i, i + max_push));
}
}
};
Module.bind = function (fn, self) {
if (arguments.length > 2) {
var i = 0,
ii = arguments.length - 2,
args = new Array(ii);
for (; i < ii; ++i) args[i] = arguments[i + 2];
return function () {
var full_args = args.slice(),
i, ii;
for (i = 0, ii = arguments.length; i < ii; ++i) {
full_args.push(arguments[i]);
}
return fn.apply(self, full_args);
};
}
else {
return function () {
return fn.apply(self, arguments);
};
}
};
var mouseenterleave_event_validate = function (self, parent) {
try {
for (; parent; parent = parent.parentNode) {
if (parent === self) return false;
}
return true;
}
catch (e) {}
return false;
};
Module.wrap_mouseenterleave_event = function (fn) {
return function (event) {
return mouseenterleave_event_validate(this, event.relatedTarget) ? fn.call(this, event) : undefined;
};
};
var parse_url = function (url) {
var ret = {
protocol: null,
host: null,
pathname: null,
search: null,
hash: null
},
m = /^[\w\-]+:/.exec(url);
if (m !== null) {
ret.protocol = m[0];
m = /^\/{0,2}([^\/\?#]*)(\/[^\?#]*)?(\?[^#]*)?(#[\w\W]*)?/.exec(url.substr(m.index + m[0].length));
}
else {
m = /^(?:\/\/([^\/\?#]*))?([^\?#]+)?(\?[^#]*)?(#[\w\W]*)?/.exec(url);
}
if (m !== null) {
if (m[1] !== undefined) {
ret.host = m[1];
ret.pathname = m[2] || "/";
ret.search = m[3] || "";
ret.hash = m[4] || "";
}
else if (m[2] !== undefined) {
ret.pathname = m[2];
ret.search = m[3] || "";
ret.hash = m[4] || "";
}
else if (m[3] !== undefined) {
ret.search = m[3];
ret.hash = m[4] || "";
}
else if (m[4] !== undefined) {
ret.hash = m[4];
}
}
return ret;
};
Module.resolve = function (url, from) {
var url_loc = parse_url(url || ""),
from_loc = parse_url(from !== undefined ? from : window.location.href),
url_path = url_loc.pathname,
from_path = from_loc.pathname;
if (url_loc.protocol === null) url_loc.protocol = from_loc.protocol;
if (url_loc.host === null) url_loc.host = from_loc.host;
if (url_loc.search === null) url_loc.search = from_loc.search;
if (url_loc.hash === null) url_loc.hash = from_loc.hash;
if (url_path === null) {
url_path = from_path;
}
else if (from_path !== null) {
if (url_path.length === 0) {
url_path = from_path;
}
else if (url_path[0] !== "/") {
url_path = from_path.replace(/[^\/]*$/, "") + url_path;
}
}
url = "";
if (url_loc.protocol !== null) url += url_loc.protocol;
if (url_loc.host !== null) url += "//" + url_loc.host;
if (url_path !== null) url += url_path;
if (url_loc.search !== null) url += url_loc.search;
if (url_loc.hash !== null) url += url_loc.hash;
return url;
};
Module.create_regex_safe = function (text, flags) {
try {
return new RegExp(text, flags);
}
catch (e) {}
return null;
};
Module.regex_escape = function (text) {
return text.replace(/[\$\(\)\*\+\-\.\/\?\[\\\]\^\{\|\}]/g, "\\$&");
};
Module.json_parse_safe = function (text, def) {
try {
return JSON.parse(text);
}
catch (e) {}
return def;
};
Module.html_parse_safe = function (text, def) {
try {
return new DOMParser().parseFromString(text, "text/html");
}
catch (e) {}
return def;
};
Module.xml_parse_safe = function (text, def) {
try {
return new DOMParser().parseFromString(text, "text/xml");
}
catch (e) {}
return def;
};
Module.get_domain = function (url) {
var m = re_short_domain.exec(url);
return (m === null) ? "" : m[1].toLowerCase();
};
Module.get_full_domain = function (url) {
var m = re_full_domain.exec(url);
return (m === null) ? "" : m[1].toLowerCase();
};
Module.change_url_domain = function (url, new_domain) {
var m = re_change_domain.exec(url);
return (m === null) ? url : m[1] + new_domain + m[3];
};
if (typeof(window.btoa) === "function") {
Module.base64_encode = function (data) {
return window.btoa(data);
};
Module.base64_decode = function (data) {
return window.atob(data);
};
}
var uint8_array_to_string = function (data) {
var s = "",
step = 1000,
ii = data.length,
i;
for (i = 0; i < ii; i += step) {
s += String.fromCharCode.apply(String, data.subarray(i, i + step));
}
return s;
};
var create_data_uri = function (data, media_type) {
return "data:" + media_type + ";base64," + Module.base64_encode(uint8_array_to_string(data));
};
Module.create_url_from_data = function (data, media_type, as_data_uri) {
try {
return as_data_uri ?
create_data_uri(data, media_type) :
(window.URL || window.webkitURL).createObjectURL(new Blob([ data ], { type: media_type }));
}
catch (e) {}
return null;
};
Module.revoke_url = function (url) {
try {
(window.URL || window.webkitURL).revokeObjectURL(url);
}
catch (e) {}
};
Module.get_regex_flags = function (regex) {
var s = "";
if (regex.global) s += "g";
if (regex.ignoreCase) s += "i";
if (regex.multiline) s += "m";
return s;
};
Module.clone = function (object) {
var target = {},
k;
for (k in object) {
if (Object.prototype.hasOwnProperty.call(object, k)) {
target[k] = object[k];
}
}
return target;
};
Module.xhr_error_string = function (xhr) {
var s = "Response error ";
s += xhr.status;
if (xhr.statusText) {
s += " - ";
s += xhr.statusText;
}
return s;
};
return Module;
})();
var Debug = (function () {
var started = false,
timer_names = null;
var dummy_fn = function () {};
var log = dummy_fn;
var timer_log = function (label, timer) {
var t = timing(),
value;
if (typeof(timer) === "string") timer = timer_names[timer];
value = (timer === undefined) ? "???ms" : (t - timer).toFixed(3) + "ms";
if (!started) return [ label, value ];
log(label, value);
};
var init = function () {
started = true;
if (!config.debug.enabled) {
timer_log = dummy_fn;
Module.timer_log = timer_log;
return;
}
// Debug functions
Module.enabled = true;
timer_names = {};
log = function () {
var args = [ Main.title + " " + Main.version.join(".") + ":" ],
i, ii;
for (i = 0, ii = arguments.length; i < ii; ++i) {
args.push(arguments[i]);
}
console.log.apply(console, args);
};
Module.log = log;
Module.timer = function (name, dont_format) {
var t1 = timing(),
t2;
t2 = timer_names[name];
timer_names[name] = t1;
if (dont_format) {
return (t2 === undefined) ? -1 : (t1 - t2);
}
return (t2 === undefined) ? "???ms" : (t1 - t2).toFixed(3) + "ms";
};
};
// Exports
var Module = {
enabled: false,
log: log,
timer: dummy_fn,
timer_log: timer_log,
init: init
};
return Module;
})();
var Post = (function () {
// Private
var file_ext = function (url) {
var m = /\.[^\.]*$/.exec(url);
return (m === null) ? "" : m[0].toLowerCase();
};
var file_name = function (url) {
url = url.split("/");
return url[url.length - 1];
};
var get_op_post_files_container_tinyboard = function (node) {
while (true) {
if ((node = node.previousSibling) === null) return null;
if (node.classList && node.classList.contains("files")) return node;
}
};
var post_selector = {
"4chan": ".postContainer,.post.inlined,#quote-preview",
"foolz": "article:not(.backlink_container)",
"fuuka": ".content>div[id],.content>table",
"tinyboard": ".post",
"ipb": ".borderwrap",
"ipb_lofi": ".postwrapper",
"meguca": "#thread-container article"
};
var post_body_selector = {
"4chan": "blockquote",
"foolz": ".text",
"fuuka": "blockquote>p",
"tinyboard": ".body",
"ipb": ".postcolor",
"ipb_lofi": ".postcontent",
"meguca": "blockquote"
};
var body_links_selector = {
"4chan": "a:not(.quotelink)",
"foolz": "a:not(.backlink)",
"fuuka": "a:not(.backlink)",
"tinyboard": "a:not([onclick])",
"ipb": "a[target=_blank]",
"ipb_lofi": "a[target=_blank]",
"meguca": "a:not(.history):not(.embed)"
};
var post_parent_find = {
"4chan": function (node) {
while ((node = node.parentNode) !== null) {
if (node.classList.contains("postContainer")) return node;
// 4chan-inline
if (node.classList.contains("post") && (node.classList.contains("inlined") || node.id === "quote-preview")) return node;
}
return null;
},
"foolz": function (node) {
while ((node = node.parentNode) !== null) {
if (node.tagName === "ARTICLE") return node;
}
return null;
},
"fuuka": function (node) {
while ((node = node.parentNode) !== null) {
if (
node.tagName === "TABLE" || // Reply
(node.tagName === "DIV" && node.id && node.parentNode.classList.contains("content")) // OP
) {
return node;
}
}
return null;
},
"tinyboard": function (node) {
while ((node = node.parentNode) !== null) {
if (node.classList.contains("post")) {
return node;
}
else if (node.classList.contains("thread")) {
return $(".post.op", node);
}
}
return null;
},
"ipb": function (node) {
while ((node = node.parentNode) !== null) {
if (node.classList.contains("borderwrap")) return node;
}
return null;
},
"ipb_lofi": function (node) {
while ((node = node.parentNode) !== null) {
if (node.classList.contains("postwrapper")) return node;
}
return null;
},
"meguca": function (node) {
return node.closest("article");
}
};
var get_file_info = {
"4chan": function (post) {
var n, ft, img, a1, url, i;
if (
(n = $(".file", post)) === null ||
!belongs_to[Config.mode].call(null, n, post) ||
(ft = $(".fileText", n)) === null ||
(img = $("img", n)) === null ||
(a1 = $("a", n)) === null
) {
return [];
}
url = a1.href;
if ((i = url.indexOf("#")) >= 0) url = url.substr(0, i);
return [{
image: img,
image_link: img.parentNode,
text_link: a1,
options: ft,
url: url,
type: file_ext(url),
name: file_name(url),
md5: img.getAttribute("data-md5") || null
}];
},
"foolz": function (post) {
var n, ft, img, a1, url, i;
if (
(n = $(".thread_image_box", post)) === null ||
!belongs_to[Config.mode].call(null, n, post) ||
(ft = $(".post_file_controls", post)) === null ||
(img = $("img", n)) === null ||
(a1 = $(".post_file_filename", post)) === null
) {
return [];
}
url = a1.href;
if ((i = url.indexOf("#")) >= 0) url = url.substr(0, i);
return [{
image: img,
image_link: img.parentNode,
text_link: a1,
options: ft,
url: url,
type: file_ext(url),
name: file_name(url),
md5: img.getAttribute("data-md5") || null
}];
},
"fuuka": function (post) {
var n, img, a1, url, i;
if (
(img = $("a>img.thumb", post)) === null ||
!belongs_to[Config.mode].call(null, img, post)
) {
return [];
}
a1 = img.parentNode;
n = a1.parentNode;
url = a1.href;
if ((i = url.indexOf("#")) >= 0) url = url.substr(0, i);
return [{
image: img,
image_link: a1,
text_link: null,
options: n,
url: url,
type: file_ext(url),
name: file_name(url),
md5: img.getAttribute("data-md5") || null
}];
},
"tinyboard": function (post) {
var results = [],
imgs, infos, img, array, ft, a1, n, url, i, ii, j;
if (post.classList.contains("op")) {
n = get_op_post_files_container_tinyboard(post);
if (n === null) return results;
imgs = $$("a>img", n);
infos = $$(".fileinfo", n);
ii = Math.min(imgs.length, infos.length);
}
else {
imgs = $$("a>img", post);
array = [];
for (i = 0, ii = imgs.length; i < ii; ++i) {
img = imgs[i];
if (belongs_to[Config.mode].call(null, img, post)) {
array.push(img);
}
}
imgs = array;
infos = $$(".fileinfo", post);
ii = Math.min(imgs.length, infos.length);
}
for (i = 0; i < ii; ++i) {
img = imgs[i];
n = infos[i];
if (
(ft = $(".unimportant", n)) === null ||
(a1 = $("a", n)) === null
) {
continue;
}
url = img.parentNode.href || a1.href;
if ((j = url.indexOf("#")) >= 0) url = url.substr(0, j);
results.push({
image: img,
image_link: img.parentNode,
text_link: a1,
options: ft,
url: url,
type: file_ext(url),
name: file_name(url),
md5: img.getAttribute("data-md5") || null
});
}
return results;
},
"ipb": function () {
return [];
},
"ipb_lofi": function () {
return [];
},
"meguca": function () {
return [];
}
};
var belongs_to_default = function (node, post) {
return (Module.get_post_container(node) === post);
};
var belongs_to_re_non_digit = /\D+/g;
var belongs_to = {
"4chan": function (node, post) {
var id1 = node.id.replace(belongs_to_re_non_digit, ""),
id2 = post.id.replace(belongs_to_re_non_digit, "");
return (id1 && id1 === id2);
},
"foolz": belongs_to_default,
"fuuka": belongs_to_default,
"tinyboard": belongs_to_default,
"ipb": belongs_to_default,
"ipb_lofi": belongs_to_default,
"meguca": belongs_to_default
};
var create_image_meta_link_default = function (file_info, node) {
var par = file_info.options;
$.add(par, $.tnode(" "));
$.add(par, node);
};
var create_image_meta_link = {
"4chan": create_image_meta_link_default,
"foolz": function (file_info, node) {
var par = file_info.options,
next;
for (next = par.lastChild; next !== null; next = next.previousSibling) {
if (next.tagName === "A" && next.hasAttribute("download")) break;
}
node.classList.add("btnr");
node.classList.add("parent");
$.before(par, next, node);
},
"fuuka": function (file_info, node) {
var par = file_info.options,
t = " [",
i = 0,
j = (par.tagName === "DIV" ? 1 : 2),
next, n;
for (next = par.firstChild; next !== null; next = next.nextSibling) {
if (next.tagName === "BR" && ++i === j) break;
}
if (
next !== null &&
(n = next.previousSibling) !== null &&
n.nodeType === Node.TEXT_NODE
) {
n.nodeValue = n.nodeValue.replace(/\]\s*$/, "]") + t;
}
else {
$.before(par, next, $.tnode(t));
}
$.before(par, next, node);
$.before(par, next, $.tnode("]"));
},
"tinyboard": create_image_meta_link_default,
"ipb": create_image_meta_link_default,
"ipb_lofi": create_image_meta_link_default,
"meguca": create_image_meta_link_default
};
// Exports
var Module = {
get_post_container: function (node) {
return post_parent_find[Config.mode].call(null, node);
},
get_text_body: function (node) {
return $(post_body_selector[Config.mode], node);
},
is_post: function (node) {
return $.test(node, post_selector[Config.mode]);
},
get_all_posts: function (parent) {
return $$(post_selector[Config.mode], parent);
},
get_file_info: function (post) {
return get_file_info[Config.mode].call(null, post);
},
get_body_links: function (post) {
return $$(body_links_selector[Config.mode], post);
},
create_image_meta_link: function (file_info, node) {
return create_image_meta_link[Config.mode].call(null, file_info, node);
},
get_op_post_files_container_tinyboard: get_op_post_files_container_tinyboard
};
return Module;
})();
var CreateURL = (function () {
// Private
var to_gallery = {
ehentai: function (data, domain) {
return "http://" + domain + "/g/" + data.gid + "/" + data.token + "/";
},
nhentai: function (data) {
return "http://" + domains.nhentai + "/g/" + data.gid + "/";
},
hitomi: function (data) {
return "https://" + domains.hitomi + "/galleries/" + data.gid + ".html";
}
};
var to_uploader = {
ehentai: function (data, domain) {
return "http://" + domain + "/uploader/" + (data.uploader || "Unknown").replace(/\s+/g, "+");
},
nhentai: function () {
return "http://" + domains.nhentai + "/";
},
hitomi: function () {
return "https://" + domains.hitomi + "/";
}
};
var to_category = {
ehentai: function (data, domain) {
return "http://" + domain + "/" + API.get_category(data.category).short_name;
},
nhentai: function (data) {
return "http://" + domains.nhentai + "/category/" + data.category.toLowerCase() + "/";
},
hitomi: function (data) {
return "https://" + domains.hitomi + "/type/" + data.category.toLowerCase() + "-all-1.html";
}
};
var to_tag = {
ehentai: function (tag, domain) {
return "http://" + domain + "/tag/" + tag.replace(/\s+/g, "+");
},
nhentai: function (tag, domain) {
return "http://" + domain + "/tag/" + tag.replace(/\s+/g, "-") + "/";
},
hitomi: function (tag, domain) {
return "https://" + domain + "/tag/" + tag + "-all-1.html";
}
};
var to_tag_ns = {
ehentai: function (tag, namespace, domain) {
return "http://" + domain + "/tag/" + namespace + ":" + tag.replace(/\s+/g, "+");
},
nhentai: function (tag, namespace, domain) {
if (namespace === "tags") namespace = "tag";
return "http://" + domain + "/" + namespace + "/" + tag.replace(/\s+/g, "-") + "/";
},
hitomi: function (tag, namespace, domain) {
if (namespace === "male" || namespace === "female") {
return "https://" + domain + "/tag/" + namespace + ":" + tag + "-all-1.html";
}
else if (namespace === "artist") {
return "https://" + domain + "/artist/" + tag + "-all-1.html";
}
else if (namespace === "parody") {
return "https://" + domain + "/series/" + tag + "-all-1.html";
}
else if (namespace === "language") {
return "https://" + domain + "/index-" + tag + "-1.html";
}
else {
return "https://" + domain + "/tag/" + tag + "-all-1.html";
}
}
};
var types = {
to_gallery: [ to_gallery, [ "data", "domain" ] ],
to_uploader: [ to_uploader, [ "data", "domain" ] ],
to_category: [ to_category, [ "data", "domain" ] ],
to_tag: [ to_tag, [ "tag", "domain" ] ],
to_tag_ns: [ to_tag_ns, [ "tag", "namespace", "domain" ] ]
};
var eq = function (a, b) { return a === b; },
neq = function (a, b) { return a !== b; },
operators = {
"==": eq,
"===": eq,
"!=": neq,
"!==": neq,
">": function (a, b) { return a > b; },
">=": function (a, b) { return a >= b; },
"<": function (a, b) { return a < b; },
"<=": function (a, b) { return a <= b; }
};
var get_var = function (vars, name) {
var i, ii, k;
name = name.split(".");
for (i = 0, ii = name.length; i < ii; ++i) {
k = name[i];
if (typeof(vars) === "object" && vars !== null && Object.prototype.hasOwnProperty.call(vars, k)) {
vars = vars[k];
}
else {
return undefined;
}
}
return vars;
};
var re_format = /\{([^\}]+)\}/g;
var format = function (str, vars) {
return str.replace(re_format, function (k, g1) {
void(k); // to make jshint ignore the unused var
return get_var(vars, g1);
});
};
var check_args = function (array, vars) {
var i, ii, op;
for (i = 1, ii = array.length; i < ii; i += 3) {
op = array[i + 1];
if (
!Object.prototype.hasOwnProperty.call(operators, op) ||
!operators[op](get_var(vars, array[i]), array[i + 2])
) {
return false;
}
}
return true;
};
var create_generic = function (data, arg_names) {
if (typeof(data) === "string") {
return function () {
var vars = {},
i, ii;
for (i = 0, ii = arg_names.length; i < ii; ++i) {
vars[arg_names[i]] = arguments[i];
}
return format(data, vars);
};
}
if (Array.isArray(data) && data.length > 0) {
// Normalize
var i, ii, d;
for (i = 0, ii = data.length; i < ii; ++i) {
d = data[i];
if (typeof(d) !== "string" && !(Array.isArray(d) && d.length > 0 && typeof(d[0]) === "string")) {
data[i] = "#";
}
}
return function () {
var vars = {},
i, ii, d;
for (i = 0, ii = arg_names.length; i < ii; ++i) {
vars[arg_names[i]] = arguments[i];
}
for (i = 0, ii = data.length; i < ii; ++i) {
d = data[i];
if (typeof(d) === "string") {
return format(d, vars);
}
if (Array.isArray(d) && check_args(d, vars)) {
return format(d[0], vars);
}
}
return "#";
};
}
return function () {
return "#";
};
};
var register = function (namespace, data) {
if (typeof(data) === "object" && data !== null) {
var keys = Object.keys(types),
i, ii, k, info;
for (i = 0, ii = keys.length; i < ii; ++i) {
k = keys[i];
if (Object.prototype.hasOwnProperty.call(data, k)) {
info = types[k];
if (!Object.prototype.hasOwnProperty.call(info[0], k)) {
info[0][namespace] = create_generic(data[k], info[1]);
}
}
}
}
};
// Exports
return {
to_gallery: function (data, domain) {
var fn = to_gallery[data.type];
return (typeof(fn) !== "function") ? "#" : fn(data, domain);
},
to_uploader: function (data, domain) {
var fn = to_uploader[data.type];
return (typeof(fn) !== "function") ? "#" : fn(data, domain);
},
to_category: function (data, domain) {
var fn = to_category[data.type];
return (typeof(fn) !== "function") ? "#" : fn(data, domain);
},
to_tag: function (tag, domain_type, domain) {
var fn = to_tag[domain_type];
return (typeof(fn) !== "function") ? "#" : fn(tag, domain);
},
to_tag_ns: function (tag, namespace, domain_type, domain) {
var fn = to_tag_ns[domain_type];
return (typeof(fn) !== "function") ? "#" : fn(tag, namespace, domain);
},
register: register
};
})();
var HttpRequest = (function () {
var debug_fn = function (type, data, callback, start_time) {
return function (xhr) {
var t = timing(),
args = [
"HttpRequest:",
data.method,
data.url,
type,
{ data: data, response: xhr, time: (t - start_time).toFixed(2) + "ms" }
];
if (type === "load") args.splice(4, 0, xhr.status, xhr.statusText);
Debug.log.apply(Debug, args);
return callback.apply(this, arguments);
};
};
var debug_begin = function (data) {
var upload = data.upload,
start = timing(),
fn;
Debug.log("HttpRequest:", data.method, data.url, { data: data });
if (typeof((fn = data.onload)) === "function") {
data.onload = debug_fn("load", data, fn, start);
}
if (typeof((fn = data.onerror)) === "function") {
data.onerror = debug_fn("error", data, fn, start);
}
if (typeof((fn = data.onabort)) === "function") {
data.onabort = debug_fn("abort", data, fn, start);
}
if (typeof(upload) === "object" && upload !== null) {
if (typeof((fn = upload.onerror)) === "function") {
upload.onerror = debug_fn("upload.error", data, fn, start);
}
if (typeof((fn = upload.onabort)) === "function") {
upload.onabort = debug_fn("upload.abort", data, fn, start);
}
}
};
var supported = function () {
try {
return (typeof(GM.xmlHttpRequest) === "function");
}
catch (e) {}
return false;
};
var request;
if (supported()) {
request = function (data) {
if (Debug.enabled || true) {
debug_begin(data);
}
GM.xmlHttpRequest(data);
};
}
else {
// Fallback
request = function (data) {
Debug.log("HttpRequest.invalid:", data.method, data.url, { data: data });
var onerror = (data && data.onerror && typeof(data.onerror) === "function") ? data.onerror : null;
if (onerror !== null) {
setTimeout(function () {
onerror.call(null, {});
}, 1);
}
};
}
// Done
return request;
})();
var UI = (function () {
// Private
var details_nodes = {},
details_nodes_creating = {},
actions_nodes = {},
actions_nodes_active = {},
actions_nodes_active_count = 0,
actions_nodes_index = 0,
actions_close_timeout = null;
var gallery_link_events_data = {
link: null,
mouse_x: 0,
mouse_y: 0
};
var gallery_link_events = {
mouseover: $.wrap_mouseenterleave_event(function (event) {
var self = this,
info = API.get_url_info_saved(this.href);
if (info === null) return;
gallery_link_events_data.link = this;
gallery_link_events_data.mouse_x = event.clientX;
gallery_link_events_data.mouse_y = event.clientY;
API.get_data_from_url_info(info, function (err, data) {
if (err === null && gallery_link_events_data.link === self) {
var details = details_nodes[info.id];
if (details !== undefined) {
details_hover_start(data, info, self, details);
}
else {
create_details(data, info, function (err, details) {
if (err === null && gallery_link_events_data.link === self) {
details_hover_start(data, info, self, details);
}
});
}
document_element.classList.add("xl-details-visible");
}
});
}),
mouseout: $.wrap_mouseenterleave_event(function () {
var details = details_nodes[get_node_id_full(this)];
gallery_link_events_data.link = null;
document_element.classList.remove("xl-details-visible");
if (details === undefined) return;
details.classList.add("xl-details-hidden");
}),
mousemove: function (event) {
var details = details_nodes[get_node_id_full(this)];
if (details === undefined) return;
gallery_link_events_data.mouse_x = event.clientX;
gallery_link_events_data.mouse_y = event.clientY;
update_details_position(details, this, event.clientX, event.clientY);
}
};
var gallery_tag_events = {
click: function (event) {
if ($.is_left_mouse(event) && config.actions.enabled) {
event.preventDefault();
var id = this.getAttribute("xl-actions-id");
if (!id) {
id = "" + actions_nodes_index;
++actions_nodes_index;
this.setAttribute("xl-actions-id", id);
}
enable_actions_menu_on_node(this, undefined, id, function (info, id, callback) {
API.get_data_from_url_info(info, function (err, data) {
if (err === null) {
create_actions(data, info, id, callback);
}
});
});
}
},
mousedown: function () {
var node = this;
node.href = node.getAttribute("data-xl-href") || "";
var on_up = function () {
setTimeout(function() {
node.removeAttribute("href");
}, 1);
$.off(document_element, "mouseup", on_up);
};
$.on(document_element, "mouseup", on_up);
}
};
var gallery_fetch_event = function (event) {
if ($.is_left_mouse(event)) {
event.preventDefault();
var link, info;
Linkifier.change_link_events(this, null);
if (
(link = get_link_from_site_tag(this)) !== null &&
(info = API.get_url_info_saved(link.href)) !== null
) {
load_link(link, info);
}
}
};
var gallery_error_event = function (event) {
if ($.is_left_mouse(event) && config.actions.enabled) {
event.preventDefault();
var id = this.getAttribute("xl-actions-id");
if (!id) {
id = "" + actions_nodes_index;
++actions_nodes_index;
this.setAttribute("xl-actions-id", id);
}
enable_actions_menu_on_node(this, undefined, id, function (info, id, callback) {
void(info); // to make jshint ignore the unused var
var actions = create_actions_menu(id, [
{
label: "An error occurred",
modify: function (container) {
var n = $(".xl-actions-table-header", container);
if (n !== null) {
n.style.paddingRight = "6em";
}
}
},
{
text: "",
modify: function (container) {
var n = $(".xl-actions-option-text", container);
if (n !== null) {
n.textContent = "If this error persists and you believe it shouldn't, clearing the cache may help";
}
}
},
null,
{
text: "Clear cache",
url: "#",
modify: function (container) {
var n = $(".xl-actions-option", container);
if (n !== null) {
$.on(n, "click", function (event) {
if ($.is_left_mouse(event)) {
event.preventDefault();
// Clear cache
var clears = API.cache_clear();
Debug.log("Cleared cache; entries_removed=" + clears);
}
}, false);
}
}
},
null,
{
text: "Support",
url: Main.support_url
}
]);
callback(null, actions);
});
return false;
}
};
var details_hover_start = function (data, info, node, details) {
if (Debug.enabled) {
var i = 1,
n = details;
while (n.parentNode !== document) {
if (!n.parentNode) {
Debug.log(
"Invalid details: parent[" + i + "] failed;",
{
link: node,
node: n,
parent: n.parentNode,
details: details,
data: data,
info: info
}
);
break;
}
n = n.parentNode;
++i;
}
}
details.classList.remove("xl-details-hidden");
details.classList.remove("xl-details-has-thumbnail");
details.classList.remove("xl-details-has-thumbnail-visible");
update_details_position(details, node, gallery_link_events_data.mouse_x, gallery_link_events_data.mouse_y);
if (data.subtype === "gallery" && info.page !== undefined && info.page > 1) {
update_details_page_thumbnail(info.page, data, info, details, node);
}
};
var set_node_id = function (node, info) {
node.setAttribute("data-xl-id", info.id);
};
var get_node_id_full = function (node) {
return node.getAttribute("data-xl-id") || "";
};
var get_site_tag_from_link = function (node) {
// Assume the button is the previous (or previous-previous) sibling
if (
(node = node.previousSibling) !== null &&
(node.classList || ((node = node.previousSibling) !== null && node.classList)) &&
node.classList.contains("xl-site-tag")
) {
return node;
}
return null;
};
var get_link_from_site_tag = function (node) {
// Assume the link is the next (or next-next) sibling
if (
(node = node.nextSibling) !== null &&
(node.classList || ((node = node.nextSibling) !== null && node.classList)) &&
node.classList.contains("xl-link")
) {
return node;
}
return null;
};
var pad = function (n, sep) {
return (n < 10 ? "0" : "") + n + sep;
};
var custom_details_functions = {}; // function (data, info, callback(err, copy_from_node))
var custom_actions_functions = {}; // function (data, info, callback(err, gen_info))
var register_details_creation = function (custom_id, callback) {
custom_details_functions[custom_id] = callback;
};
var register_actions_creation = function (custom_id, callback) {
custom_actions_functions[custom_id] = callback;
};
var highlight_nodes = function (container, data) {
var ns = $$(".xl-highlight", container),
i, ii, n, type;
for (i = 0, ii = ns.length; i < ii; ++i) {
n = ns[i];
type = n.getAttribute("data-xl-highlight");
if (type === "title" || type === "uploader" || type === "tags") {
Filter.highlight(type, n, data, Filter.None);
}
}
};
var create_details = function (data, info, callback) {
var category = API.get_category(data.category),
theme = Theme.classes,
file_size = (data.total_size / 1024 / 1024).toFixed(2),
content, n1, n2, n3, fn;
// Creating check
if (details_nodes_creating[info.id] === true) {
callback("In progress", null);
return;
}
details_nodes_creating[info.id] = true;
// Fonts
//Main.insert_custom_fonts();
// Custom
if (data.subtype !== "gallery") {
fn = custom_details_functions[info._custom_id];
if (fn !== undefined) {
// Add to container
fn(data, info, function (err, content) {
delete details_nodes_creating[info.id];
if (err === null) {
content.className = (content.className + " xl-details xl-details-hidden xl-hover-shadow" + theme).trim();
content.style.opacity = config.details.opacity;
Theme.bg(content, config.details.opacity_bg);
Theme.apply(content);
highlight_nodes(content, data);
Popup.hovering(content);
details_nodes[info.id] = content;
callback(null, content);
}
else {
callback(err, null);
}
});
}
else {
delete details_nodes_creating[info.id];
callback("Could not create details", null);
}
return;
}
// Body
content = $.node("div", "xl-details xl-details-hidden xl-hover-shadow" + theme);
content.style.opacity = config.details.opacity;
Theme.bg(content, config.details.opacity_bg);
// Image
$.add(content, n1 = $.node("div", "xl-details-thumbnail" + theme));
$.add(n1, n2 = $.node("div", "xl-details-page-thumbnail xl-hover-shadow" + theme));
$.add(n2, n3 = $.node("div", "xl-details-page-thumbnail-size" + theme));
$.add(n3, $.node("div", "xl-details-page-thumbnail-image" + theme));
API.get_thumbnail(data.thumbnail, data.flags, $.bind(function (err, url) {
if (err === null) {
this.style.backgroundImage = "url('" + url + "')";
}
}, n1));
// Sidebar
$.add(content, n1 = $.node("div", "xl-details-side-panel"));
$.add(n1, n2 = $.node("div", "xl-button xl-button-eh xl-button" + category.color_id + theme));
$.add(n2, $.node("div", "xl-noise", category.name));
if (data.rating >= 0) {
$.add(n1, n2 = $.node("div", "xl-details-side-box xl-details-side-box-rating" + theme));
$.add(n2, n3 = $.node("div", "xl-details-rating xl-stars-container"));
$.add(n3, create_rating_stars(data.rating));
$.add(n2, $.node("div", "xl-details-rating-text", "(Avg. " + data.rating.toFixed(2) + ")"));
}
if (data.file_count >= 0) {
$.add(n1, n2 = $.node("div", "xl-details-side-box xl-details-side-box-rating" + theme));
$.add(n2, $.node("div", "xl-details-file-count", data.file_count + " image" + (data.file_count === 1 ? "" : "s")));
if (data.total_size >= 0) {
$.add(n2, $.node("div", "xl-details-file-size", "(" + file_size + " MB)"));
}
}
if (data.torrent_count >= 0) {
$.add(n1, n2 = $.node("div", "xl-details-side-box xl-details-side-box-torrents" + theme));
$.add(n2, n3 = $.node("div", "xl-details-side-box-inner"));
$.add(n3, $.node("strong", "", "Torrents:"));
$.add(n3, $.node("span", "", " " + data.torrent_count));
}
if (data.removed === true) {
$.add(n1, n2 = $.node("div", "xl-details-side-box xl-details-side-box-visible" + theme));
$.add(n2, n3 = $.node("div", "xl-details-side-box-inner"));
$.add(n3, $.node("strong", "xl-details-side-box-error" + theme, "Removed"));
}
else if (data.visible !== null) {
$.add(n1, n2 = $.node("div", "xl-details-side-box xl-details-side-box-visible" + theme));
$.add(n2, n3 = $.node("div", "xl-details-side-box-inner"));
$.add(n3, $.node("strong", "", "Visible:"));
$.add(n3, $.node("span", "", data.visible ? " Yes" : " No"));
}
// Title
$.add(content, n1 = $.node("div", "xl-details-title-container" + theme));
$.add(n1, n2 = $.link(CreateURL.to_gallery(data, info.domain), "xl-details-title" + theme, data.title));
Filter.highlight("title", n2, data, Filter.None);
if (data.title_jpn !== null) {
$.add(n1, n2 = $.node("div", "xl-details-title-jp" + theme, data.title_jpn));
Filter.highlight("title", n2, data, Filter.None);
}
// Upload info
$.add(content, n1 = $.node("div", "xl-details-upload-info" + theme));
$.add(n1, n2 = $.node("div", "xl-details-favorite-info xl-details-favorite-info-hidden" + theme));
if (data.favorite_category !== null) {
update_details_favorite_info(n2, data);
}
$.add(n1, n2 = $.node("div", "xl-details-upload-info-inner"));
$.add(n2, $.tnode("Uploaded by"));
$.add(n2, n3 = $.node("strong", "xl-details-uploader", data.uploader));
Filter.highlight("uploader", n3, data, Filter.None);
$.add(n2, $.tnode("on"));
$.add(n2, $.node("strong", "xl-details-upload-date", format_date(data.date_created)));
// Tags
$.add(content, n1 = $.node("div", "xl-details-tag-block" + (config.details.tag_namespace_newline ? " xl-details-tag-block-multiline" : "") + theme));
$.add(n1, $.node("strong", "xl-details-tag-block-label", "Tags:"));
$.add(n1, n2 = $.node("span", "xl-details-tags"));
$.add(n2, create_tags(data, info.domain));
// End
$.add(content, $.node("div", "xl-details-clear"));
// Full info
if (data.type === "ehentai" && config.sites.ehentai_ext && !data.full) {
API.get_ehentai_gallery_full(info, data, function (err, data) {
if (err === null) {
update_full(data, info);
}
else {
Debug.log("Error requesting full information: " + err);
}
});
}
// Add to container
Popup.hovering(content);
details_nodes[info.id] = content;
delete details_nodes_creating[info.id];
// Done
callback(null, content);
};
var create_tags = function (data, domain) {
var tagfrag = document.createDocumentFragment(),
site = data.type,
tags_ns = data.tags_ns,
theme = Theme.classes,
tag = null,
last = null,
ns_container = null,
namespace, namespace_style, tags, link, ns_c, i, ii;
if (tags_ns === null) {
// Non-namespaced tags
tags = data.tags;
ns_container = $.node("span", "xl-tag-non-namespace" + theme);
for (i = 0, ii = tags.length; i < ii; ++i) {
tag = $.node("span", "xl-tag-block" + theme);
link = $.link(CreateURL.to_tag(tags[i], site, domain), "xl-tag", tags[i]);
Filter.highlight("tags", link, data, Filter.None);
$.add(tag, link);
$.add(tag, (last = $.tnode(",")));
$.add(ns_container, tag);
}
if (last !== null) $.remove(last);
$.add(tagfrag, ns_container);
}
else {
// Namespaced tags
for (namespace in tags_ns) {
tags = tags_ns[namespace];
ii = tags.length;
if (ii === 0) continue;
namespace_style = " xl-tag-namespace-" + namespace.replace(/\s+/g, "-") + theme;
ns_container = $.node("span", "xl-tag-namespace" + namespace_style);
tag = $.node("span", "xl-tag-namespace-block" + namespace_style);
link = $.node("span", "xl-tag-namespace-label", namespace);
ns_c = $.node("span", "xl-tag-namespace-first-tag");
$.add(tag, link);
$.add(tag, $.tnode(":"));
$.add(ns_c, tag);
$.add(ns_container, ns_c);
$.add(tagfrag, ns_container);
for (i = 0; i < ii; ++i) {
tag = $.node("span", "xl-tag-block" + theme);
link = $.link(CreateURL.to_tag_ns(tags[i], namespace, site, domain), "xl-tag", tags[i]);
Filter.highlight("tags", link, data, Filter.None);
$.add(tag, link);
if (i < ii - 1) {
$.add(tag, $.tnode(","));
}
else {
tag.classList.add("xl-tag-block-last-of-namespace");
}
$.add(ns_c, tag);
ns_c = ns_container;
}
}
if (tag !== null) {
tag.classList.add("xl-tag-block-last");
}
}
return tagfrag;
};
var update_details_page_thumbnail = function (page, data, info, details, node) {
var thumb_state = 0;
var thumb_cb = function (err, thumb_data) {
if (err === null) {
API.get_thumbnail(thumb_data.url, thumb_data.flags, function (err, thumb_url) {
if (err === null && node === gallery_link_events_data.link) {
var n0, n1, n2;
if (
(n0 = $(".xl-details-page-thumbnail", details)) !== null &&
(n1 = $(".xl-details-page-thumbnail-size", n0)) !== null &&
(n2 = $(".xl-details-page-thumbnail-image", n1)) !== null
) {
n2.style.backgroundImage = "url('" + thumb_url + "')";
if (thumb_data.width > 0 && thumb_data.height > 0) {
// Small thumbnail
var max_width = 140,
max_height = 200,
max_ratio = max_height / max_width,
scale = (thumb_data.height / thumb_data.width > max_ratio) ? max_height / thumb_data.height : max_width / thumb_data.width;
n1.style.transform = "translate(-50%,-50%) scale(" + scale + ")";
n1.style.left = "50%";
n1.style.top = "50%";
n1.style.width = thumb_data.width + "px";
n1.style.height = thumb_data.height + "px";
n2.style.backgroundSize = "auto";
n2.style.backgroundPosition = (-thumb_data.left) + "px " + (-thumb_data.top) + "px";
}
else {
// Large thumbnail
n1.style.transform = "";
n1.style.left = "";
n1.style.top = "";
n1.style.width = "";
n1.style.height = "";
n2.style.backgroundSize = "";
n2.style.backgroundPosition = "";
}
// Animate
if (thumb_state === 1) {
Theme.get_computed_style(n0).getPropertyValue("transform");
}
details.classList.add("xl-details-has-thumbnail-visible");
}
}
});
}
};
details.classList.add("xl-details-has-thumbnail");
if (info.site === "ehentai") {
API.get_ehentai_gallery_page_thumb(info.domain, data.gid, data.token, info.page_token, page, thumb_cb);
}
else if (info.site === "nhentai") {
API.get_nhentai_gallery_page_thumb(data.gid, page, thumb_cb);
}
else if (info.site === "hitomi") {
API.get_hitomi_gallery_page_thumb(data.gid, page, thumb_cb);
}
else {
thumb_cb("Invalid", null);
}
if (thumb_state === 0) ++thumb_state;
};
var update_full = function (data, info) {
var domain = domains.exhentai,
full_id = info.id,
details = details_nodes[full_id],
tagfrag, n, n2;
if (details === undefined) return;
// Removed status
if (data.removed === true) {
if ((n2 = $(".xl-details-side-box-visible>.xl-details-side-box-inner", details)) !== null) {
n = $.node("strong", "xl-details-side-box-error" + Theme.classes, "Removed");
n2.innerHTML = "";
n2.appendChild(n);
}
}
// Update domain
if ((n = $(".xl-details-title[href]", details)) !== null) {
domain = $.get_full_domain(n.href);
}
// Update tags
if (
data.tags_ns !== null &&
(n = $(".xl-details-tags", details)) !== null
) {
tagfrag = create_tags(data, domain);
n.innerHTML = "";
$.add(n, tagfrag);
}
// Update favorites
if (
data.favorite_category !== null &&
(n = $(".xl-details-favorite-info.xl-details-favorite-info-hidden", details)) !== null
) {
update_details_favorite_info(n, data);
}
// Reposition any open details
if (
(n = gallery_link_events_data.link) !== null &&
get_node_id_full(n) === full_id
) {
update_details_position(details, n, gallery_link_events_data.mouse_x, gallery_link_events_data.mouse_y);
}
};
var update_details_position = function (details, link, mouse_x, mouse_y) {
var win_width = (document_element.clientWidth || window.innerWidth || 0),
win_height = (document_element.clientHeight || window.innerHeight || 0),
rect = details.getBoundingClientRect(),
link_rect = link.getBoundingClientRect(),
is_low = (link_rect.top + link_rect.height / 2 >= win_height / 2), // (mouse_y >= win_height / 2)
offset = 20;
mouse_x += rect.width * (config.details.hover_position || 0);
mouse_x = Math.max(1, Math.min(win_width - rect.width - 1, mouse_x));
mouse_y += is_low ? -(rect.height + offset) : offset;
details.style.left = mouse_x + "px";
details.style.top = mouse_y + "px";
};
var update_details_favorite_info = function (node, data) {
var cat = data.favorite_category,
n;
node.classList.remove("xl-details-favorite-info-hidden");
node.textContent = "";
n = $.node("div", "xl-details-favorite-category xl-button xl-button-eh xl-button" + cat[0] + Theme.classes);
$.add(n, $.node("span", "xl-details-favorite-label", "Favorited:"));
$.add(n, $.node("span", "xl-details-favorite-name", cat[1]));
n.title = cat[1];
$.add(node, n);
};
var on_window_resize = function () {
update_active_actions_position();
};
var on_document_click = function (event) {
if (actions_close_timeout === null) {
if (config.actions.close_on_click) {
if ($.is_left_mouse(event)) {
close_all_actions_menus();
}
}
else {
// Re-position
setTimeout(update_active_actions_position, 1);
}
}
};
var create_tag_bg = function (parent) {
var tag_bg = $.node("div", "xl-site-tag-bg" + Theme.classes),
outline = $.node("div", "xl-site-tag-bg-shadow xl-hover-shadow" + Theme.classes),
inner = $.node("div", "xl-site-tag-bg-inner" + Theme.classes);
Theme.bg(inner);
$.add(tag_bg, inner);
$.before(parent, parent.firstChild, tag_bg);
$.before(parent, parent.firstChild, outline);
return tag_bg;
};
var mark_site_tag = function (button, text) {
if ((button = get_site_tag_text_node(button)) !== null) {
button.textContent = button.textContent.replace(/\]\s*$/, text + "]");
}
};
var update_site_tag = function (button, info) {
var n;
if ((n = get_site_tag_text_node(button)) !== null) {
n.textContent = create_site_tag_text(info);
}
if (info.icon !== undefined && (n = $(".xl-site-tag-icon", button)) !== null) {
n.setAttribute("data-xl-site-tag-icon", info.icon);
}
};
// Actions menus
var create_actions = function (data, info, index, callback) {
var entries = null,
gid = data.gid,
token = data.token,
type = data.type,
domain = info.domain,
actions, fn;
if (data.subtype === "gallery") {
if (type === "ehentai") {
entries = [
{
label: "View on:",
text: "E-Hentai",
url: CreateURL.to_gallery(data, domains.gehentai)
},
{
label: null,
text: "ExHentai",
url: CreateURL.to_gallery(data, domains.exhentai)
},
null,
{
label: "Uploader:",
text: data.uploader,
url: CreateURL.to_uploader(data, domain),
modify: function (container) {
var n = $(".xl-actions-option", container);
if (n !== null) {
n.classList.add("xl-actions-uploader");
Filter.highlight("uploader", n, data, Filter.None);
}
}
},
null,
{
label: "Download:",
text: "Torrent (" + data.torrent_count + ")",
url: "http://" + domain + "/gallerytorrents.php?gid=" + gid + "&t=" + token,
},
{
label: null,
text: "Archiver",
url: "http://" + domains.gehentai + "/archiver.php?gid=" + gid + "&token=" + token + "&or=" + data.archiver_key,
modify: function (container) {
var n = $(".xl-actions-option", container);
if (n !== null) {
n.removeAttribute("target");
}
}
},
null,
{
label: "Other:",
text: "Favorite",
url: "http://" + domain + "/gallerypopups.php?gid=" + gid + "&t=" + token + "&act=addfav"
},
{
label: null,
text: "Stats",
url: "http://" + domains.gehentai + "/stats.php?gid=" + gid + "&t=" + token
}
];
}
else if (type === "nhentai") {
entries = [
{
label: "View on:",
text: "nhentai.net",
url: CreateURL.to_gallery(data, domain)
}
];
}
else if (type === "hitomi") {
entries = [
{
label: "View on:",
text: "hitomi.la",
url: CreateURL.to_gallery(data, domain)
}
];
}
}
// Empty
if (entries === null) {
fn = custom_actions_functions[info._custom_id];
if (fn !== undefined) {
fn(data, info, function (err, gen_info) {
if (err === null) {
var ii = gen_info.length,
entries = [],
actions, i, g;
if (ii === 0) {
entries.push({ label: "No actions available" });
}
else {
for (i = 0; i < ii; ++i) {
g = gen_info[i];
entries.push((g === null) ? null : {
label: g[0],
text: g[2],
url: g[1]
});
}
}
actions = create_actions_menu(index, entries);
callback(null, actions);
}
else {
callback(err, null);
}
});
return;
}
entries = [{ label: "No actions available" }];
}
// Done
actions = create_actions_menu(index, entries);
callback(null, actions);
};
var update_actions_position = function (actions, tag, tag_bg, de_rect, xpos, ypos) {
// Position
var rect = tag_bg.getBoundingClientRect(),
below, right, x, y;
// Positioning
if (xpos === "right") {
right = true;
}
else if (xpos === "left") {
right = false;
}
else {
right = (rect.left + rect.width / 2 <= (document_element.clientWidth || window.innerWidth || 0) / 2);
xpos = right ? "right" : "left";
}
if (ypos === "below") {
below = true;
}
else if (ypos === "above") {
below = false;
}
else {
below = (rect.top + rect.height / 2 <= (document_element.clientHeight || window.innerHeight || 0) / 2);
ypos = below ? "below" : "above";
}
// Coordinates
actions.style.maxWidth = "";
if (right) {
x = rect.left - de_rect.left;
}
else {
actions.style.left = "0";
actions.style.maxWidth = rect.right + "px";
x = rect.right - actions.getBoundingClientRect().width - de_rect.left;
}
actions.style.left = x + "px";
tag.setAttribute("data-xl-actions-hpos", xpos);
actions.setAttribute("data-xl-actions-hpos", xpos);
if (below) {
y = rect.bottom - de_rect.top - 0.0625;
}
else {
y = rect.top - actions.getBoundingClientRect().height - de_rect.top + 0.0625;
}
actions.style.top = y + "px";
tag.setAttribute("data-xl-actions-vpos", ypos);
actions.setAttribute("data-xl-actions-vpos", ypos);
};
var update_active_actions_position = function () {
var de_rect = document_element.getBoundingClientRect(),
index, actions, tag, tag_bg, xpos, ypos;
for (index in actions_nodes_active) {
actions = actions_nodes_active[index];
if (
(tag = $(".xl-site-tag.xl-site-tag-active[xl-actions-id='" + index + "']")) !== null &&
(tag_bg = $(".xl-site-tag-bg", tag)) !== null
) {
xpos = actions.getAttribute("data-xl-actions-hpos");
ypos = actions.getAttribute("data-xl-actions-vpos");
update_actions_position(actions, tag, tag_bg, de_rect, xpos, ypos);
}
}
};
var on_actions_menu_click = function (event) {
if ($.is_left_mouse(event)) {
event.stopPropagation();
}
};
var on_actions_menu_entry_click = function (actions, id, event) {
if ($.is_left_mouse(event)) {
event.stopPropagation();
if (config.actions.close_on_click) {
close_actions_menu(actions, id);
}
}
};
var close_actions_menu = function (actions, id) {
var ns = $$(".xl-site-tag.xl-site-tag-active[xl-actions-id='" + id + "']"),
i, ii;
for (i = 0, ii = ns.length; i < ii; ++i) {
ns[i].classList.remove("xl-site-tag-active");
}
actions.classList.add("xl-actions-hidden");
deactivate_actions_menu(id);
};
var close_all_actions_menus = function () {
for (var id in actions_nodes_active) {
close_actions_menu(actions_nodes_active[id], id);
}
};
var activate_actions_menu = function (node, id) {
if (config.actions.close_on_click && actions_nodes_active_count !== 0) {
close_all_actions_menus();
}
actions_nodes_active[id] = node;
if (actions_close_timeout !== null) clearTimeout(actions_close_timeout);
actions_close_timeout = setTimeout(function () { actions_close_timeout = null; }, 1);
if (++actions_nodes_active_count === 1) {
$.on(window, "resize", on_window_resize);
$.on(document_element, "click", on_document_click);
}
};
var deactivate_actions_menu = function (id) {
if (actions_nodes_active[id] === undefined) return;
delete actions_nodes_active[id];
if (--actions_nodes_active_count === 0) {
$.off(window, "resize", on_window_resize);
$.off(document_element, "click", on_document_click);
if (actions_close_timeout !== null) {
clearTimeout(actions_close_timeout);
actions_close_timeout = null;
}
}
};
var create_actions_menu = function (id, entries) {
var theme = Theme.classes,
actions = $.node("div", "xl-actions xl-hover-shadow" + theme),
count, label, text, url, fn, n1, n2, n3, n4, n5, i, ii, e;
$.on(actions, "click", on_actions_menu_click);
Theme.bg(actions);
$.add(actions, n1 = $.node("div", "xl-actions-inner" + theme));
$.add(n1, n2 = $.node("div", "xl-actions-table" + theme));
for (i = 0, ii = entries.length; i < ii; ++i) {
e = entries[i];
if (e === null) {
// Separator
n3 = $.node("div", "xl-actions-table-row" + theme);
$.add(n3, n4 = $.node("div", "xl-actions-table-cell" + theme));
$.add(n4, $.node("div", "xl-actions-table-sep"));
$.add(n2, n3);
}
else {
// Entry
label = e.label;
text = e.text;
url = e.url;
fn = e.modify;
count = 0;
n3 = $.node("div", "xl-actions-table-row" + theme);
if (label !== undefined) {
$.add(n3, n4 = $.node("div", "xl-actions-table-cell xl-actions-table-cell-label" + theme));
if (typeof(label) === "string") {
$.add(n4, $.node("div", "xl-actions-table-header", label));
}
++count;
}
if (typeof(text) === "string") {
$.add(n3, n4 = $.node("div", "xl-actions-table-cell" + theme));
if (typeof(url) === "string") {
$.add(n4, n5 = $.link(url, "xl-actions-option" + theme, text));
$.on(n5, "click", $.bind(on_actions_menu_entry_click, n5, actions, id));
}
else {
$.add(n4, n5 = $.node("span", "xl-actions-option-text" + theme, text));
}
++count;
}
if (count === 1) {
n4.classList.add("xl-actions-table-cell-full");
}
if (typeof(fn) === "function") {
fn(n3);
}
$.add(n2, n3);
}
}
// Prepare
Popup.hovering(actions);
return actions;
};
var enable_actions_menu_on_node = function (node, enabled, id, setup_fn) {
var cls = "xl-site-tag-active",
actions, tag_bg, info, link;
if (enabled !== undefined) {
if (node.classList.contains(cls) === enabled) return;
}
enabled = node.classList.toggle(cls);
if (enabled) {
// Create bg
tag_bg = $(".xl-site-tag-bg", node);
if (tag_bg === null) tag_bg = create_tag_bg(node);
// Show
actions = actions_nodes[id];
if (actions !== undefined) {
actions.classList.remove("xl-actions-hidden");
Popup.hovering(actions);
activate_actions_menu(actions, id);
// Position
update_actions_position(actions, node, tag_bg, document_element.getBoundingClientRect());
}
else {
// Create
if (
(link = get_link_from_site_tag(node)) !== null &&
(info = API.get_url_info_saved(link.href)) !== null
) {
setup_fn(info, id, function (err, actions) {
if (err === null) {
actions_nodes[id] = actions;
activate_actions_menu(actions, id);
update_actions_position(actions, node, tag_bg, document_element.getBoundingClientRect());
}
});
// API.get_data_from_url_info(info, function (err, data) {
// if (err === null) {
// create_actions(data, info, id, function (err, actions) {
// if (err === null) {
// actions_nodes[id] = actions;
// activate_actions_menu(actions, id);
// update_actions_position(actions, node, tag_bg, document_element.getBoundingClientRect());
// }
// });
// }
// });
}
}
}
else {
// Hide
actions = actions_nodes[id];
if (actions !== undefined) {
close_actions_menu(actions, id);
}
}
};
// Events
var event_listeners = {
format: []
};
var on = function (event_name, callback) {
var listeners = event_listeners[event_name];
if (listeners === undefined) return false;
listeners.push(callback);
return true;
};
var off = function (event_name, callback) {
var listeners = event_listeners[event_name],
i, ii;
if (listeners !== undefined) {
for (i = 0, ii = listeners.length; i < ii; ++i) {
if (listeners[i] === callback) {
listeners.splice(i, 1);
return true;
}
}
}
return false;
};
var trigger = function (listeners, data) {
var i, ii;
for (i = 0, ii = listeners.length; i < ii; ++i) {
listeners[i].call(null, data);
}
};
// Public
var create_rating_stars = function (rating) {
var frag = document.createDocumentFragment(),
star, tmp, i;
rating = Math.round(rating * 2);
for (i = 0; i < 5; ) {
tmp = $.clamp(rating - (i * 2), 0, 2);
star = (tmp === 2 ? "full" : (tmp === 1 ? "half" : "none"));
++i;
$.add(frag, $.node("div", "xl-star xl-star-" + i + " xl-star-" + star));
}
return frag;
};
var get_site_tag_text_node = function (button) {
return ((button = button.lastChild) !== null && button.tagName === "SPAN") ? button : null;
};
var create_site_tag_text = function (info) {
return "[" + (info.tag || "?") + "]";
};
var format_date = function (timestamp) {
var d = new Date(timestamp);
return d.getUTCFullYear() + "-" +
pad(d.getUTCMonth() + 1, "-") +
pad(d.getUTCDate(), " ") +
pad(d.getUTCHours(), ":") +
pad(d.getUTCMinutes(), "");
};
var resetup_link = function (link) {
API.get_url_info(link.href || "", function (err, info) {
if (err === null && info !== null) {
load_link(link, info);
}
});
};
var setup_link = function (link, url, info, auto_load, add_tag) {
var button, text, n;
if (add_tag) {
button = $.link(url, "xl-site-tag" + Theme.classes);
text = $.node("span", "xl-site-tag-text", create_site_tag_text(info));
button.setAttribute("data-xl-site", info.site);
button.setAttribute("data-xl-href", button.href);
button.removeAttribute("href");
if (info.icon !== undefined) {
$.add(button, n = $.node("span", "xl-site-tag-icon" + Theme.classes));
n.setAttribute("data-xl-site-tag-icon", info.icon);
}
$.add(button, text);
Linkifier.change_link_events(button, "gallery_fetch");
$.before(link.parentNode, link, button);
}
else {
link.setAttribute("data-xl-no-content-update", "true");
}
set_node_id(link, info);
Linkifier.change_link_events(link, "gallery_link");
if (auto_load) load_link(link, info);
};
var load_link = function (link, info) {
API.get_data_from_url_info(info, function (err, data) {
if (link.parentNode !== null) {
format_link_generic(link, err, data, info);
}
});
};
var format_link_generic = function (link, err, data, info) {
var monitor = (info.monitor === true) && Config.is_4chan_x3,
attr;
// Observer disconnect trigger on re-format
if (monitor) {
attr = "data-xl-remove-monitor";
if (link.hasAttribute(attr)) {
link.setAttribute(attr, "");
}
else {
link.removeAttribute(attr);
}
}
// Format
if (err === null) {
format_link(link, data, info);
if (event_listeners.format.length > 0) {
trigger(event_listeners.format, { link: link });
}
}
else {
format_link_error(link, err, info);
}
// Monitor changes from external sources
if (monitor) {
link.removeAttribute(attr);
new MutationObserver($.bind(on_formatter_link_change, link)).observe(link, { childList: true, attributes: true });
}
};
var format_link = function (link, data, info) {
var button = get_site_tag_from_link(link),
n = null,
url, hl, c, n2;
// Smart links
if ((url = API.rewrite_link_smart(link, info, data)) !== null) {
link.href = url;
if (button !== null) {
button.href = link.href;
update_site_tag(button, info);
}
}
// Class changes
hl = info.classes_remove;
if (hl !== undefined && Array.isArray(hl)) {
link.classList.remove.apply(link.classList, hl);
}
hl = info.classes_add;
if (hl !== undefined && Array.isArray(hl)) {
link.classList.add.apply(link.classList, hl);
}
// Link title
if (link.getAttribute("data-xl-no-content-update") !== "true") {
n = $.node("span", "xl-link-inner", data.title);
link.textContent = "";
$.add(link, n);
link.classList.add("xl-link-formatted");
}
// Button
if (button !== null && n !== null) {
hl = Filter.check(n, data);
if (hl[0] !== Filter.None) {
c = (hl[0] === Filter.Good) ? config.filter.good_tag_marker : config.filter.bad_tag_marker;
mark_site_tag(button, c);
Filter.highlight_tag(button, link, hl);
}
Linkifier.change_link_events(button, "gallery_tag");
}
// URL node
if (Config.is_4chan && !Config.is_4chan_x3) {
// This is for certain 4chan-inline functionality (youtube "Embed" link hover image preview)
n2 = $.node("span", "xl-link-url-text", " " + link.href + " ");
$.before(link, link.firstChild, n2);
}
// Page
if (n !== null) {
if (info.page !== undefined) {
n2 = $.node("span", "xl-link-page", " (page " + info.page + ")");
$.add(n, n2);
}
else if (info.title_extra !== undefined) {
n2 = $.node("span", "xl-link-extra", " " + info.title_extra);
$.add(n, n2);
}
}
};
var format_link_error = function (link, error) {
var button = get_site_tag_from_link(link),
text, n, n2;
if (button !== null) {
Linkifier.change_link_events(button, "gallery_error");
button.classList.add("xl-link-error");
}
link.classList.add("xl-link-formatted", "xl-link-error");
if (link.getAttribute("data-xl-no-content-update") !== "true") {
text = " (" + error.trim().replace(/\.$/, "") + ")";
n = $.node("span", "xl-link-inner");
while (link.firstChild !== null) {
$.add(n, link.firstChild);
}
$.add(link, n);
n2 = $(".xl-link-error-message", link);
if (n2 === null) {
$.add(n, n2 = $.node("span", "xl-link-error-message", text));
}
else {
n2.textContent = text;
}
n2.setAttribute("data-xl-error-message", text);
}
};
var get_links_formatted = function (parent) {
return $$("a.xl-link.xl-link-formatted", parent);
};
var on_formatter_link_change = function (records, mo) {
var change = false,
info, r, i, ii;
for (i = 0, ii = records.length; i < ii; ++i) {
r = records[i];
if (r.type === "attributes") {
if (r.attributeName === "data-xl-remove-monitor") {
mo.disconnect();
return;
}
}
else {
change = true;
}
}
if (
change &&
(info = API.get_url_info_saved(this.href)) !== null
) {
// Limit monitoring to only once
info.monitor = false;
mo.disconnect();
load_link(this, info);
}
};
var cleanup_post = function (post) {
var nodes, n, i, ii;
nodes = $$(".xl-exsauce-results:not(.xl-exsauce-results-hidden)", post);
for (i = 0, ii = nodes.length; i < ii; ++i) {
nodes[i].classList.add("xl-exsauce-results-hidden");
}
nodes = $$(".xl-actions:not(.xl-actions-hidden)", post);
for (i = 0, ii = nodes.length; i < ii; ++i) {
nodes[i].classList.add("xl-actions-hidden");
}
nodes = $$(".xl-site-tag[xl-actions-id]", post);
for (i = 0, ii = nodes.length; i < ii; ++i) {
n = nodes[i];
n.classList.remove("xl-site-tag-active");
n.removeAttribute("xl-actions-id");
}
nodes = $$(".xl-linkified:not(.xl-link-formatted)", post);
for (i = 0, ii = nodes.length; i < ii; ++i) {
resetup_link(nodes[i]);
}
};
var cleanup_post_removed = function (post) {
var nodes, index, n, i, ii;
nodes = $$(".xl-site-tag[xl-actions-id]", post);
for (i = 0, ii = nodes.length; i < ii; ++i) {
index = nodes[i].getAttribute("xl-actions-id") || "";
n = actions_nodes[index];
if (n !== undefined) {
if (n.parentNode !== null) $.remove(n);
delete actions_nodes[index];
deactivate_actions_menu(index);
}
}
};
var init = function () {
Linkifier.register_link_events({
gallery_link: gallery_link_events,
gallery_tag: gallery_tag_events,
gallery_fetch: gallery_fetch_event,
gallery_error: gallery_error_event
});
};
var strings = {
thumbnail_failed: "Thumbnail failed to load\n\nThis may be due to an extension conflict - check any adblocker or similar extensions that are installed"
};
// Exports
return {
setup_link: setup_link,
get_links_formatted: get_links_formatted,
create_rating_stars: create_rating_stars,
get_site_tag_text_node: get_site_tag_text_node,
create_site_tag_text: create_site_tag_text,
format_date: format_date,
cleanup_post: cleanup_post,
cleanup_post_removed: cleanup_post_removed,
register_details_creation: register_details_creation,
register_actions_creation: register_actions_creation,
init: init,
on: on,
off: off,
strings: strings
};
})();
var API = (function () {
// Caching
var cache_prefix = "xlinks-cache-",
cache_storage = window.localStorage,
cache_objects = {
md5_to_hash: {},
url_to_hash: {},
lookup: {},
errors: {}
},
ttl_1_hour = 60 * 60 * 1000,
ttl_1_day = 24 * ttl_1_hour,
ttl_1_year = 365 * ttl_1_day;
var cache_set = function (key, data, ttl) {
cache_storage.setItem(cache_prefix + key, JSON.stringify({
expires: Date.now() + ttl,
data: data
}));
};
var cache_get = function (key) {
var json = $.json_parse_safe(cache_storage.getItem(cache_prefix + key), null);
if (
json !== null &&
typeof(json) === "object" &&
Date.now() < json.expires &&
typeof(json.data) === "object"
) {
return json.data;
}
cache_storage.removeItem(key);
return null;
};
var cache_set_object = function (object_name) {
cache_storage.setItem(cache_prefix + object_name, JSON.stringify({
expires: null,
data: cache_objects[object_name]
}));
};
var cache_get_object = function (object_name) {
var json = $.json_parse_safe(cache_storage.getItem(cache_prefix + object_name), null),
obj, target;
if (
json !== null &&
typeof(json) === "object" &&
(obj = json.data) !== null &&
typeof(obj) === "object"
) {
target = cache_objects[object_name];
return cache_merge_objects(target, obj);
}
cache_storage.removeItem(object_name);
return false;
};
var cache_merge_objects = function (dest, obj) {
var now = Date.now(),
update = false,
entry, k;
for (k in obj) {
entry = obj[k];
if (now < entry.expires) {
dest[k] = entry;
}
else {
update = true;
}
}
return update;
};
var cache_cleanup = function () {
var storage = cache_storage,
removes = [],
time = Date.now(),
key, json, i, ii;
for (i = 0, ii = storage.length; i < ii; ++i) {
key = storage.key(i);
if (key.length >= cache_prefix.length && key.substr(0, cache_prefix.length) === cache_prefix) {
json = $.json_parse_safe(storage.getItem(key), null);
if (json === null || typeof(json) !== "object") {
// Invalid
removes.push(key);
}
else if (json.expires !== null && !(time < json.expires)) { // jshint ignore:line
// This should also cover undefined values
removes.push(key);
}
}
}
for (i = 0, ii = removes.length; i < ii; ++i) {
storage.removeItem(removes[i]);
}
return ii;
};
var cache_clear = function () {
var storage_types = [ window.localStorage, window.sessionStorage ],
removes = [],
count = 0,
storage, key, i, ii, j, jj;
for (i = 0, ii = storage_types.length; i < ii; ++i) {
storage = storage_types[i];
for (j = 0, jj = storage.length; j < jj; ++j) {
key = storage.key(j);
if (key.length >= cache_prefix.length && key.substr(0, cache_prefix.length) === cache_prefix) {
removes.push(key);
}
}
for (j = 0, jj = removes.length; j < jj; ++j) {
storage.removeItem(removes[j]);
}
count += jj;
}
return count;
};
var cache_init = function () {
// Cache mode
if (config.debug.cache_mode === "none") {
cache_storage = create_temp_storage();
}
else if (config.debug.cache_mode === "session") {
cache_storage = window.sessionStorage;
}
// Clean
cache_cleanup();
// Load
var k;
for (k in cache_objects) {
if (cache_get_object(k)) {
cache_set_object(k);
}
}
};
var cache_get_prefix = function () {
return cache_prefix;
};
var create_temp_storage = function () {
var data = {};
var fn = {
length: 0,
key: function (index) {
return Object.keys(data)[index];
},
getItem: function (key) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
return data[key];
}
return null;
},
setItem: function (key, value) {
if (!Object.prototype.hasOwnProperty.call(data, key)) {
++fn.length;
}
data[key] = value;
},
removeItem: function (key) {
if (Object.prototype.hasOwnProperty.call(data, key)) {
delete data[key];
--fn.length;
}
},
clear: function () {
data = {};
fn.length = 0;
}
};
return fn;
};
// Databasing
var saved_data = {};
var saved_thumbnails = {};
var get_saved_data = function (id) {
var data = saved_data[id];
if (data !== undefined) return data;
data = cache_get("data-" + id);
if (data !== null) {
saved_data[id] = data;
return data;
}
return null;
};
var set_saved_data = function (id, data) {
saved_data[id] = data;
cache_set("data-" + id, data, ttl_1_hour * (data.date_created >= Date.now() - ttl_1_day ? 1 : 12));
};
var set_saved_error = function (id_list, error, cache) {
var id = id_list.join("-");
cache_objects.errors[id] = {
expires: (cache ? Date.now() + ttl_1_hour * 12 : 0),
data: error
};
if (cache) {
cache_set_object("errors");
}
};
var get_saved_error = function (id_list) {
var id = id_list.join("-"),
value = cache_objects.errors[id];
return (value !== undefined) ? value.data : null;
};
var get_saved_thumbnail = function (namespace, gid, page) {
var id_full = namespace + "-" + gid + "-" + page,
data = saved_thumbnails[id_full];
if (data !== undefined) return data;
data = cache_get("thumb-" + id_full);
if (data !== null) {
saved_thumbnails[id_full] = data;
return data;
}
return null;
};
var set_saved_thumbnail = function (namespace, gid, page, data) {
var id_full = namespace + "-" + gid + "-" + page;
saved_thumbnails[id_full] = data;
cache_set("thumb-" + id_full, data, ttl_1_hour * 6);
};
var hash_get_sha1_from_md5 = function (md5) {
var value = cache_objects.md5_to_hash[md5];
return (value !== undefined) ? value.data : null;
};
var hash_get_sha1_from_url = function (url) {
var value = cache_objects.url_to_hash[url];
return (value !== undefined) ? value.data : null;
};
var hash_set_md5_to_sha1 = function (md5, sha1) {
cache_objects.md5_to_hash[md5] = {
expires: Date.now() + ttl_1_year,
data: sha1
};
cache_set_object("md5_to_hash");
};
var hash_set_url_to_sha1 = function (url, sha1) {
cache_objects.url_to_hash[url] = {
expires: Date.now() + ttl_1_day,
data: sha1
};
cache_set_object("url_to_hash");
};
var lookup_get_results = function (hash) {
var value = cache_objects.lookup[hash];
return (value !== undefined) ? value.data : null;
};
var lookup_set_results = function (data) {
cache_objects.lookup[data.hash] = {
expires: Date.now() + ttl_1_day,
data: data
};
cache_set_object("lookup");
};
// Categories
var categories = {
artistcg: { sort: 0, color_id: 3, short_name: "artistcg", name: "Artist CG" },
asianporn: { sort: 1, color_id: 9, short_name: "asianporn", name: "Asian Porn" },
cosplay: { sort: 2, color_id: 8, short_name: "cosplay", name: "Cosplay" },
doujinshi: { sort: 3, color_id: 1, short_name: "doujinshi", name: "Doujinshi" },
gamecg: { sort: 4, color_id: 4, short_name: "gamecg", name: "Game CG" },
imageset: { sort: 5, color_id: 7, short_name: "imageset", name: "Image Set" },
manga: { sort: 6, color_id: 2, short_name: "manga", name: "Manga" },
misc: { sort: 7, color_id: 0, short_name: "misc", name: "Misc" },
non_h: { sort: 8, color_id: 6, short_name: "non-h", name: "Non-H" },
"private": { sort: 9, color_id: 0, short_name: "private", name: "Private" },
western: { sort: 10, color_id: 5, short_name: "western", name: "Western" }
};
var ehentai_category_mapping = {
"artist cg sets": "artistcg",
"asian porn": "asianporn",
"cosplay": "cosplay",
"doujinshi": "doujinshi",
"game cg sets": "gamecg",
"image sets": "imageset",
"manga": "manga",
"misc": "misc",
"non-h": "non_h",
"private": "private",
"western": "western"
};
var nhentai_category_mapping = {
"doujinshi": "doujinshi",
"manga": "manga"
};
var hitomi_category_mapping = {
"doujinshi": "doujinshi",
"manga": "manga",
"artist cg": "artistcg",
"game cg": "gamecg"
};
var normalize_category = function (mapping, category) {
var t = mapping[category.toLowerCase()];
return (t !== undefined ? t : "misc");
};
var get_category = function (name) {
var c = categories[name];
return (c !== undefined) ? c : categories.misc;
};
var get_category_sort_rank = function (name) {
var c = categories[name];
return (c !== undefined) ? c.sort : Object.keys(categories).length;
};
// Private
var temp_div = $.node_simple("div"),
nhentai_tag_namespaces = {
parodies: "parody",
characters: "character",
artists: "artist",
groups: "group"
};
var ImageFlags = {
None: 0x0,
ThumbnailNoLeech: 0x1
};
var create_empty_gallery_info = function (type) {
return {
type: type,
subtype: "gallery",
gid: 0,
token: null,
title: "",
title_jpn: null,
uploader: "",
category: "misc",
thumbnail: null,
flags: 0,
date_created: 0,
file_count: 0,
total_size: -1,
favorites: -1,
favorite_category: null,
rating: -1,
torrent_count: -1,
full: false,
visible: null,
removed: null,
archiver_key: null,
tags: null,
tags_ns: null
};
};
var header_string_parse = function (header_str) {
var lines = header_str.split("\r\n"),
re_line = /^([^:]*):\s(.*)$/i,
headers = {},
i, m;
for (i = 0; i < lines.length; ++i) {
if ((m = re_line.exec(lines[i]))) {
headers[m[1].toLowerCase()] = m[2];
}
}
return headers;
};
var ehentai_simple_string = function (value, default_value) {
return (typeof(value) !== "string" || value.length === 0) ? default_value : value;
};
var ehentai_normalize_string = function (value, default_value) {
if (typeof(value) !== "string" || value.length === 0) {
return default_value;
}
temp_div.innerHTML = value;
value = temp_div.textContent;
temp_div.textContent = "";
return value;
};
var ehentai_normalize_info = function (info) {
if (info.error !== undefined) {
return { error: info.error };
}
var data = create_empty_gallery_info("ehentai"),
re_tag_ns = /^([^:]*):([\w\W]*)$/,
i, ii, m, tag, ns, tags, tags_ns;
data.gid = parseInt(info.gid, 10) || 0;
data.token = ehentai_simple_string(info.token, null);
data.archiver_key = ehentai_simple_string(info.archiver_key, null);
data.title = ehentai_normalize_string(info.title, "");
data.title_jpn = ehentai_normalize_string(info.title_jpn, null);
data.uploader = ehentai_normalize_string(info.uploader, null);
data.category = normalize_category(ehentai_category_mapping, ehentai_simple_string(info.category, ""));
data.thumbnail = ehentai_simple_string(info.thumb, null);
data.date_created = (parseInt(info.posted, 10) || 0) * 1000;
data.file_count = parseInt(info.filecount, 10) || 0;
data.total_size = parseInt(info.filesize, 10) || 0;
data.rating = parseFloat(info.rating) || 0.0;
data.torrent_count = parseInt(info.torrentcount, 10) || 0;
data.visible = !info.expunged;
tags = [];
tags_ns = {};
if (Array.isArray(info.tags)) {
for (i = 0, ii = info.tags.length; i < ii; ++i) {
ns = "misc";
tag = info.tags[i];
if ((m = re_tag_ns.exec(tag)) !== null) {
ns = m[1];
tag = m[2];
}
if (!Object.prototype.hasOwnProperty.call(tags_ns, ns)) {
tags_ns[ns] = [];
}
tags.push(tag);
tags_ns[ns].push(tag);
}
}
data.tags = tags;
data.tags_ns = tags_ns;
return data;
};
var ehentai_get_favorite_info_from_icon = function (node, data) {
var m = /background-position\s*:\s*\S+\s*([\-\+]?[\d\.]+)px/.exec(node.getAttribute("style") || "");
if (m !== null) {
data.favorite_category = [
Math.max(0, Math.min(9, Math.round(((-parseFloat(m[1]) || 0) - 2) / 19))),
node.getAttribute("title") || ""
];
}
};
var ehentai_is_not_available = function (html) {
var n;
return (
(n = $("title", html)) !== null &&
(/^\s*Gallery\s+Not\s+Available/i).test(n.textContent) &&
$("#continue", html) !== null
);
};
var ehentai_is_content_warning = function (html) {
var n;
return (
(n = $("h1", html)) !== null &&
(/^\s*Content\s+Warning/i).test(n.textContent)
);
};
var ehentai_parse_gallery_info = function (html, data) {
// Tags
var updated_tag_count = 0,
tags, tags_array, pattern, par, tds, namespace, ns, i, ii, j, jj, m, n, t;
tags = {};
tags_array = [];
pattern = /(.+):/;
data.removed = false;
par = $$("#taglist tr", html);
for (i = 0, ii = par.length; i < ii; ++i) {
// Class
tds = $$("td", par[i]);
jj = tds.length;
if (jj === 0) continue;
// Namespace
namespace = ((m = pattern.exec(tds[0].textContent)) ? m[1].trim() : "");
if (!(namespace in tags)) {
ns = [];
tags[namespace] = ns;
}
else {
ns = tags[namespace];
}
// Tags
tds = $$("div", tds[jj - 1]);
for (j = 0, jj = tds.length; j < jj; ++j) {
// Create tag
if ((n = $("a", tds[j])) !== null) {
// Add tag
t = n.textContent.trim();
ns.push(t);
tags_array.push(t);
}
}
// Remove if empty
if (ns.length === 0) {
delete tags[namespace];
}
else {
++updated_tag_count;
}
}
if (tags_array.length > 0) {
data.tags = tags_array;
data.tags_ns = tags;
}
// Favorites
if (
(n = $("#favcount", html)) !== null &&
(m = /\d+/.exec(n.textContent)) !== null
) {
data.favorite_count = parseInt(m[0], 10);
}
if ((n = $("#fav>.i[style]", html)) !== null) {
ehentai_get_favorite_info_from_icon(n, data);
}
// Done
data.full = true;
return data;
};
var ehentai_make_removed = function (data) {
data.removed = true;
data.full = true;
return data;
};
var ehentai_parse_lookup_results = function (xhr, is_similarity_scan, hash, url, md5) {
var final_url = xhr.finalUrl,
text = xhr.responseText,
err = null,
html, results, links, link, m, n, i, ii;
// Similarity scan checking
if (is_similarity_scan) {
m = /f_shash=(([0-9a-f]{40}|corrupt)(?:;(?:[0-9a-f]{40}|monotone))*)/.exec(final_url);
if (m !== null && m[2] !== "corrupt") {
hash = m[1];
if (/monotone/.test(hash)) {
err = "Similarity lookup does not work on monotone images";
}
}
else if ((m = /[?&]poni=([^\?\&\#]*)/.exec(final_url)) !== null) {
// Strange error
err = "poni-code encountered: you likely have erroneous cookies.\n" + m[1];
Debug.log("poni-code encountered: " + final_url);
}
else {
if (/please\s+wait\s+a\s+bit\s+longer\s+between\s+each\s+file\s+search/i.test(text)) {
err = "Wait longer between lookups";
}
else {
Debug.log("An error occurred while reverse image searching", xhr);
err = "Unknown error";
html = $.html_parse_safe(text, null);
if (html !== null) {
n = $("#iw", html);
if (n !== null) err = n.textContent.trim();
}
}
}
if (err !== null) return { error: err, error_mode: RequestErrorMode.None };
// Save hash
if (md5 === null) {
hash_set_url_to_sha1(url, hash);
}
else {
hash_set_md5_to_sha1(md5, hash);
}
}
// Get html
html = $.html_parse_safe(text, null);
// Process
results = [];
links = $$("div.it5 a,div.id2 a", html);
for (i = 0, ii = links.length; i < ii; ++i) {
link = links[i];
results.push({
url: link.href,
title: link.textContent
});
}
// Done
return {
url: final_url,
hash: hash,
results: results
};
};
var ehentai_create_lookup_url = function (sha1) {
var url = "http://";
url += config.sauce.lookup_domain;
url += "/?f_doujinshi=1&f_manga=1&f_artistcg=1&f_gamecg=1&f_western=1&f_non-h=1&f_imageset=1&f_cosplay=1&f_asianporn=1&f_misc=1&f_search=Search+Keywords&f_apply=Apply+Filter&f_shash=";
url += sha1;
url += "&fs_similar=0";
if (config.sauce.expunged) url += "&fs_exp=1";
return url;
};
var nhentai_normalize_tag_namespace = function (namespace) {
return nhentai_tag_namespaces[namespace] || namespace;
};
var nhentai_parse_info = function (html, url) {
var info = $("#info", html),
data, nodes, tags, tag_ns, tag_ns_list, t, m, n, i, ii, j, jj;
if (info === null) {
return { error: "Could not find info" };
}
// Create data
data = create_empty_gallery_info("nhentai");
data.uploader = "nhentai.net";
data.full = true;
data.tags = [];
data.tags_ns = {};
// Image/gid
if ((n = $("#cover>a", html)) !== null) {
m = /\/g\/(\d+)/.exec(n.getAttribute("href") || "");
if (m !== null) {
data.gid = parseInt(m[1], 10);
}
if (
(n = $("img", n)) !== null &&
(t = n.getAttribute("data-src"))
) {
data.thumbnail = $.resolve(t, url);
}
}
// Image count
data.file_count = $$("#thumbnail-container>.thumb-container", html).length;
// Titles
if ((n = $("h1", info)) !== null) {
data.title = n.textContent.trim();
}
if ((n = $("h2", info)) !== null) {
data.title_jpn = n.textContent.trim();
}
// Tags
if ((nodes = $$(".field-name", info)).length > 0) {
for (i = 0, ii = nodes.length; i < ii; ++i) {
tag_ns = nhentai_normalize_tag_namespace((
(n = nodes[i].firstChild) !== null &&
n.nodeType === Node.TEXT_NODE
) ? n.nodeValue.trim().replace(/:/, "").toLowerCase() : "");
tags = $$(".tag", nodes[i]);
if (tag_ns === "category") {
if (
tags.length > 0 &&
(n = tags[0].firstChild) !== null &&
n.nodeType === Node.TEXT_NODE
) {
data.category = normalize_category(nhentai_category_mapping, n.nodeValue.trim());
}
tags = [];
}
if (tags.length > 0) {
if (tag_ns in data.tags_ns) {
tag_ns_list = data.tags_ns[tag_ns];
}
else {
tag_ns_list = [];
data.tags_ns[tag_ns] = tag_ns_list;
}
for (j = 0, jj = tags.length; j < jj; ++j) {
if (
(n = tags[j].firstChild) !== null &&
n.nodeType === Node.TEXT_NODE
) {
// Add tag
t = n.nodeValue.trim();
tag_ns_list.push(t);
data.tags.push(t);
}
}
}
}
}
// Date
if ((n = $("time[datetime]", info)) !== null) {
m = /^(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)\.(\d{6})/i.exec(n.getAttribute("datetime") || "");
if (m !== null) {
data.date_created = new Date(
parseInt(m[1], 10),
parseInt(m[2], 10) - 1,
parseInt(m[3], 10),
parseInt(m[4], 10),
parseInt(m[5], 10),
parseInt(m[6], 10),
Math.floor(parseInt(m[7], 10) / 1000)
).getTime();
}
}
// Favorite count
if ((n = $(".buttons>.btn.btn-primary>span>.nobold", info)) !== null) {
m = /\d+/.exec(n.textContent);
if (m !== null) {
data.favorite_count = parseInt(m[0], 10);
}
}
return data;
};
var hitomi_parse_info = function (html, url) {
var info = $(".content", html),
cellmap = {},
re_gender = /\s*(\u2640|\u2642)$/, // \u2640 = female, \u2642 = male
tags_full, tags, tag_list, info2, data, nodes, cells, t, m, n, i, ii, j;
if (
info === null ||
(info2 = $(".gallery", info)) === null
) {
return { error: "Could not find info" };
}
// Create data
data = create_empty_gallery_info("hitomi");
data.flags |= ImageFlags.ThumbnailNoLeech; // no cross origin thumbnails
data.uploader = "hitomi.la";
data.full = true;
data.tags = tags = [];
data.tags_ns = tags_full = {};
// Image/gid
if ((n = $(".cover>a", html)) !== null) {
m = /\/reader\/(\d+)/.exec(n.getAttribute("href") || "");
if (m !== null) {
data.gid = parseInt(m[1], 10);
}
if (
(n = $("img", n)) !== null &&
(t = n.getAttribute("src"))
) {
data.thumbnail = $.resolve(t, url);
}
}
// Image count
data.file_count = $$(".thumbnail-list>li", html).length;
// Title
if ((n = $("h1", info2)) !== null) {
data.title = n.textContent.trim();
}
// Cell info
cells = $$(".gallery-info>table td", info2);
for (i = 0; i < cells.length; i += 2) {
cellmap[cells[i].textContent.trim().toLowerCase()] = cells[i + 1];
}
// Language
if ((n = cellmap.language) !== undefined) {
t = n.textContent.trim();
if (t.length > 0 && t !== "N/A") {
tags_full.language = [ t ];
tags.push(t);
}
}
// Parody
if ((n = cellmap.series) !== undefined) {
t = n.textContent.trim();
if (t.length > 0 && t !== "N/A") {
tags_full.parody = [ t ];
tags.push(t);
}
}
// Character
if ((n = cellmap.characters) !== undefined) {
if ((nodes = $$("li>a", n)).length > 0) {
tag_list = [];
for (i = 0, ii = nodes.length; i < ii; ++i) {
t = nodes[i].textContent.trim();
if (t.length > 0) {
tag_list.push(t);
tags.push(t);
}
}
if (tag_list.length > 0) {
tags_full.character = tag_list;
}
}
}
// Group
if ((n = cellmap.group) !== undefined) {
t = n.textContent.trim();
if (t.length > 0 && t !== "N/A") {
tags_full.group = [ t ];
tags.push(t);
}
}
// Artists
if ((nodes = $$("h2>ul>li>a", info2)).length > 0) {
tag_list = [];
for (i = 0, ii = nodes.length; i < ii; ++i) {
t = nodes[i].textContent.trim();
if (t.length > 0) {
tag_list.push(t);
tags.push(t);
}
}
if (tag_list.length > 0) {
tags_full.artist = tag_list;
}
}
// Type
if ((n = cellmap.type) !== undefined) {
t = n.textContent.trim();
if (t.length > 0 && t !== "N/A") {
data.category = normalize_category(hitomi_category_mapping, t);
}
}
// Tags
if ((n = cellmap.tags) !== undefined) {
if ((nodes = $$("li>a", n)).length > 0) {
tag_list = [ [], [], [] ]; // male, female, tags
for (i = 0, ii = nodes.length; i < ii; ++i) {
t = nodes[i].textContent.trim();
if (t.length > 0) {
if ((m = re_gender.exec(t)) === null) {
j = 2;
}
else if (m[1] === "\u2640") { // female
t = t.substr(0, m.index);
j = 1;
}
else { // male
t = t.substr(0, m.index);
j = 0;
}
tag_list[j].push(t);
}
}
if (tag_list[0].length > 0) {
Array.prototype.push.apply(tags, tag_list[0]);
tags_full.male = tag_list[0];
}
if (tag_list[1].length > 0) {
Array.prototype.push.apply(tags, tag_list[1]);
tags_full.female = tag_list[1];
}
if (tag_list[2].length > 0) {
Array.prototype.push.apply(tags, tag_list[2]);
tags_full.tags = tag_list[2];
}
}
}
// Date
if ((n = $(".date", info)) !== null) {
m = /^(\d+)-(\d+)-(\d+)\s+(\d+):(\d+):(\d+)/i.exec(n.textContent.trim());
if (m !== null) {
data.date_created = new Date(
parseInt(m[1], 10),
parseInt(m[2], 10) - 1,
parseInt(m[3], 10),
parseInt(m[4], 10),
parseInt(m[5], 10),
parseInt(m[6], 10),
0
).getTime();
}
}
return data;
};
var get_image = function (url, callback, progress_callback) {
// Note that the Uint8Array's length is longer than image_length
// callback(err, image_data, image_length);
var xhr_data = {
method: "GET",
url: url,
overrideMimeType: "text/plain; charset=x-user-defined",
onload: function (xhr) {
if (xhr.status === 200) {
var text = xhr.responseText,
ta = new Uint8Array(text.length + 1),
content_type = header_string_parse(xhr.responseHeaders)["content-type"] || "text/plain",
i, ii;
for (i = 0, ii = text.length; i < ii; ++i) {
ta[i] = text.charCodeAt(i);
}
callback.call(null, null, ta, ii, content_type, xhr.finalUrl);
}
else {
callback.call(null, $.xhr_error_string(xhr), null, 0, null, null);
}
},
onerror: function () {
callback.call(null, "Connection error", null, 0, null, null);
},
onabort: function () {
callback.call(null, "Connection aborted", null, 0, null, null);
}
};
if (progress_callback !== undefined) {
xhr_data.onprogress = function (xhr) {
progress_callback.call(null, "progress", xhr.lengthComputable, xhr.loaded, xhr.total);
};
}
HttpRequest(xhr_data);
};
var get_sha1_hash = function (url, md5, callback) {
var sha1 = null;
if (md5 !== null) {
sha1 = hash_get_sha1_from_md5(md5);
if (sha1 === null) {
sha1 = hash_get_sha1_from_url(url);
}
}
else {
sha1 = hash_get_sha1_from_url(url);
}
if (callback === undefined) {
return sha1;
}
if (sha1 !== null) {
callback.call(null, null, sha1);
}
else {
get_image(url, function (err, data) {
if (err === null) {
var sha1 = SHA1.hash(data, data.length - 1);
if (md5 === null) {
hash_set_url_to_sha1(url, sha1);
}
else {
hash_set_md5_to_sha1(md5, sha1);
}
callback.call(null, null, sha1);
}
else {
callback.call(null, err, null);
}
});
}
return null;
};
// API request base code
var request_groups = {};
var RequestGroup = function () {
this.active = 0;
this.timeout = null;
this.types = [];
};
var RequestType = function (count, concurrent, delay_okay, delay_error, group_name, namespace, type) {
this.count = count;
this.concurrent = concurrent;
this.delay_okay = delay_okay;
this.delay_error = delay_error;
this.queue = [];
this.unique = {};
this.group = request_groups[group_name];
if (this.group === undefined) {
request_groups[group_name] = this.group = new RequestGroup();
}
this.group.types.push(this);
this.namespace = namespace;
this.type = type;
this.request_init = null;
this.request_complete = null;
this.get_data = null;
this.set_data = null;
this.setup_xhr = null;
this.parse_response = null;
};
var RequestData = function (id, info, callback, progress_callback) {
this.id = id;
this.info = info;
this.callbacks = [ callback ];
this.progress_callbacks = [];
if (progress_callback !== undefined) this.progress_callbacks.push(progress_callback);
};
var Request = function (type, entries) {
var cbs, i, ii;
this.data = null;
this.type = type;
this.retry_count = 0;
this.entries = entries;
this.infos = [];
this.progress_callbacks = null;
for (i = 0, ii = entries.length; i < ii; ++i) {
this.infos.push(entries[i].info);
cbs = entries[i].progress_callbacks;
if (cbs.length > 0) {
if (this.progress_callbacks === null) {
this.progress_callbacks = cbs.slice(0);
}
else {
$.push_many(this.progress_callbacks, cbs);
}
}
}
};
var RequestErrorMode = {
None: 0,
NoCache: 1,
Save: 2
};
RequestGroup.prototype.run_delay = function (type) {
setTimeout(function () {
type.run();
}, 1);
};
RequestGroup.prototype.run = function (use_delay) {
var type, i, ii;
for (i = 0, ii = this.types.length; i < ii; ++i) {
type = this.types[i];
while (true) {
if (this.active >= type.concurrent) return;
if (type.queue.length === 0) break;
++this.active;
if (use_delay && type.count > 1) {
this.run_delay(type);
}
else {
type.run();
}
}
}
};
RequestGroup.prototype.complete = function (delay) {
if (delay > 0) {
var self = this;
setTimeout(function () {
--self.active;
self.run(false);
}, delay);
}
else {
--this.active;
this.run(false);
}
};
RequestType.prototype.add = function (unique_id, info, quick, callback, progress_callback) {
var self = this;
var get_data_callback = function (err, data) {
var u;
if (data !== null) {
if (progress_callback !== undefined) progress_callback.call(null, "start");
callback.call(null, null, data);
return;
}
if (quick) err = "Not found";
if (
err !== null ||
(err = get_saved_error([ self.namespace, self.type, unique_id ])) !== null
) {
if (progress_callback !== undefined) progress_callback.call(null, "start");
callback.call(null, err, null);
return;
}
// Add
u = self.unique[unique_id];
if (u === undefined) {
u = new RequestData(unique_id, info, callback, progress_callback);
self.unique[unique_id] = u;
self.queue.push(u);
}
else {
u.callbacks.push(callback);
if (progress_callback !== undefined) u.progress_callbacks.push(progress_callback);
}
// Run (if not already running)
self.group.run(true);
};
if (this.get_data === null) {
get_data_callback(null, null);
}
else {
this.get_data.call(null, info, get_data_callback);
}
};
RequestType.prototype.run = function () {
var req = new Request(this, this.queue.splice(0, this.count));
if (this.request_init !== null) {
this.request_init.call(this, req);
}
req.run();
};
Request.prototype.run = function () {
var i, ii, ev;
if (this.progress_callbacks !== null) {
ev = (this.retry_count === 0) ? "start" : "retry";
for (i = 0, ii = this.progress_callbacks.length; i < ii; ++i) {
this.progress_callbacks[i].call(this, ev);
}
}
this.type.setup_xhr.call(this, $.bind(this.on_xhr_setup, this));
};
Request.prototype.process_response = function (err, response, delay) {
var total = this.infos.length,
responses = Math.min(response.length, total),
set_data = this.type.set_data,
complete = 0,
data, entry, err_mode, i;
// Save
var save_callback = function (entry, err, data) {
for (var i = 0, ii = entry.callbacks.length; i < ii; ++i) {
entry.callbacks[i].call(this, err, data);
}
if (++complete >= total) this.complete(delay);
};
// Save errors
for (i = responses; i < total; ++i) {
entry = this.entries[i];
set_saved_error([ this.type.namespace, this.type.type, entry.id ], err, false);
save_callback.call(this, entry, err, null);
}
// Save datas
for (i = 0; i < responses; ++i) {
data = response[i];
entry = this.entries[i];
if (typeof((err = data.error)) === "string") {
if (typeof((err_mode = data.error_mode)) !== "number") err_mode = RequestErrorMode.Save;
set_saved_error([ this.type.namespace, this.type.type, entry.id ], err, (err_mode === RequestErrorMode.Save));
save_callback.call(this, entry, err, data);
}
else {
err = null;
if (set_data !== null) {
set_data.call(this, data, entry.info, $.bind(save_callback, this, entry, null, data));
}
else {
save_callback.call(this, entry, null, data);
}
}
}
};
Request.prototype.complete_entries = function () {
var unique = this.type.unique;
for (var i = 0, ii = this.entries.length; i < ii; ++i) {
delete unique[this.entries[i].id];
}
};
Request.prototype.complete = function (delay) {
if (this.type.request_complete !== null) {
this.type.request_complete.call(this.type, this);
}
this.type.group.complete(delay);
};
Request.prototype.xhr_error = function (err) {
var self = this;
return function () {
self.complete_entries();
self.process_response(err, [], self.type.delay_error);
};
};
Request.prototype.on_xhr_setup = function (err, xhr_data) {
var self = this,
any_status = (xhr_data.any_status === true),
i, ii, ev;
// Error
if (err !== null) {
this.xhr_error(err)();
return;
}
// Load handler
xhr_data.onload = function (xhr) {
if (xhr.status === 200 || any_status) {
self.type.parse_response.call(self, xhr, function (err, response) {
self.on_response_parse(err, response);
});
}
else {
self.xhr_error($.xhr_error_string(xhr))();
}
};
// Error handlers
xhr_data.onerror = this.xhr_error("Connection error");
xhr_data.onabort = this.xhr_error("Connection aborted");
if (xhr_data.data !== undefined) {
xhr_data.upload = {
onerror: this.xhr_error("Upload connection error"),
onabort: this.xhr_error("Upload connection aborted")
};
}
// Progress handlers
if (this.progress_callbacks !== null) {
xhr_data.onprogress = function (xhr) {
for (var i = 0, ii = self.progress_callbacks.length; i < ii; ++i) {
self.progress_callbacks[i].call(self, "progress", xhr.lengthComputable, xhr.loaded, xhr.total);
}
};
if (xhr_data.data !== undefined) {
ev = "upload";
xhr_data.upload.onprogress = xhr_data.onprogress;
xhr_data.upload.onload = function () {
for (var i = 0, ii = self.progress_callbacks.length; i < ii; ++i) {
self.progress_callbacks[i].call(self, "download");
}
};
}
else {
ev = "download";
}
for (i = 0, ii = this.progress_callbacks.length; i < ii; ++i) {
this.progress_callbacks[i].call(this, ev);
}
ev = null;
}
// Start
HttpRequest(xhr_data);
xhr_data = null;
};
Request.prototype.on_response_parse = function (err, response, delay) {
if (err !== null) {
// Error
if (typeof(delay) !== "number") delay = this.type.delay_error;
this.complete_entries();
this.process_response(err, [], delay);
}
else if (response === null) {
// Retry
++this.retry_count;
if (typeof(delay) !== "number") delay = 0;
if (delay > 0) {
var self = this;
setTimeout(function () { self.run(); }, delay);
}
else {
this.run();
}
}
else {
// Process
if (typeof(delay) !== "number") delay = this.type.delay_okay;
this.complete_entries();
this.process_response("Data not found", response, delay);
}
};
// API request specializations
var rt_ehentai_gallery_page = new RequestType(25, 1, 200, 5000, "ehentai_api", "ehentai", "page"),
rt_ehentai_gallery = new RequestType(25, 1, 200, 5000, "ehentai_api", "ehentai", "gallery"),
rt_ehentai_gallery_full = new RequestType(1, 1, 200, 5000, "ehentai_full", "ehentai", "full"),
rt_ehentai_gallery_page_thumb = new RequestType(1, 1, 200, 5000, "ehentai_full", "ehentai", "page_thumb"),
rt_ehentai_lookup = new RequestType(1, 1, 3000, 5000, "ehentai_lookup", "ehentai", "lookup"),
rt_nhentai_gallery = new RequestType(1, 1, 200, 5000, "nhentai", "nhentai", "gallery"),
rt_nhentai_gallery_page_thumb = new RequestType(1, 1, 200, 5000, "nhentai", "nhentai", "page_thumb"),
rt_hitomi_gallery = new RequestType(1, 1, 200, 5000, "hitomi", "hitomi", "gallery"),
rt_hitomi_gallery_page_thumb = new RequestType(1, 1, 200, 5000, "hitomi", "hitomi", "page_thumb");
rt_ehentai_gallery.get_data = function (info, callback) {
var data = get_saved_data(info.id);
callback(null, (data !== null && data.token === info.token) ? data : null);
};
rt_ehentai_gallery.set_data = function (data, info, callback) {
set_saved_data(info.id, data);
callback(null);
};
rt_ehentai_gallery.setup_xhr = function (callback) {
var gidlist = [],
info, i, ii;
for (i = 0, ii = this.infos.length; i < ii; ++i) {
info = this.infos[i];
gidlist.push([ info.gid, info.token ]);
}
callback(null, {
method: "POST",
url: "https://" + domains.ehentai + "/api.php",
headers: { "Content-Type": "application/json" },
data: JSON.stringify({
method: "gdata",
gidlist: gidlist,
namespace: 1
})
});
};
rt_ehentai_gallery.parse_response = function (xhr, callback) {
var response = $.json_parse_safe(xhr.responseText, null),
datas, i, ii;
if (response !== null) {
if (typeof(response) === "object") {
if (typeof(response.error) === "string") {
callback(response.error);
return;
}
else if (Array.isArray(response.gmetadata)) {
response = response.gmetadata;
datas = [];
for (i = 0, ii = response.length; i < ii; ++i) {
datas.push(ehentai_normalize_info(response[i]));
}
callback(null, datas);
return;
}
}
else if (typeof(response) === "string") {
callback(response);
return;
}
}
return "Invalid response";
};
rt_ehentai_gallery_page.get_data = function (info, callback) {
var data = get_saved_data(info.id);
if (data !== null) {
callback(null, {
gid: data.gid,
token: data.token
});
}
else {
callback(null, null);
}
};
rt_ehentai_gallery_page.setup_xhr = function (callback) {
var pagelist = [],
info, i, ii;
for (i = 0, ii = this.infos.length; i < ii; ++i) {
info = this.infos[i];
pagelist.push([ info.gid, info.page_token, info.page ]);
}
callback(null, {
method: "POST",
url: "https://" + domains.ehentai + "/api.php",
headers: { "Content-Type": "application/json" },
data: JSON.stringify({
method: "gtoken",
pagelist: pagelist
})
});
};
rt_ehentai_gallery_page.parse_response = function (xhr, callback) {
var response = $.json_parse_safe(xhr.responseText, null);
if (response !== null) {
if (typeof(response) === "object") {
if (typeof(response.error) === "string") {
callback(response.error);
return;
}
else if (Array.isArray(response.tokenlist)) {
callback(null, response.tokenlist);
return;
}
}
else if (typeof(response) === "string") {
callback(response);
return;
}
}
callback("Invalid response");
};
rt_ehentai_gallery_full.get_data = function (info, callback) {
callback(null, info.data.full ? info.data : null);
};
rt_ehentai_gallery_full.set_data = function (data, info, callback) {
set_saved_data(info.info.id, data);
callback(null);
};
rt_ehentai_gallery_full.setup_xhr = function (callback) {
var info = this.infos[0];
callback(null, {
method: "GET",
url: "http://" + info.domain + "/g/" + info.gid + "/" + info.token + "/" + info.search,
any_status: true
});
};
rt_ehentai_gallery_full.parse_response = function (xhr, callback) {
var info = this.infos[0];
if (xhr.status === 200 || xhr.status === 404) {
ehentai_response_process_generic.call(this, xhr, info, this.type.delay_okay, callback, function (err, html) {
callback(null, [ err === null ? ehentai_parse_gallery_info(html, info.data) : ehentai_make_removed(info.data) ]);
});
}
else {
callback(null, [ ehentai_make_removed(info.data) ]);
}
};
var ehentai_response_process_generic = function (xhr, info, retry_delay, callback, process_callback) {
var content_type = header_string_parse(xhr.responseHeaders)["content-type"],
html;
if (!(/^text\/html/i).test(content_type || "")) {
// Panda
if (this.retry_count === 0 && info.domain === domains.exhentai) {
// Retry
info.domain = domains.gehentai;
callback(null, null, retry_delay);
}
else {
callback("Invalid response type " + content_type);
}
return;
}
// Parse
html = $.html_parse_safe(xhr.responseText, null);
if (html === null) {
callback("Invalid response");
}
else if (ehentai_is_not_available(html)) {
if (this.retry_count === 0 && info.domain === domains.gehentai) {
// Retry
info.domain = domains.exhentai;
callback(null, null, retry_delay);
}
else {
this.retry_count = 0;
process_callback.call(this, "Not available", null);
}
}
else if (ehentai_is_content_warning(html)) {
if (this.retry_count <= 1) {
// Retry
info.search = "?nw=session"; // bypass the "Content Warning"
callback(null, null, retry_delay);
}
else {
this.retry_count = 0;
process_callback.call(this, "Content warning", null);
}
}
else {
this.retry_count = 0;
process_callback.call(this, null, html);
}
};
rt_ehentai_gallery_page_thumb.get_data = function (info, callback) {
callback(null, get_saved_thumbnail("ehentai", info.gid, info.page));
};
rt_ehentai_gallery_page_thumb.set_data = function (data, info, callback) {
set_saved_thumbnail("ehentai", info.gid, info.page, data);
callback(null);
};
rt_ehentai_gallery_page_thumb.setup_xhr = rt_ehentai_gallery_full.setup_xhr;
rt_ehentai_gallery_page_thumb.parse_response = function (xhr, callback) {
var info = this.infos[0],
retry_delay = 0; // this.type.delay_okay
ehentai_response_process_generic.call(this, xhr, info, retry_delay, callback, function (err, html) {
if (err !== null) {
callback(err);
return;
}
var n1 = $(".gtb>.gpc", html),
small = false,
re_comma = /,/g,
start, end, total, m, n2, url, t;
if (n1 !== null) {
m = /([\d,]+)\s*-\s*([\d,]+)\s*of\s*([\d,]+)/i.exec(n1.textContent);
if (m !== null) {
start = parseInt(m[1].replace(re_comma, ""), 10);
end = parseInt(m[2].replace(re_comma, ""), 10);
total = parseInt(m[3].replace(re_comma, ""), 10);
if (info.page >= start && info.page <= end) {
n1 = $("#gdt", html);
if (n1 !== null) {
n2 = $$(".gdtl", n1);
if (n2.length === 0) {
n2 = $$(".gdtm", n1);
small = true;
}
n1 = n2[info.page - start];
if (n1 !== undefined) {
// Check for image
if (small) {
if (
(n2 = $("div", n1)) !== null &&
(t = n2.getAttribute("style"))
) {
// Small image
m = [
/url\(['"]?([^'"\)]*)['"]?\)\s*-(\d+)px/.exec(t),
/width\s*:\s*(\d+)px/.exec(t),
/height\s*:\s*(\d+)px/.exec(t)
];
if (m[0] !== null && m[1] !== null && m[2] !== null) {
url = m[0][1];
callback(null, [{
url: $.resolve(url, xhr.finalUrl),
left: parseInt(m[0][2], 10),
top: 0,
width: parseInt(m[1][1], 10),
height: parseInt(m[2][1], 10),
flags: ImageFlags.None
}]);
return;
}
}
}
else {
if (
(n2 = $("img", n1)) !== null &&
(url = n2.getAttribute("src"))
) {
// Full image
callback(null, [{
url: $.resolve(url, xhr.finalUrl),
left: 0,
top: 0,
width: -1,
height: -1,
flags: ImageFlags.None
}]);
return;
}
}
}
}
}
else if (info.page >= 1 && info.page <= total) {
// Wrong page
if (this.retry_count === 0) {
// Next
info.search = "?p=" + Math.floor((info.page - 1) / (end - (start - 1)));
callback(null, null, retry_delay);
return;
}
}
}
}
callback("Thumbnail not found");
});
};
rt_ehentai_lookup.get_data = function (info, callback) {
callback(null, info.sha1 === null ? null : lookup_get_results(info.sha1));
};
rt_ehentai_lookup.set_data = function (data, info, callback) {
void(info); // to make jshint ignore the unused var
lookup_set_results(data);
callback(null);
};
rt_ehentai_lookup.setup_xhr = function (callback) {
var info = this.infos[0];
if (info.similar) {
var blob = info.blob,
form_data = new FormData(),
ext = (blob.type || "").split("/");
info.blob = null;
ext = "." + ext[ext.length - 1];
form_data.append("sfile", blob, "image" + ext);
form_data.append("fs_similar", "on");
if (config.sauce.expunged) {
form_data.append("fs_exp", "on");
}
callback(null, {
method: "POST",
url: (config.sauce.lookup_domain === domains.exhentai ? "https://" + domains.exhentai + "/upload/image_lookup.php" : "https://upload." + domains.ehentai + "/image_lookup.php"),
data: form_data
});
}
else {
callback(null, {
method: "GET",
url: ehentai_create_lookup_url(info.sha1)
});
}
};
rt_ehentai_lookup.parse_response = function (xhr, callback) {
var info = this.infos[0];
callback(null, [ ehentai_parse_lookup_results(xhr, info.similar, info.sha1, info.url, info.md5) ], info.similar ? this.type.delay_okay : 0);
};
rt_nhentai_gallery.get_data = function (info, callback) {
callback(null, get_saved_data(info.id));
};
rt_nhentai_gallery.set_data = function (data, info, callback) {
set_saved_data(info.id, data);
callback(null);
};
rt_nhentai_gallery.setup_xhr = function (callback) {
callback(null, {
method: "GET",
url: "http://" + domains.nhentai + "/g/" + this.infos[0].gid + "/",
});
};
rt_nhentai_gallery.parse_response = function (xhr, callback) {
var html = $.html_parse_safe(xhr.responseText, null);
if (html === null) {
callback("Invalid response");
}
else {
callback(null, [ nhentai_parse_info(html, xhr.finalUrl) ]);
}
};
rt_nhentai_gallery_page_thumb.get_data = function (info, callback) {
callback(null, get_saved_thumbnail("nhentai", info.gid, info.page));
};
rt_nhentai_gallery_page_thumb.set_data = function (data, info, callback) {
set_saved_thumbnail("nhentai", info.gid, info.page, data);
callback(null);
};
rt_nhentai_gallery_page_thumb.setup_xhr = function (callback) {
var info = this.infos[0];
callback(null, {
method: "GET",
url: "http://" + domains.nhentai + "/g/" + info.gid + "/" + info.page + "/"
});
};
rt_nhentai_gallery_page_thumb.parse_response = function (xhr, callback) {
var html = $.html_parse_safe(xhr.responseText, null),
n1, url;
if (html === null) {
callback("Invalid response");
return;
}
n1 = $("#image-container img[src]", html);
if (n1 !== null) {
url = n1.getAttribute("src") || "";
url = url.replace(/\/\/i\./i, "//t.");
url = url.replace(/\.\w+$/, "t$&");
url = $.resolve(url, xhr.finalUrl);
callback(null, [{
url: url,
left: 0,
top: 0,
width: -1,
height: -1,
flags: ImageFlags.None
}]);
}
else {
callback("Thumbnail not found");
}
};
rt_hitomi_gallery.get_data = function (info, callback) {
callback(null, get_saved_data(info.id));
};
rt_hitomi_gallery.set_data = function (data, info, callback) {
set_saved_data(info.id, data);
callback(null);
};
rt_hitomi_gallery.setup_xhr = function (callback) {
callback(null, {
method: "GET",
url: "https://" + domains.hitomi + "/galleries/" + this.infos[0].gid + ".html",
});
};
rt_hitomi_gallery.parse_response = function (xhr, callback) {
var html = $.html_parse_safe(xhr.responseText, null);
if (html === null) {
callback("Invalid response");
}
else {
callback(null, [ hitomi_parse_info(html, xhr.finalUrl) ]);
}
};
rt_hitomi_gallery_page_thumb.get_data = function (info, callback) {
callback(null, get_saved_thumbnail("hitomi", info.gid, info.page));
};
rt_hitomi_gallery_page_thumb.set_data = function (data, info, callback) {
set_saved_thumbnail("hitomi", info.gid, info.page, data);
callback(null);
};
rt_hitomi_gallery_page_thumb.setup_xhr = function (callback) {
callback(null, {
method: "GET",
url: "https://" + domains.hitomi + "/reader/" + this.infos[0].gid + ".html"
});
};
rt_hitomi_gallery_page_thumb.parse_response = function (xhr, callback) {
var html = $.html_parse_safe(xhr.responseText, null),
n1, url;
if (html === null) {
callback("Invalid response");
return;
}
n1 = $$(".img-url", html);
n1 = n1[this.infos[0].page - 1];
if (n1 !== undefined) {
url = n1.textContent;
url = url.replace(/\/\/g\./i, "//tn.");
url = url.replace(/galleries/i, "smalltn");
url += ".jpg";
url = $.resolve(url, xhr.finalUrl);
callback(null, [{
url: url,
left: 0,
top: 0,
width: -1,
height: -1,
flags: ImageFlags.ThumbnailNoLeech
}]);
}
else {
callback("Thumbnail not found");
}
};
// Fjord test
var re_fjord = /abortion|bestiality|incest|lolicon|shotacon|toddlercon/;
var is_fjording = function (data) {
return re_fjord.test(data.tags.join(","));
};
var rewrite_link = function (url, info) {
var rewrite, is_ex;
if (
info.site === "ehentai" &&
((is_ex = ((rewrite = config.general.rewrite_links) === domains.exhentai)) || rewrite === domains.gehentai || rewrite === domains.ehentai) &&
info.domain !== rewrite
) {
info.domain = rewrite;
info.tag = get_tag_from_domain(is_ex ? domains.exhentai : domains.ehentai);
if (info.icon !== undefined) {
info.icon = (is_ex ? "exhentai" : "ehentai");
}
url = $.change_url_domain(url, rewrite);
url_info_saved[url.replace(re_remove_protocol, "")] = info;
}
return url;
};
var rewrite_link_smart = function (node, info, data) {
if (config.general.rewrite_links === "smart" && data.type === "ehentai") {
var url = node.href,
is_ex = ($.get_domain(url) === domains.exhentai),
fjord = is_fjording(data);
if (fjord !== is_ex) {
info.domain = fjord ? domains.exhentai : domains.ehentai;
info.tag = get_tag_from_domain(info.domain);
if (info.icon !== undefined) {
info.icon = (fjord ? "exhentai" : "ehentai");
}
url = $.change_url_domain(url, info.domain);
url_info_saved[url.replace(re_remove_protocol, "")] = info;
return url;
}
}
return null;
};
// Public
var re_remove_protocol = /^https?:\/*/i,
re_url_info = /^([\w\-]+(?:\.[\w\-]+)*)((?:[\/\?\#][\w\W]*)?)/,
url_info_saved = {},
url_info_registrations = [],
url_info_to_data_registrations = [];
var get_url_info = function (url, callback) {
var save_key = url.replace(re_remove_protocol, ""),
icon_site, match, data, domain, remaining, is_ex, m;
if ((data = url_info_saved[save_key]) !== undefined) {
callback(null, data);
return;
}
match = re_url_info.exec(save_key);
if (match === null) {
callback(null, null);
return;
}
data = null;
domain = (match[1]).toLowerCase();
remaining = match[2];
if ((is_ex = (domain === domains.exhentai)) || domain === domains.gehentai || domain === domains.ehentai) {
m = /^\/(?:g|mpv)\/(\d+)\/([0-9a-f]+)/.exec(remaining);
if (m !== null) {
data = {
id: "ehentai_" + m[1],
site: "ehentai",
type: "gallery",
gid: parseInt(m[1], 10),
token: m[2],
domain: domain,
tag: get_tag_from_domain(is_ex ? domains.exhentai : domains.ehentai)
};
m = /#page(\d+)/.exec(remaining);
if (m !== null) data.page = parseInt(m[1], 10);
icon_site = is_ex ? "exhentai" : "ehentai";
}
else if ((m = /^\/s\/([0-9a-f]+)\/(\d+)-(\d+)/.exec(remaining)) !== null) {
data = {
id: "ehentai_" + m[2],
site: "ehentai",
type: "page",
gid: parseInt(m[2], 10),
page: parseInt(m[3], 10),
page_token: m[1],
domain: domain,
tag: get_tag_from_domain(is_ex ? domains.exhentai : domains.ehentai)
};
icon_site = is_ex ? "exhentai" : "ehentai";
}
}
else if (domain === domains.nhentai) {
m = /^\/g\/(\d+)(?:\/(\d+))?/.exec(remaining);
if (m !== null) {
data = {
id: "nhentai_" + m[1],
site: "nhentai",
type: "gallery",
gid: parseInt(m[1], 10),
domain: domain,
tag: get_tag_from_domain(domain)
};
if (m[2] !== undefined) data.page = parseInt(m[2], 10);
icon_site = "nhentai";
}
}
else if (domain === domains.hitomi) {
m = /^\/(galleries|reader|smalltn)\/(\d+)(?:\.html#(\d+))?/.exec(remaining);
if (m !== null) {
data = {
id: "nhentai_" + m[2],
site: "hitomi",
type: "gallery",
gid: parseInt(m[2], 10),
domain: domain,
tag: get_tag_from_domain(domain)
};
if (m[1] === "reader" && m[3] !== undefined) data.page = parseInt(m[3], 10);
icon_site = "hitomi";
}
}
if (data !== null) {
url_info_saved[save_key] = data;
if (config.general.iconify) {
data.icon = icon_site;
}
}
else if (url_info_registrations.length > 0) {
get_url_info_custom(0, url, save_key, callback);
return;
}
callback(null, data);
};
var get_url_info_saved = function (url) {
url = url.replace(re_remove_protocol, "");
var data = url_info_saved[url];
return (data !== undefined) ? data : null;
};
var get_url_info_custom = function (i, url, save_key, callback) {
// This should avoid stack overflowing when using a callback chain with many synchronous functions
var ii = url_info_registrations.length,
immediate;
var fn_cb = function (err, data) {
if (err === null && data !== null) {
var v = url_info_saved[save_key];
if (v === undefined) {
data._custom_id = i;
url_info_saved[save_key] = data;
}
else {
data = v;
}
callback(null, data);
}
else if (immediate) {
immediate = false;
}
else {
get_url_info_custom(i + 1, url, save_key, callback);
}
};
for (; i < ii; ++i) {
immediate = true;
url_info_registrations[i](url, fn_cb);
if (immediate) {
immediate = false;
return;
}
}
callback(null, null);
};
var register_url_info_function = function (check_fn, get_data_fn) {
url_info_registrations.push(check_fn);
url_info_to_data_registrations.push(get_data_fn);
return url_info_registrations.length - 1;
};
var domain_tags = {};
domain_tags[domains.exhentai] = "Ex";
domain_tags[domains.ehentai] = "EH";
domain_tags[domains.nhentai] = "n";
domain_tags[domains.hitomi] = "Hi";
var get_tag_from_domain = function (domain) {
var tag = domain_tags[domain];
return (tag === undefined) ? "?" : tag;
};
var get_ehentai_gallery_full = function (info, data, callback) {
rt_ehentai_gallery_full.add("" + data.gid, {
domain: info.domain,
search: "",
gid: data.gid,
token: data.token,
info: info,
data: data
}, false, callback);
};
var get_ehentai_gallery_page_thumb = function (domain, gid, token, page_token, page, callback) {
rt_ehentai_gallery_page_thumb.add(gid + "-" + page, {
domain: domain,
gid: gid,
token: token,
page: page,
page_token: page_token,
search: ""
}, false, callback);
};
var get_nhentai_gallery_page_thumb = function (gid, page, callback) {
rt_nhentai_gallery_page_thumb.add(gid + "-" + page, {
gid: gid,
page: page
}, false, callback);
};
var get_hitomi_gallery_page_thumb = function (gid, page, callback) {
rt_hitomi_gallery_page_thumb.add(gid + "-" + page, {
gid: gid,
page: page
}, false, callback);
};
var get_data = function (url_info) {
return get_saved_data(url_info.id);
};
var get_data_from_url_info = function (url_info, callback) {
if (url_info.site === "ehentai") {
if (url_info.type === "gallery") {
rt_ehentai_gallery.add("" + url_info.gid, url_info, false, callback);
return;
}
if (url_info.type === "page") {
rt_ehentai_gallery_page.add("" + url_info.gid, url_info, false, function (err, data) {
if (err === null) {
url_info.token = data.token;
rt_ehentai_gallery.add("" + url_info.gid, url_info, false, callback);
}
else {
callback.call(null, err, null);
}
});
return;
}
}
else if (url_info.site === "nhentai") {
if (url_info.type === "gallery") {
rt_nhentai_gallery.add("" + url_info.gid, url_info, false, callback);
return;
}
}
else if (url_info.site === "hitomi") {
if (url_info.type === "gallery") {
rt_hitomi_gallery.add("" + url_info.gid, url_info, false, callback);
return;
}
}
// Custom
var i = url_info._custom_id,
fn;
if (typeof(i) === "number" && (fn = url_info_to_data_registrations[i]) !== undefined) {
fn(url_info, callback);
return;
}
callback.call(null, "Malformed data", null);
};
var cached_thumbnail_urls = {};
var get_thumbnail = function (thumbnail_url, flags, callback) {
var url;
if (thumbnail_url === null) {
callback.call(null, "No thumbnail", null);
}
// Use direct URL
if ((flags & ImageFlags.ThumbnailNoLeech) === 0 && !config.general.image_leeching_disabled && !Config.is_8ch) {
callback.call(null, null, thumbnail_url);
return;
}
// Cached
url = cached_thumbnail_urls[thumbnail_url];
if (url !== undefined) {
callback.call(null, null, url);
return;
}
// Fetch
get_image(thumbnail_url, function (err, data, data_length, media_type) {
if (err === null) {
var img_url = $.create_url_from_data(data.subarray(0, data_length), media_type, Config.is_8ch);
if (img_url !== null) {
cached_thumbnail_urls[thumbnail_url] = img_url;
callback.call(null, null, img_url);
}
else {
callback.call(null, "Failed to load image", null);
}
}
else {
callback.call(null, err, null);
}
});
};
var lookup_on_ehentai = function (url, md5, use_similar, callback, progress_callback) {
if (use_similar) {
// Fast mode
var sha1 = get_sha1_hash(url, md5);
var get_image_callback = function (err, data, data_length, mime_type, url) {
if (progress_callback !== undefined) {
progress_callback.call(null, "image");
}
if (err === null) {
var blob = new Blob([ data.subarray(0, data_length) ], { type: mime_type });
rt_ehentai_lookup.add(url, {
similar: true,
blob: blob,
url: url,
md5: md5,
sha1: sha1
}, false, callback, progress_callback);
}
else {
callback.call(null, err, null);
}
};
if (sha1 !== null) {
rt_ehentai_lookup.add(url, {
similar: true,
blob: null,
url: url,
md5: md5,
sha1: sha1
}, true, function (err, results) {
if (err === null) {
// Already exists
callback.call(null, null, results);
}
else {
// Load image
get_image(url, get_image_callback, progress_callback);
}
});
}
else {
// Load image
get_image(url, get_image_callback, progress_callback);
}
}
else {
get_sha1_hash(url, md5, function (err, sha1) {
if (progress_callback !== undefined) {
progress_callback.call(null, "image");
}
if (err === null) {
rt_ehentai_lookup.add(url, {
similar: false,
blob: null,
url: url,
md5: md5,
sha1: sha1
}, false, callback, progress_callback);
}
else {
callback.call(null, err, null);
}
});
}
};
var init = function () {
// Clean
cache_init();
};
// Exports
return {
ImageFlags: ImageFlags,
RequestType: RequestType,
RequestErrorMode: RequestErrorMode,
create_temp_storage: create_temp_storage,
get_url_info: get_url_info,
get_url_info_saved: get_url_info_saved,
get_ehentai_gallery_full: get_ehentai_gallery_full,
get_ehentai_gallery_page_thumb: get_ehentai_gallery_page_thumb,
get_nhentai_gallery_page_thumb: get_nhentai_gallery_page_thumb,
get_hitomi_gallery_page_thumb: get_hitomi_gallery_page_thumb,
get_data: get_data,
get_data_from_url_info: get_data_from_url_info,
get_thumbnail: get_thumbnail,
lookup_on_ehentai: lookup_on_ehentai,
cache_clear: cache_clear,
cache_get_prefix: cache_get_prefix,
get_category: get_category,
get_category_sort_rank: get_category_sort_rank,
rewrite_link: rewrite_link,
rewrite_link_smart: rewrite_link_smart,
register_url_info_function: register_url_info_function,
init: init
};
})();
var SHA1 = (function () {
// SHA-1 JS implementation originally created by Chris Verness; http://movable-type.co.uk/scripts/sha1.html
// Private
var f = function (s, x, y, z) {
switch (s) {
case 0: return (x & y) ^ (~x & z);
case 1: return x ^ y ^ z;
case 2: return (x & y) ^ (x & z) ^ (y & z);
case 3: return x ^ y ^ z;
}
};
var rotl = function (x, n) {
return (x << n) | (x >>> (32 - n));
};
var hex = function (str) {
var s = "",
v, i;
for (i = 7; i >= 0; --i) {
v = (str >>> (i * 4)) & 0xf;
s += v.toString(16);
}
return s;
};
// Public
var hash = function (data, data_length) {
var H0, H1, H2, H3, H4, K, M, N, W, T,
a, b, c, d, e, i, j, l, s;
K = new Uint32Array([ 0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 ]);
data[data_length] = 0x80; // this is valid because the typed array always contains 1 extra padding byte
l = data.length / 4 + 2;
N = Math.ceil(l / 16);
M = [];
for (i = 0; i < N; ++i) {
M[i] = [];
for (j = 0; j < 16; ++j) {
M[i][j] =
(data[i * 64 + j * 4] << 24) |
(data[i * 64 + j * 4 + 1] << 16) |
(data[i * 64 + j * 4 + 2] << 8) |
(data[i * 64 + j * 4 + 3]);
}
}
M[N - 1][14] = Math.floor(((data.length - 1) * 8) / Math.pow(2, 32));
M[N - 1][15] = ((data.length - 1) * 8) & 0xffffffff;
H0 = 0x67452301;
H1 = 0xefcdab89;
H2 = 0x98badcfe;
H3 = 0x10325476;
H4 = 0xc3d2e1f0;
W = [];
for (i = 0; i < N; ++i) {
for (j = 0; j < 16; ++j) {
W[j] = M[i][j];
}
for (j = 16; j < 80; ++j) {
W[j] = rotl(W[j - 3] ^ W[j - 8] ^ W[j - 14] ^ W[j - 16], 1);
}
a = H0;
b = H1;
c = H2;
d = H3;
e = H4;
for (j = 0; j < 80; ++j) {
s = Math.floor(j / 20);
T = (rotl(a, 5) + f(s, b, c, d) + e + K[s] + W[j]) & 0xffffffff;
e = d;
d = c;
c = rotl(b, 30);
b = a;
a = T;
}
H0 = (H0 + a) & 0xffffffff;
H1 = (H1 + b) & 0xffffffff;
H2 = (H2 + c) & 0xffffffff;
H3 = (H3 + d) & 0xffffffff;
H4 = (H4 + e) & 0xffffffff;
}
return hex(H0) + hex(H1) + hex(H2) + hex(H3) + hex(H4);
};
// Exports
return {
hash: hash
};
})();
var Sauce = (function () {
// Private
var hover_nodes = {},
hover_nodes_id = 0;
var get_exresults_from_exsauce = function (node) {
var container = Post.get_post_container(node);
if (
container !== null &&
(node = $(".xl-exsauce-results[data-xl-image-index='" + node.getAttribute("data-xl-image-index") + "']", container)) !== null &&
Post.get_post_container(node) === container
) {
return node;
}
return null;
};
var on_sauce_click = function (event) {
event.preventDefault();
var results = get_exresults_from_exsauce(this),
hover;
if (results !== null) {
hover = hover_nodes[this.getAttribute("data-xl-sauce-hover-id") || ""];
if (hover === undefined) return;
if (results.classList.toggle("xl-exsauce-results-hidden")) {
hover.classList.remove("xl-exsauce-hover-hidden");
on_sauce_mousemove.call(this, event);
}
else {
hover.classList.add("xl-exsauce-hover-hidden");
}
}
};
var on_sauce_click_error = function (event) {
event.preventDefault();
var link = this,
events = this.getAttribute("data-xl-exsauce-events") || null;
if (events === null) return;
Linkifier.change_link_events(link, null);
setTimeout(function () {
Linkifier.change_link_events(link, events);
link.click();
}, 1);
};
var on_sauce_mouseover = $.wrap_mouseenterleave_event(function () {
var results = get_exresults_from_exsauce(this),
hover, err;
if (results === null || results.classList.contains("xl-exsauce-results-hidden")) {
hover = hover_nodes[this.getAttribute("data-xl-sauce-hover-id")];
if (hover === undefined) {
err = this.getAttribute("data-xl-exsauce-error");
if (!err) return;
this.removeAttribute("data-xl-exsauce-error");
hover = create_error(this, err);
}
hover.classList.remove("xl-exsauce-hover-hidden");
}
});
var on_sauce_mouseout = $.wrap_mouseenterleave_event(function () {
var hover = hover_nodes[this.getAttribute("data-xl-sauce-hover-id") || ""];
if (hover !== undefined) {
hover.classList.add("xl-exsauce-hover-hidden");
}
});
var on_sauce_mousemove = function (event) {
var hover = hover_nodes[this.getAttribute("data-xl-sauce-hover-id") || ""];
if (hover === undefined || hover.classList.contains("xl-exsauce-hover-hidden")) return;
hover.style.left = "0";
hover.style.top = "0";
var x = event.clientX,
y = event.clientY,
win_width = (document_element.clientWidth || window.innerWidth || 0),
win_height = (document_element.clientHeight || window.innerHeight || 0),
rect = hover.getBoundingClientRect();
x -= rect.width / 2;
x = Math.max(1, Math.min(win_width - rect.width - 1, x));
y += 20;
if (y + rect.height >= win_height) {
y = event.clientY - (rect.height + 20);
}
hover.style.left = x + "px";
hover.style.top = y + "px";
};
var create_hover = function (id, data) {
var results = data.results,
hover, i, ii;
hover = $.node("div", "xl-exsauce-hover xl-exsauce-hover-hidden xl-hover-shadow" + Theme.classes);
Theme.bg(hover);
hover.setAttribute("data-xl-sauce-hover-id", id);
for (i = 0, ii = results.length; i < ii; ) {
$.add(hover, $.link(results[i].url, "xl-exsauce-hover-link", results[i].title));
if (++i < ii) $.add(hover, $.node_simple("br"));
}
Popup.hovering(hover);
hover_nodes[id] = hover;
return hover;
};
var format = function (a, data) {
var count = data.results.length,
theme = Theme.classes,
index = a.getAttribute("data-xl-image-index") || "",
results, link, par, url, n1, n2, n, i, ii;
a.classList.add("xl-exsauce-link-valid");
a.textContent = "Found: " + count;
a.href = data.url;
a.target = "_blank";
a.rel = "noreferrer";
a.removeAttribute("title");
if (count > 0) {
if (
(n = Post.get_post_container(a)) !== null &&
(n = Post.get_text_body(n)) !== null &&
(par = n.parentNode) !== null
) {
results = $.node("div", "xl-exsauce-results" + theme);
results.setAttribute("data-xl-image-index", index);
$.add(results, n1 = $.node("div", "xl-exsauce-results-inner" + theme));
$.add(n1, n2 = $.node("div", "xl-exsauce-results-group" + theme));
$.add(n2, $.node("strong", "xl-exsauce-results-title", "Reverse Image Search Results"));
$.add(n2, $.node("span", "xl-exsauce-results-sep", "|"));
$.add(n2, $.node("span", "xl-exsauce-results-label", "View on:"));
if (config.sauce.lookup_domain === domains.exhentai) {
$.add(n2, $.link(data.url, "xl-exsauce-results-link", "ExHentai"));
$.add(n2, $.link($.change_url_domain(data.url, domains.gehentai), "xl-exsauce-results-link", "E-Hentai"));
}
else {
$.add(n2,$.link(data.url, "xl-exsauce-results-link", "E-Hentai"));
$.add(n2, $.link($.change_url_domain(data.url, domains.exhentai), "xl-exsauce-results-link", "ExHentai"));
}
$.add(n1, n2 = $.node("div", "xl-exsauce-results-group"));
for (i = 0, ii = data.results.length; i < ii; ++i) {
url = data.results[i].url;
link = Linkifier.create_link(n2, null, url, url, true);
if (i < ii - 1) $.add(n2, $.node_simple("br"));
}
$.before(par, n, results);
}
a.setAttribute("data-xl-sauce-hover-id", hover_nodes_id);
create_hover(hover_nodes_id, data);
++hover_nodes_id;
Linkifier.change_link_events(a, "exsauce_toggle");
}
};
var label = function () {
var label = config.sauce.label;
if (label.length === 0) {
label = (config.sauce.lookup_domain === domains.exhentai) ? "ExHentai" : "E-Hentai";
}
return label;
};
var create_error = function (node, error) {
var id = hover_nodes_id,
hover, n, i, ii;
// Update id
++hover_nodes_id;
// Create hover
hover = $.node("div", "xl-exsauce-hover xl-exsauce-hover-hidden xl-hover-shadow" + Theme.classes);
Theme.bg(hover);
$.add(hover, n = $.node("span", "xl-exsauce-hover-link"));
error = error.trim().split("\n");
for (i = 0, ii = error.length; i < ii; ++i) {
if (i > 0) $.add(n, $.node_simple("br"));
$.add(n, $.tnode(error[i]));
}
// Ids
hover.setAttribute("data-xl-sauce-hover-id", id);
node.setAttribute("data-xl-sauce-hover-id", id);
Popup.hovering(hover);
hover_nodes[id] = hover;
// Done
return hover;
};
var set_error = function (node, error) {
// Create hover
create_error(node, error);
// Link
node.classList.add("xl-exsauce-link-error");
node.textContent = "Error";
node.removeAttribute("title");
// Events
Linkifier.change_link_events(node, "exsauce_error");
};
var remove_error = function (node) {
var events = Linkifier.get_link_events(node),
id = node.getAttribute("data-xl-sauce-hover-id"),
hover = hover_nodes[id];
Linkifier.change_link_events(node, null);
node.classList.remove("xl-exsauce-link-error");
node.setAttribute("data-xl-exsauce-events", events);
node.removeAttribute("data-xl-sauce-hover-id");
if (hover !== undefined) {
if (hover.parentNode !== null) $.remove(hover);
delete hover_nodes[id];
}
};
var fetch_generic = function (link, use_similar) {
var url = link.href,
md5 = link.getAttribute("data-md5") || null,
progress;
if (use_similar) {
link.textContent = "Downloading";
progress = function (state) {
if (state === "image") {
link.textContent = "Waiting";
}
else if (state === "upload") {
link.textContent = "Uploading";
}
else if (state === "download") {
link.textContent = "Checking";
}
};
}
else {
link.textContent = "Downloading";
progress = function (state) {
if (state === "image") {
link.textContent = "Waiting";
}
else if (state === "upload") {
link.textContent = "Checking";
}
};
}
remove_error(link);
API.lookup_on_ehentai(url, md5, use_similar, function (err, data) {
if (err === null) {
format(link, data);
}
else {
set_error(link, err);
}
}, progress);
};
var fetch = function (event) {
event.preventDefault();
fetch_generic(this, false);
};
var fetch_similar = function (event) {
event.preventDefault();
fetch_generic(this, true);
};
// Public
var find_link = function (container) {
return $(".xl-exsauce-link", container);
};
var create_link = function (file_info, index) {
var event = "exsauce_fetch",
sauce, err;
sauce = $.link(file_info.url, "xl-exsauce-link", label());
sauce.setAttribute("data-xl-filename", file_info.name);
sauce.setAttribute("data-xl-image-index", index);
if (file_info.md5 !== null) {
sauce.setAttribute("data-md5", file_info.md5.replace(/\=+/g, ""));
}
if (/^\.jpe?g$/i.test(file_info.type) && !Config.is_tinyboard) {
if (browser.is_firefox) {
event = "exsauce_fetch_similarity";
sauce.title = "This will only work on colored images";
}
else {
err = "Reverse Image Search doesn't work for .jpg images because 4chan manipulates them on upload";
event = "exsauce_error";
sauce.classList.add("xl-exsauce-link-disabled");
sauce.setAttribute("data-xl-exsauce-error", err);
}
}
Linkifier.change_link_events(sauce, event);
return sauce;
};
var init = function () {
Linkifier.register_link_events({
exsauce_fetch: fetch,
exsauce_fetch_similarity: fetch_similar,
exsauce_toggle: {
click: on_sauce_click,
mouseover: on_sauce_mouseover,
mouseout: on_sauce_mouseout,
mousemove: on_sauce_mousemove
},
exsauce_error: {
click: on_sauce_click_error,
mouseover: on_sauce_mouseover,
mouseout: on_sauce_mouseout,
mousemove: on_sauce_mousemove
},
});
};
// Exports
return {
find_link: find_link,
create_link: create_link,
init: init
};
})();
var Linkifier = (function () {
// Private
var re_url = /(https?:\/*)?(?:(?:forums|lofi|gu|g|u)?\.?e[x\-]hentai\.org|nhentai\.net|hitomi\.la)(?:\/[^<>()\s\'\"]*)?/ig,
re_url_class_ignore = /(?:\binlined?\b|\bxl-)/,
re_4chan_deferrer = /^(?:https?:)?\/\/sys\.4chan(?:nel)?\.org\/derefer\?url=([\w\W]*)$/i;
// Linkification
var deep_dom_wrap = (function () {
// Constants
var NODE_PARSE = 0,
NODE_NO_PARSE = 1,
NODE_LINE_BREAK = 2,
TEXT_NODE = Node.TEXT_NODE,
ELEMENT_NODE = Node.ELEMENT_NODE;
// Main function
var deep_dom_wrap = function (container, match_fn, element_fn, setup_fn, quick) {
var offsets = [],
text = textify_node(container, offsets, element_fn),
count = 0,
match_pos = 0,
match;
if (text.length > 0) {
if (quick) {
// Quick mode: just find all the matches
while ((match = match_fn(text, match_pos)) !== null) {
++count;
match_pos = match[1];
}
}
else {
// Loop to find all matches
while ((match = match_fn(text, match_pos)) !== null) {
++count;
match_pos = match[1];
replace_match(match, text, offsets, setup_fn);
}
}
}
// Done
return count;
};
var textify_node = function (container, offsets, element_fn) {
// Create a string of the container's contents (similar to but not exactly the same as node.textContent)
// Also lists all text nodes into the offsets array
var par = container,
node = container.firstChild,
text = "",
check;
while (true) {
if (node === null) {
// Done?
if (par === container) break;
// Move up tree
node = par;
par = node.parentNode;
node = node.nextSibling;
}
else if (node.nodeType === TEXT_NODE) {
// Add to list and text
offsets.push({
node: node,
start: text.length,
length: node.nodeValue.length
});
text += node.nodeValue;
node = node.nextSibling;
}
else if (node.nodeType === ELEMENT_NODE) {
// Check element
check = element_fn(node);
// Line break
if ((check & NODE_LINE_BREAK) !== 0) {
text += "\n";
}
// Parse
if ((check & NODE_NO_PARSE) === 0) {
// Child
par = node;
node = par.firstChild;
}
else {
// Next
node = node.nextSibling;
}
}
else { // Some other type of node
// Next
node = node.nextSibling;
}
}
return text;
};
var replace_match = function (match, text, offsets, setup_fn) {
var d = document,
start = match[0],
end = match[1],
tag = match[2],
offset_count = offsets.length,
prefix, suffix, len,
node, par, next, clone,
wrapper, wrapper_node, relative_node, relative_par,
o_start, o_end, offset_start, offset_end, offset_current;
// Find the beginning and ending text nodes
for (o_start = 1; o_start < offset_count; ++o_start) {
if (offsets[o_start].start > start) break;
}
for (o_end = o_start; o_end < offset_count; ++o_end) {
if (offsets[o_end].start > end) break;
}
--o_start;
--o_end;
offset_start = offsets[o_start];
offset_end = offsets[o_end];
// Vars to create the link
prefix = text.slice(offset_start.start, start);
suffix = text.slice(end, offset_end.start + offset_end.length);
wrapper = d.createDocumentFragment();
wrapper_node = wrapper;
relative_node = null;
// Prefix update
offset_current = offsets[o_start];
node = offset_current.node;
len = prefix.length;
if (len > 0) {
// Insert prefix
next = d.createTextNode(prefix);
node.parentNode.insertBefore(next, node);
// Update text
node.nodeValue = node.nodeValue.substr(len);
// Set first relative
relative_node = next;
relative_par = next.parentNode;
// Update offset for next search
offset_current.start += len;
offset_current.length -= len;
}
else {
// Set first relative
relative_node = node.previousSibling;
relative_par = node.parentNode;
}
// Loop over ELEMENT_NODEs; add TEXT_NODEs to the link, remove empty nodes where necessary
for (; o_start < o_end; ++o_start) {
// Next
node = offsets[o_start].node;
next = node.nextSibling;
par = node.parentNode;
// Add text
wrapper_node.appendChild(node);
// Node loop
while (true) {
if (next === null) {
// Move up tree
node = par;
next = node.nextSibling;
par = node.parentNode;
if (node.firstChild === null) par.removeChild(node);
// Update link node
if (wrapper_node !== wrapper) {
// Simply move up tree (wrapper_node still has a parent)
wrapper_node = wrapper_node.parentNode;
}
else {
// Create a new wrapper node (wrapper_node has no parent; it's the wrapper)
clone = node.cloneNode(false);
clone.appendChild(wrapper); // wrapper is a DocumentFragment
wrapper.appendChild(clone);
// Placement relatives
relative_node = (next !== null) ? next.previousSibling : null;
relative_par = par;
}
}
else if (next.nodeType === TEXT_NODE) {
// Done
break;
}
else if (next.nodeType === ELEMENT_NODE) {
// Deeper
node = next;
next = node.firstChild;
par = node;
// Update link node
clone = node.cloneNode(false);
wrapper_node.appendChild(clone);
wrapper_node = clone;
}
else {
// Some other node type; continue anyway
node = next;
next = node.nextSibling;
// Update link node
wrapper_node.appendChild(node);
}
}
}
// Suffix update
offset_current = offsets[o_start];
node = offset_current.node;
par = node.parentNode;
len = suffix.length;
if (len > 0) {
// Insert suffix
next = d.createTextNode(suffix);
par.insertBefore(next, node.nextSibling);
// Update text
len = node.nodeValue.length - len;
node.nodeValue = node.nodeValue.substr(0, len);
// Update offset for next search
offset_current.text_length += len;
offset_current.length -= len;
offset_current.node = next;
}
// Add the last segment
wrapper_node.appendChild(node);
// Setup function
if (tag !== null) {
node = wrapper;
wrapper = d.createElement(tag);
wrapper.appendChild(node);
}
if (setup_fn !== null) setup_fn(wrapper, match);
// Find the proper relative node
relative_node = (relative_node !== null) ? relative_node.nextSibling : relative_par.firstChild;
// Insert link
relative_par.insertBefore(wrapper, relative_node);
// Remove empty suffix tags
while (par.firstChild === null) {
node = par;
par = par.parentNode;
par.removeChild(node);
}
// Update match position
offset_end.start = end;
};
// Exports
deep_dom_wrap.NODE_PARSE = NODE_PARSE;
deep_dom_wrap.NODE_NO_PARSE = NODE_NO_PARSE;
deep_dom_wrap.NODE_LINE_BREAK = NODE_LINE_BREAK;
return deep_dom_wrap;
})();
var linkify_groups = [{
regex: re_url,
prefix_group: 1,
prefix: "http://",
prefix_replace: [ /^\/+/, "" ],
tag: "a",
match: null
}];
var linkify = function (container, result_nodes, result_urls) {
var match_fn, node_setup;
if (linkify_groups.length === 1) {
// Normal
match_fn = function (text, pos) {
re_url.lastIndex = pos;
var m = re_url.exec(text);
if (m === null) return null;
return [ m.index , m.index + m[0].length, "a", m ];
};
node_setup = function (node, match) {
var url = match[3][0];
if (match[3][1] === undefined) url = "http://" + url.replace(/^\/+/, "");
result_nodes.push(node);
result_urls.push(url);
};
}
else {
// Multiple
match_fn = function (text, pos) {
var res = null,
group, i, ii, m;
for (i = 0, ii = linkify_groups.length; i < ii; ++i) {
group = linkify_groups[i];
if ((m = group.match) === null || m.index < pos) {
group.regex.lastIndex = pos;
group.match = m = group.regex.exec(text);
}
if (m !== null && (res === null || res[0] > m.index)) {
res = [ m.index , m.index + m[0].length, group.tag, m, group ];
}
}
return res;
};
node_setup = function (node, match) {
var url = match[3][0],
group = match[4],
re;
if (match[3][group.prefix_group] === undefined) {
if ((re = group.prefix_replace) !== null) {
url = url.replace(re[0], re[1]);
}
url = group.prefix + url;
}
result_nodes.push(node);
result_urls.push(url);
};
}
deep_dom_wrap(container, match_fn, linkify_element_checker, node_setup, false);
};
var linkify_element_checker = function (node) {
if (node.tagName === "BR" || node.tagName === "A") {
return deep_dom_wrap.NODE_NO_PARSE | deep_dom_wrap.NODE_LINE_BREAK;
}
else if (node.tagName === "WBR") {
return deep_dom_wrap.NODE_NO_PARSE;
}
else if (node.tagName === "DIV") {
if (re_url_class_ignore.test(node.className)) {
return deep_dom_wrap.NODE_NO_PARSE | deep_dom_wrap.NODE_LINE_BREAK;
}
return deep_dom_wrap.NODE_LINE_BREAK;
}
return deep_dom_wrap.NODE_PARSE;
};
var linkify_test = function (text) {
var group, re, i, ii, m;
for (i = 0, ii = linkify_groups.length; i < ii; ++i) {
group = linkify_groups[i];
re = group.regex;
re.lastIndex = 0;
if ((m = re.exec(text)) !== null) {
if (m[group.prefix_group] === undefined) {
if ((re = group.prefix_replace) !== null) {
text = text.replace(re[0], re[1]);
}
text = group.prefix + text;
}
return text;
}
}
return null;
};
var linkify_register = function (regex, prefix_group, prefix, prefix_replace_regex, prefix_replace_with) {
var prefix_replace = null;
if (prefix_replace_regex !== null && typeof(prefix_replace_with) === "string") {
prefix_replace = [ prefix_replace_regex, prefix_replace_with ];
}
linkify_groups.push({
regex: regex,
prefix_group: prefix_group,
prefix: prefix,
prefix_replace: prefix_replace,
tag: "a",
match: null
});
};
var parse_text_for_urls = function (text) {
var urls = [],
m;
re_url.lastIndex = 0;
while ((m = re_url.exec(text)) !== null) {
urls.push(m[0]);
}
return urls;
};
// Link creation and processing
var create_link = function (parent, next, url, text, auto_process) {
var link = $.link(url, "xl-link-created", text);
$.before(parent, next, link);
preprocess_link(link, url, false, auto_process);
return link;
};
var preprocess_link = function (node, url, update_on_fail, auto_load) {
if (!first_link_preprocessed) {
first_link_preprocessed = true;
trigger(event_listeners.before_first_link_preprocess, null);
}
API.get_url_info(url, function (err, info) {
var modify_link = true;
if (node.parentNode === null || node.classList.contains("xl-linkified")) return;
if (err !== null || info === null || (config.sites[info.site] === false || Config.get_custom("sites", info.site) === false)) {
if (update_on_fail) {
node.href = url;
node.target = "_blank";
node.rel = "noreferrer";
node.classList.add("xl-linkified");
}
return;
}
if (modify_link) {
url = API.rewrite_link(url, info);
node.href = url;
node.target = "_blank";
node.rel = "noreferrer";
}
node.classList.add("xl-link");
node.classList.add("xl-linkified");
UI.setup_link(node, url, info, auto_load, modify_link);
});
};
// Post queue
var post_queue = {
posts: [],
timer: null,
group_size: 25,
delay: 50
};
var queue_posts = function (posts, flags) {
if ((flags & queue_posts.Flags.Flush) !== 0) {
// Flush
if ((flags & queue_posts.Flags.FlushNoParse) === 0) {
parse_posts(post_queue.posts);
}
post_queue.posts = [];
// Clear timer
if (post_queue.timer !== null) {
clearTimeout(post_queue.timer);
post_queue.timer = null;
}
}
if ((flags & queue_posts.Flags.UseDelay) === 0) {
// Immediate
parse_posts(posts);
}
else {
// Queue
$.push_many(post_queue.posts, posts);
// Run queue
if (post_queue.timer === null) {
dequeue_posts();
}
}
};
queue_posts.Flags = {
None: 0x0,
UseDelay: 0x1,
Flush: 0x2,
FlushNoParse: 0x4
};
var dequeue_posts = function () {
var posts = post_queue.posts.splice(0, post_queue.group_size);
if (posts.length === 0) {
// Done
post_queue.timer = null;
}
else {
// Run
parse_posts(posts);
// Timer for next
post_queue.timer = setTimeout(dequeue_posts, post_queue.delay);
}
};
var setup_post_exsauce = function (post) {
var index = 0,
file_infos, file_info, node, i, ii;
// File info
file_infos = Post.get_file_info(post);
for (i = 0, ii = file_infos.length; i < ii; ++i) {
file_info = file_infos[i];
// Create if not found
node = Sauce.find_link(file_info.options);
if (node !== null) $.remove(node);
if (/^\.(png|gif|jpe?g)$/i.test(file_info.type)) {
node = Sauce.create_link(file_info, index);
Post.create_image_meta_link(file_info, node);
++index;
}
}
};
var parse_post = function (post) {
var auto_load_links = config.general.automatic_processing,
post_body, post_links, link_nodes, link_urls, link, url, valid, i, ii;
// Exsauce
if (config.sauce.enabled && !browser.is_opera) {
setup_post_exsauce(post);
}
// Linkify
if ((post_body = Post.get_text_body(post)) !== null) {
link_nodes = [];
link_urls = [];
// Existing links
post_links = Post.get_body_links(post_body);
for (i = 0, ii = post_links.length; i < ii; ++i) {
link = post_links[i];
if (link.classList.contains("xl-site-tag")) {
$.remove(link);
}
else {
if (Config.is_4chan) {
valid = (!link.classList.contains("embedder") && link.getAttribute("data-cmd") !== "embed");
}
else {
valid = true;
}
if (valid) {
url = link.href;
if (Config.is_4chan && link.classList.contains("linkified") && re_4chan_deferrer.test(url)) {
url = link.textContent.trim();
}
url = linkify_test(url);
if (url !== null) {
link_nodes.push(link);
link_urls.push(url);
}
}
}
}
// Linkify links
ii = link_nodes.length;
linkify(post_body, link_nodes, link_urls);
// Process
for (i = 0; i < ii; ++i) {
preprocess_link(link_nodes[i], link_urls[i], false, auto_load_links);
}
for (ii = link_nodes.length; i < ii; ++i) {
preprocess_link(link_nodes[i], link_urls[i], true, auto_load_links);
}
// Mark
post.classList.add("xl-post-linkified");
}
};
var parse_posts = function (posts) {
var post, i, ii;
Debug.timer("process");
for (i = 0, ii = posts.length; i < ii; ++i) {
post = posts[i];
if (post.classList.contains("xl-post-linkified")) {
UI.cleanup_post(post);
apply_link_events(post, true);
}
else {
unlinkify_post(post);
parse_post(post);
}
}
Debug.log("Total posts=" + posts.length + "; time=" + Debug.timer("process"));
};
// Link events
var link_events = {};
var register_link_events = function (events) {
var count = 0,
k;
for (k in events) {
if (!Object.prototype.hasOwnProperty.call(link_events, k)) {
link_events[k] = events[k];
++count;
}
}
return count;
};
var get_link_events = function (node) {
return node.getAttribute("data-xl-link-events") || null;
};
var set_link_events = function (node, new_events) {
var events = link_events[new_events],
k;
if (events) {
if (typeof(events) === "function") {
$.on(node, "click", events);
}
else {
// Array
for (k in events) {
$.on(node, k, events[k]);
}
}
}
};
var apply_link_events = function (node, check_children) {
var nodes = check_children ? $$("a.xl-link-events", node) : [ node ],
events, i, ii;
for (i = 0, ii = nodes.length; i < ii; ++i) {
node = nodes[i];
events = node.getAttribute("data-xl-link-events");
set_link_events(node, events);
}
};
var change_link_events = function (node, new_events) {
var old_events = node.getAttribute("data-xl-link-events"),
events, k;
events = link_events[old_events];
if (events) {
if (typeof(events) === "function") {
$.off(node, "click", events);
}
else {
// Array
for (k in events) {
$.off(node, k, events[k]);
}
}
}
if (new_events === null) {
node.classList.remove("xl-link-events");
node.removeAttribute("data-xl-link-events");
}
else {
node.classList.add("xl-link-events");
node.setAttribute("data-xl-link-events", new_events);
set_link_events(node, new_events);
}
};
// Fixing
var fix_inline_linkified_link = function (node) {
var removals, i, ii, n;
if (
(node = node.nextSibling) !== null &&
node.tagName === "A" &&
node.classList.contains("linkified")
) {
removals = [];
n = node;
while ((n = n.nextSibling) !== null) {
if (n.nodeType === Node.TEXT_NODE) {
removals.push(n);
}
else {
if (n.tagName === "SPAN" && n.classList.contains("xl-link-inner")) {
$.remove(node);
for (i = 0, ii = removals.length; i < ii; ++i) $.remove(removals[i]);
$.remove(n);
}
break;
}
}
}
};
var unlinkify_post = function (post) {
var nodes, i, ii;
nodes = $$(".xl-site-tag", post);
for (i = 0, ii = nodes.length; i < ii; ++i) {
$.remove(nodes[i]);
}
nodes = $$(".xl-link-events", post);
for (i = 0, ii = nodes.length; i < ii; ++i) {
change_link_events(nodes[i], null);
}
nodes = $$(".xl-linkified", post);
for (i = 0, ii = nodes.length; i < ii; ++i) {
nodes[i].classList.remove("xl-linkified");
fix_inline_linkified_link(nodes[i]);
}
};
var relinkify_posts = function (posts) {
var cls = "xl-post-linkified",
post, i, ii;
for (i = 0, ii = posts.length; i < ii; ++i) {
post = posts[i];
if (post.classList.contains(cls)) {
post.classList.remove(cls);
unlinkify_post(post);
}
}
queue_posts(posts, queue_posts.Flags.Flush | queue_posts.Flags.FlushNoParse | queue_posts.Flags.UseDelay);
};
var fix_broken_4chanx_linkification = function (node, event_links) {
// Somehow one of the links gets cloned, and then they all get wrapped inside another link
var fix = [],
n = node.nextSibling,
link, events, i, ii;
if (n !== null && n.tagName === "A" && n.classList.contains("xl-link")) {
$.remove(n);
}
n = node.previousSibling;
if (n !== null && n.tagName === "A" && n.classList.contains("xl-site-tag")) {
$.remove(n);
}
for (i = 0, ii = event_links.length; i < ii; ++i) {
link = event_links[i];
events = get_link_events(link);
change_link_events(link, null);
if (link.classList.contains("xl-site-tag")) {
$.remove(link);
}
else if (link.classList.contains("xl-link")) {
fix.push(link, events);
}
}
$.unwrap(node);
for (i = 0, ii = fix.length; i < ii; i += 2) {
link = fix[i];
link.classList.remove("xl-linkified");
preprocess_link(link, link.href || "", false, config.general.automatic_processing);
}
};
// Events
var first_link_preprocessed = false;
var event_listeners = {
before_first_link_preprocess: []
};
var on = function (event_name, callback) {
var listeners = event_listeners[event_name];
if (listeners === undefined) return false;
listeners.push(callback);
return true;
};
var off = function (event_name, callback) {
var listeners = event_listeners[event_name],
i, ii;
if (listeners !== undefined) {
for (i = 0, ii = listeners.length; i < ii; ++i) {
if (listeners[i] === callback) {
listeners.splice(i, 1);
return true;
}
}
}
return false;
};
var trigger = function (listeners, data) {
var i, ii;
for (i = 0, ii = listeners.length; i < ii; ++i) {
listeners[i].call(null, data);
}
};
// Exports
return {
parse_text_for_urls: parse_text_for_urls,
create_link: create_link,
queue_posts: queue_posts,
get_link_events: get_link_events,
change_link_events: change_link_events,
register_link_events: register_link_events,
relinkify_posts: relinkify_posts,
fix_broken_4chanx_linkification: fix_broken_4chanx_linkification,
linkify_register: linkify_register,
on: on,
off: off
};
})();
var Settings = (function () {
// Private
var config_temp = null,
config_custom_temp = null,
config_exts_temp = null,
export_url = null,
popup = null;
var html_filter_guide = function () {
return "
Lines starting with / will be treated as regular expressions. (This is very similar to 4chan-x style filtering) Lines starting with # are comments and will be ignored. Lines starting with neither # nor / will be treated as a case-insensitive string to match anywhere. For example, /touhou/i will highlight entries containing the string `touhou`, case-insensitive.
The lower a filter appears in this list, the greater its priority will be.
You can use these additional settings with each regular expression, separating them with semicolons:
Apply the filter to different scopes: tags;, title; or uploader;. By default the scope is title;tags;
Force a gallery to not be highlighted:If omitted, the gallery will be highlighted as normal bad:no;, bad:yes;, or just bad;
Only apply the filter to certain categories: only:doujinshi,manga;.