US_Utils

Some useful utilities for userscript development

目前为 2019-10-27 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.cn-greasyfork.org/scripts/391648/744268/US_Utils.js

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