Merge branch 'main' of pv-svartalfheim-git:fenris/munin

This commit is contained in:
Christian Fraß 2025-04-25 17:00:19 +00:00
commit 27d263a52a
19 changed files with 6146 additions and 249 deletions

29
ivaldi.json Normal file
View 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"
]
}

View file

@ -1,11 +1,11 @@
/** /**
* @author fenris * @author fenris
*/ */
type int = number; declare type int = number;
/** /**
* @author fenris * @author fenris
*/ */
type float = number; declare type float = number;
declare var process: any; declare var process: any;
declare var require: any; declare var require: any;
declare class Buffer { declare class Buffer {
@ -22,7 +22,7 @@ declare namespace lib_plankton.base {
/** /**
* @author fenris * @author fenris
*/ */
type type_pseudopointer<type_value> = { declare type type_pseudopointer<type_value> = {
value: 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_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; 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_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_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; function now(): type_pit;
@ -2237,7 +2258,9 @@ declare namespace lib_plankton.telegram {
/** /**
* @see https://core.telegram.org/bots/api#sendmessage * @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 { declare namespace lib_plankton.file {
/** /**

View file

@ -1476,7 +1476,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
function verb(n) { return function (v) { return step([n, v]); }; } function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) { function step(op) {
if (f) throw new TypeError("Generator is already executing."); 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 (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]; if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) { switch (op[0]) {
@ -3637,6 +3637,7 @@ var lib_plankton;
function is_after(pit, reference) { function is_after(pit, reference) {
return (pit > reference); return (pit > reference);
} }
pit_1.is_after = is_after;
/** /**
*/ */
function is_between(pit, reference_left, reference_right) { function is_between(pit, reference_left, reference_right) {
@ -3668,6 +3669,7 @@ var lib_plankton;
function shift_year(pit, increment) { function shift_year(pit, increment) {
return (pit + (60 * 60 * 24 * 365 * increment)); return (pit + (60 * 60 * 24 * 365 * increment));
} }
pit_1.shift_year = shift_year;
/** /**
*/ */
function trunc_minute(pit) { function trunc_minute(pit) {
@ -3695,6 +3697,7 @@ var lib_plankton;
}; };
return from_datetime(datetime_output); return from_datetime(datetime_output);
} }
pit_1.trunc_minute = trunc_minute;
/** /**
*/ */
function trunc_hour(pit) { function trunc_hour(pit) {
@ -3718,6 +3721,7 @@ var lib_plankton;
}; };
return from_datetime(datetime_output); return from_datetime(datetime_output);
} }
pit_1.trunc_hour = trunc_hour;
/** /**
*/ */
function trunc_day(pit) { function trunc_day(pit) {
@ -3737,6 +3741,7 @@ var lib_plankton;
}; };
return from_datetime(datetime_output); return from_datetime(datetime_output);
} }
pit_1.trunc_day = trunc_day;
/** /**
*/ */
function trunc_week(pit) { function trunc_week(pit) {
@ -3768,6 +3773,7 @@ var lib_plankton;
}; };
return from_datetime(datetime_output); return from_datetime(datetime_output);
} }
pit_1.trunc_month = trunc_month;
/** /**
*/ */
function trunc_year(pit) { function trunc_year(pit) {
@ -3787,6 +3793,7 @@ var lib_plankton;
}; };
return from_datetime(datetime_output); return from_datetime(datetime_output);
} }
pit_1.trunc_year = trunc_year;
/** /**
*/ */
function now() { function now() {
@ -6236,10 +6243,11 @@ var lib_plankton;
/** /**
* @see https://core.telegram.org/bots/api#sendmessage * @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", { const output = await bot_call_generic(token, "sendMessage", {
"chat_id": chat_id, "chat_id": chat_id,
"text": text, "text": text,
"parse_mode": (parse_mode ?? undefined),
}); });
} }
telegram.bot_call_send_message = bot_call_send_message; telegram.bot_call_send_message = bot_call_send_message;

View file

@ -2,3 +2,29 @@
Sendet Erinnerungen an Termine Sendet Erinnerungen an Termine
## Erstellung
### Erfordernisse
- GNU Make
- Typescript Compiler
### Anweisungen
- `tools/build`
## Verwendung
### Erfordernisse
- NodeJS
### Anweisungen
siehe `munin -h`

View file

@ -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 = { export type type_conf = {
bot_token : string;
sources : Array< sources : Array<
( (
{ {
kind : "kalender_digital", kind : "kalender_digital",
data : { data : {
id : string; path : string;
filtration : { filtration : {
category_blacklist : Array<string>; category_blacklist : Array<string>;
title_blacklist : Array<string>; title_blacklist : Array<string>;
@ -20,11 +42,30 @@ namespace _lixer_event_reminder.conf
) )
>; >;
targets : Array< targets : Array<
(
{ {
kind : "telegram_bot",
data : {
bot_token : string;
chat_id : int; chat_id : int;
/**
* in hours
*/
interval : Array<int>; 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, "nullable": false,
"type": "string", "type": "string",
}, },
"bot_token": {
"nullable": false,
"type": "string",
},
"sources": { "sources": {
"nullable": false, "nullable": false,
"type": "array", "type": "array",
@ -60,7 +97,7 @@ namespace _lixer_event_reminder.conf
"nullable": false, "nullable": false,
"type": "object", "type": "object",
"properties": { "properties": {
"id": { "path": {
"nullable": false, "nullable": false,
"type": "string" "type": "string"
}, },
@ -74,7 +111,8 @@ namespace _lixer_event_reminder.conf
"items": { "items": {
"nullable": false, "nullable": false,
"type": "string", "type": "string",
} },
"default": [],
}, },
"title_blacklist": { "title_blacklist": {
"nullable": false, "nullable": false,
@ -82,20 +120,19 @@ namespace _lixer_event_reminder.conf
"items": { "items": {
"nullable": false, "nullable": false,
"type": "string", "type": "string",
} },
"default": [],
}, },
}, },
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"category_blacklist", ],
"title_blacklist", "default": {}
]
}, },
}, },
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"id", "id",
"filtration",
] ]
} }
}, },
@ -112,9 +149,25 @@ namespace _lixer_event_reminder.conf
"nullable": false, "nullable": false,
"type": "array", "type": "array",
"items": { "items": {
"nullable": false,
"anyOf": [
{
"nullable": false, "nullable": false,
"type": "object", "type": "object",
"properties": { "properties": {
"kind": {
"nullable": false,
"type": "string",
"enum": ["telegram_bot"]
},
"data": {
"nullable": false,
"type": "object",
"properties": {
"bot_token": {
"nullable": false,
"type": "string",
},
"chat_id": { "chat_id": {
"nullable": false, "nullable": false,
"type": "integer", "type": "integer",
@ -125,23 +178,67 @@ namespace _lixer_event_reminder.conf
"items": { "items": {
"nullable": false, "nullable": false,
"type": "integer" "type": "integer"
} },
"default": [24.0],
}, },
}, },
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"bot_token",
"chat_id", "chat_id",
"interval",
] ]
} }
}, },
"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, "additionalProperties": false,
"required": [ "required": [
"version", "version",
"bot_token", "sources",
"chat_id", "targets",
"intval",
], ],
}; };

