Merge branch 'main' of pv-svartalfheim-git:fenris/munin
This commit is contained in:
commit
27d263a52a
29
ivaldi.json
Normal file
29
ivaldi.json
Normal file
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "munin",
|
||||
"libs": [
|
||||
{
|
||||
"kind": "plankton",
|
||||
"data": {
|
||||
"modules": [
|
||||
"pit",
|
||||
"http",
|
||||
"ical",
|
||||
"telegram",
|
||||
"conf",
|
||||
"log",
|
||||
"args"
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"sources": [
|
||||
"types.ts",
|
||||
"sources/kalender_digital.ts",
|
||||
"sources/_functions.ts",
|
||||
"targets/telegram_bot.ts",
|
||||
"targets/_functions.ts",
|
||||
"conf.ts",
|
||||
"main.ts"
|
||||
]
|
||||
}
|
||||
|
31
libs/plankton/plankton.d.ts
vendored
31
libs/plankton/plankton.d.ts
vendored
|
@ -1,11 +1,11 @@
|
|||
/**
|
||||
* @author fenris
|
||||
*/
|
||||
type int = number;
|
||||
declare type int = number;
|
||||
/**
|
||||
* @author fenris
|
||||
*/
|
||||
type float = number;
|
||||
declare type float = number;
|
||||
declare var process: any;
|
||||
declare var require: any;
|
||||
declare class Buffer {
|
||||
|
@ -22,7 +22,7 @@ declare namespace lib_plankton.base {
|
|||
/**
|
||||
* @author fenris
|
||||
*/
|
||||
type type_pseudopointer<type_value> = {
|
||||
declare type type_pseudopointer<type_value> = {
|
||||
value: type_value;
|
||||
};
|
||||
/**
|
||||
|
@ -1205,6 +1205,9 @@ declare namespace lib_plankton.pit {
|
|||
/**
|
||||
*/
|
||||
function is_before(pit: type_pit, reference: type_pit): boolean;
|
||||
/**
|
||||
*/
|
||||
function is_after(pit: type_pit, reference: type_pit): boolean;
|
||||
/**
|
||||
*/
|
||||
function is_between(pit: type_pit, reference_left: type_pit, reference_right: type_pit): boolean;
|
||||
|
@ -1217,9 +1220,27 @@ declare namespace lib_plankton.pit {
|
|||
/**
|
||||
*/
|
||||
function shift_week(pit: type_pit, increment: int): type_pit;
|
||||
/**
|
||||
*/
|
||||
function shift_year(pit: type_pit, increment: int): type_pit;
|
||||
/**
|
||||
*/
|
||||
function trunc_minute(pit: type_pit): type_pit;
|
||||
/**
|
||||
*/
|
||||
function trunc_hour(pit: type_pit): type_pit;
|
||||
/**
|
||||
*/
|
||||
function trunc_day(pit: type_pit): type_pit;
|
||||
/**
|
||||
*/
|
||||
function trunc_week(pit: type_pit): type_pit;
|
||||
/**
|
||||
*/
|
||||
function trunc_month(pit: type_pit): type_pit;
|
||||
/**
|
||||
*/
|
||||
function trunc_year(pit: type_pit): type_pit;
|
||||
/**
|
||||
*/
|
||||
function now(): type_pit;
|
||||
|
@ -2237,7 +2258,9 @@ declare namespace lib_plankton.telegram {
|
|||
/**
|
||||
* @see https://core.telegram.org/bots/api#sendmessage
|
||||
*/
|
||||
function bot_call_send_message(token: string, chat_id: (int | string), text: string): Promise<void>;
|
||||
function bot_call_send_message(token: string, chat_id: (int | string), text: string, { "parse_mode": parse_mode, }?: {
|
||||
parse_mode?: (null | string);
|
||||
}): Promise<void>;
|
||||
}
|
||||
declare namespace lib_plankton.file {
|
||||
/**
|
||||
|
|
|
@ -1476,7 +1476,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||||
function step(op) {
|
||||
if (f) throw new TypeError("Generator is already executing.");
|
||||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
||||
while (_) try {
|
||||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||||
switch (op[0]) {
|
||||
|
@ -3637,6 +3637,7 @@ var lib_plankton;
|
|||
function is_after(pit, reference) {
|
||||
return (pit > reference);
|
||||
}
|
||||
pit_1.is_after = is_after;
|
||||
/**
|
||||
*/
|
||||
function is_between(pit, reference_left, reference_right) {
|
||||
|
@ -3668,6 +3669,7 @@ var lib_plankton;
|
|||
function shift_year(pit, increment) {
|
||||
return (pit + (60 * 60 * 24 * 365 * increment));
|
||||
}
|
||||
pit_1.shift_year = shift_year;
|
||||
/**
|
||||
*/
|
||||
function trunc_minute(pit) {
|
||||
|
@ -3695,6 +3697,7 @@ var lib_plankton;
|
|||
};
|
||||
return from_datetime(datetime_output);
|
||||
}
|
||||
pit_1.trunc_minute = trunc_minute;
|
||||
/**
|
||||
*/
|
||||
function trunc_hour(pit) {
|
||||
|
@ -3718,6 +3721,7 @@ var lib_plankton;
|
|||
};
|
||||
return from_datetime(datetime_output);
|
||||
}
|
||||
pit_1.trunc_hour = trunc_hour;
|
||||
/**
|
||||
*/
|
||||
function trunc_day(pit) {
|
||||
|
@ -3737,6 +3741,7 @@ var lib_plankton;
|
|||
};
|
||||
return from_datetime(datetime_output);
|
||||
}
|
||||
pit_1.trunc_day = trunc_day;
|
||||
/**
|
||||
*/
|
||||
function trunc_week(pit) {
|
||||
|
@ -3768,6 +3773,7 @@ var lib_plankton;
|
|||
};
|
||||
return from_datetime(datetime_output);
|
||||
}
|
||||
pit_1.trunc_month = trunc_month;
|
||||
/**
|
||||
*/
|
||||
function trunc_year(pit) {
|
||||
|
@ -3787,6 +3793,7 @@ var lib_plankton;
|
|||
};
|
||||
return from_datetime(datetime_output);
|
||||
}
|
||||
pit_1.trunc_year = trunc_year;
|
||||
/**
|
||||
*/
|
||||
function now() {
|
||||
|
@ -6236,10 +6243,11 @@ var lib_plankton;
|
|||
/**
|
||||
* @see https://core.telegram.org/bots/api#sendmessage
|
||||
*/
|
||||
async function bot_call_send_message(token, chat_id, text) {
|
||||
async function bot_call_send_message(token, chat_id, text, { "parse_mode": parse_mode = null, } = {}) {
|
||||
const output = await bot_call_generic(token, "sendMessage", {
|
||||
"chat_id": chat_id,
|
||||
"text": text,
|
||||
"parse_mode": (parse_mode ?? undefined),
|
||||
});
|
||||
}
|
||||
telegram.bot_call_send_message = bot_call_send_message;
|
||||
|
|
26
readme.md
26
readme.md
|
@ -2,3 +2,29 @@
|
|||
|
||||
Sendet Erinnerungen an Termine
|
||||
|
||||
|
||||
## Erstellung
|
||||
|
||||
### Erfordernisse
|
||||
|
||||
- GNU Make
|
||||
- Typescript Compiler
|
||||
|
||||
|
||||
### Anweisungen
|
||||
|
||||
- `tools/build`
|
||||
|
||||
|
||||
## Verwendung
|
||||
|
||||
### Erfordernisse
|
||||
|
||||
- NodeJS
|
||||
|
||||
|
||||
### Anweisungen
|
||||
|
||||
siehe `munin -h`
|
||||
|
||||
|
||||
|
|
177
source/conf.ts
177
source/conf.ts
|
@ -1,16 +1,38 @@
|
|||
namespace _lixer_event_reminder.conf
|
||||
/*
|
||||
This file is part of »munin«.
|
||||
|
||||
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
|
||||
|
||||
»munin« is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
»munin« is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @todo versioning
|
||||
*/
|
||||
namespace _munin.conf
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
export type type_conf = {
|
||||
bot_token : string;
|
||||
sources : Array<
|
||||
(
|
||||
{
|
||||
kind : "kalender_digital",
|
||||
data : {
|
||||
id : string;
|
||||
path : string;
|
||||
filtration : {
|
||||
category_blacklist : Array<string>;
|
||||
title_blacklist : Array<string>;
|
||||
|
@ -20,11 +42,30 @@ namespace _lixer_event_reminder.conf
|
|||
)
|
||||
>;
|
||||
targets : Array<
|
||||
{
|
||||
chat_id : int;
|
||||
interval : Array<int>;
|
||||
}
|
||||
(
|
||||
{
|
||||
kind : "telegram_bot",
|
||||
data : {
|
||||
bot_token : string;
|
||||
chat_id : int;
|
||||
/**
|
||||
* in hours
|
||||
*/
|
||||
interval : Array<int>;
|
||||
}
|
||||
}
|
||||
)
|
||||
>;
|
||||
/**
|
||||
* in hours
|
||||
*/
|
||||
frequency : float;
|
||||
labels : {
|
||||
head : string;
|
||||
title : string;
|
||||
time : string;
|
||||
location : string;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
|
@ -38,10 +79,6 @@ namespace _lixer_event_reminder.conf
|
|||
"nullable": false,
|
||||
"type": "string",
|
||||
},
|
||||
"bot_token": {
|
||||
"nullable": false,
|
||||
"type": "string",
|
||||
},
|
||||
"sources": {
|
||||
"nullable": false,
|
||||
"type": "array",
|
||||
|
@ -60,7 +97,7 @@ namespace _lixer_event_reminder.conf
|
|||
"nullable": false,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"path": {
|
||||
"nullable": false,
|
||||
"type": "string"
|
||||
},
|
||||
|
@ -74,7 +111,8 @@ namespace _lixer_event_reminder.conf
|
|||
"items": {
|
||||
"nullable": false,
|
||||
"type": "string",
|
||||
}
|
||||
},
|
||||
"default": [],
|
||||
},
|
||||
"title_blacklist": {
|
||||
"nullable": false,
|
||||
|
@ -82,20 +120,19 @@ namespace _lixer_event_reminder.conf
|
|||
"items": {
|
||||
"nullable": false,
|
||||
"type": "string",
|
||||
}
|
||||
},
|
||||
"default": [],
|
||||
},
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"category_blacklist",
|
||||
"title_blacklist",
|
||||
]
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"id",
|
||||
"filtration",
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -113,35 +150,95 @@ namespace _lixer_event_reminder.conf
|
|||
"type": "array",
|
||||
"items": {
|
||||
"nullable": false,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"chat_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"nullable": false,
|
||||
"type": "integer",
|
||||
},
|
||||
"interval": {
|
||||
"nullable": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"nullable": false,
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"chat_id",
|
||||
"interval",
|
||||
]
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"kind": {
|
||||
"nullable": false,
|
||||
"type": "string",
|
||||
"enum": ["telegram_bot"]
|
||||
},
|
||||
"data": {
|
||||
"nullable": false,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"bot_token": {
|
||||
"nullable": false,
|
||||
"type": "string",
|
||||
},
|
||||
"chat_id": {
|
||||
"nullable": false,
|
||||
"type": "integer",
|
||||
},
|
||||
"interval": {
|
||||
"nullable": false,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"nullable": false,
|
||||
"type": "integer"
|
||||
},
|
||||
"default": [24.0],
|
||||
},
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"bot_token",
|
||||
"chat_id",
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"kind",
|
||||
"data",
|
||||
]
|
||||
}
|
||||
],
|
||||
}
|
||||
},
|
||||
"frequency": {
|
||||
"nullable": false,
|
||||
"type": "number",
|
||||
"default": 1.0,
|
||||
},
|
||||
"labels": {
|
||||
"nullable": false,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"head": {
|
||||
"nullable": false,
|
||||
"type": "string",
|
||||
"default": "Termin-Erinnerung"
|
||||
},
|
||||
"title": {
|
||||
"nullable": false,
|
||||
"type": "string",
|
||||
"default": "was"
|
||||
},
|
||||
"time": {
|
||||
"nullable": false,
|
||||
"type": "string",
|
||||
"default": "wann"
|
||||
},
|
||||
"location": {
|
||||
"nullable": false,
|
||||
"type": "string",
|
||||
"default": "wo"
|
||||
},
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"version",
|
||||
"bot_token",
|
||||
"chat_id",
|
||||
"intval",
|
||||
"sources",
|
||||
"targets",
|
||||
],
|
||||
};
|
||||
|
||||
|
|
310
source/main.ts
310
source/main.ts
|
@ -1,6 +1,143 @@
|
|||
namespace _lixer_event_reminder
|
||||
/*
|
||||
This file is part of »munin«.
|
||||
|
||||
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
|
||||
|
||||
»munin« is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
»munin« is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace _munin
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
async function run_iteration(
|
||||
conf : _munin.conf.type_conf,
|
||||
sources : Array<_munin.type_source>,
|
||||
targets : Array<_munin.type_target>,
|
||||
{
|
||||
"dry_run": dry_run = false,
|
||||
} : {
|
||||
dry_run ?: boolean;
|
||||
} = {
|
||||
}
|
||||
) : Promise<void>
|
||||
{
|
||||
const now : lib_plankton.pit.type_pit = lib_plankton.pit.now();
|
||||
const events : Array<_munin.type_event> = (
|
||||
(await Promise.all(sources.map(source => source.fetch())))
|
||||
.reduce((x, y) => x.concat(y), [])
|
||||
);
|
||||
for (const target of targets) {
|
||||
lib_plankton.log._info(
|
||||
"munin.run.iteration",
|
||||
{
|
||||
"details": {
|
||||
"target": target,
|
||||
}
|
||||
}
|
||||
);
|
||||
for (const hours of target.interval) {
|
||||
const window_from : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
|
||||
now,
|
||||
hours + 0
|
||||
);
|
||||
const window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
|
||||
now,
|
||||
hours + (conf.frequency / 2)
|
||||
);
|
||||
for (const event of events) {
|
||||
const event_begin : lib_plankton.pit.type_pit = lib_plankton.pit.from_datetime(event.begin);
|
||||
lib_plankton.log._debug(
|
||||
"munin.run.check_dueness",
|
||||
{
|
||||
"details": {
|
||||
"event_begin": lib_plankton.pit.datetime_format(lib_plankton.pit.to_datetime(event_begin)),
|
||||
"window_from": lib_plankton.pit.datetime_format(lib_plankton.pit.to_datetime(window_from)),
|
||||
"window_to": lib_plankton.pit.datetime_format(lib_plankton.pit.to_datetime(window_to)),
|
||||
}
|
||||
}
|
||||
);
|
||||
const remind : boolean = lib_plankton.pit.is_between(
|
||||
event_begin,
|
||||
window_from,
|
||||
window_to
|
||||
);
|
||||
if (! remind) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
lib_plankton.log._info(
|
||||
"munin.remind",
|
||||
{
|
||||
"details": {
|
||||
"event": event,
|
||||
"target": target,
|
||||
}
|
||||
}
|
||||
);
|
||||
if (dry_run) {
|
||||
// do nothing
|
||||
}
|
||||
else {
|
||||
await target.send(conf.labels, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
function run(
|
||||
conf : _munin.conf.type_conf,
|
||||
{
|
||||
"dry_run": dry_run = false,
|
||||
} : {
|
||||
dry_run ?: boolean;
|
||||
} = {
|
||||
}
|
||||
) : void
|
||||
{
|
||||
const sources : Array<_munin.type_source> = conf.sources.map(
|
||||
source_raw => _munin.sources.factory(source_raw)
|
||||
);
|
||||
const targets : Array<_munin.type_target> = conf.targets.map(
|
||||
target_raw => _munin.targets.factory(target_raw)
|
||||
);
|
||||
lib_plankton.log._info(
|
||||
"munin.run.start",
|
||||
{
|
||||
"details": {
|
||||
}
|
||||
}
|
||||
);
|
||||
run_iteration(conf, sources, targets, {"dry_run": dry_run});
|
||||
/**
|
||||
* outsource setInterval logic
|
||||
*/
|
||||
setInterval(
|
||||
() => {run_iteration(conf, sources, targets, {"dry_run": dry_run});},
|
||||
(conf.frequency * 60 * 60 * 1000)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export async function main(
|
||||
|
@ -27,14 +164,32 @@ namespace _lixer_event_reminder
|
|||
"info": "path to configuration file",
|
||||
"name": "conf-path",
|
||||
}),
|
||||
"message": lib_plankton.args.class_argument.volatile({
|
||||
"indicators_long": ["message"],
|
||||
"indicators_short": ["m"],
|
||||
"conf_expose": lib_plankton.args.class_argument.volatile({
|
||||
"indicators_long": ["conf-expose"],
|
||||
"indicators_short": ["e"],
|
||||
"type": lib_plankton.args.enum_type.boolean,
|
||||
"mode": lib_plankton.args.enum_mode.replace,
|
||||
"default": false,
|
||||
"info": "whether to expose the full configuration",
|
||||
"name": "conf-expose",
|
||||
}),
|
||||
"verbosity": lib_plankton.args.class_argument.volatile({
|
||||
"indicators_long": ["verbosity"],
|
||||
"indicators_short": ["v"],
|
||||
"type": lib_plankton.args.enum_type.string,
|
||||
"mode": lib_plankton.args.enum_mode.replace,
|
||||
"default": "test",
|
||||
"info": "message to send",
|
||||
"name": "message",
|
||||
"default": "notice",
|
||||
"info": "error | warning | notice | info | debug",
|
||||
"name": "verbosity",
|
||||
}),
|
||||
"dry_run": lib_plankton.args.class_argument.volatile({
|
||||
"indicators_long": ["dry-run"],
|
||||
"indicators_short": ["q"],
|
||||
"type": lib_plankton.args.enum_type.boolean,
|
||||
"mode": lib_plankton.args.enum_mode.replace,
|
||||
"default": false,
|
||||
"info": "whether to not skip the sending of reminders (logs will be written)",
|
||||
"name": "dry-run",
|
||||
}),
|
||||
"help": lib_plankton.args.class_argument.volatile({
|
||||
"indicators_long": ["help"],
|
||||
|
@ -52,102 +207,81 @@ namespace _lixer_event_reminder
|
|||
args_raw.join(" ")
|
||||
);
|
||||
|
||||
// init
|
||||
const conf : _lixer_event_reminder.conf.type_conf = await lib_plankton.conf.load<_lixer_event_reminder.conf.type_conf>(
|
||||
_lixer_event_reminder.conf.schema,
|
||||
args.conf_path
|
||||
);
|
||||
|
||||
// exec
|
||||
if (args.help || (args.action === "help")) {
|
||||
process.stdout.write(
|
||||
arg_handler.generate_help(
|
||||
{
|
||||
"programname": "lixer-event-reminder",
|
||||
"description": "a telegram bot, which sends reminders about upcoming events",
|
||||
"executable": "node build/event-reminder",
|
||||
"programname": "munin",
|
||||
"description": "sends reminders about upcoming events",
|
||||
"executable": "node build/munin",
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
else {
|
||||
// init
|
||||
const conf : _munin.conf.type_conf = await lib_plankton.conf.load<_munin.conf.type_conf>(
|
||||
_munin.conf.schema,
|
||||
args.conf_path
|
||||
);
|
||||
lib_plankton.log.set_main_logger(
|
||||
[
|
||||
{
|
||||
"kind": "filtered",
|
||||
"data": {
|
||||
"core": {
|
||||
"kind": "std",
|
||||
"data": {
|
||||
"target": "stdout",
|
||||
"format": {
|
||||
"kind": "human_readable",
|
||||
"data": {
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"predicate": [
|
||||
[
|
||||
{
|
||||
"item": {
|
||||
"kind": "level",
|
||||
"data": {
|
||||
"threshold": args.verbosity,
|
||||
}
|
||||
},
|
||||
}
|
||||
]
|
||||
],
|
||||
}
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
// exec
|
||||
if (args.conf_expose) {
|
||||
process.stdout.write(
|
||||
lib_plankton.json.encode(
|
||||
conf,
|
||||
{
|
||||
"formatted": true,
|
||||
}
|
||||
)
|
||||
+
|
||||
"\n"
|
||||
);
|
||||
}
|
||||
switch (args.action) {
|
||||
default: {
|
||||
throw (new Error("unhandled action: " + args.action));
|
||||
break;
|
||||
}
|
||||
case "fetch": {
|
||||
const updates : Array<any> = await lib_plankton.telegram.bot_call_get_updates(
|
||||
conf.bot_token
|
||||
);
|
||||
process.stdout.write(JSON.stringify(updates, undefined, "\t") + "\n");
|
||||
break;
|
||||
}
|
||||
case "send": {
|
||||
for (const target of conf.targets) {
|
||||
const message = await lib_plankton.telegram.bot_call_send_message(
|
||||
conf.bot_token,
|
||||
target.chat_id,
|
||||
args.message
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "run": {
|
||||
const sources : Array<_lixer_event_reminder.type_source> = conf.sources.map(
|
||||
source_raw => _lixer_event_reminder.sources.factory(source_raw)
|
||||
);
|
||||
const targets : Array<_lixer_event_reminder.type_target> = conf.targets.map(
|
||||
target_raw => target_raw
|
||||
);
|
||||
const now : lib_plankton.pit.type_pit = lib_plankton.pit.now();
|
||||
|
||||
const events : Array<_lixer_event_reminder.type_event> = (
|
||||
(await Promise.all(sources.map(source => source.fetch())))
|
||||
.reduce((x, y) => x.concat(y), [])
|
||||
);
|
||||
for (const event of events) {
|
||||
const begin : lib_plankton.pit.type_pit = lib_plankton.pit.from_datetime(event.begin);
|
||||
for (const target of targets) {
|
||||
for (const hours of target.interval) {
|
||||
const remind : boolean = lib_plankton.pit.is_between(
|
||||
lib_plankton.pit.shift_hour(begin, hours),
|
||||
now,
|
||||
/**
|
||||
* @todo parametrize window
|
||||
*/
|
||||
lib_plankton.pit.shift_hour(now, +24)
|
||||
);
|
||||
if (remind) {
|
||||
lib_plankton.log._info(
|
||||
"remind",
|
||||
{
|
||||
"details": {
|
||||
"event": event,
|
||||
"target": target,
|
||||
}
|
||||
}
|
||||
);
|
||||
/**
|
||||
* @todo activate
|
||||
* @todo format better
|
||||
*/
|
||||
const message = await lib_plankton.telegram.bot_call_send_message(
|
||||
conf.bot_token,
|
||||
target.chat_id,
|
||||
lib_plankton.string.coin(
|
||||
"{{title}} | {{begin}}",
|
||||
{
|
||||
"title": event.title,
|
||||
"begin": lib_plankton.pit.datetime_format(event.begin),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
run(
|
||||
conf,
|
||||
{
|
||||
"dry_run": args.dry_run,
|
||||
}
|
||||
}
|
||||
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -157,7 +291,7 @@ namespace _lixer_event_reminder
|
|||
}
|
||||
}
|
||||
|
||||
_lixer_event_reminder.main(process.argv.slice(2))
|
||||
_munin.main(process.argv.slice(2))
|
||||
.then(() => {})
|
||||
.catch((reason) => {process.stderr.write(String(reason) + "\n");})
|
||||
;
|
||||
|
|
|
@ -1,4 +1,24 @@
|
|||
namespace _lixer_event_reminder.sources
|
||||
/*
|
||||
This file is part of »munin«.
|
||||
|
||||
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
|
||||
|
||||
»munin« is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
»munin« is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace _munin.sources
|
||||
{
|
||||
|
||||
/**
|
||||
|
@ -8,7 +28,7 @@ namespace _lixer_event_reminder.sources
|
|||
kind : string,
|
||||
data : any
|
||||
}
|
||||
) : type_source
|
||||
) : _munin.type_source
|
||||
{
|
||||
switch (description.kind) {
|
||||
default: {
|
||||
|
@ -16,8 +36,8 @@ namespace _lixer_event_reminder.sources
|
|||
break;
|
||||
}
|
||||
case "kalender_digital": {
|
||||
return _lixer_event_reminder.sources.kalender_digital.implementation_source(
|
||||
description.data as _lixer_event_reminder.sources.kalender_digital.type_parameters
|
||||
return _munin.sources.kalender_digital.implementation_source(
|
||||
description.data as _munin.sources.kalender_digital.type_parameters
|
||||
);
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
namespace _lixer_event_reminder.sources.kalender_digital
|
||||
namespace _munin.sources.kalender_digital
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
export type type_parameters = {
|
||||
id : string;
|
||||
path : string;
|
||||
filtration : {
|
||||
category_blacklist : Array<string>;
|
||||
title_blacklist : Array<string>;
|
||||
|
@ -16,15 +16,15 @@ namespace _lixer_event_reminder.sources.kalender_digital
|
|||
*/
|
||||
async function fetch(
|
||||
parameters : type_parameters
|
||||
) : Promise<Array<_lixer_event_reminder.type_event>>
|
||||
) : Promise<Array<_munin.type_event>>
|
||||
{
|
||||
const http_request : lib_plankton.http.type_request = {
|
||||
"scheme": "https",
|
||||
"host": "export.kalender.digital",
|
||||
"path": lib_plankton.string.coin(
|
||||
"/ics/0/{{id}}/gesamterkalender.ics",
|
||||
"/ics/{{path}}.ics",
|
||||
{
|
||||
"id": parameters.id,
|
||||
"path": parameters.path,
|
||||
}
|
||||
),
|
||||
"version": "HTTP/2",
|
||||
|
@ -33,7 +33,10 @@ namespace _lixer_event_reminder.sources.kalender_digital
|
|||
"?past_months={{past_months}}&future_months={{future_months}}",
|
||||
{
|
||||
"past_months": (0).toFixed(0),
|
||||
"future_months": (2).toFixed(0),
|
||||
/**
|
||||
* anpassen an frequency?
|
||||
*/
|
||||
"future_months": (1).toFixed(0),
|
||||
}
|
||||
),
|
||||
"headers": {},
|
||||
|
@ -42,13 +45,34 @@ namespace _lixer_event_reminder.sources.kalender_digital
|
|||
const http_response : lib_plankton.http.type_response = await lib_plankton.http.call(http_request);
|
||||
const ics : string = http_response.body.toString();
|
||||
const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode(ics);
|
||||
const events : Array<_lixer_event_reminder.type_event> = (
|
||||
const events : Array<_munin.type_event> = (
|
||||
vcalendar.vevents
|
||||
.filter(
|
||||
vevent => (
|
||||
vevent.categories.every(category => ! parameters.filtration.category_blacklist.includes(category))
|
||||
// category
|
||||
(
|
||||
vevent.categories.every(
|
||||
category => (
|
||||
! (
|
||||
parameters.filtration.category_blacklist
|
||||
.map(category_ => category_.toLowerCase())
|
||||
.includes(category.toLowerCase())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
&&
|
||||
parameters.filtration.title_blacklist.every(title => ! vevent.summary.toLowerCase().includes(title.toLowerCase()))
|
||||
// title
|
||||
(
|
||||
parameters.filtration.title_blacklist.every(
|
||||
title => (
|
||||
! (
|
||||
vevent.summary.toLowerCase()
|
||||
.includes(title.toLowerCase())
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
.map(
|
||||
|
@ -63,11 +87,11 @@ namespace _lixer_event_reminder.sources.kalender_digital
|
|||
"date": vevent.dtend.value.date,
|
||||
"time": vevent.dtend.value.time,
|
||||
};
|
||||
const event : _lixer_event_reminder.type_event = {
|
||||
const event : _munin.type_event = {
|
||||
"title": vevent.summary,
|
||||
"begin": begin,
|
||||
"end": end,
|
||||
"location": vevent.location,
|
||||
"location": (vevent.location ?? null),
|
||||
};
|
||||
return event;
|
||||
}
|
||||
|
@ -81,7 +105,7 @@ namespace _lixer_event_reminder.sources.kalender_digital
|
|||
*/
|
||||
export function implementation_source(
|
||||
parameters : type_parameters
|
||||
) : _lixer_event_reminder.type_source
|
||||
) : _munin.type_source
|
||||
{
|
||||
return {
|
||||
"fetch": () => fetch(parameters),
|
||||
|
|
48
source/targets/_functions.ts
Normal file
48
source/targets/_functions.ts
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
This file is part of »munin«.
|
||||
|
||||
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
|
||||
|
||||
»munin« is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
»munin« is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace _munin.targets
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
export function factory(
|
||||
description : {
|
||||
kind : string,
|
||||
data : any
|
||||
}
|
||||
) : _munin.type_target
|
||||
{
|
||||
switch (description.kind) {
|
||||
default: {
|
||||
throw (new Error("unhandled target kind: " + description.kind));
|
||||
break;
|
||||
}
|
||||
case "telegram_bot": {
|
||||
return _munin.targets.telegram_bot.implementation_target(
|
||||
description.data as _munin.targets.telegram_bot.type_parameters
|
||||
);
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
87
source/targets/telegram_bot.ts
Normal file
87
source/targets/telegram_bot.ts
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
This file is part of »munin«.
|
||||
|
||||
Copyright 2025 'Fenris Wolf' <fenris@folksprak.org>
|
||||
|
||||
»munin« is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
»munin« is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with »munin«. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
namespace _munin.targets.telegram_bot
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
export type type_parameters = {
|
||||
bot_token : string;
|
||||
chat_id : int;
|
||||
interval : Array<int>;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
async function send(
|
||||
parameters : type_parameters,
|
||||
labels : _munin.type_labels,
|
||||
event : _munin.type_event
|
||||
) : Promise<void>
|
||||
{
|
||||
await lib_plankton.telegram.bot_call_send_message(
|
||||
parameters.bot_token,
|
||||
parameters.chat_id,
|
||||
lib_plankton.string.coin(
|
||||
"*{{head}}*\n\n\{{title_label}} | {{title_value}}\n{{time_label}} | {{time_value}}{{macro_location}}",
|
||||
{
|
||||
"head": labels.head,
|
||||
"title_label": labels.title.toUpperCase(),
|
||||
"title_value": event.title,
|
||||
"time_label": labels.time.toUpperCase(),
|
||||
"time_value": lib_plankton.pit.timespan_format(event.begin, event.end),
|
||||
"macro_location": (
|
||||
(event.location === null)
|
||||
?
|
||||
""
|
||||
:
|
||||
lib_plankton.string.coin(
|
||||
"\n{{location_label}} | {{location_value}}",
|
||||
{
|
||||
"location_label": labels.location.toUpperCase(),
|
||||
"location_value": event.location,
|
||||
}
|
||||
)
|
||||
),
|
||||
}
|
||||
),
|
||||
{
|
||||
"parse_mode": "markdown",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export function implementation_target(
|
||||
parameters : type_parameters
|
||||
) : _munin.type_target
|
||||
{
|
||||
return {
|
||||
"interval": parameters.interval,
|
||||
"send": (labels, event) => send(parameters, labels, event),
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,6 +1,16 @@
|
|||
namespace _lixer_event_reminder
|
||||
namespace _munin
|
||||
{
|
||||
|
||||
/**
|
||||
*/
|
||||
export type type_labels = {
|
||||
head : string;
|
||||
title : string;
|
||||
time : string;
|
||||
location : string;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export type type_event = {
|
||||
|
@ -9,20 +19,32 @@ namespace _lixer_event_reminder
|
|||
end : (null | lib_plankton.pit.type_datetime),
|
||||
location : (null | string);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export type type_source = {
|
||||
fetch : (() => Promise<Array<type_event>>);
|
||||
fetch : (
|
||||
(
|
||||
)
|
||||
=>
|
||||
Promise<Array<type_event>>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
*/
|
||||
export type type_target = {
|
||||
chat_id : int;
|
||||
interval : Array<int>;
|
||||
send : (
|
||||
(
|
||||
labels : type_labels,
|
||||
event : type_event
|
||||
)
|
||||
=>
|
||||
Promise<void>
|
||||
);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
16
tools/build
16
tools/build
|
@ -1,16 +1,4 @@
|
|||
#!/usr/bin/env python3
|
||||
#!/usr/bin/env sh
|
||||
|
||||
import os as _os
|
||||
import argparse as _argparse
|
||||
tools/ivaldi build
|
||||
|
||||
|
||||
def main():
|
||||
argument_parser = _argparse.ArgumentParser(
|
||||
)
|
||||
args = argument_parser.parse_args()
|
||||
|
||||
if True:
|
||||
_os.system("make -f tools/makefile")
|
||||
|
||||
|
||||
main()
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
rm -r -f temp
|
||||
rm -r -f build
|
||||
tools/ivaldi clear $@
|
||||
|
||||
|
|
16
tools/deploy
16
tools/deploy
|
@ -1,18 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
## args
|
||||
tools/ivaldi deploy $@
|
||||
|
||||
# if [ $# -ge 1 ] ; then target=$1 ; else target="pv-svartalfheim-user:~/skripte" ; fi
|
||||
target=$1
|
||||
|
||||
|
||||
## exec
|
||||
|
||||
rsync \
|
||||
--recursive \
|
||||
--update \
|
||||
--delete \
|
||||
--verbose \
|
||||
--rsh=ssh \
|
||||
build/ \
|
||||
${target}
|
||||
|
|
5461
tools/ivaldi
Executable file
5461
tools/ivaldi
Executable file
File diff suppressed because it is too large
Load diff
|
@ -1,40 +0,0 @@
|
|||
## commands
|
||||
|
||||
cmd_mkdir := mkdir -p
|
||||
cmd_cp := cp -r -u
|
||||
cmd_tsc := tsc
|
||||
cmd_log := echo "--"
|
||||
cmd_cat := cat
|
||||
cmd_echo := echo
|
||||
cmd_chmod := chmod
|
||||
|
||||
|
||||
## rules
|
||||
|
||||
.PHONY: _default
|
||||
_default: _root
|
||||
|
||||
temp/termine-reminder-unlinked.js: \
|
||||
libs/plankton/plankton.d.ts \
|
||||
source/types.ts \
|
||||
source/sources/kalender_digital.ts \
|
||||
source/sources/_functions.ts \
|
||||
source/conf.ts \
|
||||
source/main.ts
|
||||
@ ${cmd_log} "compiling logic …"
|
||||
@ ${cmd_mkdir} temp
|
||||
@ ${cmd_tsc} $^ --lib es2020,dom --target es6 --outFile $@
|
||||
|
||||
build/termine-reminder: libs/plankton/plankton.js temp/termine-reminder-unlinked.js
|
||||
@ ${cmd_log} "linking …"
|
||||
@ ${cmd_echo} "#!/usr/bin/env node" > temp/head.js
|
||||
@ ${cmd_mkdir} build
|
||||
@ ${cmd_cat} temp/head.js $^ > $@
|
||||
@ ${cmd_chmod} +x $@
|
||||
|
||||
.PHONY: sources
|
||||
sources: build/termine-reminder
|
||||
|
||||
.PHONY: _root
|
||||
_root: sources
|
||||
|
4
tools/update-ivaldi
Executable file
4
tools/update-ivaldi
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
cp -u -v ../ivaldi/build/ivaldi tools/ivaldi
|
||||
|
4
tools/update-libs
Executable file
4
tools/update-libs
Executable file
|
@ -0,0 +1,4 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
tools/ivaldi fetch $@
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
## consts
|
||||
|
||||
dir="libs/plankton"
|
||||
|
||||
|
||||
## vars
|
||||
|
||||
modules=""
|
||||
modules="${modules} pit"
|
||||
modules="${modules} http"
|
||||
modules="${modules} ical"
|
||||
modules="${modules} telegram"
|
||||
modules="${modules} conf"
|
||||
modules="${modules} args"
|
||||
|
||||
|
||||
## exec
|
||||
|
||||
mkdir -p ${dir}
|
||||
cd ${dir}
|
||||
ptk bundle node ${modules}
|
Loading…
Reference in a new issue