- // US Utils library
- //
- // Some useful utilities for userscript development.
- //
- // https://greasyfork.org/scripts/******-us-utils
- // Copyright (C) 2019, Guido Villa
- //
- // For information/instructions on user scripts, see:
- // https://greasyfork.org/help/installing-user-scripts
- //
- // To use this library in a userscript you must add to script header:
- // @require https://greasyfork.org/scripts/******-us-utils/code/US_Utils.js
- // @grant GM_xmlhttpRequest (only if using UU.GM_xhR)
- //
- // --------------------------------------------------------------------
- //
- // ==UserScript==
- // @namespace https://greasyfork.org/users/373199-guido-villa
- // @exclude *
- //
- // ==UserLibrary==
- // @name US_Utils
- // @description Some useful utilities for userscript development
- // @version 0.1
- // @author guidovilla
- // @date 26.10.2019
- // @copyright 2019, Guido Villa (https://greasyfork.org/users/373199-guido-villa)
- // @license GPL-3.0-or-later
- // @homepageURL https://greasyfork.org/scripts/******-us-utils
- // @supportURL https://gitlab.com/gv-browser/userscripts/issues
- // @contributionURL https://tinyurl.com/gv-donate-ed
- // @attribution Trevor Dixon (https://stackoverflow.com/users/711902/trevor-dixon)
- // ==/UserScript==
- //
- // ==/UserLibrary==
- //
- // --------------------------------------------------------------------
- //
- // To-do (priority: [H]igh, [M]edium, [L]ow):
- // - [H] GM_xhR: remove workaround responseXML2 and make responseXML work
- // - [M] add other functions
- //
- // Changelog:
- // ----------
- // 2019.10.26 [0.1] First test version, private use only
- //
-
- /* jshint esversion: 6, laxbreak: true, -W008, supernew: true */
- /* exported UU, Library_Version_US_UTILS */
-
- const Library_Version_US_UTILS = '0.1';
-
- /* How to use the library
-
- This library instantitates an UU object with utility variables and methods:
-
- - me: script name as returned by GM_info
-
- - isUndef(p): check if p is undefined
-
- - le(...args): like ocnsole.error, prepending the script name
- - lw(...args): like ocnsole.warn, prepending the script name
- - li(...args): like ocnsole.info, prepending the script name
- - ld(...args): like ocnsole.debug, prepending the script name
-
- - checkProperty(object, property, type, optional)
- Check if object "object" has property "property" of type "type".
- If property is "optional" (default false), it is only checked for type
- Used to test if object "implements" a specific interface
-
- - parseCSV(csv): simple CSV parsing function, by Trevor Dixon (see below)
- Take a CSV string as input and return an array of rows, each containing
- an array of fields.
- NOTE: it is not strict in RFC 4180 compliance as it handles unquoted
- double quotes inside a field (this is not allowed in the RFC specifications).
-
- - wait(waitTime, result)
- return a Promise to wait for "waitTime" ms, then resolve with value "result"
- - thenWait(waitTime)
- like wait(), to be used inside a Promise.then(). Passes through the
- received fulfillment value.
-
- - GM_xhR(method, url, purpose, opts): GM_xmlhttpRequest wrapped in a Promise.
- Return a Promise resolving with the GM_xmlhttpRequest response, or failing
- with an error message (which is also logged). Arguments:
- - mathod: HTTP method (GET, POST, ...)
- - url: URL to call
- - purpose: string describing XHR call (for error logging and reporting)
- - opts: details to be passed to GM_xmlhttpRequest; the following properties
- will be ignored:
- - method, url: overwritten by function arguments
- - onload: overwritten to resolve the Promise
- - onabort, onerror, ontimeout: overwritten to reject the Promise
- if no context is specified, purpose is passed as context
- */
-
-
- window.UU = new (function() {
- 'use strict';
- var self = this;
-
-
-
- // the name of the running script
- this.me = GM_info.script.name;
-
-
-
- // check if parameter is undefined
- this.isUndef = function(p) {
- return (typeof p === 'undefined');
- };
-
-
-
- // logging
- var bracketMe = '[' + this.me + ']';
- this.le = function(...args) { console.error(bracketMe, ...args); };
- this.lw = function(...args) { console.warn (bracketMe, ...args); };
- this.li = function(...args) { console.info (bracketMe, ...args); };
- this.ld = function(...args) { console.debug(bracketMe, ...args); };
-
-
-
- // Check if "object" has "property" of "type"
- // used to test if object "implements" a specific interface
- this.checkProperty = function(object, property, type, optional = false) {
-
- if (self.isUndef(object[property])) {
- if (optional) return true;
-
- self.le('Invalid object: missing property "' + property + '" of type "' + type + '"');
- return false;
- }
- if (typeof object[property] !== type) {
- self.le('Invalid object: ' + (optional ? 'optional ' : '') + 'property "' + property + '" must be of type "' + type + '"');
- return false;
- }
- return true;
- };
-
-
-
- // Simple CSV parsing function, by Trevor Dixon:
- // https://stackoverflow.com/a/14991797
- // take a CSV as input and returns an array of arrays (rows, fields)
- /* eslint-disable max-statements, max-statements-per-line, max-len */
- this.parseCSV = function(csv) {
- var arr = [];
- var quote = false; // true means we're inside a quoted field
-
- // iterate over each character, keep track of current row and column (of the returned array)
- var row, col, c;
- for (row = col = c = 0; c < csv.length; c++) {
- var cc = csv[c], nc = csv[c+1]; // current character, next character
- arr[row] = arr[row] || []; // create a new row if necessary
- arr[row][col] = arr[row][col] || ''; // create a new column (start with empty string) if necessary
-
- // If the current character is a quotation mark, and we're inside a
- // quoted field, and the next character is also a quotation mark,
- // add a quotation mark to the current column and skip the next character
- if (cc == '"' && quote && nc == '"') { arr[row][col] += cc; ++c; continue; }
-
- // If it's just one quotation mark, begin/end quoted field
- if (cc == '"') { quote = !quote; continue; }
-
- // If it's a comma and we're not in a quoted field, move on to the next column
- if (cc == ',' && !quote) { ++col; continue; }
-
- // If it's a newline (CRLF) and we're not in a quoted field, skip the next character
- // and move on to the next row and move to column 0 of that new row
- if (cc == '\r' && nc == '\n' && !quote) { ++row; col = 0; ++c; continue; }
-
- // If it's a newline (LF or CR) and we're not in a quoted field,
- // move on to the next row and move to column 0 of that new row
- if (cc == '\n' && !quote) { ++row; col = 0; continue; }
- if (cc == '\r' && !quote) { ++row; col = 0; continue; }
-
- // Otherwise, append the current character to the current column
- arr[row][col] += cc;
- }
- return arr;
- };
- /* eslint-enable max-statements, max-statements-per-line, max-len */
-
-
-
- // setTimeout wrapped in a Promise
- this.wait = function(waitTime, result) {
- return new Promise(function(resolve, _I_reject) {
- setTimeout(resolve, waitTime, result);
- });
- };
-
- // setTimeout wrapped in a Promise, if called iside "then"
- this.thenWait = function(waitTime) {
- return (function(result) { return self.wait(waitTime, result); });
- };
-
-
-
- // handle download error in a Promise-enhanced GM_xmlhttpRequest
- function xhrError(rejectFunc, response, method, url, purpose, reason) {
- var m = purpose + ' - HTTP ' + method + ' error' + (reason ? ' (' + reason + ')' : '') + ': '
- + response.status + (response.statusText ? ' - ' + response.statusText : '');
- self.le(m, 'URL: ' + url, 'Response:', response);
- rejectFunc(m);
- }
- function xhrErrorFunc(rejectFunc, method, url, purpose, reason) {
- return (function(resp) {
- xhrError(rejectFunc, resp, method, url, purpose, reason);
- });
- }
-
-
- // wrap GM_xmlhttpRequest in a Promise
- // returns a Promise resolving with the GM_xmlhttpRequest response
- this.GM_xhR = function(method, url, purpose, opts) {
- return new Promise(function(resolve, reject) {
- var details = opts || {};
- details.method = method;
- details.url = url;
- details.onload = function(response) {
- if (response.status !== 200) xhrError(reject, response, method, url, purpose);
- // else resolve(response);
- else {
- if (details.responseType === 'document') {
- try {
- const d = document.implementation.createHTMLDocument().documentElement;
- d.innerHTML = response.responseText;
- response.responseXML2 = d;
- } catch(e) {
- xhrError(reject, response, method, url, purpose, e);
- }
- }
- resolve(response);
- }
- };
- details.onabort = xhrErrorFunc(reject, method, url, purpose, 'abort');
- details.onerror = xhrErrorFunc(reject, method, url, purpose, 'error');
- details.ontimeout = xhrErrorFunc(reject, method, url, purpose, 'timeout');
- if (self.isUndef(details.synchronous)) details.synchronous = false;
- if (self.isUndef(details.context)) details.context = purpose;
- GM_xmlhttpRequest(details);
- });
- };
-
-
-
- })();