Greasy Fork is available in English.

JMUL

utilities for monkey scripts

אין להתקין סקריפט זה ישירות. זוהי ספריה עבור סקריפטים אחרים // @require https://update.greasyfork.org/scripts/31793/209567/JMUL.js

// ==UserScript==
// @name              JMUL
// @name:zh-CN        自用脚本工具库
// @namespace         https://greasyfork.org/users/34556
// @version           0.1.2
// @description       utilities for monkey scripts
// @description:zh-CN 工具库
// @author            jferroal
// @grant             GM_xmlhttpRequest
// ==/UserScript==

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
function selectable(je) {
    je.select = select;
    je.getSelection = getSelection;
    je.copyToClipboard = copyToClipboard;
    return je;
    function select(start, end) {
        if (!je.element.focus) {
            console.error('this element can not be selected.');
        }
        je.element.focus();
        start = !!start ? start : 0;
        end = !!end ? end : -1;
        je.element.setSelectionRange(start, end);
    }

    function getSelection(start, end) {
        start = !start ? 0 : start;
        // default will get the selected text
        let result = String.prototype.slice.apply(document.getSelection(), [start, end]);
        // if not selected, get current element's text
        if (!result) {
            this.select(start, end);
            result = String.prototype.slice.apply(document.getSelection(), [start, end === -1 ? end += 1 : end]);
        }
        return result;
    }

    function copyToClipboard(start, end) {
        start = !start ? 0 : start;
        document.getSelection().removeAllRanges();
        const range = document.createRange();
        range.setStart(je.element, start);
        range.setEnd(je.element, end);
        range.selectNode(je.element);
        document.getSelection().addRange(range);
        try {
            document.execCommand('copy');
        } catch (err) {
            console.error('Oops, unable to copy');
        }
        document.getSelection().removeAllRanges();
    }
}

function addParser(request, parser) {
    request.parse = parse;
    request.sendAndParse = function () {
        return request.send().then((responseText) => {
            return parse(responseText);
        });
    };
    return request;

    function parse(responseText) {
        return new Promise((resolve) => {
            const result = {};
            for (const key of Object.keys(parser.rules)) {
                result[key] = parser.rules[key](responseText);
            }
            resolve(result);
        });
    }
}

