Fimfiction Events API (ref FimfictionAdvanced, Nosey Hound)

Adds events for script authors to hook into on FimFiction

بۇ قوليازمىنى بىۋاسىتە قاچىلاشقا بولمايدۇ. بۇ باشقا قوليازمىلارنىڭ ئىشلىتىشى ئۈچۈن تەمىنلەنگەن ئامبار بولۇپ، ئىشلىتىش ئۈچۈن مېتا كۆرسەتمىسىگە قىستۇرىدىغان كود: // @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();
	}
})();