node-red-contrib-nostr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -0
  3. package/dist/core/event.d.ts +6 -0
  4. package/dist/core/event.js +58 -0
  5. package/dist/core/filter.d.ts +14 -0
  6. package/dist/core/filter.js +52 -0
  7. package/dist/crypto/keys.d.ts +38 -0
  8. package/dist/crypto/keys.js +124 -0
  9. package/dist/nips/nip01.d.ts +22 -0
  10. package/dist/nips/nip01.js +65 -0
  11. package/dist/nips/nip02.d.ts +15 -0
  12. package/dist/nips/nip02.js +35 -0
  13. package/dist/nips/nip04.d.ts +8 -0
  14. package/dist/nips/nip04.js +89 -0
  15. package/dist/nips/nip05.d.ts +8 -0
  16. package/dist/nips/nip05.js +42 -0
  17. package/dist/nodes/example/example.d.ts +1 -0
  18. package/dist/nodes/example/example.html +27 -0
  19. package/dist/nodes/example/example.js +14 -0
  20. package/dist/nodes/index.d.ts +3 -0
  21. package/dist/nodes/index.js +20 -0
  22. package/dist/nodes/nostr-filter/nostr-filter.d.ts +2 -0
  23. package/dist/nodes/nostr-filter/nostr-filter.html +142 -0
  24. package/dist/nodes/nostr-filter/nostr-filter.js +174 -0
  25. package/dist/nodes/nostr-npub-filter/nostr-npub-filter.d.ts +2 -0
  26. package/dist/nodes/nostr-npub-filter/nostr-npub-filter.html +130 -0
  27. package/dist/nodes/nostr-npub-filter/nostr-npub-filter.js +104 -0
  28. package/dist/nodes/nostr-relay/nostr-relay.d.ts +2 -0
  29. package/dist/nodes/nostr-relay/nostr-relay.html +66 -0
  30. package/dist/nodes/nostr-relay/nostr-relay.js +49 -0
  31. package/dist/nodes/nostr-relay-config/nostr-relay-config.d.ts +9 -0
  32. package/dist/nodes/nostr-relay-config/nostr-relay-config.html +56 -0
  33. package/dist/nodes/nostr-relay-config/nostr-relay-config.js +106 -0
  34. package/dist/nodes/shared/types.d.ts +26 -0
  35. package/dist/nodes/shared/types.js +2 -0
  36. package/dist/nodes/shared/utils.d.ts +28 -0
  37. package/dist/nodes/shared/utils.js +86 -0
  38. package/dist/types.d.ts +34 -0
  39. package/dist/types.js +2 -0
  40. package/dist/utils/nip19.d.ts +8 -0
  41. package/dist/utils/nip19.js +34 -0
  42. package/dist/utils/validation.d.ts +8 -0
  43. package/dist/utils/validation.js +31 -0
  44. package/examples/basic-relay.json +48 -0
  45. package/examples/jack-monitor.json +69 -0
  46. package/examples/multi-user-monitor.json +129 -0
  47. package/locales/en-US/messages.json +63 -0
  48. package/package.json +86 -0