module.exports = {
    selectable: selectable,
    addParser: addParser,
};
},{}],2:[function(require,module,exports){
function toArray(s) {
    return Array.from(s, (v) => v);
}

class JMElement {
    constructor(tagOrElement) {
        this.element = tagOrElement;
        if (typeof tagOrElement === 'string') {
            this.element = document.createElement(tagOrElement);
        }
    }

    setAttribute(attrName, value) {
        this.element.setAttribute(attrName, value);
    }

    getAttribute(attrName) {
        return this.element.getAttribute(attrName);
    }

    setStyle(styleName, value) {
        this.element.style[styleName] = value;
    }

    setCss(styles) {
        if (!styles) return;
        for (const styleName in styles) {
            if (!styles.hasOwnProperty(styleName)) return;
            this.setStyle(styleName, styles[styleName]);
        }
    }

    setInnerHTML(value) {
        this.element.innerHTML = value;
    }
    setInnerText(value) {
	this.element.innerText = value;
    }

    setId(id) {
        this.setAttribute('id', id);
    }

    _setClass(_class) {
        this.setAttribute('class', _class);
    }

    addClass(newClass) {
        const oldClassStr = this.getAttribute('class');
        if (oldClassStr.indexOf(newClass) < 0) {
            this._setClass(oldClassStr + ' ' + newClass);
        }
        return this;
    }

    removeClass(className) {
        const oldClassStr = this.getAttribute('class');
        const idx = oldClassStr.indexOf(className);
        if (idx > -1) {
            const tmp = toArray(oldClassStr);
            tmp.splice(idx, className.length);
            this._setClass(tmp.join(''));
        }
        return this;
    }

    get innerHTML() {
        return this.element.innerHTML;
    }

    get innerText() {
        return this.element.innerText;
    }

    setValue(value) {
        this.element.value = value;
    }

    position(type) {
        const rect = this.element.getBoundingClientRect();
        switch (type) {
            case 'left-top':
                return {x: rect.left, y: rect.top};
            case 'right-top':
                return {x: rect.right, y: rect.top};
            case 'left-bottom':
                return {x: rect.left, y: rect.bottom};
            case 'right-bottom':
                return {x: rect.right, y: rect.bottom};
            case 'center':
                return {x: rect.left + rect.height / 2, y: rect.top + rect.height / 2};
        }
    }

    appendTo(parent) {
        parent.appendChild(this.element);
    }

    appendChild(child) {
        this.element.appendChild(child.element || child);
    }

    listen(eventName, fn) {
        JMElement.addEvent(this.element, eventName, fn);
    }

    toString() {
        return this.element.toString();
    }

    valueOf() {
        return this.element;
    }

    get value() {
        return this.element.value;
    }

    static query(selector, index) {
        const els = document.querySelectorAll(selector);
        if (!els) throw new Error('element not found. ');
        if (index === -1) return els.map((el) => new JMElement(el));
        if (index === undefined) return new JMElement(els[0]);
        if (els.length < index + 1) throw new Error('index element not found. ');
        return new JMElement(els[index]);
    }

    static addEvent(element, eventName, fn) {
        element.addEventListener(eventName, fn, false);
    }

    static getSelectedPosition(type = 'left-top') {
        const focusNode = document.getSelection().focusNode;
        if (!focusNode) throw new Error('no selection, should not create node');
        const focusParentElement = focusNode.parentElement;
        return (new JMElement(focusParentElement)).position(type);
    }
}

module.exports = JMElement;


},{}],3:[function(require,module,exports){
(function() {
    window.JMUL = {
    Element: require('./element'),
    Decorator: require('./decorator'),
    Request: require('./request').Request,
    Header: require('./request').Header,
    Parser: require('./parser'),
    UI: require('./ui/main'),
};
})();
},{"./decorator":1,"./element":2,"./parser":4,"./request":5,"./ui/main":7}],4:[function(require,module,exports){

const helper = {
    isEmpty: a => !a || !a.length,
    toArray: s => Array.from(s, (v) => v),
};

class JMXMLResult {
    constructor(tagName, innerText, matches) {
        this.tagName = tagName;
        this.innerText = innerText;
        this.matches = matches;
    }
}

class JMParser {
    constructor() {
        this.rules = {};
        this.filters = {};
    }

    addRule(key, pattern) {
        const tmp = {
            tagName: '',
            attrName: '',
            attrValue: '',
            prevCh: '',
            filterName: '',
            filterParams: [],
            currentFilterParams: ''
        };
        this.rules[key] = helper.toArray(pattern).reduce((res, ch, idx) => {
            switch (ch) {
                case ' ':
                    break;
                case '(':
                    tmp.prevCh = ch;
                    break;
                case '@':
                    if (!tmp.tagName) throw new Error('No tagName. ');
                    tmp.prevCh = ch;
                    break;
                case ')':
                    res = JMParser.generate(tmp.tagName, JMParser.attr(tmp.attrName, tmp.attrValue));
                    tmp.prevCh = tmp.tagName = tmp.attrName = tmp.attrValue = '';
                    break;
                case '|':
                    tmp.prevCh = '|';
                    const filter = this.filters[tmp.filterName];
                    if (filter) {
                        res = filter(res, ...tmp.filterParams);
                    }
                    tmp.filterName = tmp.currentFilterParams = '';
                    tmp.filterParams = [];
                    break;
                case ',':
                    tmp.prevCh = ',';
                    if (tmp.currentFilterParams) {
                        tmp.filterParams.push(tmp.currentFilterParams);
                    }
                    tmp.currentFilterParams = '';
                    break;
                default:
                    if (tmp.prevCh === '@') {
                        tmp.attrName += ch;
                    } else if (tmp.prevCh === '(') {
                        tmp.attrValue += ch;
                    } else if(tmp.prevCh === '|') {
                        tmp.filterName += ch;
                    } else if (tmp.prevCh === ',') {
                        tmp.currentFilterParams += ch;
                    } else {
                        tmp.tagName += ch;
                    }
                    break;
            }
            return res;
        }, undefined);
    }

    addFilter(name, fn) {
        this.filters[name] = (prevFn, ...params) => {
            return (responseText) => {
                const parseRes = prevFn(responseText);
                return fn(parseRes, ...params);
            }
        }
    }

    static generate(tagName, attr) {
        const pattern = JMParser.tag(tagName, attr);
        return (responseText) => {
            const allMatch = responseText.match(pattern);
            if (helper.isEmpty(allMatch)) return {found: false};
            return allMatch.reduce((res, matchItem) => {
                const execRes = pattern.exec(matchItem);
                pattern.lastIndex = 0;
                const matchRes = execRes && execRes.slice(1) || [];
                return res.concat([new JMXMLResult(tagName, matchRes[1], matchRes)]);
            }, []);
        }
    }

    static tag(name, attr) {
        return new RegExp(`${name}[\\s\\S]*?${attr}[\\s\\S]*?>([\\s\\S]*?)<\/${name}>`, 'gi');
    }

    static attr(name, value) {
        return `${name}="(${value})"`;
    }
}

module.exports = JMParser;
},{}],5:[function(require,module,exports){
GM_xmlhttpRequest = window.GM_xmlhttpRequest;

const FnMethodNameMap = {
    'abort': 'onabort',
    'failed': 'onerror',
    'fail': 'onerror',
    'error': 'onerror',
    'loaded': 'onload',
    'load': 'onload',
    'success': 'onload',
    'onload': 'onload',
    'progress': 'onprogress',
    'onprogress': 'onprogress',
    'ready': 'onreadystatechange',
    'readystatechange': 'onreadystatechange',
    'onreadystatechange': 'onreadystatechange',
    'timeout': 'ontimeout',
    'ontimeout': 'ontimeout',
};

const MethodNameMap = {
    'get': 'GET',
    'Get': 'GET',
    'GET': 'GET',
    'post': 'POST',
    'Post': 'POST',
    'POST': 'POST',
    'head': 'HEAD',
    'Head': 'HEAD',
    'HEAD': 'HEAD',
    'delete': 'DELETE',
    'Delete': 'DELETE',
    'DELETE': 'DELETE',
    'patch': 'PATCH',
    'Patch': 'PATCH',
    'PATCH': 'PATCH',
    'put': 'PUT',
    'Put': 'PUT',
    'PUT': 'PUT',
};

class JMRequestHeader {
    constructor(headers) {
        if (headers instanceof JMRequestHeader) {
            headers = headers.value();
        }
        this.headerObj = headers;
    }

    option(key, value) {
        this.headerObj[key] = value;
        return this;
    } // chain
    setAccept(value) {
        this._accept = this.headerObj.accept = value;
        return this;
    }

    setAcceptCharset(value) {
        this.acceptCharset = this.headerObj['Accept-Charset'] = value;
        return this;
    }

    setAcceptEncoding(value) {
        this.acceptEncoding = this.headerObj['Accept-Encoding'] = value;
        return this;
    }

    setAge(value) {
        this.age = this.headerObj.age = value;
        return this;
    }

    setAuthorization(value) {
        this.authorization = this.headerObj.Authorization = value;
        return this;
    }

    setContentEncoding(value) {
        this.contentEncoding = this.headerObj['Content-Encoding'] = value;
        return this;
    }

    setContentLength(value) {
        this.contentLength = this.headerObj['Content-Length'] = value;
        return this;
    }

    setContentType(value) {
        this.contentType = this.headerObj['Content-Type'] = value;
        return this;
    }

    setCookie(value) {
        this.cookie = this.headerObj.Cookie = value;
        return this;
    }

    setUA(value) {
        this.ua = this.headerObj['User-Agent'] = value;
        return this;
    }

    value() {
        return this.headerObj;
    }
}

class JMRequest {
    constructor(options) {
        this._method = options.method && MethodNameMap[options.method] || 'GET';
        this._url = options.url || '';
        this.options = {};
        this.options.headers = new JMRequestHeader(options.headers || {});
        for (let key of Object.keys(options)) {
            if (FnMethodNameMap[key] && typeof options[key] === 'function') {
                this.options[FnMethodNameMap[key]] = options[key];
            }
        }
        this.options.data = this.handleRequestData(options.data)
    }

    handleRequestData(data) {
        if (!data) return '';
        const contentType = this.options.headers.contentType;
        if (!contentType || contentType === 'application/json') {
            return JMRequest.toJsonData(data);
        } else if (contentType === 'application/x-www-form-urlencoded') {
            return JMRequest.toFormData(data);
        } else {
            // treat other as plain/text, do not support multipart/form-data
            return data.toString();
        }
    }

    setMethod(_method) {
        this._method = MethodNameMap[_method];
        return this;
    }

    setUrl(_url) {
        this._url = _url;
        return this;
    }

    setHeaders(headers) {
        this.options.headers = headers;
        return this;
    }

    setData(obj) {
        this.options.data = this.handleRequestData(obj);
        return this;
    }

    load(fn) {
        this.options.onload = fn;
        return this;
    }

    error(fn) {
        this.options.onerror = fn;
        return this;
    }

    timeout(fn) {
        this.options.ontimeout = fn;
        return this;
    }

    readyStateChange(fn) {
        this.options.onreadystatechange = fn;
        return this;
    }

    abort(fn) {
        this.options.onabort = fn;
        return this;
    }

    progress(fn) {
        this.options.onprogress = fn;
        return this;
    }

    send() {
        return JMRequest.request(this._method, this._url, this.options);
    }

    static toFormData(data) {
        if (typeof data === 'string') {
            return data;
        } else {
            let result = '';
            for (let key of Object.keys(data)) {
                result += key + '=' + data[key] + '&';
            }
            return result.slice(0, -1);
        }
    }

    static toJsonData(data) {
        if (typeof data === 'object') {
            return JSON.stringify(data);
        } else {
            return data;
        }
    }

    static request(method, url, options) {
        return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: MethodNameMap[method],
                url: url,
                headers: options.headers.value(),
                data: options.data,
                onreadystatechange: (response) => {
                    if (!options.onreadystatechange) return console.log('on ready state change. ');
                    const fn = options.onreadystatechange;
                    (!fn.then ? Promise.resolve(fn(response)) : fn(response)).then(function (res) {
                        resolve(res);
                    });
                },
                onabort: (response) => {
                    if (!options.onabort) {
                        console.error('on abort. ');
                        reject({cause: 'abort'});
                    } else {
                        const fn = options.onabort;
                        (!fn.then ? Promise.resolve(fn(response)) : fn(response)).then(function (res) {
                            resolve(res);
                        });
                    }

                },
                onerror: (response) => {
                    if (!options.onerror) {
                        console.error('on error. ');
                        reject({cause: 'error', response: response});
                    } else {
                        const fn = options.onerror;
                        (!fn.then ? Promise.resolve(fn(response)) : fn(response)).then(function (res) {
                            resolve(res);
                        });
                    }
                },
                onprogress: (response) => {
                    if (!options.onprogress) return console.log('on progress. ');
                    const fn = options.onprogress;
                    (!fn.then ? Promise.resolve(fn(response)) : fn(response)).then(function (res) {
                        resolve(res);
                    });
                }
                ,
                ontimeout: (response) => {
                    if (!options.ontimeout) {
                        console.error('on timeout. ');
                        reject({cause: 'timeout', response: response});
                    }
                    const fn = options.ontimeout;
                    (!fn.then ? Promise.resolve(fn(response)) : fn(response)).then(function (res) {
                        resolve(res);
                    });
                },
                onload: (response) => {
                    if (!options.onload) {
                        console.log('on load. ');
                        resolve(response);
                    } else {
                        const fn = options.onload;
                        (!fn.then ? Promise.resolve(fn(response)) : fn(response)).then(function (res) {
                            resolve(res);
                        });
                    }
                },
            })
        });
    }

    static get(url, options) {
        return JMRequest.request('GET', url, options);
    }

    static post(url, options) {
        return JMRequest.request('POST', url, options);
    }

    static put(url, options) {
        return JMRequest.request('PUT', url, options);
    }

    static delete(url, options) {
        return JMRequest.request('DELETE', url, options);
    }

    static head(url, options) {
        return JMRequest.request('HEAD', url, options);
    }

    static patch(url, options) {
        return JMRequest.request('PATCH', url, options);
    }
}

