Fimfiction Events API (ref FimfictionAdvanced, Nosey Hound)

Adds events for script authors to hook into on FimFiction

Dieses Skript sollte nicht direkt installiert werden. Es handelt sich hier um eine Bibliothek für andere Skripte, welche über folgenden Befehl in den Metadaten eines Skriptes eingebunden wird // @require https://update.greasyfork.org/scripts/5855/1450330/Fimfiction%20Events%20API%20%28ref%20FimfictionAdvanced%2C%20Nosey%20Hound%29.js

// ==UserScript==
// @name        Fimfiction Events API (ref FimfictionAdvanced, Nosey Hound)
// @author      Sollace
// @namespace   fimfiction-sollace
// @version     4.2.2
// @match       *://www.fimfiction.net/*
// @grant       none
// @run-at      document-start
// ==/UserScript==

function RunScript(func, immediate, params) {
	const pars = Array.apply(null, arguments);
	pars.slice(0, 2);
	RunScript.build(func.toString(), pars).run(immediate);
}
RunScript.ready = function(func, immediate, params) {
	window.addEventListener('DOMContentLoaded', () => RunScript(func, immediate, params));
};
RunScript.build = (functionText, params) => {
	return {
		run: function(immediate) {
			if (!document.body) return window.addEventListener('DOMContentLoaded', () => this.run(immediate));
			const scr = document.createElement('SCRIPT');
			scr.innerHTML = immediate ? `(${functionText}).apply(this, ${params ? JSON.stringify(params) : ''});` : functionText;
			document.body.appendChild(scr);
			scr.parentNode.removeChild(scr);
		}
	};
};

