commit 66644cd66d097ad6246a870a70e5d4f2eb3a1e93
parent e502583b90f1a0c89ee41688a236112d54b55dce
Author: Bruno <b-coimbra@hotmail.com>
Date: Thu, 26 Mar 2020 16:04:39 -0300
added cron command
Diffstat:
4 files changed, 190 insertions(+), 40 deletions(-)
diff --git a/lib/commands/cron.ts b/lib/commands/cron.ts
@@ -1,9 +1,18 @@
+import { FORMATS } from '../extensions';
+import { help_info } from '../utils';
+import DEFAULT_GUILD_CONFIG from '../default';
+
+type Greenwich = 'pm' | 'am';
+type Ordinal = 'st' | 'nd' | 'rd' | 'th';
+
interface Schedule {
hours?: string;
minutes?: string;
dayOfMonth?: string;
month?: string;
dayOfWeek?: string;
+ greenwich?: Greenwich;
+ ordinal?: Ordinal
}
interface Command {
@@ -12,19 +21,10 @@ interface Command {
};
interface Cron {
- job: number,
+ id: number,
schedule?: Schedule,
command?: Command
-};
-
-const RESPONSES = {
- help: {
- rm: '.cron rm #[job-index]',
- command: ':warning: There is no command to execute the cron job on.',
- schedule: ':warning: There is no schedule to execute the command on.'
- },
- added: 'New cron job has been added', // TODO
- empty: ":warning: There are no cron jobs being executed."
+ executed_at?: number;
};
const MATCHERS = {
@@ -32,58 +32,201 @@ const MATCHERS = {
/^(((0|1)[0-9])|2[0-3]):[0-5][0-9]\s?(pm|am)?$/i,
day_of_month:
/(?:\b)(([1-9]|0[1-9]|[1-2][0-9]|3[0-1])(st|nd|rd|th)?)(?:\b|\/)/i,
- weekdays: 'mon tue wed thu fri sat sun'.split(' '),
- prefix: (prefix: string) => new RegExp(`^${prefix}`),
+ weekdays: 'sun mon tue wed thu fri sat'.split(' '),
+ months: 'jan feb mar apr may jun jul aug sep oct nov dec'
+ .split(' ').map(month => month.capitalize()),
+ prefix: (x: string) => new RegExp("^\\" + x),
ordinals: /st|nd|rd|th/,
greenwich: /pm|am/
};
+const RESPONSES = {
+ help: {
+ rm: 'The syntax for removing a cron job is: '
+ + '.cron rm #[job-index]'.format(FORMATS.block),
+ command: ':warning: There is no command to execute the cron job on.',
+ schedule: ':warning: There is no schedule to execute the command on.'
+ },
+ empty: ":warning: There are no cron jobs being executed.",
+ removed: (id: number) =>
+ `Removed cron job #${id.toString().format(FORMATS.bold)}.`,
+ added: (cron: Cron) =>
+ `New cron (#${cron.id.toString().format(FORMATS.bold)}) has been added.`,
+ list: (cron: Cron) => {
+ let { schedule } = cron;
+ let result: string = "";
+
+ result += `#${cron.id} `.format(FORMATS.bold);
+ result += `${cron.command.name.shorten(20)}`.format(FORMATS.block);
+
+ if (schedule?.hours && schedule?.minutes) {
+ result += `: ${schedule.hours}:${schedule.minutes}`;
+ if (schedule?.greenwich)
+ result += `${schedule.greenwich.toUpperCase()}`;
+ result += ' :clock3: ';
+ }
+
+ if (schedule?.dayOfWeek) {
+ let weekday = MATCHERS.weekdays[
+ Number(schedule.dayOfWeek) - 1
+ ]?.toUpperCase();
+
+ result += `${weekday}, `;
+ }
+
+ if (schedule?.dayOfMonth) {
+ let month = MATCHERS.months[
+ Number(schedule.month) - 1
+ ]?.capitalize();
+
+ result += `${month} ${schedule.dayOfMonth}`;
+
+ if (schedule?.ordinal)
+ result += `${schedule.ordinal}`;
+ }
+
+ return result;
+ }
+};
+
+export class Timer {
+ private homescope: HomeScope;
+
+ constructor(homescope: HomeScope) {
+ this.homescope = homescope;
+ }
+
+ compare(job: Cron): void {
+ let current = new Date();
+ current.setDate(current.getDate());
+ current.setUTCHours(current.getHours() % 12);
+ current.setSeconds(0);
+ current.setMilliseconds(0);
+
+ if (current.getTime() === this.timestamp(job))
+ this.dispatch(job, current.getTime());
+ }
+
+ timestamp(job: Cron): number {
+ let date = new Date();
+ let { hours, minutes, month, dayOfMonth } = job.schedule;
+
+ date.setUTCHours(Number(hours), Number(minutes), 0);
+ date.setMonth(Number(month) - 1);
+ date.setMilliseconds(0);
+ date.setDate(Number(dayOfMonth));
+
+ return date.getTime();
+ }
+
+ dispatch(job: Cron, timespan: number) {
+ if (job.executed_at === timespan)
+ return;
+
+ console.log('Executed cron job #' + job.id);
+
+ this.homescope.message.content =
+ `${this.homescope.CONFIG.commands.prefix} ${job.command.name} ${job.command.args.join(' ')}`;
+
+ job.executed_at = timespan;
+
+ this.homescope.main.process_command(
+ this.homescope.message
+ );
+ }
+
+ verify(jobs: Cron[]): void {
+ jobs.forEach((job: Cron) => this.compare(job))
+ }
+}
+
export default (home_scope: HomeScope) => {
const { message, args, CONFIG } = home_scope;
- // let command = `20:32pm 26th 7 fri ${prefix}echo poopoo`;
- let crons: Cron[] = CONFIG.cron_jobs;
+ if (args.length === 0 || args[0] === 'help') {
+ return message.channel.send(
+ help_info('cron', CONFIG.commands.prefix)
+ );
+ }
+
+ const cleanup = (jobs: Cron[]): Cron[] => jobs
+ .filter(x => x != null)
+ .map((x, i) => {
+ x.id = i;
+ return x;
+ });
+
+ let crons: Cron[] = cleanup(CONFIG.cron_jobs);
+ const timer = new Timer(home_scope);
+
+ setInterval(() => {
+ timer.verify(crons);
+ }, DEFAULT_GUILD_CONFIG.cron_interval);
+
+ const submit = () =>
+ CONFIG.cron_jobs = crons;
const matches = (value: string, regex: RegExp): string | undefined =>
(value.match(regex) || {})?.input;
- const rm = (job: number) =>
- delete crons[crons.map(cron => cron.job).indexOf(job)];
+ const rm = (job: number) => {
+ delete crons[crons.map(x => x.id).indexOf(job)];
+ crons = cleanup(crons);
+ submit();
+ message.answer(RESPONSES.removed(job));
+ };
- // TODO
const list = () => {
if (crons.length == 0)
return message.answer(RESPONSES.empty);
- crons.each(cron => message.answer("Can't list jobs yet..."));
+ console.log('list command:', crons
+ .filter(x => x != null)
+ .map(x => RESPONSES.list(x))
+ .join("\n"));
+
+ message.channel.send(
+ crons
+ .filter(x => x != null)
+ .map(x => RESPONSES.list(x))
+ .join("\n")
+ );
}
- const parse = (args: string[]): Cron => {
+ const parse = (argm: string[]): Cron => {
let cron: Cron = {
- job: crons.slice(-1)[0]?.job + 1 || 0
+ id: crons.slice(-1)[0]?.id + 1 || 0
};
- args.some((argument, i) => {
+ argm.some((argument, i) => {
+ argument = argument.trim();
+
switch (argument) {
- case (matches(argument, MATCHERS.prefix(CONFIG.commands.prefix))):
- cron.command = {
- name: argument,
- args: args.slice(i + 1)
+ case (
+ matches(argument, MATCHERS.prefix(CONFIG.commands.prefix))
+ ): cron.command = {
+ name: argument.split(CONFIG.commands.prefix)[1],
+ args: argm.slice(i + 1)
};
break;
case (matches(argument, MATCHERS.hour_mins)):
- const [hour, min] = argument.split(':');
+ const [hour, mins] = argument.split(':');
+ const [min, greenwich] = mins.split(MATCHERS.greenwich);
cron.schedule = {
hours: hour,
- minutes: min.split(MATCHERS.greenwich)[0]
+ minutes: min,
+ greenwich: greenwich as Greenwich
};
break;
case (matches(argument, MATCHERS.day_of_month)):
+ const [dayOfMonth, ordinal] =
+ argument.split(MATCHERS.ordinals);
+
const date =
matches(argument, MATCHERS.ordinals) == undefined
? { month: argument }
- : { dayOfMonth: argument.split(MATCHERS.ordinals)[0] };
+ : { dayOfMonth, ordinal: ordinal as Ordinal };
cron.schedule = {
...cron.schedule,
@@ -107,27 +250,26 @@ export default (home_scope: HomeScope) => {
return cron;
};
- let options = args.join(' ').split(' ');
-
- if (options[0] === 'ls')
+ if (args[0] === 'ls')
list();
- else if (options[0] === 'rm') {
- let job: number = Number(options[1]);
+ else if (args[0] === 'rm') {
+ let job: number = Number(args[1]);
(isNaN(job))
? message.answer(RESPONSES.help.rm)
: rm(job);
}
else {
- const cron: Cron = parse(options);
+ const cron: Cron = parse(args);
if (!cron?.command)
message.answer(RESPONSES.help.command);
else if (!cron?.schedule)
message.answer(RESPONSES.help.schedule)
else {
- crons.push(parse(options));
- message.answer(RESPONSES.added);
+ crons.push(cron);
+ submit();
+ message.answer(RESPONSES.added(cron));
}
}
};
diff --git a/lib/default.ts b/lib/default.ts
@@ -12,6 +12,7 @@ const DEFAULT_GUILD_CONFIG : Types.Config = {
'541761315887120399': 'Moscow'
},
cron_jobs: [],
+ cron_interval: 2000,
commands: {
prefix: '!',
max_history: 40,
diff --git a/lib/extensions.ts b/lib/extensions.ts
@@ -1,3 +1,5 @@
+import { SimpOMatic } from './main';
+
// Global Extensions:
declare global {
type HomeScope = {
@@ -6,7 +8,8 @@ declare global {
GIT_URL: string, HELP_MESSAGES: string[],
HELP_SECTIONS: string[] , ALL_HELP: string[],
CONFIG: Types.Config, SECRETS: any, KNOWN_COMMANDS: string[],
- expand_alias: (operator: string, args: string[], message: Message) => string
+ expand_alias: (operator: string, args: string[], message: Message) => string,
+ main: SimpOMatic;
};
namespace Types {
@@ -26,6 +29,7 @@ declare global {
system_channel: string,
pp_sizes: { [key: string]: number }
cron_jobs: any[],
+ cron_interval: number;
weather_locations: { [key: string]: string },
commands: {
prefix: string,
@@ -197,7 +201,7 @@ export const FORMATS: TextFormat = {
};
String.prototype.format = function (fmt: string) {
- return `${fmt}${this}${fmt}`;
+ return `${fmt}${this.toString()}${fmt}`;
};
String.prototype.shorten = function (width=40) {
diff --git a/lib/main.ts b/lib/main.ts
@@ -27,6 +27,7 @@ import { pastebin_latest,
pastebin_update,
pastebin_url } from './api/pastebin';
import { Guild } from 'discord.js';
+import { Timer } from './commands/cron';
// Anything that hasn't been defined in `bot.json`
// will be taken care of by the defaults.
@@ -254,10 +255,12 @@ export class SimpOMatic {
HELP_SOURCE, HELP_KEY, GIT_URL,
HELP_MESSAGES, HELP_SECTIONS, ALL_HELP,
CONFIG, SECRETS, KNOWN_COMMANDS,
- expand_alias: this.expand_alias };
+ expand_alias: this.expand_alias,
+ main: this };
const commands = read_dir(`${__dirname}/commands`)
.map(n => n.slice(0, -3));
+
if (commands.includes(operator))
return import(`./commands/${operator}`).then(mod =>
mod.default(homescope));