[ini]
This commit is contained in:
commit
a975fa98a0
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/build/
|
||||||
|
/temp/
|
||||||
|
/conf/
|
2828
libs/plankton/plankton.d.ts
vendored
Normal file
2828
libs/plankton/plankton.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
8590
libs/plankton/plankton.js
Normal file
8590
libs/plankton/plankton.js
Normal file
File diff suppressed because it is too large
Load diff
149
source/conf.ts
Normal file
149
source/conf.ts
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
namespace _lixer_event_reminder.conf
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type type_conf = {
|
||||||
|
bot_token : string;
|
||||||
|
sources : Array<
|
||||||
|
(
|
||||||
|
{
|
||||||
|
kind : "kalender_digital",
|
||||||
|
data : {
|
||||||
|
id : string;
|
||||||
|
filtration : {
|
||||||
|
category_blacklist : Array<string>;
|
||||||
|
title_blacklist : Array<string>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
)
|
||||||
|
>;
|
||||||
|
targets : Array<
|
||||||
|
{
|
||||||
|
chat_id : int;
|
||||||
|
interval : Array<int>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export const schema : lib_plankton.conf.type_schema = {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"version": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"bot_token": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"sources": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"nullable": false,
|
||||||
|
"anyOf": [
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"kind": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["kalender_digital"]
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"filtration": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"category_blacklist": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title_blacklist": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"category_blacklist",
|
||||||
|
"title_blacklist",
|
||||||
|
]
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"id",
|
||||||
|
"filtration",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"kind",
|
||||||
|
"data",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"targets": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"chat_id": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "integer",
|
||||||
|
},
|
||||||
|
"interval": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"chat_id",
|
||||||
|
"interval",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"version",
|
||||||
|
"bot_token",
|
||||||
|
"chat_id",
|
||||||
|
"intval",
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
164
source/main.ts
Normal file
164
source/main.ts
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
namespace _lixer_event_reminder
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export async function main(
|
||||||
|
args_raw : Array<string>
|
||||||
|
): Promise<void>
|
||||||
|
{
|
||||||
|
// 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",
|
||||||
|
"name": "action",
|
||||||
|
}),
|
||||||
|
"conf_path": lib_plankton.args.class_argument.volatile({
|
||||||
|
"indicators_long": ["conf-path"],
|
||||||
|
"indicators_short": ["c"],
|
||||||
|
"type": lib_plankton.args.enum_type.string,
|
||||||
|
"mode": lib_plankton.args.enum_mode.replace,
|
||||||
|
"default": "conf.json",
|
||||||
|
"info": "path to configuration file",
|
||||||
|
"name": "conf-path",
|
||||||
|
}),
|
||||||
|
"message": lib_plankton.args.class_argument.volatile({
|
||||||
|
"indicators_long": ["message"],
|
||||||
|
"indicators_short": ["m"],
|
||||||
|
"type": lib_plankton.args.enum_type.string,
|
||||||
|
"mode": lib_plankton.args.enum_mode.replace,
|
||||||
|
"default": "test",
|
||||||
|
"info": "message to send",
|
||||||
|
"name": "message",
|
||||||
|
}),
|
||||||
|
"help": lib_plankton.args.class_argument.volatile({
|
||||||
|
"indicators_long": ["help"],
|
||||||
|
"indicators_short": ["h"],
|
||||||
|
"type": lib_plankton.args.enum_type.boolean,
|
||||||
|
"mode": lib_plankton.args.enum_mode.replace,
|
||||||
|
"default": false,
|
||||||
|
"info": "alias for action 'help'",
|
||||||
|
"name": "help",
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
const args : Record<string, any> = arg_handler.read(
|
||||||
|
lib_plankton.args.enum_environment.cli,
|
||||||
|
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",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve<void>(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_lixer_event_reminder.main(process.argv.slice(2))
|
||||||
|
.then(() => {})
|
||||||
|
.catch((reason) => {process.stderr.write(String(reason) + "\n");})
|
||||||
|
;
|
||||||
|
|
28
source/sources/_functions.ts
Normal file
28
source/sources/_functions.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
namespace _lixer_event_reminder.sources
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function factory(
|
||||||
|
description : {
|
||||||
|
kind : string,
|
||||||
|
data : any
|
||||||
|
}
|
||||||
|
) : type_source
|
||||||
|
{
|
||||||
|
switch (description.kind) {
|
||||||
|
default: {
|
||||||
|
throw (new Error("unhandled source kind: " + description.kind));
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
92
source/sources/kalender_digital.ts
Normal file
92
source/sources/kalender_digital.ts
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
namespace _lixer_event_reminder.sources.kalender_digital
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type type_parameters = {
|
||||||
|
id : string;
|
||||||
|
filtration : {
|
||||||
|
category_blacklist : Array<string>;
|
||||||
|
title_blacklist : Array<string>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
async function fetch(
|
||||||
|
parameters : type_parameters
|
||||||
|
) : Promise<Array<_lixer_event_reminder.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",
|
||||||
|
{
|
||||||
|
"id": parameters.id,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"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),
|
||||||
|
"future_months": (2).toFixed(0),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
"headers": {},
|
||||||
|
"body": null,
|
||||||
|
};
|
||||||
|
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> = (
|
||||||
|
vcalendar.vevents
|
||||||
|
.filter(
|
||||||
|
vevent => (
|
||||||
|
vevent.categories.every(category => ! parameters.filtration.category_blacklist.includes(category))
|
||||||
|
&&
|
||||||
|
parameters.filtration.title_blacklist.every(title => ! vevent.summary.toLowerCase().includes(title.toLowerCase()))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
vevent => {
|
||||||
|
const begin : lib_plankton.pit.type_datetime = {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": vevent.dtstart.value.date,
|
||||||
|
"time": vevent.dtstart.value.time,
|
||||||
|
};
|
||||||
|
const end : lib_plankton.pit.type_datetime = {
|
||||||
|
"timezone_shift": 0,
|
||||||
|
"date": vevent.dtend.value.date,
|
||||||
|
"time": vevent.dtend.value.time,
|
||||||
|
};
|
||||||
|
const event : _lixer_event_reminder.type_event = {
|
||||||
|
"title": vevent.summary,
|
||||||
|
"begin": begin,
|
||||||
|
"end": end,
|
||||||
|
"location": vevent.location,
|
||||||
|
};
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export function implementation_source(
|
||||||
|
parameters : type_parameters
|
||||||
|
) : _lixer_event_reminder.type_source
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
"fetch": () => fetch(parameters),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
29
source/types.ts
Normal file
29
source/types.ts
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
namespace _lixer_event_reminder
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type type_event = {
|
||||||
|
title : string;
|
||||||
|
begin : lib_plankton.pit.type_datetime,
|
||||||
|
end : (null | lib_plankton.pit.type_datetime),
|
||||||
|
location : (null | string);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type type_source = {
|
||||||
|
fetch : (() => Promise<Array<type_event>>);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
export type type_target = {
|
||||||
|
chat_id : int;
|
||||||
|
interval : Array<int>;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
16
tools/build
Executable file
16
tools/build
Executable file
|
@ -0,0 +1,16 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import os as _os
|
||||||
|
import argparse as _argparse
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_parser = _argparse.ArgumentParser(
|
||||||
|
)
|
||||||
|
args = argument_parser.parse_args()
|
||||||
|
|
||||||
|
if True:
|
||||||
|
_os.system("make -f tools/makefile")
|
||||||
|
|
||||||
|
|
||||||
|
main()
|
5
tools/clear
Executable file
5
tools/clear
Executable file
|
@ -0,0 +1,5 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
rm -r -f temp
|
||||||
|
rm -r -f build
|
||||||
|
|
18
tools/deploy
Executable file
18
tools/deploy
Executable file
|
@ -0,0 +1,18 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
## args
|
||||||
|
|
||||||
|
# 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}
|
40
tools/makefile
Normal file
40
tools/makefile
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
## 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
|
||||||
|
|
23
tools/update-plankton
Executable file
23
tools/update-plankton
Executable file
|
@ -0,0 +1,23 @@
|
||||||
|
#!/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