mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-11-04 13:46:13 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			133 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			133 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const { log } = require("../../src/util");
 | 
						|
const NotificationProvider = require("./notification-provider");
 | 
						|
const {
 | 
						|
    relayInit,
 | 
						|
    getPublicKey,
 | 
						|
    getEventHash,
 | 
						|
    getSignature,
 | 
						|
    nip04,
 | 
						|
    nip19
 | 
						|
} = require("nostr-tools");
 | 
						|
 | 
						|
// polyfills for node versions
 | 
						|
const semver = require("semver");
 | 
						|
const nodeVersion = process.version;
 | 
						|
if (semver.lt(nodeVersion, "16.0.0")) {
 | 
						|
    log.warn("monitor", "Node <= 16 is unsupported for nostr, sorry :(");
 | 
						|
} else if (semver.lt(nodeVersion, "18.0.0")) {
 | 
						|
    // polyfills for node 16
 | 
						|
    global.crypto = require("crypto");
 | 
						|
    global.WebSocket = require("isomorphic-ws");
 | 
						|
    if (typeof crypto !== "undefined" && !crypto.subtle && crypto.webcrypto) {
 | 
						|
        crypto.subtle = crypto.webcrypto.subtle;
 | 
						|
    }
 | 
						|
} else if (semver.lt(nodeVersion, "20.0.0")) {
 | 
						|
    // polyfills for node 18
 | 
						|
    global.crypto = require("crypto");
 | 
						|
    global.WebSocket = require("isomorphic-ws");
 | 
						|
} else {
 | 
						|
    // polyfills for node 20
 | 
						|
    global.WebSocket = require("isomorphic-ws");
 | 
						|
}
 | 
						|
 | 
						|
class Nostr extends NotificationProvider {
 | 
						|
    name = "nostr";
 | 
						|
 | 
						|
    /**
 | 
						|
     * @inheritdoc
 | 
						|
     */
 | 
						|
    async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
 | 
						|
        // All DMs should have same timestamp
 | 
						|
        const createdAt = Math.floor(Date.now() / 1000);
 | 
						|
 | 
						|
        const senderPrivateKey = await this.getPrivateKey(notification.sender);
 | 
						|
        const senderPublicKey = getPublicKey(senderPrivateKey);
 | 
						|
        const recipientsPublicKeys = await this.getPublicKeys(notification.recipients);
 | 
						|
 | 
						|
        // Create NIP-04 encrypted direct message event for each recipient
 | 
						|
        const events = [];
 | 
						|
        for (const recipientPublicKey of recipientsPublicKeys) {
 | 
						|
            const ciphertext = await nip04.encrypt(senderPrivateKey, recipientPublicKey, msg);
 | 
						|
            let event = {
 | 
						|
                kind: 4,
 | 
						|
                pubkey: senderPublicKey,
 | 
						|
                created_at: createdAt,
 | 
						|
                tags: [[ "p", recipientPublicKey ]],
 | 
						|
                content: ciphertext,
 | 
						|
            };
 | 
						|
            event.id = getEventHash(event);
 | 
						|
            event.sig = getSignature(event, senderPrivateKey);
 | 
						|
            events.push(event);
 | 
						|
        }
 | 
						|
 | 
						|
        // Publish events to each relay
 | 
						|
        const relays = notification.relays.split("\n");
 | 
						|
        let successfulRelays = 0;
 | 
						|
 | 
						|
        // Connect to each relay
 | 
						|
        for (const relayUrl of relays) {
 | 
						|
            const relay = relayInit(relayUrl);
 | 
						|
            try {
 | 
						|
                await relay.connect();
 | 
						|
                successfulRelays++;
 | 
						|
 | 
						|
                // Publish events
 | 
						|
                for (const event of events) {
 | 
						|
                    relay.publish(event);
 | 
						|
                }
 | 
						|
            } catch (error) {
 | 
						|
                continue;
 | 
						|
            } finally {
 | 
						|
                relay.close();
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        // Report success or failure
 | 
						|
        if (successfulRelays === 0) {
 | 
						|
            throw Error("Failed to connect to any relays.");
 | 
						|
        }
 | 
						|
        return `${successfulRelays}/${relays.length} relays connected.`;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get the private key for the sender
 | 
						|
     * @param {string} sender Sender to retrieve key for
 | 
						|
     * @returns {nip19.DecodeResult} Private key
 | 
						|
     */
 | 
						|
    async getPrivateKey(sender) {
 | 
						|
        try {
 | 
						|
            const senderDecodeResult = await nip19.decode(sender);
 | 
						|
            const { data } = senderDecodeResult;
 | 
						|
            return data;
 | 
						|
        } catch (error) {
 | 
						|
            throw new Error(`Failed to get private key: ${error.message}`);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get public keys for recipients
 | 
						|
     * @param {string} recipients Newline delimited list of recipients
 | 
						|
     * @returns {Promise<nip19.DecodeResult[]>} Public keys
 | 
						|
     */
 | 
						|
    async getPublicKeys(recipients) {
 | 
						|
        const recipientsList = recipients.split("\n");
 | 
						|
        const publicKeys = [];
 | 
						|
        for (const recipient of recipientsList) {
 | 
						|
            try {
 | 
						|
                const recipientDecodeResult = await nip19.decode(recipient);
 | 
						|
                const { type, data } = recipientDecodeResult;
 | 
						|
                if (type === "npub") {
 | 
						|
                    publicKeys.push(data);
 | 
						|
                } else {
 | 
						|
                    throw new Error("not an npub");
 | 
						|
                }
 | 
						|
            } catch (error) {
 | 
						|
                throw new Error(`Error decoding recipient: ${error}`);
 | 
						|
            }
 | 
						|
        }
 | 
						|
        return publicKeys;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
module.exports = Nostr;
 |