@@ -0,0 +1,8 @@
1
+ import { NostrEvent } from '../nodes/shared/types';
2
+ export declare class NIP04 {
3
+ static encrypt(privateKey: string, publicKey: string, text: string): Promise<string>;
4
+ static decrypt(privateKey: string, publicKey: string, encryptedText: string): Promise<string>;
5
+ static createEncryptedEvent(recipientPubkey: string, content: string, privateKey: string): Promise<NostrEvent>;
6
+ static decryptEvent(event: NostrEvent, privateKey: string): Promise<string>;
7
+ static getDMFilter(pubkey: string, otherPubkey?: string): any;
8
+ }
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.NIP04 = void 0;
37
+ const secp256k1 = __importStar(require("@noble/secp256k1"));
38
+ const utils_1 = require("@noble/hashes/utils");
39
+ const sha256_1 = require("@noble/hashes/sha256");
40
+ const event_1 = require("../core/event");
41
+ class NIP04 {
42
+ static async encrypt(privateKey, publicKey, text) {
43
+ const sharedPoint = secp256k1.getSharedSecret((0, utils_1.hexToBytes)(privateKey), (0, utils_1.hexToBytes)(publicKey));
44
+ const sharedX = sharedPoint.slice(1, 33);
45
+ // In a real implementation, we'd use this shared secret with
46
+ // proper encryption. This is just a placeholder.
47
+ const key = (0, sha256_1.sha256)(sharedX);
48
+ // TODO: Implement actual encryption using AES-256-CBC
49
+ return (0, utils_1.bytesToHex)(key);
50
+ }
51
+ static async decrypt(privateKey, publicKey, encryptedText) {
52
+ const sharedPoint = secp256k1.getSharedSecret((0, utils_1.hexToBytes)(privateKey), (0, utils_1.hexToBytes)(publicKey));
53
+ const sharedX = sharedPoint.slice(1, 33);
54
+ // TODO: Implement actual decryption using AES-256-CBC
55
+ return "decrypted text";
56
+ }
57
+ static async createEncryptedEvent(recipientPubkey, content, privateKey) {
58
+ const encryptedContent = await this.encrypt(privateKey, recipientPubkey, content);
59
+ const tags = [['p', recipientPubkey]];
60
+ return await event_1.EventBuilder.createEvent(4, // NIP-04 Direct Message kind
61
+ encryptedContent, privateKey, tags);
62
+ }
63
+ static async decryptEvent(event, privateKey) {
64
+ if (event.kind !== 4)
65
+ throw new Error('Not a DM event');
66
+ const recipientTag = event.tags.find(tag => tag[0] === 'p');
67
+ if (!recipientTag)
68
+ throw new Error('No recipient tag found');
69
+ return await this.decrypt(privateKey, event.pubkey, event.content);
70
+ }
71
+ static getDMFilter(pubkey, otherPubkey) {
72
+ const filter = {
73
+ kinds: [4],
74
+ limit: 100
75
+ };
76
+ if (otherPubkey) {
77
+ filter['#p'] = [otherPubkey];
78
+ filter.authors = [pubkey];
79
+ }
80
+ else {
81
+ filter.$or = [
82
+ { authors: [pubkey] },
83
+ { '#p': [pubkey] }
84
+ ];
85
+ }
86
+ return filter;
87
+ }
88
+ }
89
+ exports.NIP04 = NIP04;
@@ -0,0 +1,8 @@
1
+ export declare class InternetIdentifier {
2
+ static verify(identifier: string, pubkey: string): Promise<boolean>;
3
+ static parseIdentifier(identifier: string): {
4
+ name: string;
5
+ domain: string;
6
+ };
7
+ static getRelays(identifier: string, pubkey: string): Promise<string[]>;
8
+ }
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InternetIdentifier = void 0;
4
+ class InternetIdentifier {
5
+ static async verify(identifier, pubkey) {
6
+ try {
7
+ const [name, domain] = identifier.split('@');
8
+ if (!name || !domain)
9
+ return false;
10
+ const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
11
+ const response = await fetch(url);
12
+ const data = await response.json();
13
+ return data.names[name] === pubkey;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ static parseIdentifier(identifier) {
20
+ const [name, domain] = identifier.split('@');
21
+ if (!name || !domain) {
22
+ throw new Error('Invalid NIP-05 identifier format');
23
+ }
24
+ return { name, domain };
25
+ }
26
+ static async getRelays(identifier, pubkey) {
27
+ const { name, domain } = this.parseIdentifier(identifier);
28
+ const url = `https://${domain}/.well-known/nostr.json?name=${name}`;
29
+ try {
30
+ const response = await fetch(url);
31
+ const data = await response.json();
32
+ if (data.relays && data.relays[pubkey]) {
33
+ return data.relays[pubkey];
34
+ }
35
+ return [];
36
+ }
37
+ catch {
38
+ return [];
39
+ }
40
+ }
41
+ }
42
+ exports.InternetIdentifier = InternetIdentifier;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,27 @@
1
+ <script type="text/html" data-template-name="example">
2
+ <div class="form-row">
3
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
4
+ <input type="text" id="node-input-name" placeholder="Name">
5
+ </div>
6
+ <div class="form-row">
7
+ <label for="node-input-message"><i class="fa fa-envelope"></i> Message</label>
8
+ <input type="text" id="node-input-message" placeholder="Hello World">
9
+ </div>
10
+ </script>
11
+
12
+ <script type="text/javascript">
13
+ RED.nodes.registerType('example',{
14
+ category: 'example',
15
+ color: '#a6bbcf',
16
+ defaults: {
17
+ name: {value:""},
18
+ message: {value:"Hello World"}
19
+ },
20
+ inputs:1,
21
+ outputs:1,
22
+ icon: "file.png",
23
+ label: function() {
24
+ return this.name||"example";
25
+ }
26
+ });
27
+ </script>
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ module.exports = function (RED) {
4
+ function ExampleNode(config) {
5
+ RED.nodes.createNode(this, config);
6
+ const node = this;
7
+ node.on('input', function (msg) {
8
+ msg.payload = config.message;
9
+ node.send(msg);
10
+ });
11
+ }
12
+ RED.nodes.registerType("example", ExampleNode);
13
+ };
14
+ //# sourceMappingURL=example.js.map
@@ -0,0 +1,3 @@
1
+ export * from './nostr-filter/nostr-filter';
2
+ export * from './nostr-relay/nostr-relay';
3
+ export * from './nostr-relay-config/nostr-relay-config';
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ // This file is just to export all nodes
18
+ __exportStar(require("./nostr-filter/nostr-filter"), exports);
19
+ __exportStar(require("./nostr-relay/nostr-relay"), exports);
20
+ __exportStar(require("./nostr-relay-config/nostr-relay-config"), exports);
@@ -0,0 +1,2 @@
1
+ import { NodeAPI } from 'node-red';
2
+ export default function (RED: NodeAPI): void;
@@ -0,0 +1,142 @@
1
+ <script type="text/html" data-template-name="nostr-filter">
2
+ <div class="form-row">
3
+ <label for="node-input-name"><i class="fa fa-tag"></i> Name</label>
4
+ <input type="text" id="node-input-name" placeholder="Name">
5
+ </div>
6
+ <div class="form-row">
7
+ <label for="node-input-relay"><i class="fa fa-globe"></i> Relay</label>
8
+ <input type="text" id="node-input-relay">
9
+ </div>
10
+ <div class="form-row">
11
+ <label for="node-input-filterType"><i class="fa fa-filter"></i> Filter Type</label>
12
+ <select id="node-input-filterType">
13
+ <option value="npub">Track User (npub)</option>
14
+ <option value="kind">Event Kind</option>
15
+ <option value="tag">Tag</option>
16
+ <option value="since">Time Since</option>
17
+ <option value="custom">Custom</option>
18
+ </select>
19
+ </div>
20
+ <div class="form-row filter-npub">
21
+ <label for="node-input-npubValue"><i class="fa fa-user"></i> Nostr Public Key</label>
22
+ <input type="text" id="node-input-npubValue" placeholder="npub1...">
23
+ <div class="form-tips">Enter the npub of the user you want to track</div>
24
+ </div>
25
+ <div class="form-row filter-npub">
26
+ <label for="node-input-npubEventKinds"><i class="fa fa-list"></i> Event Types</label>
27
+ <select id="node-input-npubEventKinds" multiple>
28
+ <option value="0">Profile Metadata</option>
29
+ <option value="1">Text Notes</option>
30
+ <option value="3">Contacts</option>
31
+ <option value="6">Reposts</option>
32
+ <option value="7">Reactions</option>
33
+ <option value="9735">Zaps</option>
34
+ </select>
35
+ <div class="form-tips">Select which types of events to track for this user</div>
36
+ </div>
37
+ <div class="form-row filter-kind">
38
+ <label for="node-input-eventKinds"><i class="fa fa-list"></i> Event Kinds</label>
39
+ <select id="node-input-eventKinds" multiple>
40
+ <option value="0">Metadata (0)</option>
41
+ <option value="1">Text Note (1)</option>
42
+ <option value="3">Contacts (3)</option>
43
+ <option value="4">DM (4)</option>
44
+ <option value="6">Repost (6)</option>
45
+ <option value="7">Reaction (7)</option>
46
+ <option value="9735">Zap (9735)</option>
47
+ </select>
48
+ </div>
49
+ <div class="form-row filter-tag">
50
+ <label for="node-input-tagName"><i class="fa fa-tag"></i> Tag Name</label>
51
+ <input type="text" id="node-input-tagName" placeholder="e, p, t, etc">
52
+ </div>
53
+ <div class="form-row filter-tag">
54
+ <label for="node-input-tagValue"><i class="fa fa-tag"></i> Tag Value</label>
55
+ <input type="text" id="node-input-tagValue" placeholder="Tag value to match">
56
+ </div>
57
+ <div class="form-row filter-since">
58
+ <label for="node-input-sinceMinutes"><i class="fa fa-clock-o"></i> Minutes</label>
59
+ <input type="number" id="node-input-sinceMinutes" placeholder="Time window in minutes">
60
+ </div>
61
+ <div class="form-row filter-custom">
62
+ <label for="node-input-customFilter"><i class="fa fa-code"></i> Custom Filter</label>
63
+ <input type="text" id="node-input-customFilter" placeholder='{"kinds":[1],"#e":["..."]}'>
64
+ </div>
65
+ </script>
66
+
67
+ <script type="text/javascript">
68
+ RED.nodes.registerType('nostr-filter', {
69
+ category: 'protocol',
70
+ color: '#a6bbcf',
71
+ defaults: {
72
+ name: { value: "" },
73
+ relay: { type: "nostr-relay-config", required: true },
74
+ filterType: { value: "npub" },
75
+ npubValue: { value: "", required: false },
76
+ npubEventKinds: { value: [0, 1], required: false },
77
+ eventKinds: { value: [1], required: false },
78
+ tagName: { value: "", required: false },
79
+ tagValue: { value: "", required: false },
80
+ sinceMinutes: { value: 60, required: false, validate: RED.validators.number() },
81
+ customFilter: { value: "", required: false }
82
+ },
83
+ label: function() {
84
+ if (this.filterType === 'npub' && this.npubValue) {
85
+ return this.name || `Track ${this.npubValue.substring(0, 12)}...`;
86
+ }
87
+ return this.name || "nostr filter";
88
+ },
89
+ oneditprepare: function() {
90
+ function showHideFields() {
91
+ $('.form-row').hide();
92
+ $('.form-row:first-child').show();
93
+ $('.form-row:nth-child(2)').show();
94
+ $('.form-row:nth-child(3)').show();
95
+ $(`.filter-${$('#node-input-filterType').val()}`).show();
96
+ }
97
+
98
+ $('#node-input-filterType').on('change', showHideFields);
99
+ showHideFields();
100
+ }
101
+ });
102
+ </script>
103
+
104
+ <script type="text/html" data-help-name="nostr-filter">
105
+ <p>Filters Nostr events based on various criteria.</p>
106
+
107
+ <h3>Properties</h3>
108
+ <dl class="message-properties">
109
+ <dt>Relay
110
+ <span class="property-type">config</span>
111
+ </dt>
112
+ <dd>Select the Nostr relay configuration to use</dd>
113
+ </dl>
114
+
115
+ <h3>Filter Types</h3>
116
+ <dl class="message-properties">
117
+ <dt>Track User (npub)
118
+ <span class="property-type">string</span>
119
+ </dt>
120
+ <dd>Track events from a specific Nostr user by their npub. Select which types of events to track.</dd>
121
+
122
+ <dt>Event Kind
123
+ <span class="property-type">array</span>
124
+ </dt>
125
+ <dd>Filter events by their kind (e.g., text notes, reactions, etc.)</dd>
126
+
127
+ <dt>Tag
128
+ <span class="property-type">string</span>
129
+ </dt>
130
+ <dd>Filter events by tag name and value</dd>
131
+
132
+ <dt>Time Since
133
+ <span class="property-type">number</span>
134
+ </dt>
135
+ <dd>Filter events within a time window (in minutes)</dd>
136
+
137
+ <dt>Custom
138
+ <span class="property-type">json</span>
139
+ </dt>
140
+ <dd>Specify a custom Nostr filter in JSON format</dd>
141
+ </dl>
142
+ </script>
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.default = default_1;
37
+ function default_1(RED) {
38
+ // Create a function to initialize the node
39
+ async function initializeNode(config) {
40
+ RED.nodes.createNode(this, config);
41
+ // Get the relay configuration node
42
+ this.relay = RED.nodes.getNode(config.relay);
43
+ if (!this.relay) {
44
+ this.error("No relay configuration found");
45
+ this.status({ fill: "red", shape: "ring", text: "Missing relay config" });
46
+ return;
47
+ }
48
+ this.filterType = config.filterType;
49
+ this.npubValue = config.npubValue;
50
+ this.npubEventKinds = config.npubEventKinds || [0, 1]; // Default to metadata and text notes
51
+ this.eventKinds = config.eventKinds;
52
+ this.tagName = config.tagName;
53
+ this.tagValue = config.tagValue;
54
+ this.sinceMinutes = config.sinceMinutes;
55
+ this.customFilter = config.customFilter;
56
+ try {
57
+ // Dynamically import ESM dependencies
58
+ const { nip19 } = await Promise.resolve().then(() => __importStar(require('nostr-tools')));
59
+ // Convert npub to hex if needed
60
+ if (this.filterType === 'npub' && this.npubValue) {
61
+ try {
62
+ const decoded = nip19.decode(this.npubValue);
63
+ if (decoded.type === 'npub') {
64
+ this.hexPubkey = decoded.data;
65
+ this.status({ fill: "green", shape: "dot", text: "Ready" });
66
+ }
67
+ else {
68
+ throw new Error("Invalid npub format");
69
+ }
70
+ }
71
+ catch (err) {
72
+ this.error("Invalid npub: " + err.message);
73
+ this.status({ fill: "red", shape: "dot", text: "Invalid npub" });
74
+ return;
75
+ }
76
+ }
77
+ // Set up message handler
78
+ this.relay._ws?.on('message', (msg) => {
79
+ if (msg.type === 'EVENT' && msg.event) {
80
+ const event = msg.event;
81
+ // Apply filters based on type
82
+ let shouldForward = false;
83
+ switch (this.filterType) {
84
+ case 'npub':
85
+ if (this.hexPubkey && event.pubkey === this.hexPubkey) {
86
+ shouldForward = this.npubEventKinds.includes(event.kind);
87
+ }
88
+ break;
89
+ case 'kind':
90
+ shouldForward = this.eventKinds.includes(event.kind);
91
+ break;
92
+ case 'tag':
93
+ if (this.tagName && this.tagValue) {
94
+ shouldForward = event.tags.some(tag => tag[0] === this.tagName && tag[1] === this.tagValue);
95
+ }
96
+ break;
97
+ case 'since':
98
+ if (this.sinceMinutes > 0) {
99
+ const cutoff = Math.floor(Date.now() / 1000) - (this.sinceMinutes * 60);
100
+ shouldForward = event.created_at >= cutoff;
101
+ }
102
+ break;
103
+ case 'custom':
104
+ if (this.customFilter) {
105
+ try {
106
+ const filter = JSON.parse(this.customFilter);
107
+ shouldForward = Object.entries(filter).every(([key, value]) => {
108
+ if (Array.isArray(value)) {
109
+ return value.includes(event[key]);
110
+ }
111
+ return event[key] === value;
112
+ });
113
+ }
114
+ catch (err) {
115
+ this.error("Invalid custom filter: " + err.message);
116
+ }
117
+ }
118
+ break;
119
+ }
120
+ if (shouldForward) {
121
+ this.send({ payload: event });
122
+ }
123
+ }
124
+ });
125
+ // Subscribe to events based on filter
126
+ const filter = {};
127
+ switch (this.filterType) {
128
+ case 'npub':
129
+ if (this.hexPubkey) {
130
+ filter.authors = [this.hexPubkey];
131
+ filter.kinds = this.npubEventKinds;
132
+ }
133
+ break;
134
+ case 'kind':
135
+ filter.kinds = this.eventKinds;
136
+ break;
137
+ case 'tag':
138
+ if (this.tagName && this.tagValue) {
139
+ filter[`#${this.tagName}`] = [this.tagValue];
140
+ }
141
+ break;
142
+ case 'since':
143
+ if (this.sinceMinutes > 0) {
144
+ filter.since = Math.floor(Date.now() / 1000) - (this.sinceMinutes * 60);
145
+ }
146
+ break;
147
+ case 'custom':
148
+ if (this.customFilter) {
149
+ try {
150
+ Object.assign(filter, JSON.parse(this.customFilter));
151
+ }
152
+ catch (err) {
153
+ this.error("Invalid custom filter: " + err.message);
154
+ }
155
+ }
156
+ break;
157
+ }
158
+ if (Object.keys(filter).length > 0) {
159
+ this.relay._ws?.subscribe('sub', filter);
160
+ }
161
+ }
162
+ catch (err) {
163
+ this.error("Failed to initialize node: " + err.message);
164
+ this.status({ fill: "red", shape: "dot", text: "Error" });
165
+ }
166
+ }
167
+ // Register the node
168
+ RED.nodes.registerType("nostr-filter", function (config) {
169
+ // Initialize asynchronously
170
+ initializeNode.call(this, config).catch((err) => {
171
+ this.error("Failed to initialize node: " + err.message);
172
+ });
173
+ });
174
+ }
@@ -0,0 +1,2 @@
1
+ import { NodeAPI } from 'node-red';
2
+ export default function (RED: NodeAPI): void;