Add support for combined notifications
This commit is contained in:
@@ -16,13 +16,27 @@ export abstract class Backend<C extends BackendSettings> {
|
||||
return config as C;
|
||||
}
|
||||
|
||||
public remind(config: Config, task: Task) {
|
||||
public async remind(config: Config, tasks: Task[]): Promise<void> {
|
||||
const cfg = config?.backend?.[this.name] as Partial<C> | undefined;
|
||||
|
||||
if (cfg?.enable !== true) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
this.notify(this.#validate(cfg), task);
|
||||
if (tasks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tasks.length === 1) {
|
||||
return await this.notify(this.#validate(cfg), tasks[0]);
|
||||
}
|
||||
|
||||
return await this.notifyCombined(this.#validate(cfg), tasks);
|
||||
}
|
||||
|
||||
protected async notifyCombined(config: C, tasks: Task[]) {
|
||||
for (const task of tasks) {
|
||||
this.notify(config, task);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,15 +28,7 @@ async function run(config: Config, db: TaskDatabase, tasks?: Task[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const task of tasks) {
|
||||
console.info(`Dispatching a notification: [${task.label}]`)
|
||||
for (const backend of backends) {
|
||||
backend.remind(config, task);
|
||||
await snooze(1500);
|
||||
}
|
||||
for (const backend of backends) {
|
||||
await backend.remind(config, tasks);
|
||||
}
|
||||
}
|
||||
|
||||
function snooze(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
@@ -8,7 +8,8 @@ import { jsMapper } from "../util/code";
|
||||
type Config = {
|
||||
url: string;
|
||||
token: string;
|
||||
mapper?: string;
|
||||
map?: string;
|
||||
combineMap?: string;
|
||||
topic?: string;
|
||||
} & BackendSettings;
|
||||
|
||||
@@ -26,20 +27,51 @@ export class NtfySH extends Backend<Config> {
|
||||
protected requiredFields = ['url', 'token'] as const;
|
||||
|
||||
protected notify(config: Config, task: Task): void {
|
||||
const token = enhancedStringConfig(config.token);
|
||||
const mapper = config.mapper
|
||||
? jsMapper<object, NtfyDTO>(config.mapper, { mapPriority })
|
||||
const context = {
|
||||
mapPriority,
|
||||
template: defaultMapper
|
||||
};
|
||||
|
||||
const mapper = config.map
|
||||
? jsMapper<object, NtfyDTO>(config.map, context)
|
||||
: defaultMapper;
|
||||
|
||||
const dto = mapper(task);
|
||||
|
||||
this.#doNotify(config, dto);
|
||||
}
|
||||
|
||||
protected async notifyCombined(config: Config, tasks: Task[]): Promise<void> {
|
||||
const chunks = chunkArray(tasks, 4);
|
||||
|
||||
const context = {
|
||||
mapPriority,
|
||||
template: defaultCombineMapper
|
||||
};
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const mapper = config.combineMap
|
||||
? jsMapper<object, NtfyDTO>(config.combineMap, context)
|
||||
: defaultCombineMapper;
|
||||
|
||||
const dto = mapper(chunk);
|
||||
|
||||
await this.#doNotify(config, dto);
|
||||
|
||||
await snooze(2500);
|
||||
}
|
||||
}
|
||||
|
||||
async #doNotify(config: Config, dto: NtfyDTO): Promise<Response> {
|
||||
const token = enhancedStringConfig(config.token);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${token}`
|
||||
};
|
||||
|
||||
buildHeaders(dto, headers);
|
||||
|
||||
fetch(`https://${config.url}/${config.topic || 'obsidian'}`, {
|
||||
return fetch(`https://${config.url}/${config.topic || 'obsidian'}`, {
|
||||
method: 'POST',
|
||||
body: dto.text ?? "",
|
||||
headers
|
||||
@@ -47,6 +79,21 @@ export class NtfySH extends Backend<Config> {
|
||||
}
|
||||
}
|
||||
|
||||
function snooze(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function chunkArray<T>(array: T[], size: number): T[][] {
|
||||
const chunks = [];
|
||||
|
||||
while (array.length) {
|
||||
chunks.push(array.slice(0, size));
|
||||
array = array.slice(size);
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
function buildHeaders(dto: NtfyDTO, headers: Record<string, string>) {
|
||||
if (dto.title) {
|
||||
headers.Title = dto.title;
|
||||
@@ -74,6 +121,23 @@ function defaultMapper(task: Task): NtfyDTO {
|
||||
}
|
||||
}
|
||||
|
||||
function defaultCombineMapper(tasks: Task[], bullet = "-"): NtfyDTO {
|
||||
const text = tasks
|
||||
.toSorted((a, b) => b.priority - a.priority)
|
||||
.map(t => `${bullet} ${t.label}`)
|
||||
.join("\n");
|
||||
|
||||
const priority = mapPriority(Math.max(...tasks.map(t => t.priority)));
|
||||
const tags = tasks.flatMap(t => t.tags);
|
||||
|
||||
return {
|
||||
title: `Obsidian Task Reminder (${tasks.length})`,
|
||||
text,
|
||||
priority,
|
||||
tags
|
||||
}
|
||||
}
|
||||
|
||||
function mapPriority(priority?: TaskPriority): string {
|
||||
if (!priority) {
|
||||
return 'default';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export function jsMapper<I, O>(code: string, context: Record<string, unknown>): (task: I) => O {
|
||||
const filter = new Function('$', ...Object.keys(context), `return ${code};`);
|
||||
const filter = new Function('$', ...Object.keys(context), code);
|
||||
return (task: I) => filter(task, ...Object.values(context));
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"target": "ES2023",
|
||||
"module": "commonjs",
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
|
||||
Reference in New Issue
Block a user