[mod] conf [del] kalender_digital [add] ical_feed

This commit is contained in:
fenris 2025-04-27 22:30:22 +02:00
parent 591fea76c6
commit ac1daf8a68
8 changed files with 810 additions and 209 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
/build/
/temp/
/conf/
/.geany

View file

@ -8,7 +8,9 @@
"pit",
"http",
"ical",
"string",
"telegram",
"url",
"conf",
"log",
"args"
@ -18,7 +20,7 @@
],
"sources": [
"types.ts",
"sources/kalender_digital.ts",
"sources/ical_feed.ts",
"sources/_functions.ts",
"targets/telegram_bot.ts",
"targets/_functions.ts",

View file

@ -650,6 +650,9 @@ declare namespace lib_plankton.call {
value: (null | type_value);
error: (null | any);
}>;
/**
*/
export function sleep(seconds: float): Promise<void>;
export {};
}
declare namespace lib_plankton.email {
@ -2262,6 +2265,57 @@ declare namespace lib_plankton.telegram {
parse_mode?: (null | string);
}): Promise<void>;
}
declare namespace lib_plankton.url {
/**
* @author fenris
*/
type type_url = {
scheme: (null | string);
host: (null | string);
username: (null | string);
password: (null | string);
port: (null | int);
path: (null | string);
query: (null | string);
hash: (null | string);
};
}
declare namespace lib_plankton.url {
/**
* @author fenris
*/
function encode(url: type_url): string;
/**
* @author fenris
* @todo arguments
*/
function decode(url_raw: string): type_url;
/**
* @author fenris
*/
function implementation_code(): lib_plankton.code.type_code<type_url, string>;
}
declare namespace lib_plankton.url {
/**
* @author fenris
*/
class class_url implements lib_plankton.code.interface_code<type_url, string> {
/**
* @author fenris
*/
constructor();
/**
* @implementation
* @author fenris
*/
encode(x: any): string;
/**
* @implementation
* @author fenris
*/
decode(x: string): any;
}
}
declare namespace lib_plankton.file {
/**
* @author fenris
@ -2607,6 +2661,12 @@ declare namespace lib_plankton.conf {
} | {
not: type_schema;
});
/**
*/
type type_sheet<type_content> = {
version: (null | string);
content: type_content;
};
/**
*/
type type_report = {
@ -2619,15 +2679,24 @@ declare namespace lib_plankton.conf {
reports: Array<type_report>;
result: lib_plankton.pod.type_pod<type_result>;
};
/**
*/
type type_migration<type_from, type_to> = (null | {
target: string;
function: ((content: type_from) => type_to);
});
}
declare namespace lib_plankton.conf {
/**
* @todo versioning
*/
function refine<type_result>(schema: type_schema, value_raw: any): type_result;
function refine<type_result>(schema: type_schema, content: any): type_result;
/**
* @deprecated
*/
function load<type_result>(schema: type_schema, path: (null | string)): Promise<type_result>;
/**
*/
function load_versioned(path: string, get_schema: ((version: string) => type_schema), migrations: Record<string, type_migration<any, any>>): Promise<type_sheet<any>>;
}
declare namespace lib_plankton.args {
/**

View file

@ -1459,6 +1459,16 @@ var lib_plankton;
})));
}
call.try_catch_wrap_async = try_catch_wrap_async;
/**
*/
function sleep(seconds) {
return (new Promise((resolve, reject) => {
setTimeout(() => {
resolve(undefined);
}, Math.floor(seconds * 1000));
}));
}
call.sleep = sleep;
})(call = lib_plankton.call || (lib_plankton.call = {}));
})(lib_plankton || (lib_plankton = {}));
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
@ -6254,6 +6264,202 @@ var lib_plankton;
})(telegram = lib_plankton.telegram || (lib_plankton.telegram = {}));
})(lib_plankton || (lib_plankton = {}));
/*
This file is part of »bacterio-plankton:url«.
Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR'
<info@greenscale.de>
»bacterio-plankton:url« 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.
»bacterio-plankton:url« 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 »bacterio-plankton:url«. If not, see <http://www.gnu.org/licenses/>.
*/
/*
This file is part of »bacterio-plankton:url«.
Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR'
<info@greenscale.de>
»bacterio-plankton:url« 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.
»bacterio-plankton:url« 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 »bacterio-plankton:url«. If not, see <http://www.gnu.org/licenses/>.
*/
var lib_plankton;
(function (lib_plankton) {
var url;
(function (url_1) {
/**
* @author fenris
*/
function encode(url) {
let result = "";
// scheme
{
if (url.scheme !== null) {
result += (url.scheme + ":");
}
}
// host
{
if (url.host !== null) {
result += "//";
// username
{
if (url.username !== null) {
result += url.username;
// password
{
if (url.password !== null) {
result += (":" + url.password);
}
}
result += "@";
}
}
result += url.host;
}
}
// port
{
if (url.port !== null) {
result += (":" + url.port.toString());
}
}
// path
{
if (url.path !== null) {
result += url.path;
}
}
// query
{
if (url.query !== null) {
result += ("?" + url.query);
}
}
// hash
{
if (url.hash !== null) {
result += ("#" + url.hash);
}
}
return result;
}
url_1.encode = encode;
/**
* @author fenris
* @todo arguments
*/
function decode(url_raw) {
const builtin_url = new URL(url_raw);
return {
"scheme": builtin_url.protocol.slice(0, -1),
"host": builtin_url.hostname,
"username": ((builtin_url.username !== "")
?
builtin_url.username
:
null),
"password": ((builtin_url.password !== "")
?
builtin_url.password
:
null),
"port": ((builtin_url.port !== "")
?
parseInt(builtin_url.port)
:
null),
"path": builtin_url.pathname,
"query": builtin_url.search.slice(1),
"hash": ((builtin_url.hash !== "")
?
builtin_url.hash.slice(1)
:
null),
};
}
url_1.decode = decode;
/**
* @author fenris
*/
function implementation_code() {
return {
"encode": encode,
"decode": decode,
};
}
url_1.implementation_code = implementation_code;
})(url = lib_plankton.url || (lib_plankton.url = {}));
})(lib_plankton || (lib_plankton = {}));
/*
This file is part of »bacterio-plankton:url«.
Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR'
<info@greenscale.de>
»bacterio-plankton:url« 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.
»bacterio-plankton:url« 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 »bacterio-plankton:url«. If not, see <http://www.gnu.org/licenses/>.
*/
var lib_plankton;
(function (lib_plankton) {
var url;
(function (url) {
/**
* @author fenris
*/
class class_url {
/**
* @author fenris
*/
constructor() {
}
/**
* @implementation
* @author fenris
*/
encode(x) {
return url.encode(x);
}
/**
* @implementation
* @author fenris
*/
decode(x) {
return url.decode(x);
}
}
url.class_url = class_url;
})(url = lib_plankton.url || (lib_plankton.url = {}));
})(lib_plankton || (lib_plankton = {}));
/*
This file is part of »bacterio-plankton:file«.
Copyright 2016-2024 'Christian Fraß, Christian Neubauer, Martin Springwald GbR'
@ -7786,10 +7992,9 @@ var lib_plankton;
}
}
/**
* @todo versioning
*/
function refine(schema, value_raw) {
const adaption = adapt(schema, value_raw);
function refine(schema, content) {
const adaption = adapt(schema, content);
if (!lib_plankton.pod.is_filled(adaption.result)) {
throw (new Error("conf could not be loaded:\n"
+
@ -7803,6 +8008,7 @@ var lib_plankton;
}
conf.refine = refine;
/**
* @deprecated
*/
function load(schema, path) {
return (((path === null)
@ -7814,6 +8020,37 @@ var lib_plankton;
.then((data_raw) => Promise.resolve(refine(schema, data_raw))));
}
conf.load = load;
/**
*/
async function load_versioned(path, get_schema, migrations) {
const file_content = await lib_plankton.file.read(path);
const sheet_raw = lib_plankton.json.decode(file_content);
const schema = get_schema(sheet_raw.version);
let sheet = {
"version": sheet_raw.version,
"content": refine(schema, sheet_raw.content),
};
while (sheet.version in migrations) {
const migration = migrations[sheet.version];
if (!(migration === null)) {
lib_plankton.log._info("plankton.conf.load.migration", {
"details": {
"from": sheet.version,
"to": migration.target,
}
});
sheet = {
"version": migration.target,
"content": migration.function(sheet.content),
};
}
else {
break;
}
}
return sheet;
}
conf.load_versioned = load_versioned;
})(conf = lib_plankton.conf || (lib_plankton.conf = {}));
})(lib_plankton || (lib_plankton = {}));
/*

View file

@ -26,7 +26,7 @@ namespace _munin.conf
/**
*/
export type type_conf = {
type type_conf_v1 = {
sources : Array<
(
{
@ -56,9 +56,6 @@ namespace _munin.conf
}
)
>;
/**
* in hours
*/
frequency : float;
labels : {
head : string;
@ -71,21 +68,108 @@ namespace _munin.conf
/**
*/
export const schema : lib_plankton.conf.type_schema = {
"nullable": false,
"type": "object",
"properties": {
"version": {
"nullable": false,
"type": "string",
},
"sources": {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"anyOf": [
type type_conf_v2 = {
sources : Array<
(
{
kind : "ical_feed",
data : {
url : string;
filtration : {
category_blacklist : Array<string>;
title_blacklist : Array<string>;
};
};
}
)
>;
targets : Array<
(
{
kind : "telegram_bot",
data : {
bot_token : string;
chat_id : int;
/**
* in hours
*/
interval : Array<int>;
}
}
)
>;
settings : {
poll_delay : float;
};
labels : {
head : string;
title : string;
time : string;
location : string;
};
};
/**
*/
export type type_conf = type_conf_v2;
/**
*/
function convert_from_v1(
conf_v1 : type_conf_v1
) : type_conf_v2
{
return {
"sources": conf_v1.sources.map(
source => {
switch (source.kind) {
case "kalender_digital": {
return {
"kind": "ical_feed",
"data": {
"url": lib_plankton.url.encode(
{
"scheme": "https",
"host": "export.kalender.digital",
"username": null,
"password": null,
"port": null,
"path": ("/ics/" + source.data.path + ".ics"),
"query": "past_months=0&future_months=1",
"hash": null,
}
),
"filtration": source.data.filtration,
}
};
break;
}
default: {
// return source;
throw (new Error("unhandled source kind: " + source.kind));
break;
}
}
}
),
"targets": conf_v1.targets,
"settings": {
"poll_delay": conf_v1.frequency,
},
"labels": conf_v1.labels,
};
}
/**
*/
function schema_source_kalender_digital(
version : string
) : lib_plankton.conf.type_schema
{
return {
"type": "object",
"properties": {
"kind": {
@ -132,7 +216,7 @@ namespace _munin.conf
},
"additionalProperties": false,
"required": [
"id",
"path",
]
}
},
@ -141,17 +225,121 @@ namespace _munin.conf
"kind",
"data",
]
};
}
/**
*/
function schema_source_ical_feed(
version : string
) : lib_plankton.conf.type_schema
{
return {
"type": "object",
"properties": {
"kind": {
"nullable": false,
"type": "string",
"enum": ["ical_feed"]
},
"data": {
"nullable": false,
"type": "object",
"properties": {
"url": {
"nullable": false,
"type": "string"
},
"filtration": {
"nullable": false,
"type": "object",
"properties": {
"category_blacklist": {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"type": "string",
},
"default": [],
},
"title_blacklist": {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"type": "string",
},
"default": [],
},
},
"additionalProperties": false,
"required": [
],
"default": {}
},
},
"additionalProperties": false,
"required": [
"url",
]
}
},
"targets": {
"additionalProperties": false,
"required": [
"kind",
"data",
]
};
}
/**
*/
function schema_sources(
version : string
) : lib_plankton.conf.type_schema
{
switch (version) {
case "1": {
return {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"anyOf": [
schema_source_kalender_digital(version),
],
}
};
break;
}
default:
case "2": {
return {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"anyOf": [
schema_source_ical_feed(version),
],
}
};
break;
}
}
}
/**
*/
function schema_target_telegram_bot(
version : string
) : lib_plankton.conf.type_schema
{
return {
"nullable": false,
"type": "object",
"properties": {
@ -194,16 +382,61 @@ namespace _munin.conf
"kind",
"data",
]
};
}
/**
*/
function schema_targets(
version : string
) : lib_plankton.conf.type_schema
{
return {
"nullable": false,
"type": "array",
"items": {
"nullable": false,
"anyOf": [
schema_target_telegram_bot(version),
],
}
},
"frequency": {
};
}
/**
*/
function schema_settings(
version : string
) : lib_plankton.conf.type_schema
{
return {
"nullable": false,
"type": "object",
"properties": {
"poll_delay": {
"nullable": false,
"type": "number",
"default": 1.0,
"default": 1.0
}
},
"labels": {
"additionalProperties": false,
"required": [
],
"default": {
}
};
}
/**
*/
function schema_labels(
version : string
) : lib_plankton.conf.type_schema
{
return {
"nullable": false,
"type": "object",
"properties": {
@ -232,15 +465,93 @@ namespace _munin.conf
"required": [
],
"default": {}
};
}
/**
*/
function schema(
version : string
) : lib_plankton.conf.type_schema
{
switch (version) {
case "1": {
return {
"nullable": false,
"type": "object",
"properties": {
"sources": schema_sources(version),
"targets": schema_targets(version),
"frequency": {
"nullable": false,
"type": "number",
"default": 1.0,
},
"labels": schema_labels(version),
},
"additionalProperties": false,
"required": [
"version",
"sources",
"targets",
],
};
break;
}
default:
case "2": {
return {
"nullable": false,
"type": "object",
"properties": {
"sources": schema_sources(version),
"targets": schema_targets(version),
"settings": schema_settings(version),
"labels": schema_labels(version),
},
"additionalProperties": false,
"required": [
"sources",
"targets",
],
};
break;
}
}
}
/**
*/
export async function load(
path : string
) : Promise<type_conf>
{
const conf_raw : {version : string; content : any} = await lib_plankton.conf.load_versioned(
path,
schema,
{
"1": {"target": "2", "function": convert_from_v1},
"2": null,
}
);
switch (conf_raw.version) {
case "1": {
const conf_v2 : type_conf_v2 = convert_from_v1(conf_raw.content);
return conf_v2;
break;
}
case "2": {
const conf_v2 : type_conf_v2 = (conf_raw.content as type_conf_v2);
return conf_v2;
break;
}
default: {
throw (new Error("invalid version: " + conf_raw.version));
break;
}
}
}
}

View file

@ -56,7 +56,7 @@ namespace _munin
);
const window_to : lib_plankton.pit.type_pit = lib_plankton.pit.shift_hour(
now,
hours + (conf.frequency / 2)
hours + (conf.settings.poll_delay / 2)
);
for (const event of events) {
const event_begin : lib_plankton.pit.type_pit = lib_plankton.pit.from_datetime(event.begin);
@ -103,7 +103,7 @@ namespace _munin
/**
*/
function run(
async function run(
conf : _munin.conf.type_conf,
{
"dry_run": dry_run = false,
@ -111,7 +111,7 @@ namespace _munin
dry_run ?: boolean;
} = {
}
) : void
) : Promise<void>
{
const sources : Array<_munin.type_source> = conf.sources.map(
source_raw => _munin.sources.factory(source_raw)
@ -126,15 +126,10 @@ namespace _munin
}
}
);
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)
);
while (true) {
await run_iteration(conf, sources, targets, {"dry_run": dry_run});
await lib_plankton.call.sleep(conf.settings.poll_delay * 60 * 60);
}
}
@ -147,14 +142,16 @@ namespace _munin
// args
const arg_handler : lib_plankton.args.class_handler = new lib_plankton.args.class_handler(
{
/*
"action": lib_plankton.args.class_argument.positional({
"index": 0,
"type": lib_plankton.args.enum_type.string,
"mode": lib_plankton.args.enum_mode.replace,
"default": "run",
"info": "what to do : help | fetch | send | run",
"info": "what to do : help | run",
"name": "action",
}),
*/
"conf_path": lib_plankton.args.class_argument.volatile({
"indicators_long": ["conf-path"],
"indicators_short": ["c"],
@ -207,7 +204,7 @@ namespace _munin
args_raw.join(" ")
);
if (args.help || (args.action === "help")) {
if (args.help) {
process.stdout.write(
arg_handler.generate_help(
{
@ -220,10 +217,7 @@ namespace _munin
}
else {
// init
const conf : _munin.conf.type_conf = await lib_plankton.conf.load<_munin.conf.type_conf>(
_munin.conf.schema,
args.conf_path
);
const conf : _munin.conf.type_conf = await _munin.conf.load(args.conf_path);
lib_plankton.log.set_main_logger(
[
{
@ -270,7 +264,7 @@ namespace _munin
"\n"
);
}
switch (args.action) {
switch (/*args.action*/"run") {
default: {
throw (new Error("unhandled action: " + args.action));
break;

View file

@ -35,9 +35,9 @@ namespace _munin.sources
throw (new Error("unhandled source kind: " + description.kind));
break;
}
case "kalender_digital": {
return _munin.sources.kalender_digital.implementation_source(
description.data as _munin.sources.kalender_digital.type_parameters
case "ical_feed": {
return _munin.sources.ical_feed.implementation_source(
description.data as _munin.sources.ical_feed.type_parameters
);
return
}

View file

@ -1,10 +1,10 @@
namespace _munin.sources.kalender_digital
namespace _munin.sources.ical_feed
{
/**
*/
export type type_parameters = {
path : string;
url : string;
filtration : {
category_blacklist : Array<string>;
title_blacklist : Array<string>;
@ -18,27 +18,14 @@ namespace _munin.sources.kalender_digital
parameters : type_parameters
) : Promise<Array<_munin.type_event>>
{
const url : lib_plankton.url.type_url = lib_plankton.url.decode(parameters.url);
const http_request : lib_plankton.http.type_request = {
"scheme": "https",
"host": "export.kalender.digital",
"path": lib_plankton.string.coin(
"/ics/{{path}}.ics",
{
"path": parameters.path,
}
),
"scheme": (url.scheme as ("http" | "https")),
"host": url.host,
"path": url.path,
"version": "HTTP/2",
"method": lib_plankton.http.enum_method.get,
"query": lib_plankton.string.coin(
"?past_months={{past_months}}&future_months={{future_months}}",
{
"past_months": (0).toFixed(0),
/**
* anpassen an frequency?
*/
"future_months": (1).toFixed(0),
}
),
"query": url.query,
"headers": {},
"body": null,
};