InstaSynchP Core

The core for a modular plugin system for InstaSync

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

  1. // ==UserScript==
  2. // @name InstaSynchP Core
  3. // @namespace InstaSynchP
  4. // @description The core for a modular plugin system for InstaSync
  5. // @version 1.4.5
  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=49210
  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=49212
  19. // @require https://greasyfork.org/scripts/5651-instasynchp-event-hooks/code/code.js?version=49211
  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.5';
  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. };
  285.  
  286. Core.prototype.finishUpFramework = function () {
  287. 'use strict';
  288. var _this = this;
  289. _this.log({
  290. event: 'Finish up Framework'
  291. });
  292. events.on(plugins.eventHooks, 'ExecuteOnce',
  293. plugins.eventHooks.executeOnceCore);
  294. events.on(_this, 'LoadUserlist', _this.onConnect);
  295. };
  296.  
  297. Core.prototype.log = function (opts) {
  298. 'use strict';
  299. var args = [];
  300. opts.type = opts.type || 'debug';
  301. args.push(this.name);
  302. args.push(opts.event);
  303. if (opts.plugin) {
  304. args.push(opts.plugin.name);
  305. args.push(opts.plugin.version);
  306. }
  307. logger()[opts.type].apply(logger(), args);
  308. };
  309.  
  310. Core.prototype.preparePlugin = function (plugin) {
  311. 'use strict';
  312. var _this = this;
  313. if (!plugin.enabled) {
  314. _this.log({
  315. event: 'Skipping disabled plugin',
  316. type: 'info',
  317. plugin: plugin
  318. });
  319. return;
  320. }
  321.  
  322. _this.log({
  323. event: 'Found plugin',
  324. type: 'info',
  325. plugin: plugin
  326. });
  327.  
  328. events.on(plugin, 'PreConnect', plugin.preConnect);
  329. events.on(plugin, 'PostConnect', plugin.postConnect);
  330. events.on(plugin, 'ExecuteOnce', plugin.executeOnce);
  331. events.on(plugin, 'ResetVariables', plugin.resetVariables);
  332.  
  333. commands.bind(plugin.commands);
  334.  
  335. //refactor these into the plugins later
  336. if (Array.isArray(plugin.settings)) {
  337. plugins.settings.fields = plugins.settings.fields.concat(plugin.settings);
  338. }
  339.  
  340. if (Array.isArray(plugin.styles)) {
  341. plugin.styles.forEach(function (style) {
  342. plugins.settings.fields.push({
  343. 'label': '',
  344. 'id': style.name + '-css-content',
  345. 'type': 'hidden',
  346. 'value': '',
  347. 'section': ['Core']
  348. });
  349. plugins.settings.fields.push({
  350. 'label': '',
  351. 'id': style.name + '-css-url',
  352. 'type': 'hidden',
  353. 'value': '',
  354. 'section': ['Core']
  355. });
  356. events.on(plugins.cssLoader, 'ExecuteOnce', function () {
  357. plugins.cssLoader.addStyle(style);
  358. });
  359. });
  360. }
  361. };
  362.  
  363. Core.prototype.preparePlugins = function () {
  364. 'use strict';
  365. var _this = this;
  366. _this.log({
  367. event: 'Prepare plugins'
  368. });
  369. for (var pluginName in plugins) {
  370. if (plugins.hasOwnProperty(pluginName)) {
  371. _this.preparePlugin(plugins[pluginName]);
  372. }
  373. }
  374. };
  375.  
  376. Core.prototype.slowLoadingCompensate = function () {
  377. 'use strict';
  378. this.connected = true;
  379. events.fire('Connected');
  380. events.fire('Joining');
  381. events.fire('Joined');
  382. window.room.playlist.load(window.room.playlist.videos);
  383. window.room.userlist.load(window.room.userlist.users);
  384. reloadPlayer();
  385. };
  386.  
  387. Core.prototype.fireConnect = function () {
  388. 'use strict';
  389. var _this = this;
  390. events.fire('PreConnect');
  391. if (thisUser()) {
  392. _this.log({
  393. event: 'Script was loading slowly'
  394. });
  395. _this.slowLoadingCompensate();
  396. }
  397. };
  398.  
  399. Core.prototype.fireExecuteOnce = function () {
  400. 'use strict';
  401. this.isMainLoaded = true;
  402. events.fire('ExecuteOnce');
  403. };
  404.  
  405. Core.prototype.onConnect = function () {
  406. 'use strict';
  407. this.connected = true;
  408. events.fire('PostConnect');
  409. };
  410.  
  411. Core.prototype.executeOnce = function () {
  412. 'use strict';
  413. logger().info(this.name, navigator.userAgent);
  414. };
  415.  
  416. Core.prototype.start = function () {
  417. 'use strict';
  418. var _this = this;
  419. _this.log({
  420. event: 'Start'
  421. });
  422. _this.fireExecuteOnce();
  423. _this.fireConnect();
  424. events.on(_this, 'PreConnect, Disconnect', function fireResetVariables() {
  425. events.fire('ResetVariables');
  426. });
  427. };
  428.  
  429. Core.prototype.main = function () {
  430. 'use strict';
  431. var _this = this;
  432.  
  433. _this.prepareFramework();
  434. _this.preparePlugins();
  435. _this.finishUpFramework();
  436.  
  437. _this.start();
  438. };
  439.  
  440. window.plugins = window.plugins || {};
  441. window.plugins.core = new Core();
  442. if (window.document.readyState === 'complete') {
  443. window.plugins.core.main();
  444. window.plugins.core.log({
  445. event: 'Page was already loaded'
  446. });
  447. } else {
  448. window.addEventListener('load', function () {
  449. 'use strict';
  450. if (!window.plugins.core.isMainLoaded) {
  451. window.plugins.core.main();
  452. }
  453. window.plugins.core.log({
  454. event: 'Page load'
  455. });
  456. }, false);
  457. }