Userscript App Core

Userscript App Core For Userscript Web Apps

As of 2023-07-22. See the latest version.

  1. /* eslint-disable no-multi-spaces */
  2.  
  3. // ==UserScript==
  4. // @name Userscript App Core
  5. // @name:zh-CN 用户脚本应用核心
  6. // @name:en Userscript App Core
  7. // @namespace Userscript-App
  8. // @version 0.3
  9. // @description Userscript App Core For Userscript Web Apps
  10. // @description:zh-CN 用户脚本网页应用核心
  11. // @description:en Userscript App Core For Userscript Web Apps
  12. // @author PY-DNG
  13. // @license GPL-v3
  14. // @match http*://*/*
  15. // @connect *
  16. // @grant GM_info
  17. // @grant GM_addStyle
  18. // @grant GM_addElement
  19. // @grant GM_deleteValue
  20. // @grant GM_listValues
  21. // @grant GM_addValueChangeListener
  22. // @grant GM_removeValueChangeListener
  23. // @grant GM_setValue
  24. // @grant GM_getValue
  25. // @grant GM_log
  26. // @grant GM_getResourceText
  27. // @grant GM_getResourceURL
  28. // @grant GM_registerMenuCommand
  29. // @grant GM_unregisterMenuCommand
  30. // @grant GM_openInTab
  31. // @grant GM_xmlhttpRequest
  32. // @grant GM_download
  33. // @grant GM_getTab
  34. // @grant GM_saveTab
  35. // @grant GM_getTabs
  36. // @grant GM_notification
  37. // @grant GM_setClipboard
  38. // @grant GM_info
  39. // @grant unsafeWindow
  40. // ==/UserScript==
  41.  
  42. (function __MAIN__() {
  43. 'use strict';
  44.  
  45. // function DoLog() {}
  46. // Arguments: level=LogLevel.Info, logContent, trace=false
  47. const [LogLevel, DoLog] = (function() {
  48. const LogLevel = {
  49. None: 0,
  50. Error: 1,
  51. Success: 2,
  52. Warning: 3,
  53. Info: 4,
  54. };
  55.  
  56. return [LogLevel, DoLog];
  57. function DoLog() {
  58. // Get window
  59. const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window;
  60.  
  61. const LogLevelMap = {};
  62. LogLevelMap[LogLevel.None] = {
  63. prefix: '',
  64. color: 'color:#ffffff'
  65. }
  66. LogLevelMap[LogLevel.Error] = {
  67. prefix: '[Error]',
  68. color: 'color:#ff0000'
  69. }
  70. LogLevelMap[LogLevel.Success] = {
  71. prefix: '[Success]',
  72. color: 'color:#00aa00'
  73. }
  74. LogLevelMap[LogLevel.Warning] = {
  75. prefix: '[Warning]',
  76. color: 'color:#ffa500'
  77. }
  78. LogLevelMap[LogLevel.Info] = {
  79. prefix: '[Info]',
  80. color: 'color:#888888'
  81. }
  82. LogLevelMap[LogLevel.Elements] = {
  83. prefix: '[Elements]',
  84. color: 'color:#000000'
  85. }
  86.  
  87. // Current log level
  88. DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  89.  
  90. // Log counter
  91. DoLog.logCount === undefined && (DoLog.logCount = 0);
  92.  
  93. // Get args
  94. let [level, logContent, trace] = parseArgs([...arguments], [
  95. [2],
  96. [1,2],
  97. [1,2,3]
  98. ], [LogLevel.Info, 'DoLog initialized.', false]);
  99.  
  100. // Log when log level permits
  101. if (level <= DoLog.logLevel) {
  102. let msg = '%c' + LogLevelMap[level].prefix + (typeof GM_info === 'object' ? `[${GM_info.script.name}]` : '') + (LogLevelMap[level].prefix ? ' ' : '');
  103. let subst = LogLevelMap[level].color;
  104.  
  105. switch (typeof(logContent)) {
  106. case 'string':
  107. msg += '%s';
  108. break;
  109. case 'number':
  110. msg += '%d';
  111. break;
  112. default:
  113. msg += '%o';
  114. break;
  115. }
  116.  
  117. if (++DoLog.logCount > 512) {
  118. console.clear();
  119. DoLog.logCount = 0;
  120. }
  121. console[trace ? 'trace' : 'log'](msg, subst, logContent);
  122. }
  123. }
  124. }) ();
  125.  
  126. main();
  127. function main() {
  128. unsafeWindow.GM_grant = GM_grant;
  129. unsafeWindow.dispatchEvent(new Event('gmready'));
  130. }
  131.  
  132. function GM_grant(name) {
  133. const GMFuncs = {
  134. // Tampermonkey provides
  135. GM_addStyle: typeof GM_addStyle === 'function' ? GM_addStyle : null,
  136. GM_addElement: typeof GM_addElement === 'function' ? GM_addElement : null,
  137. GM_deleteValue: typeof GM_deleteValue === 'function' ? GM_deleteValue : null,
  138. GM_listValues: typeof GM_listValues === 'function' ? GM_listValues : null,
  139. GM_addValueChangeListener: typeof GM_addValueChangeListener === 'function' ? GM_addValueChangeListener : null,
  140. GM_removeValueChangeListener: typeof GM_removeValueChangeListener === 'function' ? GM_removeValueChangeListener : null,
  141. GM_setValue: typeof GM_setValue === 'function' ? GM_setValue : null,
  142. GM_getValue: typeof GM_getValue === 'function' ? GM_getValue : null,
  143. GM_log: typeof GM_log === 'function' ? GM_log : null,
  144. GM_getResourceText: typeof GM_getResourceText === 'function' ? GM_getResourceText : null,
  145. GM_getResourceURL: typeof GM_getResourceURL === 'function' ? GM_getResourceURL : null,
  146. GM_registerMenuCommand: typeof GM_registerMenuCommand === 'function' ? GM_registerMenuCommand : null,
  147. GM_unregisterMenuCommand: typeof GM_unregisterMenuCommand === 'function' ? GM_unregisterMenuCommand : null,
  148. GM_openInTab: typeof GM_openInTab === 'function' ? GM_openInTab : null,
  149. GM_xmlhttpRequest: typeof GM_xmlhttpRequest === 'function' ? GM_xmlhttpRequest : null,
  150. GM_download: typeof GM_download === 'function' ? GM_download : null,
  151. GM_getTab: typeof GM_getTab === 'function' ? GM_getTab : null,
  152. GM_saveTab: typeof GM_saveTab === 'function' ? GM_saveTab : null,
  153. GM_getTabs: typeof GM_getTabs === 'function' ? GM_getTabs : null,
  154. GM_notification: typeof GM_notification === 'function' ? GM_notification : null,
  155. GM_setClipboard: typeof GM_setClipboard === 'function' ? GM_setClipboard : null,
  156. GM_info: typeof GM_info === 'object' ? GM_info : null,
  157. };
  158. if (GMFuncs.hasOwnProperty(name)) {
  159. return GMFuncs[name];
  160. } else {
  161. return null;
  162. }
  163. }
  164.  
  165. function parseArgs(args, rules, defaultValues=[]) {
  166. // args and rules should be array, but not just iterable (string is also iterable)
  167. if (!Array.isArray(args) || !Array.isArray(rules)) {
  168. throw new TypeError('parseArgs: args and rules should be array')
  169. }
  170.  
  171. // fill rules[0]
  172. (!Array.isArray(rules[0]) || rules[0].length === 1) && rules.splice(0, 0, []);
  173.  
  174. // max arguments length
  175. const count = rules.length - 1;
  176.  
  177. // args.length must <= count
  178. if (args.length > count) {
  179. throw new TypeError(`parseArgs: args has more elements(${args.length}) longer than ruless'(${count})`);
  180. }
  181.  
  182. // rules[i].length should be === i if rules[i] is an array, otherwise it should be a function
  183. for (let i = 1; i <= count; i++) {
  184. const rule = rules[i];
  185. if (Array.isArray(rule)) {
  186. if (rule.length !== i) {
  187. throw new TypeError(`parseArgs: rules[${i}](${rule}) should have ${i} numbers, but given ${rules[i].length}`);
  188. }
  189. if (!rule.every((num) => (typeof num === 'number' && num <= count))) {
  190. throw new TypeError(`parseArgs: rules[${i}](${rule}) should contain numbers smaller than count(${count}) only`);
  191. }
  192. } else if (typeof rule !== 'function') {
  193. throw new TypeError(`parseArgs: rules[${i}](${rule}) should be an array or a function.`)
  194. }
  195. }
  196.  
  197. // Parse
  198. const rule = rules[args.length];
  199. let parsed;
  200. if (Array.isArray(rule)) {
  201. parsed = [...defaultValues];
  202. for (let i = 0; i < rule.length; i++) {
  203. parsed[rule[i]-1] = args[i];
  204. }
  205. } else {
  206. parsed = rule(args, defaultValues);
  207. }
  208. return parsed;
  209. }
  210. })();