NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name Masterani+ // @namespace https://github.com/Juici/masterani-plus // @version 0.4.1 // @author Juici // @description Enhancements and additions to Masterani // @copyright 2018, Juici (https://github.com/Juici/masterani-plus) // @license MIT; https://github.com/Juici/masterani-plus/raw/master/LICENSE // @homepageURL https://github.com/Juici/masterani-plus // @updateURL https://github.com/Juici/masterani-plus/raw/master/dist/ma-plus.meta.js // @downloadURL https://github.com/Juici/masterani-plus/raw/master/dist/ma-plus.user.js // @supportURL https://github.com/Juici/masterani-plus // @include /^https?:\/\/www\.masterani\.me\/.*/ // @connect api.jikan.moe // @connect myanimelist.net // @run-at document-start // @grant GM_xmlhttpRequest // @grant GM_deleteValue // @grant GM_getValue // @grant GM_setValue // ==/UserScript== (function () { function outer(modules, cache, entry) { const previousRequire = typeof require === 'function' && require; function newRequire(name, jumped) { if (!cache[name]) { if (!modules[name]) { const currentRequire = typeof require === 'function' && require; if (!jumped && currentRequire) { return currentRequire(name, true); } if (previousRequire) { return previousRequire(name, true); } const err = new Error('Cannot find module \'' + name + '\''); err.code = 'MODULE_NOT_FOUND'; throw err; } const m = cache[name] = { exports: {} }; modules[name][0].call(m.exports, function (x) { const id = modules[name][1][x]; return newRequire(id ? id : x); }, m, m.exports, outer, modules, cache, entry); } return cache[name].exports; } for (let i = 0; i < entry.length; i++) { newRequire(entry[i]); } return newRequire; } return outer; })()({ 1: [function (require, module, exports) { const { matches } = require('./info'); if (!matches) { return; } require('./mal-link'); }, { "./info": 2, "./mal-link": 3 }], 2: [function (require, module, exports) { const regex = /^https?:\/\/www\.masterani\.me\/anime\/info\/((\d+)-.*)[?#]?/i; const info = {}; const matches = regex.exec(window.location.href); if (matches) { info.matches = true; const anime = {}; anime.id = matches[2]; anime.slug = matches[1]; let title = document.querySelector('meta[property="og:title"]'); anime.title = title ? title.content.replace(' - Masterani', '') : ''; let description = document.querySelector('meta[property="og:title"]'); anime.description = description ? description.context : ''; info.anime = anime; } else { info.matches = false; } module.exports = info; }, {}], 3: [function (require, module, exports) { const http = require('../http'); const storage = require('../storage'); const { anime } = require('./info'); const _ = require('../util'); const search = `https://myanimelist.net/anime.php?q=${encodeURIComponent(anime.title)}`; const result = /<a[^>]*?class="[^"]*?\bhoverinfo_trigger\b[^"]*?"[^>]*?href="https?:\/\/myanimelist\.net\/anime\/(\d+)\/[^"]*?"[^>]*?>/i; const linker = { _add(id) { _.q('.ui.sections.list').then(sections => { const link = document.createElement('a'); link.className = 'item'; link.href = `https://myanimelist.net/anime/${id}`; link.target = '_blank'; link.innerText = 'MyAnimeList'; sections.appendChild(link); }); }, add(id) { if (document.readyState !== 'loading') { this._add(id); } else { document.addEventListener('DOMContentLoaded', () => this._add(id)); } }, request() { return http.request({ url: search, method: 'GET', }).then(res => new Promise((resolve, reject) => { const page = res.responseText; const match = result.exec(page); if (match) { resolve(match[1]); } else { reject(); } })); }, }; const cache = JSON.parse(storage.get('cache', '{}')); let id = cache[anime.id]; if (id != null) { linker.add(id); } else { linker.request().then(id => { cache[anime.id] = id; storage.set('cache', JSON.stringify(cache)); linker.add(id); }); } }, { "../http": 4, "../storage": 6, "../util": 7, "./info": 2 }], 4: [function (require, module, exports) { exports.request = function (url, init) { const opts = {}; if (typeof url === 'string') { init = init || {}; } else if (typeof url === 'object') { init = url; url = init.url || ''; } opts.url = url; opts.method = (init.method || 'GET').toUpperCase(); opts.headers = init.headers || {}; if (init.data) { opts.data = init.data; } if (init.timeout) { opts.timeout = init.timeout; } if (init.username && init.password) { opts.username = init.username; opts.password = init.password; } return new Promise((resolve, reject) => { opts.onload = function (res) { resolve(res); }; opts.onabort = opts.onerror = opts.ontimeout = function (res) { reject(res); }; GM_xmlhttpRequest(opts); }); }; }, {}], 5: [function (require, module, exports) { const start = window.performance.now(); // load modules require('./anime-info'); const end = window.performance.now(); // finished loading const info = GM_info.script; console.log(`${info.name} ${info.version} loaded in ${end - start}ms!`); }, { "./anime-info": 1 }], 6: [function (require, module, exports) { /** * A utility module for interfacing with the userscript storage. */ const storage = { /** * Remove an item from the storage. * * @param {string} key - The key of the item to remove. */ remove(key) { GM_deleteValue(key); }, /** * Gets the value of an item in the storage. * * @param {string} key - The key of the item. * @param {string|boolean|number} [def] - The default value if the item does not exist. * * @returns {string|boolean|number} The value of the item, the default value, or `null`. */ get(key, def) { return GM_getValue(key, def); }, /** * Set the value of an item in the storage. * * @param {string} key - The key of the item. * @param {string|boolean|number} value - The value of the item. */ set(key, value) { GM_setValue(key, value); }, }; module.exports = storage; }, {}], 7: [function (require, module, exports) { const query = require('./query'); /** * Query the document with the given selector. * * @param {string} selector - The query selector. * @returns {Promise<HTMLElement>} */ function q(selector) { return query(selector); } exports.q = q; }, { "./query": 8 }], 8: [function (require, module, exports) { class PendingQuery { constructor(query, resolve) { this.query = query; this.resolve = resolve; } } class Observer { constructor() { this._config = { attributes: true, childList: true, subtree: true, }; this._pending = []; this._observer = new MutationObserver(mutations => this._observe(mutations)); this._running = false; } _observe(mutations) { for (let mut of mutations) { let i = this._pending.length; while (i--) { let query = this._pending[i]; if (mut.type === 'attributes' && mut.target.matches(query.query)) { this._pending.slice(i, 1); query.resolve(mut.target); } else if (mut.type === 'childList') { const el = mut.target.querySelector(query.query); if (el != null) { this._pending.slice(i, 1); query.resolve(el); } } } } } start() { if (this._running) { return; } this._observer.observe(document.documentElement, this._config); this._running = true; } stop() { if (!this._running) { return; } this._observer.disconnect(); this._running = false; } add(pending) { this._pending.push(pending); } } function query(selector) { return new Promise(resolve => { let el = document.querySelector(selector); if (el != null) { resolve(el); } else { const pending = new PendingQuery(selector, resolve); observer.add(pending); } }); } const observer = new Observer(); observer.start(); module.exports = query; }, {}] }, {}, [5]);