View file

@ -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( export async function main(
@ -27,14 +164,32 @@ namespace _lixer_event_reminder
"info": "path to configuration file", "info": "path to configuration file",
"name": "conf-path", "name": "conf-path",
}), }),
"message": lib_plankton.args.class_argument.volatile({ "conf_expose": lib_plankton.args.class_argument.volatile({
"indicators_long": ["message"], "indicators_long": ["conf-expose"],
"indicators_short": ["m"], "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, "type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace, "mode": lib_plankton.args.enum_mode.replace,
"default": "test", "default": "notice",
"info": "message to send", "info": "error | warning | notice | info | debug",
"name": "message", "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({ "help": lib_plankton.args.class_argument.volatile({
"indicators_long": ["help"], "indicators_long": ["help"],
@ -52,102 +207,81 @@ namespace _lixer_event_reminder
args_raw.join(" ") 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")) { if (args.help || (args.action === "help")) {
process.stdout.write( process.stdout.write(
arg_handler.generate_help( arg_handler.generate_help(
{ {
"programname": "lixer-event-reminder", "programname": "munin",
"description": "a telegram bot, which sends reminders about upcoming events", "description": "sends reminders about upcoming events",
"executable": "node build/event-reminder", "executable": "node build/munin",
} }
) )
); );
} }
else { 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) { switch (args.action) {
default: { default: {
throw (new Error("unhandled action: " + args.action)); throw (new Error("unhandled action: " + args.action));
break; 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": { case "run": {
const sources : Array<_lixer_event_reminder.type_source> = conf.sources.map( run(
source_raw => _lixer_event_reminder.sources.factory(source_raw) conf,
);
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": { "dry_run": args.dry_run,
"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),
}
)
);
}
}
}
}
break; break;
} }
} }
@ -157,7 +291,7 @@ namespace _lixer_event_reminder
} }
} }
_lixer_event_reminder.main(process.argv.slice(2)) _munin.main(process.argv.slice(2))
.then(() => {}) .then(() => {})
.catch((reason) => {process.stderr.write(String(reason) + "\n");}) .catch((reason) => {process.stderr.write(String(reason) + "\n");})
; ;

View file

