generals.io+

A simple script that helps you to make generals.io better

  1. /* eslint-disable no-multi-spaces */
  2.  
  3. // ==UserScript==
  4. // @name generals.io+
  5. // @name:zh-CN generals.io+
  6. // @name:en generals.io+
  7. // @namespace generals.io_plus
  8. // @version 0.4.2.1
  9. // @description A simple script that helps you to make generals.io better
  10. // @description:zh-CN 一个简单的generals.io增强脚本
  11. // @description:en A simple script that helps you to make generals.io better
  12. // @author PY-DNG
  13. // @license MIT
  14. // @match https://generals.io/*
  15. // @icon data:image/png;base64,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
  16. // @grant GM_registerMenuCommand
  17. // @grant GM_unregisterMenuCommand
  18. // @grant GM_getValue
  19. // @grant GM_setValue
  20. // @grant unsafeWindow
  21. // @require https://cdn.jsdelivr.net/gh/MohammadYounes/AlertifyJS@3151fa0d65909936afcbb2f1665ed4f20767bee5/build/alertify.min.js
  22. // @resource alertify-css https://cdn.jsdelivr.net/gh/MohammadYounes/AlertifyJS@3151fa0d65909936afcbb2f1665ed4f20767bee5/build/css/alertify.min.css
  23. // @resource alertify-theme https://cdn.jsdelivr.net/gh/MohammadYounes/AlertifyJS@3151fa0d65909936afcbb2f1665ed4f20767bee5/build/css/themes/default.min.css
  24. // ==/UserScript==
  25.  
  26. (function __MAIN__() {
  27. 'use strict';
  28.  
  29. // Polyfills
  30. const script_name = 'generals.io+';
  31. const script_version = '0.4.2.1';
  32. const NMonkey_Info = {
  33. GM_info: {
  34. script: {
  35. name: script_name,
  36. author: 'PY-DNG',
  37. version: script_version
  38. }
  39. },
  40. requires: [
  41. {
  42. src: 'https://cdn.jsdelivr.net/gh/MohammadYounes/AlertifyJS@3151fa0d65909936afcbb2f1665ed4f20767bee5/build/alertify.min.js',
  43. loaded: () => (typeof(alertify) === 'object'),
  44. execmode: 'function'
  45. }
  46. ],
  47. resources: [
  48. {
  49. src: 'https://cdn.jsdelivr.net/gh/MohammadYounes/AlertifyJS@3151fa0d65909936afcbb2f1665ed4f20767bee5/build/css/alertify.min.css',
  50. name: 'alertify-css'
  51. },
  52. {
  53. src: 'https://cdn.jsdelivr.net/gh/MohammadYounes/AlertifyJS@3151fa0d65909936afcbb2f1665ed4f20767bee5/build/css/themes/default.min.css',
  54. name: 'alertify-theme'
  55. }
  56. ],
  57. mainFunc: __MAIN__
  58. };
  59. const NMonkey_Ready = NMonkey(NMonkey_Info);
  60. if (!NMonkey_Ready) {return false;}
  61. polyfill_replaceAll();
  62.  
  63. // Constances
  64. const CONST = {
  65. Number: {
  66. Interval: 100
  67. },
  68. Storage: {
  69. Key: {
  70. CustomColor: 'User-Preferred-Color'
  71. }
  72. },
  73. Colors: ['red', 'green', 'lightblue', 'purple', 'teal', 'blue', 'orange', 'maroon', 'yellow', 'pink', 'brown', 'lightgreen', 'purple-blue', 'white'],
  74. Color: {},
  75. Css: {
  76. alertify: '.ajs-content>p {color: black;}',
  77. CustomColor: '.{MC} {background-color: {UCV} !important;} .{UC} {background-color: {MCV} !important;}'
  78. },
  79. Text: {
  80. 'zh-CN': {
  81. CustomColor: '自定义颜色',
  82. EnterColor: '请输入你想要的颜色的英文名称,</br>可用的颜色为{AC},</br>留空即使用服务器提供的颜色:',
  83. ColorTitle: '自定义颜色',
  84. DefaultColor: '',
  85. InvalidColor: '颜色 "{C}" 不受支持!'
  86. },
  87. 'en': {
  88. CustomColor: 'Custom Preferred Color',
  89. EnterColor: 'Enter your preferred color name,</br>Available colors are {AC},</br>Leave it blank if you don\'t want to custom your color: ',
  90. ColorTitle: 'Custom Preferred Color',
  91. DefaultColor: '',
  92. InvalidColor: 'Color "{C}" is not supported!'
  93. },
  94. 'default': {
  95. CustomColor: 'Custom Preferred Color',
  96. EnterColor: 'Enter your preferred color name,</br>Available colors are {AC},</br>Leave it blank if you don\'t want to custom your color: ',
  97. ColorTitle: 'Custom Preferred Color',
  98. DefaultColor: '',
  99. InvalidColor: 'Color "{C}" is not supported!'
  100. }
  101. }
  102. }
  103. for (const color of CONST.Colors) {
  104. CONST.Color[color] = getColorValue(color);
  105. }
  106.  
  107. // Init language
  108. let i18n = navigator.language;
  109. if (!Object.keys(CONST.Text).includes(i18n)) {i18n = 'default';}
  110.  
  111. // Arguments: level=LogLevel.Info, logContent, asObject=false
  112. // Needs one call "DoLog();" to get it initialized before using it!
  113. function DoLog() {
  114. // Get window
  115. const win = (typeof(unsafeWindow) === 'object' && unsafeWindow !== null) ? unsafeWindow : window ;
  116.  
  117. // Global log levels set
  118. win.LogLevel = {
  119. None: 0,
  120. Error: 1,
  121. Success: 2,
  122. Warning: 3,
  123. Info: 4,
  124. }
  125. win.LogLevelMap = {};
  126. win.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'}
  127. win.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'}
  128. win.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'}
  129. win.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'}
  130. win.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'}
  131. win.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'}
  132.  
  133. // Current log level
  134. DoLog.logLevel = win.isPY_DNG ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  135.  
  136. // Log counter
  137. DoLog.logCount === undefined && (DoLog.logCount = 0);
  138.  
  139. // Get args
  140. let level, logContent, asObject;
  141. switch (arguments.length) {
  142. case 1:
  143. level = LogLevel.Info;
  144. logContent = arguments[0];
  145. asObject = false;
  146. break;
  147. case 2:
  148. level = arguments[0];
  149. logContent = arguments[1];
  150. asObject = false;
  151. break;
  152. case 3:
  153. level = arguments[0];
  154. logContent = arguments[1];
  155. asObject = arguments[2];
  156. break;
  157. default:
  158. level = LogLevel.Info;
  159. logContent = 'DoLog initialized.';
  160. asObject = false;
  161. break;
  162. }
  163.  
  164. // Log when log level permits
  165. if (level <= DoLog.logLevel) {
  166. let msg = '%c' + LogLevelMap[level].prefix;
  167. let subst = LogLevelMap[level].color;
  168.  
  169. if (asObject) {
  170. msg += ' %o';
  171. } else {
  172. switch(typeof(logContent)) {
  173. case 'string': msg += ' %s'; break;
  174. case 'number': msg += ' %d'; break;
  175. case 'object': msg += ' %o'; break;
  176. }
  177. }
  178.  
  179. if (++DoLog.logCount > 512) {
  180. console.clear();
  181. DoLog.logCount = 0;
  182. }
  183. console.log(msg, subst, logContent);
  184. }
  185. }
  186. DoLog();
  187.  
  188. let EB;
  189. main();
  190. function main() {
  191. // Common actions
  192. loadinResourceCSS();
  193. myCss();
  194.  
  195. // Event-based actions
  196. EB = new EventBroadcast();
  197. EB.time = CONST.Number.Interval;
  198. EB.name = GM_info.script.name;
  199. initEvents();
  200. EB.start();
  201.  
  202. // Clear advertisements
  203. EB.addEventListener('nogame', clearAds);
  204.  
  205. // Gaming improvements
  206. EB.addEventListener('gamestart', mapEnhance);
  207.  
  208. // Custom your preferred color
  209. const cfuncs = colorCustom();
  210. EB.addEventListener('gamestart', cfuncs.apply);
  211. EB.addEventListener('gameover', cfuncs.remove);
  212. EB.addEventListener('colorpanelopen', cfuncs.enable);
  213. EB.addEventListener('colorpanel', cfuncs.correct);
  214.  
  215. function initEvents() {
  216. // Game status events
  217. EB.setEventTrigger('game', gaming);
  218. EB.setEventTrigger('nogame', () => (!gaming()));
  219.  
  220. // Game status change events
  221. let game_on = gaming();
  222. EB.setEventTrigger('gamestart', function() {
  223. if (!game_on && gaming()) {
  224. game_on = gaming();
  225. return true;
  226. }
  227. return false;
  228. });
  229. EB.setEventTrigger('gameover', function() {
  230. if (game_on && !gaming()) {
  231. game_on = gaming();
  232. return true;
  233. }
  234. return false;
  235. });
  236.  
  237. // Color-select panel displayed
  238. const display = () => ($('center>div>.supporter-circle') ? true : false);
  239. let display_on = false;
  240. EB.setEventTrigger('colorpanelopen', function() {
  241. const returnValue = (!display_on && display()) ? true : false;
  242. display_on = display();
  243. return returnValue;
  244. });
  245. EB.setEventTrigger('colorpanel', display);
  246. }
  247. }
  248.  
  249. function myCss() {
  250. addStyle(CONST.Css.alertify);
  251. }
  252.  
  253. function clearAds() {
  254. const adsSelectors = ['#main-menu-upsell-banner', '#custom-queue-ad', '#custom-queue-ad-skyscraper', '#custom-queue-ad-top', 'center>div.tips-banner+div', '#replay-ad-container'];
  255. $('#custom-queue-content') && ($('#custom-queue-content').style.marginLeft = '0px');
  256. for (const ad of adsSelectors) {
  257. $H(ad);
  258. }
  259. }
  260.  
  261. function mapEnhance() {
  262. const elmMap = $('#gameMap');
  263. const capital = $('.general');
  264.  
  265. markForever();
  266.  
  267. // Once a grid had been visible, keep its type visible forever
  268. function markForever() {
  269. // Data
  270. const grids = elmMap.querySelectorAll('td');
  271. const fields = [];
  272. const mounts = [];
  273. const cities = [];
  274. const capitals = [];
  275.  
  276. // Launch
  277. const interval = setInterval(mark, CONST.Number.Interval*3);
  278. EB.addEventListener('gameover', () => (clearInterval(interval)));
  279. mark();
  280.  
  281. function mark() {
  282. if (!$('#gameMap')) {
  283. clearInterval(interval);
  284. mapEnhance();
  285. return false;
  286. }
  287. for (const grid of grids) {
  288. const classes = grid.className.split(' ');
  289.  
  290. if (classes.includes('city') && !cities.includes(grid)) {
  291. cities.push(grid);
  292. keepBgImage(grid);
  293. DoLog(LogLevel.Success, 'Marked a city.');
  294. } else if (classes.includes('general') && !capitals.includes(grid)) {
  295. capitals.push(grid);
  296. keepBgImage(grid);
  297. DoLog(LogLevel.Success, 'Marked a capital.');
  298. } else if (classes.includes('mountain') && !mounts.includes(grid)) {
  299. mounts.push(grid);
  300. keepBgImage(grid);
  301. DoLog(LogLevel.Success, 'Marked a Mountain.');
  302. } else if (!classes.includes('obstacle') && !fields.includes(grid)) {
  303. fields.push(grid);
  304. }
  305. }
  306.  
  307. for (const visible_grid of mounts.concat(cities).concat(capitals)) {
  308. markVisible(visible_grid);
  309. }
  310. }
  311.  
  312. function keepBgImage(grid) {
  313. const computedStyle = getComputedStyle(grid);
  314. grid.style.backgroundImage = computedStyle.backgroundImage;
  315. grid.style.backgroundRepeat = computedStyle.backgroundRepeat;
  316. grid.style.backgroundPosition = computedStyle.backgroundPosition;
  317. }
  318. function markVisible(grid) {
  319. grid.classList.remove('fog');
  320. }
  321. }
  322. }
  323.  
  324. // Custom preferred Color without premium account
  325. function colorCustom() {
  326. GM_registerMenuCommand(CONST.Text[i18n].CustomColor, custom);
  327. const cssid = 'User-Preferred-Color';
  328.  
  329. function custom() {
  330. const box = alertify.prompt(CONST.Text[i18n].ColorTitle, CONST.Text[i18n].EnterColor.replace('{AC}', CONST.Colors.join(', ')), CONST.Text[i18n].DefaultColor, colorGot, cancel);
  331.  
  332. function colorGot(e, color) {
  333. if (!CONST.Colors.includes(color)) {
  334. alertify.alert(CONST.Text[i18n].InvalidColor);
  335. }
  336. GM_setValue(CONST.Storage.Key.CustomColor, color);
  337. apply();
  338. }
  339.  
  340. function cancel() {}
  341. }
  342.  
  343. function apply() {
  344. // Get color provided by server
  345. const elmMap = $('#gameMap');
  346. const capital = $('.general');
  347. const myColor = getColor(capital);
  348. const myValue = getColorValue(myColor);
  349. const color = GM_getValue(CONST.Storage.Key.CustomColor, '');
  350. color && loadColor(color);
  351.  
  352. // Apply user preferred color
  353. function loadColor(color) {
  354. const userColor = color;
  355. const userValue = getColorValue(userColor);
  356. addStyle(CONST.Css.CustomColor.replace('{MC}', myColor).replace('{MCV}', myValue).replace('{UC}', userColor).replace('{UCV}', userValue), cssid);
  357. }
  358. }
  359.  
  360. function remove() {
  361. $R('#'+cssid);
  362. }
  363.  
  364. function enable() {
  365. const div = $('center>div>.supporter-circle').parentElement;
  366. div.style.pointerEvents = 'auto';
  367. let selected;
  368. for (const span of div.children) {
  369. // Transmition effect
  370. span.style.transitionDuration = '0.3s';
  371.  
  372. // Display current preferred color
  373. const color = span.style.boxShadow.match(/rgb\(\d+, \d+, \d+\)/)[0];
  374. if (isSameColor(color, getColorValue(GM_getValue(CONST.Storage.Key.CustomColor)))) {
  375. span.style.boxShadow = span.style.boxShadow.replace('0px 0px 0px 15px inset', '0px 0px 0px 3px inset');
  376. selected = span;
  377. } else {
  378. span.style.boxShadow = span.style.boxShadow.replace('0px 0px 0px 3px inset', '0px 0px 0px 15px inset');
  379. }
  380.  
  381. // Save when clicked
  382. span.addEventListener('click', circleClick);
  383. }
  384.  
  385. function circleClick(e) {
  386. const span = e.target;
  387. const color = span.style.boxShadow.match(/rgb\(\d+, \d+, \d+\)/)[0];
  388. GM_setValue(CONST.Storage.Key.CustomColor, getColorName(color));
  389.  
  390. // Display
  391. span.style.boxShadow = span.style.boxShadow.replace('0px 0px 0px 15px inset', '0px 0px 0px 3px inset');
  392. selected.style.boxShadow = selected.style.boxShadow.replace('0px 0px 0px 3px inset', '0px 0px 0px 15px inset');
  393. selected = span;
  394. }
  395. }
  396.  
  397. function correct() {
  398. const div = $('center>div>.supporter-circle').parentElement;
  399. div.style.pointerEvents = 'auto';
  400.  
  401. for (const span of div.children) {
  402. // Transmition effect
  403. span.style.transitionDuration = '0.3s';
  404.  
  405. // unselect not preferred color
  406. const color = span.style.boxShadow.match(/rgb\(\d+, \d+, \d+\)/)[0];
  407. if (!isSameColor(color, getColorValue(GM_getValue(CONST.Storage.Key.CustomColor)))) {
  408. span.style.boxShadow = span.style.boxShadow.replace('0px 0px 0px 3px inset', '0px 0px 0px 15px inset');
  409. }
  410. }
  411. }
  412.  
  413. return {
  414. custom: custom,
  415. apply: apply,
  416. remove: remove,
  417. enable: enable,
  418. correct: correct
  419. }
  420. }
  421.  
  422. // Whether gameboard exists
  423. function gaming() {
  424. const API = getAPI();
  425. return $('#gameMap .general') && (!API || !['replays', 'mapcreator'].includes(API[0])) ? true : false;
  426. }
  427.  
  428. // Get the color of a grid
  429. function getColor(grid) {
  430. for (const cls of grid.classList) {
  431. if (CONST.Colors.includes(cls)) {
  432. return cls;
  433. }
  434. }
  435. }
  436.  
  437. // Get the value of color name
  438. function getColorValue(color) {
  439. const elm = document.createElement('td');
  440. elm.classList.add(color);
  441. document.body.appendChild(elm);
  442. const value = getComputedStyle(elm).backgroundColor;
  443. document.body.removeChild(elm);
  444. return value;
  445. }
  446.  
  447. // Get the name of color value
  448. function getColorName(color) {
  449. for (const [name, value] of Object.entries(CONST.Color)) {
  450. if (isSameColor(color, value)) {
  451. return name;
  452. }
  453. }
  454. }
  455.  
  456. // Whether two given color is the same
  457. function isSameColor(c1, c2) {
  458. const rgb = /^rgb/;
  459. c1 = c1.trim().toLowerCase();
  460. c2 = c2.trim().toLowerCase();
  461. !rgb.test(c1) && (c1 = CONST.Color[c1]);
  462. !rgb.test(c2) && (c2 = CONST.Color[c2]);
  463. c1 = c1.replaceAll(', ', ',').replaceAll(' ', ',');
  464. c2 = c2.replaceAll(', ', ',').replaceAll(' ', ',');
  465. return c1 === c2;
  466. }
  467.  
  468. // Basic functions
  469. function $(e) {return document.querySelector(e);}
  470. function $R(e) {const el = $(e); return el && el.parentElement.removeChild(el);}
  471. function $H(e) {const el = $(e); return el && (el.style.display = 'none');}
  472.  
  473. function loadinResourceCSS() {
  474. for (const res of NMonkey_Info.resources) {
  475. const css = GM_getResourceText(res.name);
  476. css && addStyle(css);
  477. }
  478. }
  479.  
  480. function EventBroadcast() {
  481. const EB = this;
  482. const _EB = {};
  483.  
  484. // Init
  485. _EB.event = {};
  486. _EB.time = 500;
  487. _EB.interval = null;
  488. _EB.listeners = {};
  489. _EB.name = null;
  490. defineProxy(EB, _EB, 'event', false);
  491. defineProxy(EB, _EB, 'time', true);
  492. defineProxy(EB, _EB, 'interval', false);
  493. defineProxy(EB, _EB, 'name', true);
  494.  
  495. // Start listen-interval
  496. setTimeout(EB.start, 0);
  497.  
  498. // Add an event trigger
  499. EB.setEventTrigger = function(eventName, trigger) {
  500. EB.initEvent(eventName);
  501. _EB.event[eventName].trigger = trigger;
  502. }
  503.  
  504. // Add an event listener
  505. EB.addEventListener = function(eventName, eventFunc, once=false) {
  506. const id = makeid();
  507. const listenerObj = {
  508. eventName: eventName,
  509. eventFunc: eventFunc,
  510. once: once
  511. }
  512.  
  513. // Add to event.listeners
  514. EB.initEvent(eventName);
  515. _EB.event[eventName].listeners[id] = listenerObj;
  516.  
  517. // Add to _EB.listeners
  518. _EB.listeners[id] = listenerObj;
  519. }
  520.  
  521. // Remove an event listener
  522. EB.removeEventListener = function(id) {
  523. if (_EB.listeners[id]) {
  524. const listenerObj = _EB.listeners[id];
  525.  
  526. // Remove from event.listeners
  527. delete _EB.event[listenerObj.eventName].listeners[id];
  528.  
  529. // Remove from _EB.listeners
  530. delete _EB.listeners[id];
  531. }
  532. }
  533.  
  534. // Init an event obj
  535. EB.initEvent = function(eventName=null) {
  536. const event = _EB.event[eventName] || {trigger: null, listeners: {}};
  537. !_EB.event[eventName] && Object.defineProperty(event.listeners, 'length', {
  538. configurable: false,
  539. enumerable: false,
  540. get: function() {return Object.keys(event.listeners).length;}
  541. });
  542. eventName && !_EB.event[eventName] && (_EB.event[eventName] = event);
  543. return event;
  544. }
  545.  
  546. // Get all listeners of an event or all events
  547. EB.getEventListeners = function(eventName) {
  548. const event = _EB.event[eventName] || EB.initEvent();
  549. return event.listeners;
  550. }
  551.  
  552. // Remove all event listeners
  553. EB.removeAllListeners = function(eventName=null) {
  554. if (eventName) {
  555. const event = _EB.event[eventName] || EB.initEvent();
  556. for (const [id, listenerObj] of Object.entries(event.listeners)) {
  557. EB.removeEventListener(id);
  558. }
  559. } else {
  560. for (const [id, listenerObj] of Object.entries(_EB.listeners)) {
  561. EB.removeEventListener(id);
  562. }
  563. }
  564. }
  565.  
  566. // Start listening
  567. EB.start = function() {
  568. //_EB.interval = setInterval(EB.listen, _EB.time);
  569. next();
  570.  
  571. function next() {
  572. _EB.interval = setTimeout(next, _EB.time);
  573. EB.listen();
  574. }
  575. }
  576.  
  577. // Stop listening
  578. EB.stop = function() {
  579. clearInterval(_EB.interval);
  580. _EB.interval = null;
  581. }
  582.  
  583. EB.listen = function() {
  584. for (const [eventName, event] of Object.entries(_EB.event)) {
  585. if (event.trigger()) {
  586. //DoLog((_EB.name ? _EB.name + ': ' : '') + 'Dispatching {} event'.replace('{}', eventName));
  587. for (const [id, listenerObj] of Object.entries(event.listeners)) {
  588. listenerObj.eventFunc();
  589. listenerObj.once && EB.removeEventListener(id);
  590. }
  591. }
  592. }
  593. }
  594.  
  595. const makeid = (function() {
  596. let cur = 0;
  597. return function() {
  598. return ++cur;
  599. }
  600. }) ();
  601.  
  602. function defineProxy(outerObj, innerObj, propName, writable) {
  603. Object.defineProperty(outerObj, propName, {
  604. configurable: false,
  605. enumerable: true,
  606. get: function() {
  607. return innerObj[propName];
  608. },
  609. set: function(newValue) {
  610. writable && (innerObj[propName] = newValue);
  611. }
  612. })
  613. }
  614. }
  615.  
  616. // Just stopPropagation and preventDefault
  617. function destroyEvent(e) {
  618. if (!e) {return false;};
  619. if (!e instanceof Event) {return false;};
  620. e.stopPropagation();
  621. e.preventDefault();
  622. }
  623.  
  624. // GM_XHR HOOK: The number of running GM_XHRs in a time must under maxXHR
  625. // Returns the abort function to stop the request anyway(no matter it's still waiting, or requesting)
  626. // (If the request is invalid, such as url === '', will return false and will NOT make this request)
  627. // If the abort function called on a request that is not running(still waiting or finished), there will be NO onabort event
  628. // Requires: function delItem(){...} & function uniqueIDMaker(){...}
  629. function GMXHRHook(maxXHR=5) {
  630. const GM_XHR = GM_xmlhttpRequest;
  631. const getID = uniqueIDMaker();
  632. let todoList = [], ongoingList = [];
  633. GM_xmlhttpRequest = safeGMxhr;
  634.  
  635. function safeGMxhr() {
  636. // Get an id for this request, arrange a request object for it.
  637. const id = getID();
  638. const request = {id: id, args: arguments, aborter: null};
  639.  
  640. // Deal onload function first
  641. dealEndingEvents(request);
  642.  
  643. /* DO NOT DO THIS! KEEP ITS ORIGINAL PROPERTIES!
  644. // Stop invalid requests
  645. if (!validCheck(request)) {
  646. return false;
  647. }
  648. */
  649.  
  650. // Judge if we could start the request now or later?
  651. todoList.push(request);
  652. checkXHR();
  653. return makeAbortFunc(id);
  654.  
  655. // Decrease activeXHRCount while GM_XHR onload;
  656. function dealEndingEvents(request) {
  657. const e = request.args[0];
  658.  
  659. // onload event
  660. const oriOnload = e.onload;
  661. e.onload = function() {
  662. reqFinish(request.id);
  663. checkXHR();
  664. oriOnload ? oriOnload.apply(null, arguments) : function() {};
  665. }
  666.  
  667. // onerror event
  668. const oriOnerror = e.onerror;
  669. e.onerror = function() {
  670. reqFinish(request.id);
  671. checkXHR();
  672. oriOnerror ? oriOnerror.apply(null, arguments) : function() {};
  673. }
  674.  
  675. // ontimeout event
  676. const oriOntimeout = e.ontimeout;
  677. e.ontimeout = function() {
  678. reqFinish(request.id);
  679. checkXHR();
  680. oriOntimeout ? oriOntimeout.apply(null, arguments) : function() {};
  681. }
  682.  
  683. // onabort event
  684. const oriOnabort = e.onabort;
  685. e.onabort = function() {
  686. reqFinish(request.id);
  687. checkXHR();
  688. oriOnabort ? oriOnabort.apply(null, arguments) : function() {};
  689. }
  690. }
  691.  
  692. // Check if the request is invalid
  693. function validCheck(request) {
  694. const e = request.args[0];
  695.  
  696. if (!e.url) {
  697. return false;
  698. }
  699.  
  700. return true;
  701. }
  702.  
  703. // Call a XHR from todoList and push the request object to ongoingList if called
  704. function checkXHR() {
  705. if (ongoingList.length >= maxXHR) {return false;};
  706. if (todoList.length === 0) {return false;};
  707. const req = todoList.shift();
  708. const reqArgs = req.args;
  709. const aborter = GM_XHR.apply(null, reqArgs);
  710. req.aborter = aborter;
  711. ongoingList.push(req);
  712. return req;
  713. }
  714.  
  715. // Make a function that aborts a certain request
  716. function makeAbortFunc(id) {
  717. return function() {
  718. let i;
  719.  
  720. // Check if the request haven't been called
  721. for (i = 0; i < todoList.length; i++) {
  722. const req = todoList[i];
  723. if (req.id === id) {
  724. // found this request: haven't been called
  725. delItem(todoList, i);
  726. return true;
  727. }
  728. }
  729.  
  730. // Check if the request is running now
  731. for (i = 0; i < ongoingList.length; i++) {
  732. const req = todoList[i];
  733. if (req.id === id) {
  734. // found this request: running now
  735. req.aborter();
  736. reqFinish(id);
  737. checkXHR();
  738. }
  739. }
  740.  
  741. // Oh no, this request is already finished...
  742. return false;
  743. }
  744. }
  745.  
  746. // Remove a certain request from ongoingList
  747. function reqFinish(id) {
  748. let i;
  749. for (i = 0; i < ongoingList.length; i++) {
  750. const req = ongoingList[i];
  751. if (req.id === id) {
  752. ongoingList = delItem(ongoingList, i);
  753. return true;
  754. }
  755. }
  756. return false;
  757. }
  758. }
  759. }
  760.  
  761. // Get a url argument from lacation.href
  762. // also recieve a function to deal the matched string
  763. // returns defaultValue if name not found
  764. // Args: name, dealFunc=(function(a) {return a;}), defaultValue=null
  765. function getUrlArgv(details) {
  766. typeof(details) === 'string' && (details = {name: details});
  767. typeof(details) === 'undefined' && (details = {});
  768. if (!details.name) {return null;};
  769.  
  770. const url = details.url ? details.url : location.href;
  771. const name = details.name ? details.name : '';
  772. const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;});
  773. const defaultValue = details.defaultValue ? details.defaultValue : null;
  774. const matcher = new RegExp(name + '=([^&]+)');
  775. const result = url.match(matcher);
  776. const argv = result ? dealFunc(result[1]) : defaultValue;
  777.  
  778. return argv;
  779. }
  780.  
  781. // Append a style text to document(<head>) with a <style> element
  782. function addStyle(css, id) {
  783. const style = document.createElement("style");
  784. id && (style.id = id);
  785. style.textContent = css;
  786. for (const elm of document.querySelectorAll('#'+id)) {
  787. elm.parentElement && elm.parentElement.removeChild(elm);
  788. }
  789. document.head.appendChild(style);
  790. }
  791.  
  792. // File download function
  793. // details looks like the detail of GM_xmlhttpRequest
  794. // onload function will be called after file saved to disk
  795. function downloadFile(details) {
  796. if (!details.url || !details.name) {return false;};
  797.  
  798. // Configure request object
  799. const requestObj = {
  800. url: details.url,
  801. responseType: 'blob',
  802. onload: function(e) {
  803. // Save file
  804. saveFile(URL.createObjectURL(e.response), details.name);
  805.  
  806. // onload callback
  807. details.onload ? details.onload(e) : function() {};
  808. }
  809. }
  810. if (details.onloadstart ) {requestObj.onloadstart = details.onloadstart;};
  811. if (details.onprogress ) {requestObj.onprogress = details.onprogress;};
  812. if (details.onerror ) {requestObj.onerror = details.onerror;};
  813. if (details.onabort ) {requestObj.onabort = details.onabort;};
  814. if (details.onreadystatechange) {requestObj.onreadystatechange = details.onreadystatechange;};
  815. if (details.ontimeout ) {requestObj.ontimeout = details.ontimeout;};
  816.  
  817. // Send request
  818. GM_xmlhttpRequest(requestObj);
  819. }
  820.  
  821. // get '/' splited API array from a url
  822. function getAPI(url=location.href) {
  823. return url.replace(/https?:\/\/(.*?\.){1,2}.*?\//, '').replace(/\?.*/, '').match(/[^\/]+?(?=(\/|$))/g);
  824. }
  825.  
  826. // get host part from a url(includes '^https://', '/$')
  827. function getHost(url=location.href) {
  828. const match = location.href.match(/https?:\/\/[^\/]+\//);
  829. return match ? match[0] : match;
  830. }
  831.  
  832. function AsyncManager() {
  833. const AM = this;
  834.  
  835. // Ongoing xhr count
  836. this.taskCount = 0;
  837.  
  838. // Whether generate finish events
  839. let finishEvent = false;
  840. Object.defineProperty(this, 'finishEvent', {
  841. configurable: true,
  842. enumerable: true,
  843. get: () => (finishEvent),
  844. set: (b) => {
  845. finishEvent = b;
  846. b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
  847. }
  848. });
  849.  
  850. // Add one task
  851. this.add = () => (++AM.taskCount);
  852.  
  853. // Finish one task
  854. this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
  855. }
  856.  
  857. // NMonkey By PY-DNG, 2021.07.18 - 2022.02.18, License GPL-3
  858. // NMonkey: Provides GM_Polyfills and make your userscript compatible with non-script-manager environment
  859. // Description:
  860. /*
  861. Simulates a script-manager environment("NMonkey Environment") for non-script-manager browser, load @require & @resource, provides some GM_functions(listed below), and also compatible with script-manager environment.
  862. Provides GM_setValue, GM_getValue, GM_deleteValue, GM_listValues, GM_xmlhttpRequest, GM_openInTab, GM_setClipboard, GM_getResourceText, GM_getResourceURL, GM_addStyle, GM_addElement, GM_log, unsafeWindow(object), GM_info(object)
  863. Also provides an object called GM_POLYFILLED which has the following properties that shows you which GM_functions are actually polyfilled.
  864. Returns true if polyfilled is environment is ready, false for not. Don't worry, just follow the usage below.
  865. */
  866. // Note: DO NOT DEFINE GM-FUNCTION-NAMES IN YOUR CODE. DO NOT DEFINE GM_POLYFILLED AS WELL.
  867. // Note: NMonkey is an advanced version of GM_PolyFill (and BypassXB), it includes more functions than GM_PolyFill, and provides better stability and compatibility. Do NOT use NMonkey and GM_PolyFill (and BypassXB) together in one script.
  868. // Usage:
  869. /*
  870. // ==UserScript==
  871. // @name xxx
  872. // @namespace xxx
  873. // @version 1.0
  874. // ...
  875. // @require https://.../xxx.js
  876. // @require ...
  877. // ...
  878. // @resource https://.../xxx
  879. // @resource ...
  880. // ...
  881. // ==/UserScript==
  882.  
  883. // Use a closure to wrap your code. Make sure you have it a name.
  884. (function YOUR_MAIN_FUNCTION() {
  885. 'use strict';
  886. // Strict mode is optional. You can use strict mode or not as you want.
  887. // Polyfill first. Do NOT do anything before Polyfill.
  888. var NMonkey_Ready = NMonkey({
  889. mainFunc: YOUR_MAIN_FUNCTION,
  890. name: "script-storage-key, aims to separate different scripts' storage area. Use your script's @namespace value if you don't how to fill this field.",
  891. requires: [
  892. {
  893. src: "https://.../xxx.js",
  894. loaded: function() {return boolean_value_shows_whether_this_js_has_already_loaded;}
  895. execmode: "'eval' for eval code in current scope or 'function' for Function(code)() in global scope or 'script' for inserting a <script> element to document.head"
  896. },
  897. ...
  898. ],
  899. resources: [
  900. {
  901. src: "https://.../xxx"
  902. name: "@resource name. Will try to get it from @resource using this name before fetch it from src",
  903. },
  904. ...
  905. ],
  906. GM_info: {
  907. // You can get GM_info object, if you provide this argument(and there is no GM_info provided by the script-manager).
  908. // You can provide any object here, what you provide will be what you get.
  909. // Additionally, two property of NMonkey itself will be attached to GM_info if polyfilled:
  910. // {
  911. // scriptHandler: "NMonkey"
  912. // version: "NMonkey's version, it should look like '0.1'"
  913. // }
  914. // The following is just an example.
  915. script: {
  916. name: 'my first userscript for non-scriptmanager browsers!',
  917. description: 'this script works well both in my PC and my mobile!',
  918. version: '1.0',
  919. released: true,
  920. version_num: 1,
  921. authors: ['Johnson', 'Leecy', 'War Mars']
  922. update_history: {
  923. '0.9': 'First beta version',
  924. '1.0': 'Finally released!'
  925. }
  926. }
  927. surprise: 'if you check GM_info.surprise and you will read this!'
  928. // And property "scriptHandler" & "version" will be attached here
  929. }
  930. });
  931. if (!NMonkey_Ready) {
  932. // Stop executing of polyfilled environment not ready.
  933. // Don't worry, during polyfill progress YOUR_MAIN_FUNCTION will be called twice, and on the second call the polyfilled environment will be ready.
  934. return;
  935. }
  936.  
  937. // Your code here...
  938. // Make sure your code is written after NMonkey be called
  939. if
  940. // ...
  941.  
  942. // Just place NMonkey function code here
  943. function NMonkey(details) {
  944. ...
  945. }
  946. }) ();
  947.  
  948. // Oh you want to write something here? Fine. But code you write here cannot get into the simulated script-manager-environment.
  949. */
  950. function NMonkey(details) {
  951. // Init DoLog
  952. DoLog();
  953.  
  954. // Get argument
  955. const mainFunc = details.mainFunc;
  956. const name = details.name || 'default';
  957. const requires = details.requires || [];
  958. const resources = details.resources || [];
  959. details.GM_info = details.GM_info || {};
  960. details.GM_info.scriptHandler = 'NMonkey';
  961. details.GM_info.version = '1.0';
  962.  
  963. // Run in variable-name-polifilled environment
  964. if (InNPEnvironment()) {
  965. // Already in polifilled environment === polyfill has alredy done, just return
  966. return true;
  967. }
  968.  
  969. // Not in polifilled environment, then polyfill functions and create & move into the environment
  970. // Bypass xbrowser's useless GM_functions
  971. bypassXB();
  972.  
  973. // Start polyfill
  974. const GM_POLYFILL_KEY_STORAGE = 'GM_STORAGE_POLYFILL';
  975. let GM_POLYFILL_storage;
  976. const Supports = {
  977. GetStorage: function() {
  978. let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
  979. gstorage = gstorage ? JSON.parse(gstorage) : {};
  980. let storage = gstorage[name] ? gstorage[name] : {};
  981. return storage;
  982. },
  983.  
  984. SaveStorage: function() {
  985. let gstorage = localStorage.getItem(GM_POLYFILL_KEY_STORAGE);
  986. gstorage = gstorage ? JSON.parse(gstorage) : {};
  987. gstorage[name] = GM_POLYFILL_storage;
  988. localStorage.setItem(GM_POLYFILL_KEY_STORAGE, JSON.stringify(gstorage));
  989. },
  990. };
  991. const Provides = {
  992. // GM_setValue
  993. GM_setValue: function(name, value) {
  994. GM_POLYFILL_storage = Supports.GetStorage();
  995. name = String(name);
  996. GM_POLYFILL_storage[name] = value;
  997. Supports.SaveStorage();
  998. },
  999.  
  1000. // GM_getValue
  1001. GM_getValue: function(name, defaultValue) {
  1002. GM_POLYFILL_storage = Supports.GetStorage();
  1003. name = String(name);
  1004. if (GM_POLYFILL_storage.hasOwnProperty(name)) {
  1005. return GM_POLYFILL_storage[name];
  1006. } else {
  1007. return defaultValue;
  1008. }
  1009. },
  1010.  
  1011. // GM_deleteValue
  1012. GM_deleteValue: function(name) {
  1013. GM_POLYFILL_storage = Supports.GetStorage();
  1014. name = String(name);
  1015. if (GM_POLYFILL_storage.hasOwnProperty(name)) {
  1016. delete GM_POLYFILL_storage[name];
  1017. Supports.SaveStorage();
  1018. }
  1019. },
  1020.  
  1021. // GM_listValues
  1022. GM_listValues: function() {
  1023. GM_POLYFILL_storage = Supports.GetStorage();
  1024. return Object.keys(GM_POLYFILL_storage);
  1025. },
  1026.  
  1027. // unsafeWindow
  1028. unsafeWindow: window,
  1029.  
  1030. // GM_xmlhttpRequest
  1031. // not supported properties of details: synchronous binary nocache revalidate context fetch
  1032. // not supported properties of response(onload arguments[0]): finalUrl
  1033. // ---!IMPORTANT!--- DOES NOT SUPPORT CROSS-ORIGIN REQUESTS!!!!! ---!IMPORTANT!---
  1034. // details.synchronous is not supported as Tampermonkey
  1035. GM_xmlhttpRequest: function(details) {
  1036. const xhr = new XMLHttpRequest();
  1037.  
  1038. // open request
  1039. const openArgs = [details.method, details.url, true];
  1040. if (details.user && details.password) {
  1041. openArgs.push(details.user);
  1042. openArgs.push(details.password);
  1043. }
  1044. xhr.open.apply(xhr, openArgs);
  1045.  
  1046. // set headers
  1047. if (details.headers) {
  1048. for (const key of Object.keys(details.headers)) {
  1049. xhr.setRequestHeader(key, details.headers[key]);
  1050. }
  1051. }
  1052. details.cookie ? xhr.setRequestHeader('cookie', details.cookie) : function () {};
  1053. details.anonymous ? xhr.setRequestHeader('cookie', '') : function () {};
  1054.  
  1055. // properties
  1056. xhr.timeout = details.timeout;
  1057. xhr.responseType = details.responseType;
  1058. details.overrideMimeType ? xhr.overrideMimeType(details.overrideMimeType) : function () {};
  1059.  
  1060. // events
  1061. xhr.onabort = details.onabort;
  1062. xhr.onerror = details.onerror;
  1063. xhr.onloadstart = details.onloadstart;
  1064. xhr.onprogress = details.onprogress;
  1065. xhr.onreadystatechange = details.onreadystatechange;
  1066. xhr.ontimeout = details.ontimeout;
  1067. xhr.onload = function (e) {
  1068. const response = {
  1069. readyState: xhr.readyState,
  1070. status: xhr.status,
  1071. statusText: xhr.statusText,
  1072. responseHeaders: xhr.getAllResponseHeaders(),
  1073. response: xhr.response
  1074. };
  1075. (details.responseType === '' || details.responseType === 'text') ? (response.responseText = xhr.responseText) : function () {};
  1076. (details.responseType === '' || details.responseType === 'document') ? (response.responseXML = xhr.responseXML) : function () {};
  1077. details.onload(response);
  1078. }
  1079.  
  1080. // send request
  1081. details.data ? xhr.send(details.data) : xhr.send();
  1082.  
  1083. return {
  1084. abort: xhr.abort
  1085. };
  1086. },
  1087.  
  1088. // NOTE: options(arg2) is NOT SUPPORTED! if provided, then will just be skipped.
  1089. GM_openInTab: function(url) {
  1090. window.open(url);
  1091. },
  1092.  
  1093. // NOTE: needs to be called in an event handler function, and info(arg2) is NOT SUPPORTED!
  1094. GM_setClipboard: function(text) {
  1095. // Create a new textarea for copying
  1096. const newInput = document.createElement('textarea');
  1097. document.body.appendChild(newInput);
  1098. newInput.value = text;
  1099. newInput.select();
  1100. document.execCommand('copy');
  1101. document.body.removeChild(newInput);
  1102. },
  1103.  
  1104. GM_getResourceText: function(name) {
  1105. const _get = typeof(GM_getResourceText) === 'function' ? GM_getResourceText : () => (null);
  1106. let text = _get(name);
  1107. if (text) {return text;}
  1108. for (const resource of resources) {
  1109. if (resource.name === name) {
  1110. return resource.content ? resource.content : null;
  1111. }
  1112. }
  1113. return null;
  1114. },
  1115.  
  1116. GM_getResourceURL: function(name) {
  1117. const _get = typeof(GM_getResourceURL) === 'function' ? GM_getResourceURL : () => (null);
  1118. let url = _get(name);
  1119. if (url) {return url;}
  1120. for (const resource of resources) {
  1121. if (resource.name === name) {
  1122. return resource.src ? btoa(resource.src) : null;
  1123. }
  1124. }
  1125. return null;
  1126. },
  1127.  
  1128. GM_addStyle: function(css) {
  1129. const style = document.createElement('style');
  1130. style.innerHTML = css;
  1131. document.head.appendChild(style)
  1132. },
  1133.  
  1134. GM_addElement: function() {
  1135. let parent_node, tag_name, attributes;
  1136. const head_elements = ['title', 'base', 'link', 'style', 'meta', 'script', 'noscript'/*, 'template'*/];
  1137. if (arguments.length === 2) {
  1138. tag_name = arguments[0];
  1139. attributes = arguments[1];
  1140. parent_node = head_elements.includes(tag_name.toLowerCase()) ? document.head : document.body;
  1141. } else if (arguments.length === 3) {
  1142. parent_node = arguments[0];
  1143. tag_name = arguments[1];
  1144. attributes = arguments[2];
  1145. }
  1146. const element = document.createElement(tag_name);
  1147. for (const [prop, value] of Object.entries(attributes)) {
  1148. element[prop] = value;
  1149. }
  1150. parent_node.appendChild(element);
  1151. },
  1152.  
  1153. GM_log: function() {
  1154. const args = []
  1155. for (let i = 0; i < arguments.length; i++) {
  1156. args[i] = arguments[i];
  1157. }
  1158. console.log.apply(null, args);
  1159. },
  1160.  
  1161. GM_info: details.GM_info,
  1162.  
  1163. GM: {info: details.GM_info}
  1164. };
  1165. const _GM_POLYFILLED = Provides.GM_POLYFILLED = {};
  1166. for (const pname of Object.keys(Provides)) {
  1167. _GM_POLYFILLED[pname] = true;
  1168. }
  1169.  
  1170. // Create & move into polifilled environment
  1171. ExecInNPEnv();
  1172.  
  1173. return false;
  1174.  
  1175. // Bypass xbrowser's useless GM_functions
  1176. function bypassXB() {
  1177. if (typeof(mbrowser) === 'object' || (typeof(GM_info) === 'object' && GM_info.scriptHandler === 'XMonkey')) {
  1178. // Useless functions in XMonkey 1.0
  1179. const GM_funcs = [
  1180. 'unsafeWindow',
  1181. 'GM_getValue',
  1182. 'GM_setValue',
  1183. 'GM_listValues',
  1184. 'GM_deleteValue',
  1185. 'GM_xmlhttpRequest'
  1186. ];
  1187. for (const GM_func of GM_funcs) {
  1188. window[GM_func] = undefined;
  1189. eval('typeof({F}) === "function" && ({F} = undefined);'.replaceAll('{F}', GM_func));
  1190. }
  1191. // Delete dirty data saved by these stupid functions before
  1192. for (let i = 0; i < localStorage.length; i++) {
  1193. const key = localStorage.key(i);
  1194. const value = localStorage.getItem(key);
  1195. value === '[object Object]' && localStorage.removeItem(key);
  1196. }
  1197. }
  1198. }
  1199.  
  1200. // Check if already in name-predefined environment
  1201. // I think there won't be anyone else wants to use this fxxking variable name...
  1202. function InNPEnvironment() {
  1203. return (typeof(GM_POLYFILLED) === 'object' && GM_POLYFILLED !== null) ? true : false;
  1204. }
  1205.  
  1206. function ExecInNPEnv() {
  1207. const NG = new NameGenerator();
  1208.  
  1209. // Init names
  1210. const tnames = ['context', 'fapply', 'CDATA', 'uneval', 'define', 'module', 'exports', 'window', 'globalThis', 'console', 'cloneInto', 'exportFunction', 'createObjectIn', 'GM', 'GM_info'];
  1211. const pnames = Object.keys(Provides);
  1212. const fnames = tnames.slice();
  1213. const argvlist = [];
  1214. const argvs = [];
  1215.  
  1216. // Add provides
  1217. for (const pname of pnames) {
  1218. !fnames.includes(pname) && fnames.push(pname);
  1219. }
  1220.  
  1221. // Add grants
  1222. if (typeof(GM_info) === 'object' && GM_info.script && GM_info.script.grant) {
  1223. for (const gname of GM_info.script.grant) {
  1224. !fnames.includes(gname) && fnames.push(gname);
  1225. }
  1226. }
  1227.  
  1228. // Make name code
  1229. for (let i = 0; i < fnames.length; i++) {
  1230. const fname = fnames[i];
  1231. const exist = eval('typeof ' + fname) !== 'undefined' ? true : false;
  1232. argvlist[i] = exist ? fname : (Provides.hasOwnProperty(fname) ? 'Provides.'+fname : '');
  1233. argvs[i] = exist ? eval(fname) : (Provides.hasOwnProperty(fname) ? Provides[name] : undefined);
  1234. pnames.includes(fname) && (_GM_POLYFILLED[fname] = !exist);
  1235. }
  1236.  
  1237. // Load all @require and @resource
  1238. loadRequires(requires, resources, function(requires, resources) {
  1239. // Join requirecode
  1240. let requirecode = '';
  1241. for (const require of requires) {
  1242. const mode = require.execmode ? require.execmode : 'eval';
  1243. const content = require.content;
  1244. if (!content) {continue;}
  1245. switch(mode) {
  1246. case 'eval':
  1247. requirecode += content + '\n';
  1248. break;
  1249. case 'function': {
  1250. const func = Function.apply(null, fnames.concat(content));
  1251. func.apply(null, argvs);
  1252. break;
  1253. }
  1254. case 'script': {
  1255. const s = document.createElement('script');
  1256. s.innerHTML = content;
  1257. document.head.appendChild(s);
  1258. break;
  1259. }
  1260. }
  1261.  
  1262. }
  1263.  
  1264. // Make final code & eval
  1265. const varnames = ['NG', 'tnames', 'pnames', 'fnames', 'argvist', 'argvs', 'code', 'finalcode', 'wrapper', 'ExecInNPEnv', 'GM_POLYFILL_KEY_STORAGE', 'GM_POLYFILL_storage', 'InNPEnvironment', 'NameGenerator', 'LocalCDN', 'loadRequires', 'requestText', 'Provides', 'Supports', 'bypassXB', 'details', 'mainFunc', 'name', 'requires', 'resources', '_GM_POLYFILLED', 'NMonkey', 'polyfill_status'];
  1266. const code = requirecode + 'let ' + varnames.join(', ') + ';\n(' + mainFunc.toString() + ') ();';
  1267. const wrapper = Function.apply(null, fnames.concat(code));
  1268. const finalcode = '(' + wrapper.toString() + ').apply(this, [' + argvlist.join(', ') + ']);';
  1269. eval(finalcode);
  1270. });
  1271.  
  1272. function NameGenerator() {
  1273. const NG = this;
  1274. const letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
  1275. let index = [0];
  1276.  
  1277. NG.generate = function() {
  1278. const chars = [];
  1279. indexIncrease();
  1280. for (let i = 0; i < index.length; i++) {
  1281. chars[i] = letters.charAt(index[i]);
  1282. }
  1283. return chars.join('');
  1284. }
  1285.  
  1286. NG.randtext = function(len=32) {
  1287. const chars = [];
  1288. for (let i = 0; i < len; i++) {
  1289. chars[i] = letters[randint(0, letter.length-1)];
  1290. }
  1291. return chars.join('');
  1292. }
  1293.  
  1294. function indexIncrease(i=0) {
  1295. index[i] === undefined && (index[i] = -1);
  1296. ++index[i] >= letters.length && (index[i] = 0, indexIncrease(i+1));
  1297. }
  1298.  
  1299. function randint(min, max) {
  1300. return Math.floor(Math.random() * (max - min + 1)) + min;
  1301. }
  1302. }
  1303. }
  1304.  
  1305. // Load all @require and @resource for non-GM/TM environments (such as Alook javascript extension)
  1306. // Requirements: function AsyncManager(){...}, function LocalCDN(){...}
  1307. function loadRequires(requires, resoures, callback, args=[]) {
  1308. // LocalCDN
  1309. const LCDN = new LocalCDN();
  1310.  
  1311. // AsyncManager
  1312. const AM = new AsyncManager();
  1313. AM.onfinish = function() {
  1314. callback.apply(null, [requires, resoures].concat(args));
  1315. }
  1316.  
  1317. // Load js
  1318. for (const js of requires) {
  1319. !js.loaded() && loadinJs(js);
  1320. }
  1321.  
  1322. // Load resource
  1323. for (const resource of resoures) {
  1324. loadinResource(resource);
  1325. }
  1326.  
  1327. AM.finishEvent = true;
  1328.  
  1329. function loadinJs(js) {
  1330. AM.add();
  1331. LCDN.get(js.src, function(content) {
  1332. js.content = content;
  1333. AM.finish();
  1334. });
  1335. }
  1336.  
  1337. function loadinResource(resource) {
  1338. let content;
  1339. if (typeof(GM_getResourceText) === 'function' && (content = GM_getResourceText(resource.name))) {
  1340. resource.content = content;
  1341. } else {
  1342. AM.add();
  1343. LCDN.get(resource.src, function(content) {
  1344. resource.content = content;
  1345. AM.finish();
  1346. });
  1347. }
  1348. }
  1349. }
  1350.  
  1351. // Loads web resources and saves them to GM-storage
  1352. // Tries to load web resources from GM-storage in subsequent calls
  1353. // Updates resources every $(this.expire) hours, or use $(this.refresh) function to update all resources instantly
  1354. // Dependencies: GM_getValue(), GM_setValue(), requestText(), AsyncManager(), KEY_LOCALCDN
  1355. function LocalCDN() {
  1356. const LC = this;
  1357. const GM_getValue = Provides.GM_getValue, GM_setValue = Provides.GM_setValue;
  1358.  
  1359. const KEY_LOCALCDN = 'LOCAL-CDN';
  1360. const KEY_LOCALCDN_VERSION = 'version';
  1361. const VALUE_LOCALCDN_VERSION = '0.2';
  1362.  
  1363. // Default expire time (by hour)
  1364. LC.expire = 72;
  1365.  
  1366. // Try to get resource content from loaclCDN first, if failed/timeout, request from web && save to LocalCDN
  1367. // Accepts callback only
  1368. // Returns true if got from LocalCDN, false if got from web
  1369. LC.get = function(url, callback, args=[]) {
  1370. const CDN = GM_getValue(KEY_LOCALCDN, {});
  1371. const resource = CDN[url];
  1372. const time = (new Date()).getTime();
  1373.  
  1374. if (resource && !expired(time, resource.time)) {
  1375. callback.apply(null, [resource.content].concat(args));
  1376. return true;
  1377. } else {
  1378. LC.request(url, function(content) {
  1379. callback.apply(null, [content].concat(args));
  1380. });
  1381. return false;
  1382. }
  1383. }
  1384.  
  1385. // Generate resource obj and set to CDN[url]
  1386. // Returns resource obj
  1387. LC.set = function(url, content) {
  1388. const CDN = GM_getValue(KEY_LOCALCDN, {});
  1389. const time = (new Date()).getTime();
  1390. const resource = {
  1391. url: url,
  1392. time: time,
  1393. content: content
  1394. }
  1395. CDN[url] = resource;
  1396. GM_setValue(KEY_LOCALCDN, CDN);
  1397. return resource;
  1398. }
  1399.  
  1400. // Delete one resource from LocalCDN
  1401. LC.delete = function(url) {
  1402. const CDN = GM_getValue(KEY_LOCALCDN, {});
  1403. if (!CDN[url]) {
  1404. return false;
  1405. } else {
  1406. delete CDN[url];
  1407. GM_setValue(KEY_LOCALCDN, CDN);
  1408. return true;
  1409. }
  1410. }
  1411.  
  1412. // Delete all resources in LocalCDN
  1413. LC.clear = function() {
  1414. GM_setValue(KEY_LOCALCDN, {});
  1415. upgradeConfig();
  1416. }
  1417.  
  1418. // List all resource saved in LocalCDN
  1419. LC.list = function() {
  1420. const CDN = GM_getValue(KEY_LOCALCDN, {});
  1421. const urls = LC.listurls();
  1422. const resources = [];
  1423.  
  1424. for (const url of urls) {
  1425. resources.push(CDN[url]);
  1426. }
  1427.  
  1428. return resources;
  1429. }
  1430.  
  1431. // List all resource's url saved in LocalCDN
  1432. LC.listurls = function() {
  1433. const CDN = GM_getValue(KEY_LOCALCDN, {});
  1434. const keys = Object.keys(CDN);
  1435. const urls = [];
  1436.  
  1437. for (const key of keys) {
  1438. if (key === KEY_LOCALCDN_VERSION) {continue;}
  1439. urls.push(key);
  1440. }
  1441.  
  1442. return urls;
  1443. }
  1444.  
  1445. // Request content from web and save it to CDN[url]
  1446. // Accepts callback only
  1447. LC.request = function(url, callback, args=[]) {
  1448. const CDN = GM_getValue(KEY_LOCALCDN, {});
  1449. requestText(url, function(content) {
  1450. LC.set(url, content);
  1451. callback.apply(null, [content].concat(args));
  1452. });
  1453. }
  1454.  
  1455. // Re-request all resources in CDN instantly, ignoring LC.expire
  1456. LC.refresh = function(callback, args=[]) {
  1457. const urls = LC.listurls();
  1458.  
  1459. const AM = new AsyncManager();
  1460. AM.onfinish = function() {
  1461. callback.apply(null, [].concat(args))
  1462. };
  1463.  
  1464. for (const url of urls) {
  1465. AM.add();
  1466. LC.request(url, function() {
  1467. AM.finish();
  1468. });
  1469. }
  1470.  
  1471. AM.finishEvent = true;
  1472. }
  1473.  
  1474. function upgradeConfig() {
  1475. const CDN = GM_getValue(KEY_LOCALCDN, {});
  1476. switch(CDN[KEY_LOCALCDN_VERSION]) {
  1477. case undefined:
  1478. init();
  1479. break;
  1480. case '0.1':
  1481. v01_To_v02();
  1482. logUpgrade();
  1483. break;
  1484. case VALUE_LOCALCDN_VERSION:
  1485. DoLog('LocalCDN is in latest version.');
  1486. break;
  1487. default:
  1488. DoLog(LogLevel.Error, 'LocalCDN.upgradeConfig: Invalid config version({V}) for LocalCDN. '.replace('{V}', CDN[KEY_LOCALCDN_VERSION]));
  1489. }
  1490. CDN[KEY_LOCALCDN_VERSION] = VALUE_LOCALCDN_VERSION;
  1491. GM_setValue(KEY_LOCALCDN, CDN);
  1492.  
  1493. function logUpgrade() {
  1494. DoLog(LogLevel.Success, 'LocalCDN successfully upgraded From v{V1} to v{V2}. '.replaceAll('{V1}', CDN[KEY_LOCALCDN_VERSION]).replaceAll('{V2}', VALUE_LOCALCDN_VERSION));
  1495. }
  1496.  
  1497. function init() {
  1498. // Nothing to do here
  1499. }
  1500.  
  1501. function v01_To_v02() {
  1502. const urls = LC.listurls();
  1503. for (const url of urls) {
  1504. if (url === KEY_LOCALCDN_VERSION) {continue;}
  1505. CDN[url] = {
  1506. url: url,
  1507. time: 0,
  1508. content: CDN[url]
  1509. };
  1510. }
  1511. }
  1512. }
  1513.  
  1514. function clearExpired() {
  1515. const resources = LC.list();
  1516. const time = (new Date()).getTime();
  1517.  
  1518. for (const resource of resources) {
  1519. expired(resource.time, time) && LC.delete(resource.url);
  1520. }
  1521. }
  1522.  
  1523. function expired(t1, t2) {
  1524. return (t2 - t1) > (LC.expire * 60 * 60 * 1000);
  1525. }
  1526.  
  1527. upgradeConfig();
  1528. clearExpired();
  1529. }
  1530.  
  1531. function requestText(url, callback, args=[]) {
  1532. Provides.GM_xmlhttpRequest({
  1533. method: 'GET',
  1534. url: url,
  1535. responseType: 'text',
  1536. onload: function(response) {
  1537. const text = response.responseText;
  1538. const argvs = [text].concat(args);
  1539. callback.apply(null, argvs);
  1540. }
  1541. })
  1542. }
  1543.  
  1544. function AsyncManager() {
  1545. const AM = this;
  1546.  
  1547. // Ongoing xhr count
  1548. this.taskCount = 0;
  1549.  
  1550. // Whether generate finish events
  1551. let finishEvent = false;
  1552. Object.defineProperty(this, 'finishEvent', {
  1553. configurable: true,
  1554. enumerable: true,
  1555. get: () => (finishEvent),
  1556. set: (b) => {
  1557. finishEvent = b;
  1558. b && AM.taskCount === 0 && AM.onfinish && AM.onfinish();
  1559. }
  1560. });
  1561.  
  1562. // Add one task
  1563. this.add = () => (++AM.taskCount);
  1564.  
  1565. // Finish one task
  1566. this.finish = () => ((--AM.taskCount === 0 && AM.finishEvent && AM.onfinish && AM.onfinish(), AM.taskCount));
  1567. }
  1568.  
  1569. // Arguments: level=LogLevel.Info, logContent, asObject=false
  1570. // Needs one call "DoLog();" to get it initialized before using it!
  1571. function DoLog() {
  1572. const win = typeof(unsafeWindow) !== 'undefined' ? unsafeWindow : window;
  1573.  
  1574. // Global log levels set
  1575. win.LogLevel = {
  1576. None: 0,
  1577. Error: 1,
  1578. Success: 2,
  1579. Warning: 3,
  1580. Info: 4,
  1581. }
  1582. win.LogLevelMap = {};
  1583. win.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'}
  1584. win.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'}
  1585. win.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'}
  1586. win.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'}
  1587. win.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'}
  1588. win.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'}
  1589.  
  1590. // Current log level
  1591. DoLog.logLevel = win.isPY_DNG ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  1592.  
  1593. // Log counter
  1594. DoLog.logCount === undefined && (DoLog.logCount = 0);
  1595. if (++DoLog.logCount > 512) {
  1596. console.clear();
  1597. DoLog.logCount = 0;
  1598. }
  1599.  
  1600. // Get args
  1601. let level, logContent, asObject;
  1602. switch (arguments.length) {
  1603. case 1:
  1604. level = LogLevel.Info;
  1605. logContent = arguments[0];
  1606. asObject = false;
  1607. break;
  1608. case 2:
  1609. level = arguments[0];
  1610. logContent = arguments[1];
  1611. asObject = false;
  1612. break;
  1613. case 3:
  1614. level = arguments[0];
  1615. logContent = arguments[1];
  1616. asObject = arguments[2];
  1617. break;
  1618. default:
  1619. level = LogLevel.Info;
  1620. logContent = 'DoLog initialized.';
  1621. asObject = false;
  1622. break;
  1623. }
  1624.  
  1625. // Log when log level permits
  1626. if (level <= DoLog.logLevel) {
  1627. let msg = '%c' + LogLevelMap[level].prefix;
  1628. let subst = LogLevelMap[level].color;
  1629.  
  1630. if (asObject) {
  1631. msg += ' %o';
  1632. } else {
  1633. switch(typeof(logContent)) {
  1634. case 'string': msg += ' %s'; break;
  1635. case 'number': msg += ' %d'; break;
  1636. case 'object': msg += ' %o'; break;
  1637. }
  1638. }
  1639.  
  1640. console.log(msg, subst, logContent);
  1641. }
  1642. }
  1643. }
  1644.  
  1645. // Polyfill String.prototype.replaceAll
  1646. // replaceValue does NOT support regexp match groups($1, $2, etc.)
  1647. function polyfill_replaceAll() {
  1648. String.prototype.replaceAll = String.prototype.replaceAll ? String.prototype.replaceAll : PF_replaceAll;
  1649.  
  1650. function PF_replaceAll(searchValue, replaceValue) {
  1651. const str = String(this);
  1652.  
  1653. if (searchValue instanceof RegExp) {
  1654. const global = RegExp(searchValue, 'g');
  1655. if (/\$/.test(replaceValue)) {console.error('Error: Polyfilled String.protopype.replaceAll does support regexp groups');};
  1656. return str.replace(global, replaceValue);
  1657. } else {
  1658. return str.split(searchValue).join(replaceValue);
  1659. }
  1660. }
  1661. }
  1662.  
  1663. function randint(min, max) {
  1664. return Math.floor(Math.random() * (max - min + 1)) + min;
  1665. }
  1666.  
  1667. // Del a item from an array using its index. Returns the array but can NOT modify the original array directly!!
  1668. function delItem(arr, delIndex) {
  1669. arr = arr.slice(0, delIndex).concat(arr.slice(delIndex+1));
  1670. return arr;
  1671. }
  1672. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址