MediaWiki:Gadget-cron.js

来自萌娘文库
星海-adminbot讨论 | 贡献2022年3月28日 (一) 12:46的版本 (同步小工具)
(差异) ←上一版本 | 最后版本 (差异) | 下一版本→ (差异)
跳转至: 导航搜索

注意:在保存之后,您可能需要清除浏览器缓存才能看到所作出的变更的影响。

  • Firefox或Safari:按住Shift的同时单击刷新,或按Ctrl-F5Ctrl-R(Mac为⌘-R
  • Google Chrome:Ctrl-Shift-R(Mac为⌘-Shift-R
  • Internet Explorer:按住Ctrl的同时单击刷新,或按Ctrl-F5
  • Opera:前往菜单 → 设置(Mac为Opera → Preferences),然后隐私和安全 → 清除浏览数据 → 缓存的图片和文件
// https://cdn.jsdelivr.net/npm/cron@1.8.2/lib/cron.js
"use strict";
(function (root, factory) {
    if (typeof define === 'function' && define.amd) {
        define(['moment-timezone'], factory);
    }
    else if (typeof exports === 'object') {
        module.exports = factory(require('moment-timezone'), require('child_process'));
    }
    else {
        root.Cron = factory(root.moment);
    }
})(window, function (moment, childProcess) {
    var exports = {};
    var timeUnits = [
        'second',
        'minute',
        'hour',
        'dayOfMonth',
        'month',
        'dayOfWeek'
    ];
    var spawn = childProcess && childProcess.spawn;
    function CronTime(source, zone, utcOffset) {
        this.source = source;
        if (zone) {
            if (moment.tz.names().indexOf(zone) === -1) {
                throw new Error('Invalid timezone.');
            }
            this.zone = zone;
        }
        if (typeof utcOffset !== 'undefined')
            this.utcOffset = utcOffset;
        var that = this;
        timeUnits.map(function (timeUnit) {
            that[timeUnit] = {};
        });
        if (this.source instanceof Date || this.source._isAMomentObject) {
            this.source = moment(this.source);
            this.realDate = true;
        }
        else {
            this._parse();
            this._verifyParse();
        }
    }
    CronTime.constraints = [[0, 59], [0, 59], [0, 23], [1, 31], [0, 11], [0, 6]];
    CronTime.monthConstraints = [
        31,
        29,
        31,
        30,
        31,
        30,
        31,
        31,
        30,
        31,
        30,
        31
    ];
    CronTime.parseDefaults = ['0', '*', '*', '*', '*', '*'];
    CronTime.aliases = {
        jan: 0,
        feb: 1,
        mar: 2,
        apr: 3,
        may: 4,
        jun: 5,
        jul: 6,
        aug: 7,
        sep: 8,
        oct: 9,
        nov: 10,
        dec: 11,
        sun: 0,
        mon: 1,
        tue: 2,
        wed: 3,
        thu: 4,
        fri: 5,
        sat: 6
    };
    CronTime.prototype = {
        _verifyParse: function () {
            var months = Object.keys(this.month);
            var ok = false;
            /* if a dayOfMonth is not found in all months, we only need to fix the last
             wrong month  to prevent infinite loop */
            var lastWrongMonth = NaN;
            for (var i = 0; i < months.length; i++) {
                var m = months[i];
                var con = CronTime.monthConstraints[parseInt(m, 10)];
                var dsom = Object.keys(this.dayOfMonth);
                for (var j = 0; j < dsom.length; j++) {
                    var dom = dsom[j];
                    if (dom <= con) {
                        ok = true;
                    }
                }
                if (!ok) {
                    // save the month in order to be fixed if all months fails (infinite loop)
                    lastWrongMonth = m;
                    console.warn("Month '" + m + "' is limited to '" + con + "' days.");
                }
            }
            // infinite loop detected (dayOfMonth is not found in all months)
            if (!ok) {
                var con = CronTime.monthConstraints[parseInt(lastWrongMonth, 10)];
                var dsom = Object.keys(this.dayOfMonth);
                for (var k = 0; k < dsom.length; k++) {
                    var dom = dsom[k];
                    if (dom > con) {
                        delete this.dayOfMonth[dom];
                        var fixedDay = Number(dom) % con;
                        this.dayOfMonth[fixedDay] = true;
                    }
                }
            }
        },
        /**
         * calculates the next send time
         */
        sendAt: function (i) {
            var date = this.realDate ? this.source : moment();
            // Set the timezone if given (http://momentjs.com/timezone/docs/#/using-timezones/parsing-in-zone/)
            if (this.zone) {
                date = date.tz(this.zone);
            }
            if (typeof this.utcOffset !== 'undefined') {
                date = date.utcOffset(this.utcOffset);
            }
            if (this.realDate) {
                var diff = moment().diff(date, 's');
                if (diff > 0) {
                    throw new Error('WARNING: Date in past. Will never be fired.');
                }
                return date;
            }
            // If the i argument is not given, return the next send time
            if (isNaN(i) || i < 0) {
                date = this._getNextDateFrom(date);
                return date;
            }
            else {
                // Else return the next i send times
                var dates = [];
                for (; i > 0; i--) {
                    date = this._getNextDateFrom(date);
                    dates.push(moment(date));
                }
                return dates;
            }
        },
        /**
         * Get the number of milliseconds in the future at which to fire our callbacks.
         */
        getTimeout: function () {
            return Math.max(-1, this.sendAt() - moment());
        },
        /**
         * writes out a cron string
         */
        toString: function () {
            return this.toJSON().join(' ');
        },
        /**
         * Json representation of the parsed cron syntax.
         */
        toJSON: function () {
            var self = this;
            return timeUnits.map(function (timeName) {
                return self._wcOrAll(timeName);
            });
        },
        /**
         * get next date that matches parsed cron time
         */
        _getNextDateFrom: function (start, zone) {
            var date;
            var firstDate = moment(start).valueOf();
            if (zone) {
                date = moment(start).tz(zone);
            }
            else {
                date = moment(start);
            }
            if (!this.realDate) {
                var milliseconds = (start.milliseconds && start.milliseconds()) ||
                    (start.getMilliseconds && start.getMilliseconds()) ||
                    0;
                if (milliseconds > 0) {
                    date.milliseconds(0);
                    date.seconds(date.seconds() + 1);
                }
            }
            if (date.toString() === 'Invalid date') {
                throw new Error('ERROR: You specified an invalid date.');
            }
            // it shouldn't take more than 5 seconds to find the next execution time
            // being very generous with this. Throw error if it takes too long to find the next time to protect from
            // infinite loop.
            var timeout = Date.now() + 5000;
            // determine next date
            while (true) {
                var diff = date - start;
                var prevMonth = date.month();
                var prevDay = date.days();
                var prevMinute = date.minutes();
                var prevSeconds = date.seconds();
                var origDate = new Date(date);
                if (Date.now() > timeout) {
                    throw new Error("Something went wrong. cron reached maximum iterations.\n\t\t\t\t\t\tPlease open an  issue (https://github.com/kelektiv/node-cron/issues/new) and provide the following string\n\t\t\t\t\t\tTime Zone: " + (zone || '""') + " - Cron String: " + this + " - UTC offset: " + date.format('Z') + " - current Date: " + moment().toString());
                }
                if (!(date.month() in this.month) &&
                    Object.keys(this.month).length !== 12) {
                    date.add(1, 'M');
                    if (date.month() === prevMonth) {
                        date.add(1, 'M');
                    }
                    date.date(1);
                    date.hours(0);
                    date.minutes(0);
                    date.seconds(0);
                    continue;
                }
                if (!(date.date() in this.dayOfMonth) &&
                    Object.keys(this.dayOfMonth).length !== 31 &&
                    !(date.day() in this.dayOfWeek &&
                        Object.keys(this.dayOfWeek).length !== 7)) {
                    date.add(1, 'd');
                    if (date.days() === prevDay) {
                        date.add(1, 'd');
                    }
                    date.hours(0);
                    date.minutes(0);
                    date.seconds(0);
                    continue;
                }
                if (!(date.day() in this.dayOfWeek) &&
                    Object.keys(this.dayOfWeek).length !== 7 &&
                    !(date.date() in this.dayOfMonth &&
                        Object.keys(this.dayOfMonth).length !== 31)) {
                    date.add(1, 'd');
                    if (date.days() === prevDay) {
                        date.add(1, 'd');
                    }
                    date.hours(0);
                    date.minutes(0);
                    date.seconds(0);
                    if (date <= origDate) {
                        date = this._findDST(origDate);
                    }
                    continue;
                }
                if (!(date.hours() in this.hour) &&
                    Object.keys(this.hour).length !== 24) {
                    origDate = moment(date);
                    var curHour = date.hours();
                    date.hours(date.hours() === 23 && diff > 86400000 ? 0 : date.hours() + 1);
                    /*
                     * Moment Date will not allow you to set the time to 2 AM if there is no 2 AM (on the day we change the clock)
                     * We will therefore jump to 3AM if time stayed at 1AM
                     */
                    if (curHour === date.hours()) {
                        date.hours(date.hours() + 2);
                    }
                    date.minutes(0);
                    date.seconds(0);
                    if (date <= origDate) {
                        date = this._findDST(origDate);
                    }
                    continue;
                }
                if (!(date.minutes() in this.minute) &&
                    Object.keys(this.minute).length !== 60) {
                    origDate = moment(date);
                    date.minutes(date.minutes() === 59 && diff > 60 * 60 * 1000
                        ? 0
                        : date.minutes() + 1);
                    date.seconds(0);
                    if (date <= origDate) {
                        date = this._findDST(origDate);
                    }
                    continue;
                }
                if (!(date.seconds() in this.second) &&
                    Object.keys(this.second).length !== 60) {
                    origDate = moment(date);
                    date.seconds(date.seconds() === 59 && diff > 60 * 1000 ? 0 : date.seconds() + 1);
                    if (date <= origDate) {
                        date = this._findDST(origDate);
                    }
                    continue;
                }
                if (date.valueOf() === firstDate) {
                    date.seconds(date.seconds() + 1);
                    continue;
                }
                break;
            }
            return date;
        },
        /**
         * get next date that is a valid DST date
         */
        _findDST: function (date) {
            var newDate = moment(date);
            while (newDate <= date) {
                // eslint seems to trigger here, it is wrong
                newDate.add(1, 's');
            }
            return newDate;
        },
        /**
         * wildcard, or all params in array (for to string)
         */
        _wcOrAll: function (type) {
            if (this._hasAll(type))
                return '*';
            var all = [];
            for (var time in this[type]) {
                all.push(time);
            }
            return all.join(',');
        },
        _hasAll: function (type) {
            var constrain = CronTime.constraints[timeUnits.indexOf(type)];
            for (var i = constrain[0], n = constrain[1]; i < n; i++) {
                if (!(i in this[type]))
                    return false;
            }
            return true;
        },
        _parse: function () {
            var aliases = CronTime.aliases;
            var source = this.source.replace(/[a-z]{1,3}/gi, function (alias) {
                alias = alias.toLowerCase();
                if (alias in aliases) {
                    return aliases[alias];
                }
                throw new Error('Unknown alias: ' + alias);
            });
            var split = source.replace(/^\s\s*|\s\s*$/g, '').split(/\s+/);
            var cur;
            var i = 0;
            var len = timeUnits.length;
            // seconds are optional
            if (split.length < timeUnits.length - 1) {
                throw new Error('Too few fields');
            }
            if (split.length > timeUnits.length) {
                throw new Error('Too many fields');
            }
            for (; i < timeUnits.length; i++) {
                // If the split source string doesn't contain all digits,
                // assume defaults for first n missing digits.
                // This adds support for 5-digit standard cron syntax
                cur = split[i - (len - split.length)] || CronTime.parseDefaults[i];
                this._parseField(cur, timeUnits[i], CronTime.constraints[i]);
            }
        },
        _parseField: function (field, type, constraints) {
            var rangePattern = /^(\d+)(?:-(\d+))?(?:\/(\d+))?$/g;
            var typeObj = this[type];
            var pointer;
            var low = constraints[0];
            var high = constraints[1];
            var fields = field.split(',');
            fields.forEach(function (field) {
                var wildcardIndex = field.indexOf('*');
                if (wildcardIndex !== -1 && wildcardIndex !== 0) {
                    throw new Error('Field (' + field + ') has an invalid wildcard expression');
                }
            });
            // * is a shortcut to [lower-upper] range
            field = field.replace(/\*/g, low + '-' + high);
            // commas separate information, so split based on those
            var allRanges = field.split(',');
            for (var i = 0; i < allRanges.length; i++) {
                if (allRanges[i].match(rangePattern)) {
                    allRanges[i].replace(rangePattern, function ($0, lower, upper, step) {
                        lower = parseInt(lower, 10);
                        upper = parseInt(upper, 10) || undefined;
                        var wasStepDefined = !isNaN(parseInt(step, 10));
                        if (step === '0') {
                            throw new Error('Field (' + field + ') has a step of zero');
                        }
                        step = parseInt(step, 10) || 1;
                        if (upper && lower > upper) {
                            throw new Error('Field (' + field + ') has an invalid range');
                        }
                        var outOfRangeError = lower < low ||
                            (upper && upper > high) ||
                            (!upper && lower > high);
                        if (outOfRangeError) {
                            throw new Error('Field (' + field + ') value is out of range');
                        }
                        // Positive integer higher than constraints[0]
                        lower = Math.min(Math.max(low, ~~Math.abs(lower)), high);
                        // Positive integer lower than constraints[1]
                        if (upper) {
                            upper = Math.min(high, ~~Math.abs(upper));
                        }
                        else {
                            // If step is provided, the default upper range is the highest value
                            upper = wasStepDefined ? high : lower;
                        }
                        // Count from the lower barrier to the upper
                        pointer = lower;
                        do {
                            typeObj[pointer] = true;
                            pointer += step;
                        } while (pointer <= upper);
                    });
                }
                else {
                    throw new Error('Field (' + field + ') cannot be parsed');
                }
            }
        }
    };
    function command2function(cmd) {
        var command;
        var args;
        switch (typeof cmd) {
            case 'string':
                args = cmd.split(' ');
                command = args.shift();
                cmd = spawn.bind(undefined, command, args);
                break;
            case 'object':
                command = cmd && cmd.command;
                if (command) {
                    args = cmd.args;
                    var options = cmd.options;
                    cmd = spawn.bind(undefined, command, args, options);
                }
                break;
        }
        return cmd;
    }
    function CronJob(cronTime, onTick, onComplete, startNow, timeZone, context, runOnInit, utcOffset, unrefTimeout) {
        var _cronTime = cronTime;
        var argCount = 0;
        for (var i = 0; i < arguments.length; i++) {
            if (arguments[i] !== undefined) {
                argCount++;
            }
        }
        if (typeof cronTime !== 'string' && argCount === 1) {
            // crontime is an object...
            onTick = cronTime.onTick;
            onComplete = cronTime.onComplete;
            context = cronTime.context;
            startNow = cronTime.start || cronTime.startNow || cronTime.startJob;
            timeZone = cronTime.timeZone;
            runOnInit = cronTime.runOnInit;
            _cronTime = cronTime.cronTime;
            utcOffset = cronTime.utcOffset;
            unrefTimeout = cronTime.unrefTimeout;
        }
        this.context = context || this;
        this._callbacks = [];
        this.onComplete = command2function(onComplete);
        this.cronTime = new CronTime(_cronTime, timeZone, utcOffset);
        this.unrefTimeout = unrefTimeout;
        addCallback.call(this, command2function(onTick));
        if (runOnInit) {
            this.lastExecution = new Date();
            fireOnTick.call(this);
        }
        if (startNow) {
            start.call(this);
        }
        return this;
    }
    var addCallback = function (callback) {
        if (typeof callback === 'function')
            this._callbacks.push(callback);
    };
    CronJob.prototype.addCallback = addCallback;
    CronJob.prototype.setTime = function (time) {
        if (!(time instanceof CronTime))
            throw new Error('time must be an instance of CronTime.');
        this.stop();
        this.cronTime = time;
    };
    CronJob.prototype.nextDate = function () {
        return this.cronTime.sendAt();
    };
    var fireOnTick = function () {
        for (var i = this._callbacks.length - 1; i >= 0; i--)
            this._callbacks[i].call(this.context, this.onComplete);
    };
    CronJob.prototype.fireOnTick = fireOnTick;
    CronJob.prototype.nextDates = function (i) {
        return this.cronTime.sendAt(i);
    };
    var start = function () {
        if (this.running)
            return;
        var MAXDELAY = 2147483647; // The maximum number of milliseconds setTimeout will wait.
        var self = this;
        var timeout = this.cronTime.getTimeout();
        var remaining = 0;
        var startTime;
        if (this.cronTime.realDate)
            this.runOnce = true;
        function _setTimeout(timeout) {
            startTime = Date.now();
            self._timeout = setTimeout(callbackWrapper, timeout);
            if (self.unrefTimeout && typeof self._timeout.unref === 'function') {
                self._timeout.unref();
            }
        }
        // The callback wrapper checks if it needs to sleep another period or not
        // and does the real callback logic when it's time.
        function callbackWrapper() {
            var diff = startTime + timeout - Date.now();
            if (diff > 0) {
                var newTimeout = self.cronTime.getTimeout();
                if (newTimeout > diff) {
                    newTimeout = diff;
                }
                remaining += newTimeout;
            }
            // If there is sleep time remaining, calculate how long and go to sleep
            // again. This processing might make us miss the deadline by a few ms
            // times the number of sleep sessions. Given a MAXDELAY of almost a
            // month, this should be no issue.
            self.lastExecution = new Date();
            if (remaining) {
                if (remaining > MAXDELAY) {
                    remaining -= MAXDELAY;
                    timeout = MAXDELAY;
                }
                else {
                    timeout = remaining;
                    remaining = 0;
                }
                _setTimeout(timeout);
            }
            else {
                // We have arrived at the correct point in time.
                self.running = false;
                // start before calling back so the callbacks have the ability to stop the cron job
                if (!self.runOnce)
                    self.start();
                self.fireOnTick();
            }
        }
        if (timeout >= 0) {
            this.running = true;
            // Don't try to sleep more than MAXDELAY ms at a time.
            if (timeout > MAXDELAY) {
                remaining = timeout - MAXDELAY;
                timeout = MAXDELAY;
            }
            _setTimeout(timeout);
        }
        else {
            this.stop();
        }
    };
    CronJob.prototype.start = start;
    CronJob.prototype.lastDate = function () {
        return this.lastExecution;
    };
    /**
     * Stop the cronjob.
     */
    CronJob.prototype.stop = function () {
        if (this._timeout)
            clearTimeout(this._timeout);
        this.running = false;
        if (typeof this.onComplete === 'function')
            this.onComplete();
    };
    exports.job = function (cronTime, onTick, onComplete, startNow, timeZone, context, runOnInit, utcOffset, unrefTimeout) {
        return new CronJob(cronTime, onTick, onComplete, startNow, timeZone, context, runOnInit, utcOffset, unrefTimeout);
    };
    exports.time = function (cronTime, timeZone) {
        return new CronTime(cronTime, timeZone);
    };
    exports.sendAt = function (cronTime) {
        return exports.time(cronTime).sendAt();
    };
    exports.timeout = function (cronTime) {
        return exports.time(cronTime).getTimeout();
    };
    exports.CronJob = CronJob;
    exports.CronTime = CronTime;
    return exports;
});