@ -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, kind : string,
data : any data : any
} }
) : type_source ) : _munin.type_source
{ {
switch (description.kind) { switch (description.kind) {
default: { default: {
@ -16,8 +36,8 @@ namespace _lixer_event_reminder.sources
break; break;
} }
case "kalender_digital": { case "kalender_digital": {
return _lixer_event_reminder.sources.kalender_digital.implementation_source( return _munin.sources.kalender_digital.implementation_source(
description.data as _lixer_event_reminder.sources.kalender_digital.type_parameters description.data as _munin.sources.kalender_digital.type_parameters
); );
return return
} }

View file

@ -1,10 +1,10 @@
namespace _lixer_event_reminder.sources.kalender_digital namespace _munin.sources.kalender_digital
{ {
/** /**
*/ */
export type type_parameters = { export type type_parameters = {
id : string; path : string;
filtration : { filtration : {
category_blacklist : Array<string>; category_blacklist : Array<string>;
title_blacklist : Array<string>; title_blacklist : Array<string>;
@ -16,15 +16,15 @@ namespace _lixer_event_reminder.sources.kalender_digital
*/ */
async function fetch( async function fetch(
parameters : type_parameters parameters : type_parameters
) : Promise<Array<_lixer_event_reminder.type_event>> ) : Promise<Array<_munin.type_event>>
{ {
const http_request : lib_plankton.http.type_request = { const http_request : lib_plankton.http.type_request = {
"scheme": "https", "scheme": "https",
"host": "export.kalender.digital", "host": "export.kalender.digital",
"path": lib_plankton.string.coin( "path": lib_plankton.string.coin(
"/ics/0/{{id}}/gesamterkalender.ics", "/ics/{{path}}.ics",
{ {
"id": parameters.id, "path": parameters.path,
} }
), ),
"version": "HTTP/2", "version": "HTTP/2",
@ -33,7 +33,10 @@ namespace _lixer_event_reminder.sources.kalender_digital
"?past_months={{past_months}}&future_months={{future_months}}", "?past_months={{past_months}}&future_months={{future_months}}",
{ {
"past_months": (0).toFixed(0), "past_months": (0).toFixed(0),
"future_months": (2).toFixed(0), /**
* anpassen an frequency?
*/
"future_months": (1).toFixed(0),
} }
), ),
"headers": {}, "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 http_response : lib_plankton.http.type_response = await lib_plankton.http.call(http_request);
const ics : string = http_response.body.toString(); const ics : string = http_response.body.toString();
const vcalendar : lib_plankton.ical.type_vcalendar = lib_plankton.ical.ics_decode(ics); 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 vcalendar.vevents
.filter( .filter(
vevent => ( 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( .map(
@ -63,11 +87,11 @@ namespace _lixer_event_reminder.sources.kalender_digital
"date": vevent.dtend.value.date, "date": vevent.dtend.value.date,
"time": vevent.dtend.value.time, "time": vevent.dtend.value.time,
}; };
const event : _lixer_event_reminder.type_event = { const event : _munin.type_event = {
"title": vevent.summary, "title": vevent.summary,
"begin": begin, "begin": begin,
"end": end, "end": end,
"location": vevent.location, "location": (vevent.location ?? null),
}; };
return event; return event;
} }
@ -81,7 +105,7 @@ namespace _lixer_event_reminder.sources.kalender_digital
*/ */
export function implementation_source( export function implementation_source(
parameters : type_parameters parameters : type_parameters
) : _lixer_event_reminder.type_source ) : _munin.type_source
{ {
return { return {
"fetch": () => fetch(parameters), "fetch": () => fetch(parameters),

View 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
}
}
}
}

View 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),
};
}
}

View file

@ -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 = { export type type_event = {
@ -14,15 +24,27 @@ namespace _lixer_event_reminder
/** /**
*/ */
export type type_source = { export type type_source = {
fetch : (() => Promise<Array<type_event>>); fetch : (
(
)
=>
Promise<Array<type_event>>
);
}; };
/** /**
*/ */
export type type_target = { export type type_target = {
chat_id : int;
interval : Array<int>; interval : Array<int>;
send : (
(
labels : type_labels,
event : type_event
)
=>
Promise<void>
);
}; };
} }

View file

@ -1,16 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env sh
import os as _os tools/ivaldi build
import argparse as _argparse
def main():
argument_parser = _argparse.ArgumentParser(
)
args = argument_parser.parse_args()
if True:
_os.system("make -f tools/makefile")
main()

View file

@ -1,5 +1,4 @@
#!/usr/bin/env sh #!/usr/bin/env sh
rm -r -f temp tools/ivaldi clear $@
rm -r -f build

View file

@ -1,18 +1,4 @@
#!/usr/bin/env sh #!/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

File diff suppressed because it is too large Load diff

View file

@ -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
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
cp -u -v ../ivaldi/build/ivaldi tools/ivaldi

4
tools/update-libs Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
tools/ivaldi fetch $@

View file

@ -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}