US_Utils

Some useful utilities for userscript development

当前为 2019-10-27 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.cn-greasyfork.org/scripts/391648/744271/US_Utils.js

  1. // US Utils library
  2. //
  3. // Some useful utilities for userscript development.
  4. //
  5. // https://greasyfork.org/scripts/391648-us-utils
  6. // Copyright (C) 2019, Guido Villa
  7. //
  8. // For information/instructions on user scripts, see:
  9. // https://greasyfork.org/help/installing-user-scripts
  10. //
  11. // To use this library in a userscript you must add to script header:
  12. // @require https://greasyfork.org/scripts/391648-us-utils/code/US_Utils.js
  13. // @grant GM_xmlhttpRequest (only if using UU.GM_xhR)
  14. //
  15. // --------------------------------------------------------------------
  16. //
  17. // ==UserScript==
  18. // @namespace https://greasyfork.org/users/373199-guido-villa
  19. // @exclude *
  20. //
  21. // ==UserLibrary==
  22. // @name US_Utils
  23. // @description Some useful utilities for userscript development
  24. // @version 1.0
  25. // @author guidovilla
  26. // @date 27.10.2019
  27. // @copyright 2019, Guido Villa (https://greasyfork.org/users/373199-guido-villa)
  28. // @license GPL-3.0-or-later
  29. // @homepageURL https://greasyfork.org/scripts/391648-us-utils
  30. // @supportURL https://gitlab.com/gv-browser/userscripts/issues
  31. // @contributionURL https://tinyurl.com/gv-donate-ed
  32. // @attribution Trevor Dixon (https://stackoverflow.com/users/711902/trevor-dixon)
  33. // ==/UserScript==
  34. //
  35. // ==/UserLibrary==
  36. //
  37. // --------------------------------------------------------------------
  38. //
  39. // To-do (priority: [H]igh, [M]edium, [L]ow):
  40. // - [H] GM_xhR: remove workaround responseXML2 and make responseXML work
  41. // - [M] add other functions
  42. //
  43. // Changelog:
  44. // ----------
  45. // 2019.10.27 [1.0] First version
  46. // 2019.10.26 [0.1] First test version, private use only
  47. //
  48.  
  49. /* jshint esversion: 6, laxbreak: true, -W008, supernew: true */
  50. /* exported UU, Library_Version_US_UTILS */
  51.  
  52. const Library_Version_US_UTILS = '1.0';
  53.  
  54. /* How to use the library
  55.  
  56. This library instantitates an UU object with utility variables and methods:
  57.  
  58. - me: script name as returned by GM_info
  59.  
  60. - isUndef(p): check if p is undefined
  61.  
  62. - le(...args): like ocnsole.error, prepending the script name
  63. - lw(...args): like ocnsole.warn, prepending the script name
  64. - li(...args): like ocnsole.info, prepending the script name
  65. - ld(...args): like ocnsole.debug, prepending the script name
  66.  
  67. - checkProperty(object, property, type, optional)
  68. Check if object "object" has property "property" of type "type".
  69. If property is "optional" (default false), it is only checked for type
  70. Used to test if object "implements" a specific interface
  71.  
  72. - parseCSV(csv): simple CSV parsing function, by Trevor Dixon (see below)
  73. Take a CSV string as input and return an array of rows, each containing
  74. an array of fields.
  75. NOTE: it is not strict in RFC 4180 compliance as it handles unquoted
  76. double quotes inside a field (this is not allowed in the RFC specifications).
  77.  
  78. - wait(waitTime, result)
  79. return a Promise to wait for "waitTime" ms, then resolve with value "result"
  80. - thenWait(waitTime)
  81. like wait(), to be used inside a Promise.then(). Passes through the
  82. received fulfillment value.
  83.  
  84. - GM_xhR(method, url, purpose, opts): GM_xmlhttpRequest wrapped in a Promise.
  85. Return a Promise resolving with the GM_xmlhttpRequest response, or failing
  86. with an error message (which is also logged). Arguments:
  87. - mathod: HTTP method (GET, POST, ...)
  88. - url: URL to call
  89. - purpose: string describing XHR call (for error logging and reporting)
  90. - opts: details to be passed to GM_xmlhttpRequest; the following properties
  91. will be ignored:
  92. - method, url: overwritten by function arguments
  93. - onload: overwritten to resolve the Promise
  94. - onabort, onerror, ontimeout: overwritten to reject the Promise
  95. if no context is specified, purpose is passed as context
  96. */
  97.  
  98.  
  99. window.UU = new (function() {
  100. 'use strict';
  101. var self = this;
  102.  
  103.  
  104.  
  105. // the name of the running script
  106. this.me = GM_info.script.name;
  107.  
  108.  
  109.  
  110. // check if parameter is undefined
  111. this.isUndef = function(p) {
  112. return (typeof p === 'undefined');
  113. };
  114.  
  115.  
  116.  
  117. // logging
  118. var bracketMe = '[' + this.me + ']';
  119. this.le = function(...args) { console.error(bracketMe, ...args); };
  120. this.lw = function(...args) { console.warn (bracketMe, ...args); };
  121. this.li = function(...args) { console.info (bracketMe, ...args); };
  122. this.ld = function(...args) { console.debug(bracketMe, ...args); };
  123.  
  124.  
  125.  
  126. // Check if "object" has "property" of "type"
  127. // used to test if object "implements" a specific interface
  128. this.checkProperty = function(object, property, type, optional = false) {
  129.  
  130. if (self.isUndef(object[property])) {
  131. if (optional) return true;
  132.  
  133. self.le('Invalid object: missing property "' + property + '" of type "' + type + '"');
  134. return false;
  135. }
  136. if (typeof object[property] !== type) {
  137. self.le('Invalid object: ' + (optional ? 'optional ' : '') + 'property "' + property + '" must be of type "' + type + '"');
  138. return false;
  139. }
  140. return true;
  141. };
  142.  
  143.  
  144.  
  145. // Simple CSV parsing function, by Trevor Dixon:
  146. // https://stackoverflow.com/a/14991797
  147. // take a CSV as input and returns an array of arrays (rows, fields)
  148. /* eslint-disable max-statements, max-statements-per-line, max-len */
  149. this.parseCSV = function(csv) {
  150. var arr = [];
  151. var quote = false; // true means we're inside a quoted field
  152.  
  153. // iterate over each character, keep track of current row and column (of the returned array)
  154. var row, col, c;
  155. for (row = col = c = 0; c < csv.length; c++) {
  156. var cc = csv[c], nc = csv[c+1]; // current character, next character
  157. arr[row] = arr[row] || []; // create a new row if necessary
  158. arr[row][col] = arr[row][col] || ''; // create a new column (start with empty string) if necessary
  159.  
  160. // If the current character is a quotation mark, and we're inside a
  161. // quoted field, and the next character is also a quotation mark,
  162. // add a quotation mark to the current column and skip the next character
  163. if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
  164.  
  165. // If it's just one quotation mark, begin/end quoted field
  166. if (cc == '"') { quote = !quote; continue; }
  167.  
  168. // If it's a comma and we're not in a quoted field, move on to the next column
  169. if (cc == ',' && !quote) { ++col; continue; }
  170.  
  171. // If it's a newline (CRLF) and we're not in a quoted field, skip the next character
  172. // and move on to the next row and move to column 0 of that new row
  173. if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }
  174.  
  175. // If it's a newline (LF or CR) and we're not in a quoted field,
  176. // move on to the next row and move to column 0 of that new row
  177. if (cc == '\n' && !quote) { ++row; col = 0; continue; }
  178. if (cc == '\r' && !quote) { ++row; col = 0; continue; }
  179.  
  180. // Otherwise, append the current character to the current column
  181. arr[row][col] += cc;
  182. }
  183. return arr;
  184. };
  185. /* eslint-enable max-statements, max-statements-per-line, max-len */
  186.  
  187.  
  188.  
  189. // setTimeout wrapped in a Promise
  190. this.wait = function(waitTime, result) {
  191. return new Promise(function(resolve, _I_reject) {
  192. setTimeout(resolve, waitTime, result);
  193. });
  194. };
  195.  
  196. // setTimeout wrapped in a Promise, if called iside "then"
  197. this.thenWait = function(waitTime) {
  198. return (function(result) { return self.wait(waitTime, result); });
  199. };
  200.  
  201.  
  202.  
  203. // handle download error in a Promise-enhanced GM_xmlhttpRequest
  204. function xhrError(rejectFunc, response, method, url, purpose, reason) {
  205. var m = purpose + ' - HTTP ' + method + ' error' + (reason ? ' (' + reason + ')' : '') + ': '
  206. + response.status + (response.statusText ? ' - ' + response.statusText : '');
  207. self.le(m, 'URL: ' + url, 'Response:', response);
  208. rejectFunc(m);
  209. }
  210. function xhrErrorFunc(rejectFunc, method, url, purpose, reason) {
  211. return (function(resp) {
  212. xhrError(rejectFunc, resp, method, url, purpose, reason);
  213. });
  214. }
  215.  
  216.  
  217. // wrap GM_xmlhttpRequest in a Promise
  218. // returns a Promise resolving with the GM_xmlhttpRequest response
  219. this.GM_xhR = function(method, url, purpose, opts) {
  220. return new Promise(function(resolve, reject) {
  221. var details = opts || {};
  222. details.method = method;
  223. details.url = url;
  224. details.onload = function(response) {
  225. if (response.status !== 200) xhrError(reject, response, method, url, purpose);
  226. // else resolve(response);
  227. else {
  228. if (details.responseType === 'document') {
  229. try {
  230. const d = document.implementation.createHTMLDocument().documentElement;
  231. d.innerHTML = response.responseText;
  232. response.responseXML2 = d;
  233. } catch(e) {
  234. xhrError(reject, response, method, url, purpose, e);
  235. }
  236. }
  237. resolve(response);
  238. }
  239. };
  240. details.onabort = xhrErrorFunc(reject, method, url, purpose, 'abort');
  241. details.onerror = xhrErrorFunc(reject, method, url, purpose, 'error');
  242. details.ontimeout = xhrErrorFunc(reject, method, url, purpose, 'timeout');
  243. if (self.isUndef(details.synchronous)) details.synchronous = false;
  244. if (self.isUndef(details.context)) details.context = purpose;
  245. GM_xmlhttpRequest(details);
  246. });
  247. };
  248.  
  249.  
  250.  
  251. })();