feat: sends message
Message sending is not yet feature complete Missing: - attachments - priority 2 -> retry & expire Format: text/markdown Milestone: minor
This commit is contained in:
259
src/index.ts
259
src/index.ts
@@ -1,111 +1,168 @@
|
|||||||
import { z } from "zod";
|
import https from "node:https";
|
||||||
|
import { URLSearchParams } from "node:url";
|
||||||
|
|
||||||
|
export interface PushoverMessage {
|
||||||
|
message: string;
|
||||||
|
title?: string;
|
||||||
|
link?: {
|
||||||
|
url: string;
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
priority?: -2 | -1 | 0 | 1 | 2;
|
||||||
|
sound?: string;
|
||||||
|
timestamp?: number;
|
||||||
|
html?: 0 | 1;
|
||||||
|
monospace?: 0 | 1;
|
||||||
|
ttl?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PushoverResponse {
|
||||||
|
status: number;
|
||||||
|
request: string;
|
||||||
|
errors?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PushoverConfig {
|
||||||
|
token: string;
|
||||||
|
defaultUser?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SendOptions {
|
||||||
|
recipients?: string | string[];
|
||||||
|
device?: string | string[];
|
||||||
|
verbose?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Pushover {
|
||||||
|
private token: string;
|
||||||
|
private defaultUser?: string;
|
||||||
|
private apiUrl = "https://api.pushover.net/1/messages.json";
|
||||||
|
|
||||||
|
constructor(config: PushoverConfig) {
|
||||||
|
this.token = config.token;
|
||||||
|
this.defaultUser = config.defaultUser;
|
||||||
|
}
|
||||||
|
|
||||||
const messageSchema = z.object({
|
|
||||||
/**
|
/**
|
||||||
* The message to send.
|
* Send a notification to one or multiple recipients
|
||||||
*/
|
*/
|
||||||
message: z.string(),
|
public async send(
|
||||||
/**
|
message: PushoverMessage,
|
||||||
* The title of the message.
|
options: SendOptions = {},
|
||||||
*/
|
): Promise<PushoverResponse[]> {
|
||||||
title: z.string().optional(),
|
const recipients = this.getRecipients(options);
|
||||||
/**
|
|
||||||
* The URL to open when the notification is clicked.
|
if (recipients.length === 0) {
|
||||||
*/
|
throw new Error(
|
||||||
url: z
|
"No recipients specified. Provide recipients in options or set a defaultUser.",
|
||||||
.object({
|
);
|
||||||
/**
|
}
|
||||||
* The URL itself.
|
|
||||||
*/
|
if (options.verbose) {
|
||||||
url: z.string(),
|
console.log("Verbose mode enabled. Logging message and options:");
|
||||||
/**
|
console.log(message);
|
||||||
* The title of the URL to be displayed.
|
console.log(options);
|
||||||
*/
|
console.log("----------------------");
|
||||||
title: z.string().optional(),
|
console.log("Sending message...");
|
||||||
})
|
}
|
||||||
.optional(),
|
|
||||||
priority: z
|
const promises = recipients.map((recipient) =>
|
||||||
.object({
|
this.sendToSingleRecipient(message, recipient, options.device),
|
||||||
level: z.number().default(0),
|
);
|
||||||
retry: z.number().optional(),
|
|
||||||
expire: z.number().optional(),
|
const results = await Promise.all(promises);
|
||||||
callback: z.string().optional(),
|
|
||||||
})
|
// Combine results
|
||||||
.optional(),
|
const combinedResponse: PushoverResponse[] = results.map(
|
||||||
sound: z.string().optional(),
|
(result) => result,
|
||||||
timestamp: z.date().optional(),
|
);
|
||||||
html: z.boolean().default(false),
|
|
||||||
attachment: z
|
return combinedResponse;
|
||||||
.object({
|
}
|
||||||
data: z.string().or(z.instanceof(File)),
|
|
||||||
type: z.string(),
|
private getRecipients(options: SendOptions): string[] {
|
||||||
})
|
const { recipients } = options;
|
||||||
.optional(),
|
|
||||||
users: z.array(
|
if (recipients) {
|
||||||
z.object({
|
return Array.isArray(recipients) ? recipients : [recipients];
|
||||||
name: z.string(),
|
}
|
||||||
token: z.string(),
|
|
||||||
devices: z.array(z.string()),
|
return this.defaultUser ? [this.defaultUser] : [];
|
||||||
}),
|
}
|
||||||
),
|
|
||||||
appToken: z.string(),
|
private async sendToSingleRecipient(
|
||||||
|
message: PushoverMessage,
|
||||||
|
user: string,
|
||||||
|
device?: string | string[],
|
||||||
|
): Promise<PushoverResponse> {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
|
||||||
|
// Add token and user
|
||||||
|
params.append("token", this.token);
|
||||||
|
params.append("user", user);
|
||||||
|
|
||||||
|
// Add message properties
|
||||||
|
Object.entries(message).forEach(([key, value]) => {
|
||||||
|
if (value !== undefined) {
|
||||||
|
if (key === "link") {
|
||||||
|
if (typeof value === "string") {
|
||||||
|
params.append("url", value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
params.append("url", value.url);
|
||||||
|
if (value.title) {
|
||||||
|
params.append("url_title", value.title);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
params.append(key, value.toString());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export type PushoverMessage = z.infer<typeof messageSchema>;
|
// Add device if specified
|
||||||
|
if (device) {
|
||||||
|
if (Array.isArray(device)) {
|
||||||
|
params.append("device", device.join(","));
|
||||||
|
} else {
|
||||||
|
params.append("device", device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export type Pushover = {
|
return this.makeRequest(params);
|
||||||
// #################################
|
}
|
||||||
// # Helpers #
|
|
||||||
// #################################
|
|
||||||
|
|
||||||
/**
|
private makeRequest(params: URLSearchParams): Promise<PushoverResponse> {
|
||||||
* Get all available sounds.
|
return new Promise((resolve, reject) => {
|
||||||
*/
|
const options = {
|
||||||
getSounds: () => { sounds: string[] };
|
method: "POST",
|
||||||
|
headers: {
|
||||||
/**
|
"Content-Type": "application/x-www-form-urlencoded",
|
||||||
* Get all available devices for a user.
|
},
|
||||||
*/
|
|
||||||
getDevices: (userToken: string) => { devices: string[] };
|
|
||||||
|
|
||||||
// #################################
|
|
||||||
// # Sending #
|
|
||||||
// #################################
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send the notification.
|
|
||||||
*/
|
|
||||||
send: () => void;
|
|
||||||
|
|
||||||
// #################################
|
|
||||||
// # Options #
|
|
||||||
// #################################
|
|
||||||
withMessage: (message: string) => Pushover;
|
|
||||||
withTitle: (title: string) => Pushover;
|
|
||||||
withUrl: (url: { url: string; title: string }) => Pushover;
|
|
||||||
withPriority: (priority: {
|
|
||||||
level: number;
|
|
||||||
retry?: number;
|
|
||||||
expire?: number;
|
|
||||||
callback?: string;
|
|
||||||
}) => Pushover;
|
|
||||||
withSound: (sound: string) => Pushover;
|
|
||||||
withTimestamp: (timestamp: number) => Pushover;
|
|
||||||
withHtml: (enable: boolean) => Pushover;
|
|
||||||
withAttachment: (attachment: File) => Pushover;
|
|
||||||
withBase64Attachment: (attachment: {
|
|
||||||
data: string;
|
|
||||||
type: string;
|
|
||||||
}) => Pushover;
|
|
||||||
addUsers: (users: User[]) => Pushover;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type User = {
|
const req = https.request(this.apiUrl, options, (res) => {
|
||||||
name: string;
|
let data = "";
|
||||||
token: string;
|
|
||||||
devices: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(
|
res.on("data", (chunk) => {
|
||||||
"this is indented obnoxiously far and has no semi at the end. (testing previous commits",
|
data += chunk;
|
||||||
);
|
});
|
||||||
|
|
||||||
|
res.on("end", () => {
|
||||||
|
try {
|
||||||
|
const response = JSON.parse(data) as PushoverResponse;
|
||||||
|
resolve(response);
|
||||||
|
} catch (error) {
|
||||||
|
reject(new Error(`Failed to parse response: ${data}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
req.on("error", (error) => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
req.write(params.toString());
|
||||||
|
req.end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user