🏠 Home 

ExtMan

A Simple Extension Manager

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greasyfork.org/scripts/441384/1027332/ExtMan.js

  1. // ==UserScript==
  2. // @name ExtMan
  3. // @namespace ckylin-script-lib-extension-manager
  4. // @version 1.0
  5. // @match http://*
  6. // @match https://*
  7. // @author CKylinMC
  8. // @license GPLv3 License
  9. // @grant none
  10. // ==/UserScript==
  11. (function(){
  12. if(typeof(this.unsafeWindow)==='undefined'){
  13. unsafeWindow = window;
  14. }
  15. const thisInfo = {
  16. version: 1.0
  17. }
  18. class Logger{
  19. static getStack(){
  20. return new Error().stack.substr(7);
  21. }
  22. constructor(loggerName='LOG', parent=null){
  23. this.name = loggerName;
  24. this.parent = parent;
  25. this.visible = this.parent?null:true;
  26. this.serviceProvider = console;
  27. }
  28. isVisible(){
  29. if(this.visible===null){
  30. if(this.parent&&this.parent.isVisible){
  31. return this.parent.isVisible();
  32. } else return true;
  33. }
  34. return this.visible;
  35. }
  36. setVisible(yes=true){
  37. this.visible = yes;
  38. }
  39. getSubLogger(subName=null){
  40. return new Logger(subName, this);
  41. }
  42. getName(){
  43. return `${this.parent?this.parent.getName()+'/':''}${this.name}`;
  44. }
  45. getFormattedName(){
  46. return `[${this.getName()}]`
  47. }
  48. sendLogs(subMethod,...contents){
  49. if(this.isVisible()){
  50. if(this.serviceProvider.hasOwnProperty(subMethod)){
  51. this.serviceProvider[subMethod](...contents);
  52. return true;
  53. }
  54. }
  55. return false;
  56. }
  57. log(...args){
  58. return this.sendLogs('log',this.getFormattedName(),...args);
  59. }
  60. info(...args){
  61. return this.sendLogs('info',this.getFormattedName(),...args);
  62. }
  63. warn(...args){
  64. const stacks = '\nTrace:\n'+Logger.getStack().split('\n').splice(2).join('\n');
  65. return this.sendLogs('warn',this.getFormattedName(),...args,stacks);
  66. }
  67. error(...args){
  68. const stacks = '\nTrace:\n'+Logger.getStack().split('\n').splice(2).join('\n');
  69. return this.sendLogs('error',this.getFormattedName(),...args,stacks);
  70. }
  71. debug(...args){
  72. const stacks = '\nTrace:\n'+Logger.getStack().split('\n').splice(2).join('\n');
  73. return this.sendLogs('debug',this.getFormattedName(),...args,stacks);
  74. }
  75. send(methodName,...args){
  76. return this.sendLogs(methodName,this.getFormattedName(),...args);
  77. }
  78. }
  79. class ExtensionManager{
  80. static getLoggerClass(){
  81. return Logger;
  82. }
  83. static initExtObj(){
  84. if(!unsafeWindow.hasOwnProperty('ExtMan') || unsafeWindow.ExtMan.info.version<thisInfo.version){
  85. Object.assign(unsafeWindow,{
  86. ExtMan:{
  87. $ExtMan: ExtensionManager,
  88. info:thisInfo,
  89. }
  90. });
  91. }else if(unsafeWindow.ExtMan.info.version>thisInfo.version){
  92. new Logger('ExtMan').warn('A newer version of ExtMan is already loaded into this page. Skipping current initalization...');
  93. }
  94. if(!unsafeWindow.ExtMan.exts) unsafeWindow.ExtMan.exts = {};
  95. }
  96. static getExtObj(){
  97. ExtensionManager.initExtObj();
  98. return unsafeWindow.ExtMan.exts;
  99. }
  100. getInfo(){
  101. return thisInfo;
  102. }
  103. constructor(options){
  104. const opt = Object.assign({
  105. name:null,
  106. logger:null,
  107. requiredProperties:{
  108. name:"string",
  109. version:"string",
  110. //hook: ()=>true,
  111. },
  112. },options);
  113. if(!opt.name) throw new Error("Need name in options.");
  114. if(!opt.logger){
  115. opt.logger = new Logger('ExtMan').getSubLogger(opt.name);
  116. }
  117. this.opt = opt;
  118. this.init();
  119. }
  120. init(){
  121. const exts = ExtensionManager.getExtObj();
  122. if(!exts.hasOwnProperty(this.opt.name)) {
  123. exts[this.opt.name] = [];
  124. }
  125. this.modules = exts[this.opt.name];
  126. }
  127. registerModule(module){
  128. this.modules.push(module);
  129. }
  130. validModule(module){
  131. const requiredProperties = this.opt.requiredProperties;
  132. if(!requiredProperties) return true;
  133. const properties = Object.keys(requiredProperties);
  134. for(const property of properties){
  135. if(!module.hasOwnProperty(property)) return false;
  136. try{
  137. const type = requiredProperties[property];
  138. const prop = module[property];
  139. if(typeof type ==='function'){
  140. if(!type(prop,module,property)) return (this.opt.logger.warn('Module dropped due to validation failed.',module),false);
  141. }else if(typeof(prop)!=type) return (this.opt.logger.warn('Module dropped due to property validation failed.',module),false);
  142. }catch(e){
  143. this.opt.logger.error('Validation failed',e);
  144. return (this.opt.logger.warn('Module dropped due to validation errored.',module),false);
  145. }
  146. }
  147. this.opt.logger.info('Module loaded.',module);
  148. return true;
  149. }
  150. getAllModulesWhatever(){
  151. return this.modules;
  152. }
  153. getAllModules(){
  154. const mods = this.getAllModulesWhatever();
  155. if(!Array.isArray(mods)) return (this.opt.logger.warn('Empty modules list.'),[]);
  156. const validModules = [];
  157. for(const mod of mods){
  158. if(this.validModule(mod)) validModules.push(mod);
  159. else this.opt.logger.warn('Dropped one module');
  160. }
  161. return validModules;
  162. }
  163. }
  164. ExtensionManager.initExtObj();
  165. unsafeWindow.getExtMan = ()=>{return unsafeWindow.ExtMan.$ExtMan};
  166. })()