InstaSynchP Core

The core for a modular plugin system for InstaSync

目前为 2015-04-18 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name InstaSynchP Core
  3. // @namespace InstaSynchP
  4. // @description The core for a modular plugin system for InstaSync
  5. // @version 1.4.4.1
  6. // @author Zod-
  7. // @source https://github.com/Zod-/InstaSynchP-Core
  8. // @license MIT
  9. // @require https://greasyfork.org/scripts/2859-video-url-parser/code/code.js?version=30624
  10. // @require https://greasyfork.org/scripts/2855-gm-config/code/code.js?version=33973
  11. // @require https://greasyfork.org/scripts/2857-jquery-bind-first/code/code.js?version=26080
  12. // @require https://greasyfork.org/scripts/8159-log4javascript/code/code.js?version=37575
  13. // @require https://greasyfork.org/scripts/5647-instasynchp-library/code/code.js?version=41059
  14. // @require https://greasyfork.org/scripts/8177-instasynchp-logger/code/code.js?version=37872
  15. // @require https://greasyfork.org/scripts/6573-instasynchp-plugin-manager/code/code.js?version=42665
  16. // @require https://greasyfork.org/scripts/5718-instasynchp-cssloader/code/code.js?version=43457
  17. // @require https://greasyfork.org/scripts/5719-instasynchp-settings/code/code.js?version=42666
  18. // @require https://greasyfork.org/scripts/6332-instasynchp-commands/code/code.js?version=37738
  19. // @require https://greasyfork.org/scripts/5651-instasynchp-event-hooks/code/code.js?version=46928
  20. // @include *://instasync.com/r/*
  21. // @include *://*.instasync.com/r/*
  22. // @grant none
  23. // @run-at document-end
  24. // ==/UserScript==
  25.  
  26. function Events() {
  27. 'use strict';
  28. this.listeners = {};
  29. }
  30.  
  31. Events.prototype.createListener = function (name) {
  32. 'use strict';
  33. this.listeners[name] = {
  34. 'preOld': [],
  35. 'postOld': []
  36. };
  37. };
  38.  
  39. Events.prototype.createListenerIfNotExists = function (name) {
  40. 'use strict';
  41. var _this = this;
  42. if (isUdef(_this.listeners[name])) {
  43. _this.createListener(name);
  44. }
  45. };
  46.  
  47. Events.prototype.log = function (opts) {
  48. 'use strict';
  49. var args = [];
  50. opts.type = opts.type || 'debug';
  51. args.push('Events');
  52. args.push(opts.event);
  53. if (opts.eventName || opts.eventNames) {
  54. args.push(opts.eventName || opts.eventNames);
  55. }
  56. if (opts.callback) {
  57. args.push(opts.callback.name);
  58. }
  59. if (!isUdef(opts.preOld)) {
  60. args.push('preOld');
  61. args.push(opts.preOld);
  62. }
  63. if (opts.ref) {
  64. args.push(opts.ref.name);
  65. }
  66. if (opts.parameters) {
  67. try {
  68. args.push(JSON.stringify(opts.parameters));
  69. } catch (ignore) {
  70. args.push(opts.parameters);
  71. }
  72. }
  73. if (opts.err) {
  74. args.push(opts.err.message);
  75. args.push(opts.err.stack);
  76. args.push(opts.err);
  77. }
  78. logger()[opts.type].apply(logger(), args);
  79. };
  80.  
  81. Events.prototype.pushListener = function (ref, eventName, callback, preOld) {
  82. 'use strict';
  83. var _this = this;
  84. var prepost = preOld ? 'preOld' : 'postOld';
  85. _this.createListenerIfNotExists(eventName);
  86. _this.listeners[eventName][prepost].push({
  87. ref: ref,
  88. callback: callback
  89. });
  90. };
  91.  
  92.  
  93. Events.prototype.on = function (ref, eventNames, callback, preOld) {
  94. 'use strict';
  95. if (isUdef(callback)) {
  96. return;
  97. }
  98. var _this = this;
  99. preOld = preOld || false;
  100. _this.log({
  101. event: 'on',
  102. callback: callback,
  103. ref: ref,
  104. eventNames: eventNames,
  105. preOld: preOld
  106. });
  107.  
  108. eventNames.split(/\s*,\s*/).forEach(function (eventName) {
  109. eventName = eventName.trim();
  110. _this.pushListener(ref, eventName, callback, preOld);
  111. });
  112. };
  113.  
  114. Events.prototype.once = function (ref, eventNames, callback, preOld) {
  115. 'use strict';
  116. this.unbind(eventNames, callback);
  117. this.on(ref, eventNames, callback, preOld);
  118. };
  119.  
  120. Events.prototype.removeListener = function (eventName, callback, prepost) {
  121. 'use strict';
  122. var _this = this;
  123. if (isUdef(_this.listeners[eventName])) {
  124. return;
  125. }
  126. var listeners = _this.listeners[eventName][prepost];
  127. for (var i = 0; i < listeners.length; i++) {
  128. if (listeners[i].callback === callback) {
  129. listeners.splice(i, 1);
  130. i -= 1;
  131. }
  132. }
  133. };
  134.  
  135. Events.prototype.removePreOldListener = function (eventName, callback) {
  136. 'use strict';
  137. this.removeListener(eventName, callback, 'preOld');
  138. };
  139.  
  140. Events.prototype.removePostOldListener = function (eventName, callback) {
  141. 'use strict';
  142. this.removeListener(eventName, callback, 'postOld');
  143. };
  144.  
  145. Events.prototype.unbind = function (eventNames, callback) {
  146. 'use strict';
  147. var _this = this;
  148. if (isUdef(callback)) {
  149. return;
  150. }
  151.  
  152. _this.log({
  153. event: 'unbind',
  154. callback: callback,
  155. eventNames: eventNames
  156. });
  157.  
  158. eventNames.split(/\s*,\s*/).forEach(function (eventName) {
  159. _this.removePreOldListener(eventName, callback);
  160. _this.removePostOldListener(eventName, callback);
  161. });
  162. };
  163.  
  164. Events.prototype.copyPreOldListeners = function (eventName) {
  165. 'use strict';
  166. return [].concat(this.listeners[eventName].preOld);
  167. };
  168.  
  169. Events.prototype.copyPostOldListeners = function (eventName) {
  170. 'use strict';
  171. return [].concat(this.listeners[eventName].postOld);
  172. };
  173.  
  174. Events.prototype.copyListeners = function (eventName, preOldFlag) {
  175. 'use strict';
  176. if (preOldFlag) {
  177. return this.copyPreOldListeners(eventName);
  178. } else {
  179. return this.copyPostOldListeners(eventName);
  180. }
  181. };
  182.  
  183. Events.prototype.fire = function (eventName, parameters, preOld) {
  184. 'use strict';
  185. var _this = this;
  186. preOld = preOld || false;
  187.  
  188. //Don't record every single keystroke
  189. //and PageMessages from youtube and others
  190. if (eventName !== 'PageMessage' && !eventName.startsWith('Input')) {
  191. _this.log({
  192. event: 'fire',
  193. eventName: eventName,
  194. preOld: preOld,
  195. parameters: parameters
  196. });
  197. }
  198.  
  199. if (isUdef(_this.listeners[eventName])) {
  200. return;
  201. }
  202.  
  203. //make a copy of the listeners since some handlers
  204. //could remove listeners, changing the array while iterating over it
  205. _this.copyListeners(eventName, preOld).forEach(function (listener) {
  206. try {
  207. listener.callback.apply(listener.ref, parameters);
  208. } catch (err) {
  209. _this.log({
  210. callback: listener.callback,
  211. ref: listener.ref,
  212. eventName: eventName,
  213. type: 'error',
  214. event: 'fire',
  215. err: err
  216. });
  217. }
  218. });
  219. };
  220.  
  221. function Core() {
  222. 'use strict';
  223. this.version = '1.4.4.1';
  224. this.name = 'InstaSynchP Core';
  225. this.connected = false;
  226. this.Events = Events;
  227. this.styles = [{
  228. name: 'core',
  229. url: 'https://cdn.rawgit.com/Zod-/InstaSynchP-Core/1eabab1beefc635d7a59e5bad3176de063da3331/dist/core.css',
  230. autoload: true
  231. }];
  232. this.isMainLoaded = false;
  233. }
  234.  
  235. Core.prototype.createPluginsButton = function () {
  236. 'use strict';
  237. var clone = $('#user_dropdown').clone();
  238. clone.attr('id', 'plugin_dropdown');
  239. $('a', clone).attr('href', '#').attr('onClick', '');
  240. $('.fa-user', clone).toggleClass('fa-user').toggleClass('fa-plug').before(
  241. $('#tabs_chat > a > span').clone().toggleClass('updates')
  242. );
  243. $('#my_room_link', clone).parent().remove();
  244. $('#logged_in_as', clone)
  245. .attr('id', 'plugins_settings_title').text('Plugins');
  246. $('.dropdown-menu > li:first-child > a', clone)
  247. .attr('id', 'plugin_settings');
  248. $('#logout', clone).attr('id', 'plugin_manager').text('').append(
  249. $('#tabs_chat > a > span').clone().toggleClass('updates')
  250. ).append(
  251. $('<i>', {
  252. 'class': 'fa fa-database'
  253. })
  254. ).append(' Manager');
  255. $('.fa-cog', clone).toggleClass('fa-cogs').toggleClass('fa-cog');
  256. $('#user_dropdown').before(clone);
  257. $('#plugin_dropdown').show();
  258. };
  259.  
  260. Core.prototype.executeOnceCore = function () {
  261. 'use strict';
  262. var _this = this;
  263. window.events = new _this.Events();
  264. _this.createPluginsButton();
  265. };
  266.  
  267. Core.prototype.resetVariables = function () {
  268. 'use strict';
  269. this.connected = false;
  270. window.room.user.userinfo = undefined;
  271. };
  272.  
  273. Core.prototype.prepareFramework = function () {
  274. 'use strict';
  275. var _this = this;
  276. _this.executeOnceCore();
  277. plugins.logger.executeOnceCore();
  278. _this.log({
  279. event: 'Prepare Framework'
  280. });
  281. plugins.commands.executeOnceCore();
  282. plugins.pluginManager.executeOnceCore();
  283. events.on(plugins.settings, 'ExecuteOnce', plugins.settings.executeOnceCore);
  284. events.on(_this, 'PreConnect, Disconnect', function () {
  285. events.fire('ResetVariables');
  286. });
  287. };
  288.  
  289. Core.prototype.finishUpFramework = function () {
  290. 'use strict';
  291. var _this = this;
  292. _this.log({
  293. event: 'Finish up Framework'
  294. });
  295. events.on(plugins.eventHooks, 'ExecuteOnce',
  296. plugins.eventHooks.executeOnceCore);
  297. events.on(_this, 'LoadUserlist', _this.onConnect);
  298. };
  299.  
  300. Core.prototype.log = function (opts) {
  301. 'use strict';
  302. var args = [];
  303. opts.type = opts.type || 'debug';
  304. args.push(this.name);
  305. args.push(opts.event);
  306. if (opts.plugin) {
  307. args.push(opts.plugin.name);
  308. args.push(opts.plugin.version);
  309. }
  310. logger()[opts.type].apply(logger(), args);
  311. };
  312.  
  313. Core.prototype.preparePlugin = function (plugin) {
  314. 'use strict';
  315. var _this = this;
  316. if (!plugin.enabled) {
  317. _this.log({
  318. event: 'Skipping disabled plugin',
  319. type: 'info',
  320. plugin: plugin
  321. });
  322. return;
  323. }
  324.  
  325. _this.log({
  326. event: 'Found plugin',
  327. type: 'info',
  328. plugin: plugin
  329. });
  330.  
  331. events.on(plugin, 'PreConnect', plugin.preConnect);
  332. events.on(plugin, 'PostConnect', plugin.postConnect);
  333. events.on(plugin, 'ExecuteOnce', plugin.executeOnce);
  334. events.on(plugin, 'ResetVariables', plugin.resetVariables);
  335.  
  336. commands.bind(plugin.commands);
  337.  
  338. //refactor these into the plugins later
  339. if (Array.isArray(plugin.settings)) {
  340. plugins.settings.fields = plugins.settings.fields.concat(plugin.settings);
  341. }
  342.  
  343. if (Array.isArray(plugin.styles)) {
  344. plugin.styles.forEach(function (style) {
  345. plugins.settings.fields.push({
  346. 'label': '',
  347. 'id': style.name + '-css-content',
  348. 'type': 'hidden',
  349. 'value': '',
  350. 'section': ['Core']
  351. });
  352. plugins.settings.fields.push({
  353. 'label': '',
  354. 'id': style.name + '-css-url',
  355. 'type': 'hidden',
  356. 'value': '',
  357. 'section': ['Core']
  358. });
  359. events.on(plugins.cssLoader, 'ExecuteOnce', function () {
  360. plugins.cssLoader.addStyle(style);
  361. });
  362. });
  363. }
  364. };
  365.  
  366. Core.prototype.preparePlugins = function () {
  367. 'use strict';
  368. var _this = this;
  369. _this.log({
  370. event: 'Prepare plugins'
  371. });
  372. for (var pluginName in plugins) {
  373. if (plugins.hasOwnProperty(pluginName)) {
  374. _this.preparePlugin(plugins[pluginName]);
  375. }
  376. }
  377. };
  378.  
  379. Core.prototype.fireConnect = function () {
  380. 'use strict';
  381. var _this = this;
  382. events.fire('PreConnect');
  383. //if the script was loading slow and we are already connected
  384. if (thisUser()) {
  385. _this.onConnect();
  386. }
  387. };
  388.  
  389. Core.prototype.fireExecuteOnce = function () {
  390. 'use strict';
  391. this.isMainLoaded = true;
  392. events.fire('ExecuteOnce');
  393. };
  394.  
  395. Core.prototype.onConnect = function () {
  396. 'use strict';
  397. this.connected = true;
  398. events.fire('PostConnect');
  399. };
  400.  
  401. Core.prototype.executeOnce = function () {
  402. 'use strict';
  403. logger().info(this.name, navigator.userAgent);
  404. };
  405.  
  406. Core.prototype.start = function () {
  407. 'use strict';
  408. var _this = this;
  409. _this.log({
  410. event: 'Start'
  411. });
  412. _this.fireExecuteOnce();
  413. _this.fireConnect();
  414. };
  415.  
  416. Core.prototype.main = function () {
  417. 'use strict';
  418. var _this = this;
  419.  
  420. _this.prepareFramework();
  421. _this.preparePlugins();
  422. _this.finishUpFramework();
  423.  
  424. _this.start();
  425. };
  426.  
  427. window.plugins = window.plugins || {};
  428. window.plugins.core = new Core();
  429. if (window.document.readyState === 'complete') {
  430. window.plugins.core.main();
  431. window.plugins.core.log({
  432. event: 'Page was already loaded'
  433. });
  434. } else {
  435. window.addEventListener('load', function () {
  436. 'use strict';
  437. if (!window.plugins.core.isMainLoaded) {
  438. window.plugins.core.main();
  439. }
  440. window.plugins.core.log({
  441. event: 'Page load'
  442. });
  443. }, false);
  444. }