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.
- package/LICENSE +21 -0
- package/README.md +222 -0
- package/dist/core/event.d.ts +6 -0
- package/dist/core/event.js +58 -0
- package/dist/core/filter.d.ts +14 -0
- package/dist/core/filter.js +52 -0
- package/dist/crypto/keys.d.ts +38 -0
- package/dist/crypto/keys.js +124 -0
- package/dist/nips/nip01.d.ts +22 -0
- package/dist/nips/nip01.js +65 -0
- package/dist/nips/nip02.d.ts +15 -0
- package/dist/nips/nip02.js +35 -0
- package/dist/nips/nip04.d.ts +8 -0
- package/dist/nips/nip04.js +89 -0
- package/dist/nips/nip05.d.ts +8 -0
- package/dist/nips/nip05.js +42 -0
- package/dist/nodes/example/example.d.ts +1 -0
- package/dist/nodes/example/example.html +27 -0
- package/dist/nodes/example/example.js +14 -0
- package/dist/nodes/index.d.ts +3 -0
- package/dist/nodes/index.js +20 -0
- package/dist/nodes/nostr-filter/nostr-filter.d.ts +2 -0
- package/dist/nodes/nostr-filter/nostr-filter.html +142 -0
- package/dist/nodes/nostr-filter/nostr-filter.js +174 -0
- package/dist/nodes/nostr-npub-filter/nostr-npub-filter.d.ts +2 -0
- package/dist/nodes/nostr-npub-filter/nostr-npub-filter.html +130 -0
- package/dist/nodes/nostr-npub-filter/nostr-npub-filter.js +104 -0
- package/dist/nodes/nostr-relay/nostr-relay.d.ts +2 -0
- package/dist/nodes/nostr-relay/nostr-relay.html +66 -0
- package/dist/nodes/nostr-relay/nostr-relay.js +49 -0
- package/dist/nodes/nostr-relay-config/nostr-relay-config.d.ts +9 -0
- package/dist/nodes/nostr-relay-config/nostr-relay-config.html +56 -0
- package/dist/nodes/nostr-relay-config/nostr-relay-config.js +106 -0
- package/dist/nodes/shared/types.d.ts +26 -0
- package/dist/nodes/shared/types.js +2 -0
- package/dist/nodes/shared/utils.d.ts +28 -0
- package/dist/nodes/shared/utils.js +86 -0
- package/dist/types.d.ts +34 -0
- package/dist/types.js +2 -0
- package/dist/utils/nip19.d.ts +8 -0
- package/dist/utils/nip19.js +34 -0
- package/dist/utils/validation.d.ts +8 -0
- package/dist/utils/validation.js +31 -0
- package/examples/basic-relay.json +48 -0
- package/examples/jack-monitor.json +69 -0
- package/examples/multi-user-monitor.json +129 -0
- package/locales/en-US/messages.json +63 -0
- 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,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,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
|
+
}
|