module.exports = {
    Request: JMRequest,
    Header: JMRequestHeader,
};
},{}],6:[function(require,module,exports){
const JMElement = require('../element');

class BaseButton extends JMElement {
    constructor() {
        super('button');
        this.btnClickedStyleChangeTimeout = undefined;
    }

    setNormalBtnBoxShadow() {
        this.setStyle('boxShadow', '0 0 2px 2px rgba(0, 0, 0, 0.08)');
    }

    setClickedBtnBoxShadow() {
        this.setStyle('boxShadow', 'none');
    }

    listenClick(fn) {
        this.listen('click', (e) => {
            this.setClickedBtnBoxShadow();
            if (this.btnClickedStyleChangeTimeout) {
                clearTimeout(this.btnClickedStyleChangeTimeout);
                this.btnClickedStyleChangeTimeout = null;
            }
            this.btnClickedStyleChangeTimeout = setTimeout(() => {
                this.setNormalBtnBoxShadow();
            }, 100);
            fn(e, this);
        })
    }
}
class IconButton {
    constructor(icon, size, clickFn) {
        this.button = new BaseButton();
        IconButton.initBtnStyle(this.button, typeof size === 'string' ? '128px' : size + 'px');
        this.image = new JMElement('img');
        this.image.setAttribute('src', icon);
        IconButton.initImageStyle(this.image);
        this.button.appendChild(this.image);
        this.button.listenClick(clickFn)
    }

    appendTo(parent) {
        this.button.appendTo(parent);
    }

    get element() {
        return this.button;
    }

    static initBtnStyle(button, size) {
        button.setCss({
            position: 'relative',
            height: size,
            width: size,
            padding: '0',
            borderRadius: '50%',
            border: 'none',
            outline: 'none',
        });
    }

    static initImageStyle(image) {
        image.setCss({
            width: '100%',
            height: '100%',
            borderRadius: '50%',
            cursor: 'pointer'
        })
    }
}

class NormalButton {
    constructor(label, size, clickFn) {
        this.button = new BaseButton();
        NormalButton.initBtnStyle(this.button, NormalButton._handleSizeParam(size));
        this.label = new JMElement('p');
        NormalButton.initLabelStyle(this.label);
        this.label.innerText(label);
        this.button.appendChild(this.label);
        this.button.listenClick(clickFn)
    }

    appendTo(parent) {
        this.button.appendTo(parent);
    }

    get element() {
        return this.button;
    }

    static initBtnStyle(button, size) {
        button.setCss({
            height: size.height || '24px',
            width: '64px',
            borderRadius: '4px',
            border: 'none',
            backgroundColor: 'skyblue',
            cursor: 'pointer',
            outline: 'none',
        });
    }

    static initLabelStyle(label) {
        label.setCss({
            fontSize: '12px',
            color: 'rgba(255, 255,255, 0.87)',
            lineHeight: '100%',
            margin: '0',
        });
    }

    static _handleSizeParam(size) {
        switch (typeof size) {
            case 'object':
                return {
                    height: typeof size.height === 'string' ? size.height : size.height + 'px',
                    width: typeof size.width === 'string' ? size.width : size.width + 'px',
                };
            case 'string':
                return {height: size, width: size};
            case 'number':
                return {height: size + 'px', width: size + 'px'};
        }
    }
}

class JMButtonFactory {
    constructor() {
        if (!JMButtonFactory.ButtonFactory) {
            JMButtonFactory.ButtonFactory = this;
        }
        return JMButtonFactory.ButtonFactory;
    }

    static create(type, iconOrLabel, size, clickFn) {
        switch (type) {
            case 'icon':
                return new IconButton(iconOrLabel, size, clickFn);
            case 'normal':
            default:
                return new NormalButton(iconOrLabel, size, clickFn);
        }
    }
}
JMButtonFactory.ButtonFactory = null;

module.exports = JMButtonFactory;

},{"../element":2}],7:[function(require,module,exports){
module.exports = {
    Button: require('./button')
}
},{"./button":6}]},{},[3]);