Improve mapping tasks to notifications
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import { BackendSettings, Config } from "../types/config";
|
import { BackendSettings, Config } from "../types/config";
|
||||||
import { Notification } from "../types/notification";
|
import { Task } from "../types/task";
|
||||||
|
|
||||||
export abstract class Backend<C extends BackendSettings> {
|
export abstract class Backend<C extends BackendSettings> {
|
||||||
public abstract readonly name: string;
|
public abstract readonly name: string;
|
||||||
protected abstract requiredFields: readonly (keyof C)[];
|
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>): C {
|
#validate(config: Partial<C>): C {
|
||||||
for (const field of this.requiredFields) {
|
for (const field of this.requiredFields) {
|
||||||
@@ -16,13 +16,13 @@ export abstract class Backend<C extends BackendSettings> {
|
|||||||
return config as C;
|
return config as C;
|
||||||
}
|
}
|
||||||
|
|
||||||
public remind(config: Config, notification: Notification) {
|
public remind(config: Config, task: Task) {
|
||||||
const cfg = config?.backend?.[this.name] as Partial<C> | undefined;
|
const cfg = config?.backend?.[this.name] as Partial<C> | undefined;
|
||||||
|
|
||||||
if (cfg?.enable !== true) {
|
if (cfg?.enable !== true) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
this.notify(this.#validate(cfg), notification);
|
this.notify(this.#validate(cfg), task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { BackendSettings } from "../types/config";
|
import { BackendSettings } from "../types/config";
|
||||||
import { Notification } from "../types/notification";
|
import { Task } from "../types/task";
|
||||||
import { Backend } from "./base";
|
import { Backend } from "./base";
|
||||||
|
|
||||||
type Config = BackendSettings;
|
type Config = BackendSettings;
|
||||||
@@ -9,7 +9,7 @@ export class Debug extends Backend<Config> {
|
|||||||
|
|
||||||
protected requiredFields = [] as const;
|
protected requiredFields = [] as const;
|
||||||
|
|
||||||
protected notify(config: Config, notification: Notification): void {
|
protected notify(config: Config, task: Task): void {
|
||||||
console.log(JSON.stringify(notification, undefined, 2));
|
console.log(JSON.stringify(task, undefined, 2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { Notification, NotificationDatabase } from "../types/notification";
|
import { Task, TaskDatabase } from "../types/task";
|
||||||
import { Config } from "../types/config";
|
import { Config } from "../types/config";
|
||||||
import { NtfySH } from "./ntfy";
|
import { NtfySH } from "./ntfy";
|
||||||
import { Debug } from "./debug";
|
import { Debug } from "./debug";
|
||||||
@@ -13,7 +13,7 @@ const backends = [
|
|||||||
* Iterates through all the database notifications for current time
|
* Iterates through all the database notifications for current time
|
||||||
* and triggers the notification using specified backends in the config.
|
* 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");
|
const now = dayjs().format("HH:mm");
|
||||||
|
|
||||||
await run(config, db, db[now]);
|
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[]) {
|
async function run(config: Config, db: TaskDatabase, tasks?: Task[]) {
|
||||||
if(!notifications) {
|
if(!tasks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const notification of notifications) {
|
for (const task of tasks) {
|
||||||
console.info(`Dispatching a notification: [${notification.text}]`)
|
console.info(`Dispatching a notification: [${task.label}]`)
|
||||||
for (const backend of backends) {
|
for (const backend of backends) {
|
||||||
backend.remind(config, notification);
|
backend.remind(config, task);
|
||||||
await snooze(1500);
|
await snooze(1500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +1,79 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { BackendSettings } from "../types/config";
|
import { BackendSettings } from "../types/config";
|
||||||
import { Notification } from "../types/notification";
|
|
||||||
import { Backend } from "./base";
|
import { Backend } from "./base";
|
||||||
import { TaskPriority } from "../types/task";
|
import { Task, TaskPriority } from "../types/task";
|
||||||
import { enhancedStringConfig } from "../util/config";
|
import { enhancedStringConfig } from "../util/config";
|
||||||
|
import { jsMapper } from "../util/code";
|
||||||
|
|
||||||
type Config = {
|
type Config = {
|
||||||
url: string;
|
url: string;
|
||||||
token: string;
|
token: string;
|
||||||
|
mapper?: string;
|
||||||
topic?: string;
|
topic?: string;
|
||||||
} & BackendSettings;
|
} & BackendSettings;
|
||||||
|
|
||||||
|
type NtfyDTO = {
|
||||||
|
title?: string;
|
||||||
|
text?: string;
|
||||||
|
priority?: string;
|
||||||
|
tags?: string[];
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
|
||||||
export class NtfySH extends Backend<Config> {
|
export class NtfySH extends Backend<Config> {
|
||||||
public name = "ntfy";
|
public name = "ntfy";
|
||||||
|
|
||||||
protected requiredFields = ['url', 'token'] as const;
|
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 token = enhancedStringConfig(config.token);
|
||||||
|
const mapper = config.mapper
|
||||||
|
? jsMapper<object, NtfyDTO>(config.mapper, { mapPriority })
|
||||||
|
: defaultMapper;
|
||||||
|
|
||||||
|
const dto = mapper(task);
|
||||||
|
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
};
|
||||||
|
|
||||||
|
buildHeaders(dto, headers);
|
||||||
|
|
||||||
fetch(`https://${config.url}/${config.topic || 'obsidian'}`, {
|
fetch(`https://${config.url}/${config.topic || 'obsidian'}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: notification.text,
|
body: dto.text ?? "",
|
||||||
headers: {
|
headers
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
'Title': notification.title ?? "",
|
|
||||||
'Priority': mapPriority(notification.priority),
|
|
||||||
'Tags': notification.tags?.join(",") ?? ""
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildHeaders(dto: NtfyDTO, headers: Record<string, string>) {
|
||||||
|
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 {
|
function mapPriority(priority?: TaskPriority): string {
|
||||||
if (!priority) {
|
if (!priority) {
|
||||||
return 'default';
|
return 'default';
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { NotificationDatabase } from "../types/notification";
|
import { TaskDatabase } from "../types/task";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and deserializes database from JSON formatted file.
|
* 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();
|
const text = fs.readFileSync(file).toString();
|
||||||
return deserializeDatabase(text);
|
return deserializeDatabase(text);
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,6 @@ export function loadDatabase(file: string): NotificationDatabase {
|
|||||||
/**
|
/**
|
||||||
* Deserializes database from JSON format.
|
* Deserializes database from JSON format.
|
||||||
*/
|
*/
|
||||||
export function deserializeDatabase(json: string): NotificationDatabase {
|
export function deserializeDatabase(json: string): TaskDatabase {
|
||||||
return JSON.parse(json) as NotificationDatabase;
|
return JSON.parse(json) as TaskDatabase;
|
||||||
}
|
}
|
||||||
@@ -1,57 +1,35 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import { Task } from "../types/task";
|
import { Task, TaskDatabase } from "../types/task";
|
||||||
import { Notification, NotificationDatabase } from "../types/notification";
|
|
||||||
import { jsMapper } from "../util/code";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the mapper for each task from list, groups them by time and dumps the data into JSON formatted file.
|
* 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) {
|
export function dumpDatabase(file: string, tasks: Task[]) {
|
||||||
const data = serializeDatabase(tasks, mapper);
|
const data = serializeDatabase(tasks);
|
||||||
fs.writeFileSync(file, data);
|
fs.writeFileSync(file, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the mapper for each task from list, groups them by time and serializes into JSON format.
|
* 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 {
|
export function serializeDatabase(tasks: Task[]): string {
|
||||||
return JSON.stringify(createDatabase(tasks, mapper));
|
|
||||||
|
const x = JSON.stringify(createDatabase(tasks));
|
||||||
|
console.log(x);
|
||||||
|
return x;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the mapper for each task from list and groups them by time.
|
* Applies the mapper for each task from list and groups them by time.
|
||||||
*/
|
*/
|
||||||
export function createDatabase(tasks: Task[], mapper?: string): NotificationDatabase {
|
export function createDatabase(tasks: Task[]): TaskDatabase {
|
||||||
const transformer = mapper
|
|
||||||
? jsMapper<Task, Notification>(mapper, {})
|
|
||||||
: defaultMapper;
|
|
||||||
|
|
||||||
return tasks.map(wrapWithTimeFiller(transformer)).reduce((acc, n) => {
|
return tasks.filter(t => t.reminder).reduce((acc, t) => {
|
||||||
if (n.time) {
|
if (t.reminder) {
|
||||||
(acc[n.time] = (acc[n.time] || [])).push(n);
|
(acc[t.reminder] = (acc[t.reminder] || [])).push(t);
|
||||||
};
|
};
|
||||||
|
|
||||||
return acc;
|
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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -149,4 +149,26 @@ export class LazyTask implements Task {
|
|||||||
|
|
||||||
return `- [${this.status}] ${this.label} {${items.filter(x => x.length > 0).join(", ")}}`;
|
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,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,13 +6,13 @@ import { Config } from "../types/config";
|
|||||||
|
|
||||||
export async function test(config: Config) {
|
export async function test(config: Config) {
|
||||||
const tasks = await loadTasks(config.sources, config.query);
|
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)) {
|
for (const time of Object.keys(db)) {
|
||||||
console.log(time);
|
console.log(time);
|
||||||
|
|
||||||
for (const notification of db[time]) {
|
for (const task of db[time]) {
|
||||||
console.log(` - title: ${notification.title}\n text: ${notification.text}\n priority: ${notification.priority}\n tags: ${notification.tags?.join(",")}`)
|
console.log(task.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log();
|
console.log();
|
||||||
@@ -21,7 +21,7 @@ export async function test(config: Config) {
|
|||||||
|
|
||||||
export async function scan(config: Config) {
|
export async function scan(config: Config) {
|
||||||
const tasks = await loadTasks(config.sources, config.query);
|
const tasks = await loadTasks(config.sources, config.query);
|
||||||
dumpDatabase(config.databaseFile, tasks, config.mapper);
|
dumpDatabase(config.databaseFile, tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function notify(config: Config) {
|
export async function notify(config: Config) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
export type Config = {
|
export type Config = {
|
||||||
sources: string[];
|
sources: string[];
|
||||||
query?: string;
|
query?: string;
|
||||||
mapper?: string;
|
|
||||||
defaultTime?: string;
|
defaultTime?: string;
|
||||||
databaseFile: string;
|
databaseFile: string;
|
||||||
backend: Record<string, unknown>;
|
backend: Record<string, unknown>;
|
||||||
|
|||||||
@@ -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<string, Notification[]>;
|
|
||||||
@@ -17,7 +17,8 @@ export type Task = {
|
|||||||
onDelete?: string;
|
onDelete?: string;
|
||||||
id?: string;
|
id?: string;
|
||||||
dependsOn?: string[];
|
dependsOn?: string[];
|
||||||
reminder?: string;
|
reminder?: string;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum TaskPriority {
|
export enum TaskPriority {
|
||||||
@@ -27,4 +28,6 @@ export enum TaskPriority {
|
|||||||
MEDIUM,
|
MEDIUM,
|
||||||
HIGH,
|
HIGH,
|
||||||
HIGHEST,
|
HIGHEST,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type TaskDatabase = Record<string, Task[]>;
|
||||||
Reference in New Issue
Block a user