(_ => {
	function initialise() {
		const win = this['unsafeWindow'] || window;
		const VERSION = 4.2;
		
		if (window !== win && (!window.FimFicEvents || window.FimFicEvents.version() < VERSION)) {
			window.FimFicEvents = {
				on: (name, func) => (name, func) => {
					name.split(' ').forEach(a => document.addEventListener(a, func));
					return func;
				},
				off: (name, func) => name.split(' ').forEach(a => document.removeEventListener(a, func)),
				trigger: (name, e) => RunScript.build(`() => FimFicEvents.trigger("${name}", ${JSON.stringify(e)})`).run(true),
				subscribe: func => RunScript.build(`() => FimFicEvents.subscribe(${func.toString()})`).run(true),
				getEventObject: win.FimFicEvents.getEventObject,
				version: win.FimFicEvents.version,
			};
		}
		if (!win.FimFicEvents || win.FimFicEvents.version() < VERSION) {
			if (win === window) return scriptBody(VERSION);
			RunScript(scriptBody, true, VERSION);
		}
	}
	
	try {
		initialise();
	} catch (e) {console.error(e);}
	
	function scriptBody(ver) {
		const some = a => a;
		const eventMap = {
			'/ajax/bbcode/html': 'previewcontent',
			'/ajax/notifications/mark-all-read': 'note_markread',
			'/ajax/private-messages/mark-all-read': 'pm_markread',
			'/ajax/notifications/list/drop-down': 'listnotes',
			'/ajax/private-messages/list/drop-down': 'listpms',
			'/ajax/feed': 'loadfeed',
			'/ajax/emoticons/list': 'listemoticons',
			'/ajax/toolbar/stories': _ => some({ eventName: 'toolbar', type: 'stories' }),
			'/ajax/toolbar/blog-posts': _ => some({ eventName: 'toolbar', type: 'blogs' })
		};
		const complexEventMap = [
			{
				test: /\/ajax\/users\/([^\/]+)\/infocard/, func: match => some({
					eventName: 'infocard',
					user: { id: match[1], name: decodeURIComponent(/\/user\/[0-9]+\/([^\/]*)$/.exec(document.querySelector('a:hover').href)[1]) }
				})
			}, {
				test: /\/ajax\/private-messages\/new/, func: (match,url) => {
					let event = {eventName: 'composepm', recipient: '', subject: ''};
					if (match = /reciever=([^&]+)/.exec(url)) event.recipient = match[1];
					if (match = /subject=([^&]+)/.exec(url)) event.subject = match[1];
					return event;
				}
			}, {
				test: /\/ajax\/comments/, func: (match,url) => some({
					eventName: url.split('/').length == 5 ? 'editcomment' : 'pagechange'
				})
			}, {
				test: /\/ajax\/([^\/]+)\/([0-9]+)\/comments/, func: match => some({
					eventName: 'addcomment', type: match[1], id: match[2]
				})
			}, {
				test: /\/ajax\/users\/modules\/([^\/]+)(\/edit|\/modules|)/, func: match => some({
					eventName: match[2] == '/edit' ? 'editmodule' : 'savemodule', box: match[1]
				})
			}
		];
		let eventRegister = url => {
			let o = eventMap[url];
			if (o) return typeof(o) === 'string' ? { eventName: o } : o(url);
			for (let i = 0; i < complexEventMap.length; i++) {
				let match = complexEventMap[i].test.exec(url);
				if (match) return complexEventMap[i].func(match, url);
			}
			console.log(`Unhandled method ignored: "${url}"`);
			return null;
		};
		
		function getEventObject(a, callback) {
			if (a.__fimficevents__ || typeof(a.url) !== 'string') return;
			const o = eventRegister(a.url);
			a.__fimficevents__ = 1;
			if (!o) return;
			o.url = a.url;
			o.data = a.data;
			callback(o);
		}
		
		function override(obj, name, func) {
			const sup = obj[name];
			obj[name] = function(...pars) {return func(this, pars, sup);};
		}
		
		window.FimFicEvents = {
			on: (name, func) => {
				name.split(' ').forEach(a => document.addEventListener(a, func));
				return func;
			},
      one: (name, func) => {
        const f = window.FimFicEvents.on(name, function() {
          window.FimFicEvents.off(name, f);
          return func.apply(this, arguments);
        });
        return f;
      },
			off: (name, func) => name.split(' ').forEach(a => document.removeEventListener(a, func)),
			trigger: (name, event) => {
				name = new CustomEvent(name);
				name.event = event;
				document.dispatchEvent(name);
			},
			subscribe: evFunc => {
				const old = eventRegister;
				eventRegister = url => evFunc(url) || old(url);
			},
			PROXY: function(sender, func, args, m) {
        let prevented = false;
				let a = args[0];
				if (typeof a === 'string') a = args[0] = {url: a};
				getEventObject(a, event => {
          event.preventDefault = _ => prevented = true;
          this.trigger(`early${event.eventName}`, event);
					override(a, 'success', (self, pars, sup) => {
						let result = undefined;
						event.result = pars[0];
						event.request = pars[1];
						this.trigger(`before${event.eventName}`, event);
						pars[0] = event.result;
						if (sup) result = sup.apply(self, pars);
						event.result = pars[0];
						this.trigger(`after${event.eventName}`, event);
						return result;
					});
				});
        if (prevented) return;
				return func.apply(sender, args);
			},
			getEventObject: getEventObject,
			version: _ => ver
		};
		window.FimFicEvents.version.toString = window.FimFicEvents.version;
		
		const inject = () => ((obj, parent, child) => {
			child.super = obj[parent].super || obj[parent];
			child.prototype = child.super.prototype;
			Object.keys(child.super).forEach(k => child[k] = child.super[k]);
			obj[parent] = child;
		})(window, 'AjaxRequest', function() {
			return window.FimFicEvents.PROXY(this, window.AjaxRequest.super, arguments, false);
		});
		
		if (!window.AjaxRequest) return window.addEventListener('DOMContentLoaded', () => {
      if (window.AjaxRequest) inject();
    });
		inject();
	}
})();