From 0ec71cfc8e0197ed6f2f8c9571dcaa42d5692d31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Pluta?= Date: Mon, 3 Mar 2025 15:03:56 +0100 Subject: [PATCH] Improve mapping tasks to notifications --- src/backend/base.ts | 8 ++--- src/backend/debug.ts | 6 ++-- src/backend/index.ts | 14 ++++---- src/backend/ntfy.ts | 62 ++++++++++++++++++++++++++++++------ src/database/deserializer.ts | 8 ++--- src/database/serializer.ts | 48 ++++++++-------------------- src/model/task.ts | 22 +++++++++++++ src/runner/index.ts | 8 ++--- src/types/config.ts | 3 +- src/types/notification.ts | 11 ------- src/types/task.ts | 7 ++-- 11 files changed, 115 insertions(+), 82 deletions(-) delete mode 100644 src/types/notification.ts diff --git a/src/backend/base.ts b/src/backend/base.ts index bb96dab..a0ae660 100644 --- a/src/backend/base.ts +++ b/src/backend/base.ts @@ -1,10 +1,10 @@ import { BackendSettings, Config } from "../types/config"; -import { Notification } from "../types/notification"; +import { Task } from "../types/task"; export abstract class Backend { public abstract readonly name: string; protected abstract requiredFields: readonly (keyof C)[]; - protected abstract notify(config: C, notification: Notification): void; + protected abstract notify(config: C, task: Task): void; #validate(config: Partial): C { for (const field of this.requiredFields) { @@ -16,13 +16,13 @@ export abstract class Backend { return config as C; } - public remind(config: Config, notification: Notification) { + public remind(config: Config, task: Task) { const cfg = config?.backend?.[this.name] as Partial | undefined; if (cfg?.enable !== true) { return } - this.notify(this.#validate(cfg), notification); + this.notify(this.#validate(cfg), task); } } \ No newline at end of file diff --git a/src/backend/debug.ts b/src/backend/debug.ts index 98f8fb0..b6eff85 100644 --- a/src/backend/debug.ts +++ b/src/backend/debug.ts @@ -1,5 +1,5 @@ import { BackendSettings } from "../types/config"; -import { Notification } from "../types/notification"; +import { Task } from "../types/task"; import { Backend } from "./base"; type Config = BackendSettings; @@ -9,7 +9,7 @@ export class Debug extends Backend { protected requiredFields = [] as const; - protected notify(config: Config, notification: Notification): void { - console.log(JSON.stringify(notification, undefined, 2)); + protected notify(config: Config, task: Task): void { + console.log(JSON.stringify(task, undefined, 2)); } } \ No newline at end of file diff --git a/src/backend/index.ts b/src/backend/index.ts index 77c5c72..4a03471 100644 --- a/src/backend/index.ts +++ b/src/backend/index.ts @@ -1,5 +1,5 @@ import dayjs from "dayjs"; -import { Notification, NotificationDatabase } from "../types/notification"; +import { Task, TaskDatabase } from "../types/task"; import { Config } from "../types/config"; import { NtfySH } from "./ntfy"; import { Debug } from "./debug"; @@ -13,7 +13,7 @@ const backends = [ * Iterates through all the database notifications for current time * and triggers the notification using specified backends in the config. */ -export async function remind(config: Config, db: NotificationDatabase) { +export async function remind(config: Config, db: TaskDatabase) { const now = dayjs().format("HH:mm"); await run(config, db, db[now]); @@ -23,15 +23,15 @@ export async function remind(config: Config, db: NotificationDatabase) { } } -async function run(config: Config, db: NotificationDatabase, notifications?: Notification[]) { - if(!notifications) { +async function run(config: Config, db: TaskDatabase, tasks?: Task[]) { + if(!tasks) { return; } - for (const notification of notifications) { - console.info(`Dispatching a notification: [${notification.text}]`) + for (const task of tasks) { + console.info(`Dispatching a notification: [${task.label}]`) for (const backend of backends) { - backend.remind(config, notification); + backend.remind(config, task); await snooze(1500); } } diff --git a/src/backend/ntfy.ts b/src/backend/ntfy.ts index 70aec33..bc0508b 100644 --- a/src/backend/ntfy.ts +++ b/src/backend/ntfy.ts @@ -1,37 +1,79 @@ import fs from "fs"; import { BackendSettings } from "../types/config"; -import { Notification } from "../types/notification"; import { Backend } from "./base"; -import { TaskPriority } from "../types/task"; +import { Task, TaskPriority } from "../types/task"; import { enhancedStringConfig } from "../util/config"; +import { jsMapper } from "../util/code"; type Config = { url: string; token: string; + mapper?: string; topic?: string; } & BackendSettings; +type NtfyDTO = { + title?: string; + text?: string; + priority?: string; + tags?: string[]; + icon?: string; +}; + export class NtfySH extends Backend { public name = "ntfy"; protected requiredFields = ['url', 'token'] as const; - protected notify(config: Config, notification: Notification): void { + protected notify(config: Config, task: Task): void { const token = enhancedStringConfig(config.token); + const mapper = config.mapper + ? jsMapper(config.mapper, { mapPriority }) + : defaultMapper; + + const dto = mapper(task); + + const headers: Record = { + Authorization: `Bearer ${token}` + }; + + buildHeaders(dto, headers); fetch(`https://${config.url}/${config.topic || 'obsidian'}`, { method: 'POST', - body: notification.text, - headers: { - 'Authorization': `Bearer ${token}`, - 'Title': notification.title ?? "", - 'Priority': mapPriority(notification.priority), - 'Tags': notification.tags?.join(",") ?? "" - } + body: dto.text ?? "", + headers }) } } +function buildHeaders(dto: NtfyDTO, headers: Record) { + if (dto.title) { + headers.Title = dto.title; + } + + if (dto.priority) { + headers.Priority = dto.priority; + } + + if (dto.tags) { + headers.Tags = dto.tags.join(","); + } + + if (dto.icon) { + headers.Icon = dto.icon; + } +} + +function defaultMapper(task: Task): NtfyDTO { + return { + title: "Obsidian Task Reminder", + text: task.label, + priority: mapPriority(task.priority), + tags: task.tags, + } +} + function mapPriority(priority?: TaskPriority): string { if (!priority) { return 'default'; diff --git a/src/database/deserializer.ts b/src/database/deserializer.ts index cd32e7d..b85d613 100644 --- a/src/database/deserializer.ts +++ b/src/database/deserializer.ts @@ -1,10 +1,10 @@ import fs from "fs"; -import { NotificationDatabase } from "../types/notification"; +import { TaskDatabase } from "../types/task"; /** * Loads and deserializes database from JSON formatted file. */ -export function loadDatabase(file: string): NotificationDatabase { +export function loadDatabase(file: string): TaskDatabase { const text = fs.readFileSync(file).toString(); return deserializeDatabase(text); } @@ -12,6 +12,6 @@ export function loadDatabase(file: string): NotificationDatabase { /** * Deserializes database from JSON format. */ -export function deserializeDatabase(json: string): NotificationDatabase { - return JSON.parse(json) as NotificationDatabase; +export function deserializeDatabase(json: string): TaskDatabase { + return JSON.parse(json) as TaskDatabase; } \ No newline at end of file diff --git a/src/database/serializer.ts b/src/database/serializer.ts index 2c02c42..0ee09ae 100644 --- a/src/database/serializer.ts +++ b/src/database/serializer.ts @@ -1,57 +1,35 @@ import fs from "fs"; -import { Task } from "../types/task"; -import { Notification, NotificationDatabase } from "../types/notification"; -import { jsMapper } from "../util/code"; +import { Task, TaskDatabase } from "../types/task"; /** * Applies the mapper for each task from list, groups them by time and dumps the data into JSON formatted file. */ -export function dumpDatabase(file: string, tasks: Task[], mapper?: string) { - const data = serializeDatabase(tasks, mapper); +export function dumpDatabase(file: string, tasks: Task[]) { + const data = serializeDatabase(tasks); fs.writeFileSync(file, data); } /** * Applies the mapper for each task from list, groups them by time and serializes into JSON format. */ -export function serializeDatabase(tasks: Task[], mapper?: string): string { - return JSON.stringify(createDatabase(tasks, mapper)); +export function serializeDatabase(tasks: Task[]): string { + + const x = JSON.stringify(createDatabase(tasks)); + console.log(x); + return x; } /** * Applies the mapper for each task from list and groups them by time. */ -export function createDatabase(tasks: Task[], mapper?: string): NotificationDatabase { - const transformer = mapper - ? jsMapper(mapper, {}) - : defaultMapper; +export function createDatabase(tasks: Task[]): TaskDatabase { - return tasks.map(wrapWithTimeFiller(transformer)).reduce((acc, n) => { - if (n.time) { - (acc[n.time] = (acc[n.time] || [])).push(n); + return tasks.filter(t => t.reminder).reduce((acc, t) => { + if (t.reminder) { + (acc[t.reminder] = (acc[t.reminder] || [])).push(t); }; return acc; - }, {} as NotificationDatabase); + }, {} as TaskDatabase); } - -function wrapWithTimeFiller(mapper: (task: Task) => Notification): (task: Task) => Notification { - return (task: Task) => { - const notification = mapper(task); - - return { - ...notification, - time: task.reminder ?? notification.time, - } - }; -} - -function defaultMapper(task: Task): Notification { - return { - title: "Obsidian Tasks Reminder", - text: task.label, - priority: task.priority, - tags: task.tags, - }; -} \ No newline at end of file diff --git a/src/model/task.ts b/src/model/task.ts index 4922d39..45ffe68 100644 --- a/src/model/task.ts +++ b/src/model/task.ts @@ -149,4 +149,26 @@ export class LazyTask implements Task { return `- [${this.status}] ${this.label} {${items.filter(x => x.length > 0).join(", ")}}`; } + + toJSON(): Task { + return { + status: this.status, + label: this.label, + fullLabel: this.fullLabel, + tags: this.tags, + priority: this.priority, + priorityStr: this.priorityStr, + createdDate: this.createdDate, + startDate: this.startDate, + scheduledDate: this.scheduledDate, + dueDate: this.dueDate, + completedDate: this.completedDate, + cancelledDate: this.cancelledDate, + recurrenceRule: this.recurrenceRule, + onDelete: this.onDelete, + id: this.id, + dependsOn: this.dependsOn, + reminder: this.reminder, + }; + } } \ No newline at end of file diff --git a/src/runner/index.ts b/src/runner/index.ts index a2d5ff1..abfe757 100644 --- a/src/runner/index.ts +++ b/src/runner/index.ts @@ -6,13 +6,13 @@ import { Config } from "../types/config"; export async function test(config: Config) { const tasks = await loadTasks(config.sources, config.query); - const db = createDatabase(tasks, config.mapper); + const db = createDatabase(tasks); for (const time of Object.keys(db)) { console.log(time); - for (const notification of db[time]) { - console.log(` - title: ${notification.title}\n text: ${notification.text}\n priority: ${notification.priority}\n tags: ${notification.tags?.join(",")}`) + for (const task of db[time]) { + console.log(task.toString()); } console.log(); @@ -21,7 +21,7 @@ export async function test(config: Config) { export async function scan(config: Config) { const tasks = await loadTasks(config.sources, config.query); - dumpDatabase(config.databaseFile, tasks, config.mapper); + dumpDatabase(config.databaseFile, tasks); } export async function notify(config: Config) { diff --git a/src/types/config.ts b/src/types/config.ts index 605f231..83b91c7 100644 --- a/src/types/config.ts +++ b/src/types/config.ts @@ -1,7 +1,6 @@ export type Config = { sources: string[]; - query?: string; - mapper?: string; + query?: string; defaultTime?: string; databaseFile: string; backend: Record; diff --git a/src/types/notification.ts b/src/types/notification.ts deleted file mode 100644 index 115993a..0000000 --- a/src/types/notification.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TaskPriority } from "./task"; - -export type Notification = { - text: string; - time?: string; - title?: string; - priority?: TaskPriority; - tags?: string[]; -}; - -export type NotificationDatabase = Record; \ No newline at end of file diff --git a/src/types/task.ts b/src/types/task.ts index 446c87a..6e5fd79 100644 --- a/src/types/task.ts +++ b/src/types/task.ts @@ -17,7 +17,8 @@ export type Task = { onDelete?: string; id?: string; dependsOn?: string[]; - reminder?: string; + reminder?: string; + } export enum TaskPriority { @@ -27,4 +28,6 @@ export enum TaskPriority { MEDIUM, HIGH, HIGHEST, -}; \ No newline at end of file +}; + +export type TaskDatabase = Record; \ No newline at end of file