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
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Node, NodeDef } from 'node-red';
|
|
2
|
+
export interface NostrRelayConfigNode extends Node {
|
|
3
|
+
name: string;
|
|
4
|
+
url: string;
|
|
5
|
+
proxy?: boolean;
|
|
6
|
+
proxyUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface NostrRelayConfig extends NodeDef {
|
|
9
|
+
name: string;
|
|
10
|
+
url: string;
|
|
11
|
+
proxy?: boolean;
|
|
12
|
+
proxyUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export interface NostrFilterConfig extends NodeDef {
|
|
15
|
+
name: string;
|
|
16
|
+
relay: string;
|
|
17
|
+
kinds?: number[];
|
|
18
|
+
authors?: string[];
|
|
19
|
+
tags?: {
|
|
20
|
+
[key: string]: string[];
|
|
21
|
+
};
|
|
22
|
+
since?: number;
|
|
23
|
+
until?: number;
|
|
24
|
+
limit?: number;
|
|
25
|
+
}
|
|
26
|
+
export interface NostrRelayNodeConfig extends NodeDef {
|
|
27
|
+
name: string;
|
|
28
|
+
relay: string;
|
|
29
|
+
kind?: number;
|
|
30
|
+
content?: string;
|
|
31
|
+
tags?: {
|
|
32
|
+
[key: string]: string[];
|
|
33
|
+
};
|
|
34
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class NIP19 {
|
|
2
|
+
static npubEncode(pubkey: string): string;
|
|
3
|
+
static npubDecode(npub: string): string;
|
|
4
|
+
static nsecEncode(privkey: string): string;
|
|
5
|
+
static nsecDecode(nsec: string): string;
|
|
6
|
+
static noteEncode(id: string): string;
|
|
7
|
+
static noteDecode(note: string): string;
|
|
8
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NIP19 = void 0;
|
|
4
|
+
const bech32_1 = require("bech32");
|
|
5
|
+
class NIP19 {
|
|
6
|
+
static npubEncode(pubkey) {
|
|
7
|
+
const words = bech32_1.bech32.toWords(Buffer.from(pubkey, 'hex'));
|
|
8
|
+
return bech32_1.bech32.encode('npub', words);
|
|
9
|
+
}
|
|
10
|
+
static npubDecode(npub) {
|
|
11
|
+
const { words } = bech32_1.bech32.decode(npub);
|
|
12
|
+
const bytes = Buffer.from(bech32_1.bech32.fromWords(words));
|
|
13
|
+
return bytes.toString('hex');
|
|
14
|
+
}
|
|
15
|
+
static nsecEncode(privkey) {
|
|
16
|
+
const words = bech32_1.bech32.toWords(Buffer.from(privkey, 'hex'));
|
|
17
|
+
return bech32_1.bech32.encode('nsec', words);
|
|
18
|
+
}
|
|
19
|
+
static nsecDecode(nsec) {
|
|
20
|
+
const { words } = bech32_1.bech32.decode(nsec);
|
|
21
|
+
const bytes = Buffer.from(bech32_1.bech32.fromWords(words));
|
|
22
|
+
return bytes.toString('hex');
|
|
23
|
+
}
|
|
24
|
+
static noteEncode(id) {
|
|
25
|
+
const words = bech32_1.bech32.toWords(Buffer.from(id, 'hex'));
|
|
26
|
+
return bech32_1.bech32.encode('note', words);
|
|
27
|
+
}
|
|
28
|
+
static noteDecode(note) {
|
|
29
|
+
const { words } = bech32_1.bech32.decode(note);
|
|
30
|
+
const bytes = Buffer.from(bech32_1.bech32.fromWords(words));
|
|
31
|
+
return bytes.toString('hex');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
exports.NIP19 = NIP19;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare class Validation {
|
|
2
|
+
static isValidHexString(str: string): boolean;
|
|
3
|
+
static isValidPubkey(pubkey: string): boolean;
|
|
4
|
+
static isValidEventId(id: string): boolean;
|
|
5
|
+
static isValidSignature(signature: string): boolean;
|
|
6
|
+
static isValidRelayUrl(url: string): boolean;
|
|
7
|
+
static isValidTimestamp(timestamp: number): boolean;
|
|
8
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Validation = void 0;
|
|
4
|
+
class Validation {
|
|
5
|
+
static isValidHexString(str) {
|
|
6
|
+
return /^[0-9a-f]+$/i.test(str);
|
|
7
|
+
}
|
|
8
|
+
static isValidPubkey(pubkey) {
|
|
9
|
+
return this.isValidHexString(pubkey) && pubkey.length === 64;
|
|
10
|
+
}
|
|
11
|
+
static isValidEventId(id) {
|
|
12
|
+
return this.isValidHexString(id) && id.length === 64;
|
|
13
|
+
}
|
|
14
|
+
static isValidSignature(signature) {
|
|
15
|
+
return this.isValidHexString(signature) && signature.length === 128;
|
|
16
|
+
}
|
|
17
|
+
static isValidRelayUrl(url) {
|
|
18
|
+
try {
|
|
19
|
+
const parsed = new URL(url);
|
|
20
|
+
return parsed.protocol === 'wss:' || parsed.protocol === 'ws:';
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
static isValidTimestamp(timestamp) {
|
|
27
|
+
const now = Math.floor(Date.now() / 1000);
|
|
28
|
+
return timestamp > 0 && timestamp <= now;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
exports.Validation = Validation;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "basic-nostr-flow",
|
|
3
|
+
"label": "Basic Nostr Flow",
|
|
4
|
+
"nodes": [
|
|
5
|
+
{
|
|
6
|
+
"id": "relay1",
|
|
7
|
+
"type": "nostr-relay",
|
|
8
|
+
"name": "Damus Relay",
|
|
9
|
+
"relayUrl": "wss://relay.damus.io",
|
|
10
|
+
"x": 380,
|
|
11
|
+
"y": 120,
|
|
12
|
+
"wires": [["output1"]]
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"id": "input1",
|
|
16
|
+
"type": "inject",
|
|
17
|
+
"name": "Subscribe to Events",
|
|
18
|
+
"props": [
|
|
19
|
+
{
|
|
20
|
+
"p": "payload",
|
|
21
|
+
"v": "[{\"kinds\":[1],\"limit\":5}]",
|
|
22
|
+
"vt": "json"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"p": "type",
|
|
26
|
+
"v": "subscribe",
|
|
27
|
+
"vt": "str"
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
"repeat": "",
|
|
31
|
+
"crontab": "",
|
|
32
|
+
"once": false,
|
|
33
|
+
"x": 180,
|
|
34
|
+
"y": 120,
|
|
35
|
+
"wires": [["relay1"]]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"id": "output1",
|
|
39
|
+
"type": "debug",
|
|
40
|
+
"name": "Debug Output",
|
|
41
|
+
"active": true,
|
|
42
|
+
"complete": "true",
|
|
43
|
+
"x": 580,
|
|
44
|
+
"y": 120,
|
|
45
|
+
"wires": []
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Monitor Jack's Posts",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"id": "relay-config",
|
|
6
|
+
"type": "nostr-relay-config",
|
|
7
|
+
"name": "Main Relays",
|
|
8
|
+
"relay1": "wss://relay.damus.io",
|
|
9
|
+
"relay2": "wss://nos.lol",
|
|
10
|
+
"relay3": "wss://relay.nostr.band",
|
|
11
|
+
"pingInterval": 30
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "jack-monitor",
|
|
15
|
+
"type": "nostr-relay",
|
|
16
|
+
"name": "Jack's Posts",
|
|
17
|
+
"relayConfig": "relay-config",
|
|
18
|
+
"npub": "npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8yz5tc68qysh7j4xz",
|
|
19
|
+
"eventKinds": [1],
|
|
20
|
+
"x": 280,
|
|
21
|
+
"y": 120,
|
|
22
|
+
"wires": [["format-post"]]
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "format-post",
|
|
26
|
+
"type": "function",
|
|
27
|
+
"name": "Format Post",
|
|
28
|
+
"func": "const event = msg.payload;\n\n// Format the timestamp\nconst date = new Date(event.created_at * 1000);\nconst timeStr = date.toLocaleString();\n\n// Format the content (handle markdown, links, etc)\nlet content = event.content;\n\n// Extract any URLs\nconst urls = content.match(/https?:\\/\\/[^\\s]+/g) || [];\n\n// Create a nicely formatted message\nmsg.payload = {\n time: timeStr,\n content: content,\ urls: urls,\n id: event.id,\n replyTo: event.tags.find(t => t[0] === 'e')?.[1]\n};\n\n// Add a topic for easy filtering\nmsg.topic = 'jack_post';\n\nreturn msg;",
|
|
29
|
+
"outputs": 1,
|
|
30
|
+
"x": 480,
|
|
31
|
+
"y": 120,
|
|
32
|
+
"wires": [["debug", "dashboard"]]
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "debug",
|
|
36
|
+
"type": "debug",
|
|
37
|
+
"name": "Debug Output",
|
|
38
|
+
"active": true,
|
|
39
|
+
"tosidebar": true,
|
|
40
|
+
"console": false,
|
|
41
|
+
"tostatus": false,
|
|
42
|
+
"complete": "payload",
|
|
43
|
+
"targetType": "msg",
|
|
44
|
+
"statusVal": "",
|
|
45
|
+
"statusType": "auto",
|
|
46
|
+
"x": 670,
|
|
47
|
+
"y": 80,
|
|
48
|
+
"wires": []
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "dashboard",
|
|
52
|
+
"type": "ui_template",
|
|
53
|
+
"name": "Posts Dashboard",
|
|
54
|
+
"group": "Posts",
|
|
55
|
+
"order": 1,
|
|
56
|
+
"width": "12",
|
|
57
|
+
"height": "8",
|
|
58
|
+
"format": "<div ng-repeat=\"post in msg.payload\" class=\"post-card\">\n <div class=\"post-time\">{{post.time}}</div>\n <div class=\"post-content\">{{post.content}}</div>\n <div class=\"post-links\" ng-if=\"post.urls.length > 0\">\n <div ng-repeat=\"url in post.urls\">\n <a href=\"{{url}}\" target=\"_blank\">{{url}}</a>\n </div>\n </div>\n</div>",
|
|
59
|
+
"storeOutMessages": true,
|
|
60
|
+
"fwdInMessages": true,
|
|
61
|
+
"resendOnRefresh": true,
|
|
62
|
+
"templateScope": "local",
|
|
63
|
+
"className": "",
|
|
64
|
+
"x": 670,
|
|
65
|
+
"y": 160,
|
|
66
|
+
"wires": [[]]
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "Multi-User Nostr Monitor",
|
|
3
|
+
"nodes": [
|
|
4
|
+
{
|
|
5
|
+
"id": "relay-config",
|
|
6
|
+
"type": "nostr-relay-config",
|
|
7
|
+
"name": "Main Relays",
|
|
8
|
+
"relay1": "wss://relay.damus.io",
|
|
9
|
+
"relay2": "wss://nos.lol",
|
|
10
|
+
"relay3": "wss://relay.nostr.band",
|
|
11
|
+
"pingInterval": 30
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "npub-list",
|
|
15
|
+
"type": "function",
|
|
16
|
+
"name": "NPub List",
|
|
17
|
+
"func": "// List of NPubs to monitor (example list - replace with actual NPubs)\nconst npubs = [\n 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8yz5tc68qysh7j4xz', // Jack\n 'npub1qny3tkh0acurzla8x3zy4nhrjz5zd8l9sy9jys09umwng00manysew95gx', // Snowden\n 'npub1xtscya34g58tk0z605fvr788k263gsu6cy9x0mhnm87echrgufzsevkk5s' // Saylor\n];\n\n// Send each NPub to a different output\nnpubs.forEach((npub, index) => {\n node.send({ payload: npub, topic: `user_${index}` });\n});\n",
|
|
18
|
+
"outputs": 21,
|
|
19
|
+
"x": 130,
|
|
20
|
+
"y": 120,
|
|
21
|
+
"wires": [
|
|
22
|
+
["monitor-1"], ["monitor-2"], ["monitor-3"],
|
|
23
|
+
["monitor-4"], ["monitor-5"], ["monitor-6"],
|
|
24
|
+
["monitor-7"], ["monitor-8"], ["monitor-9"],
|
|
25
|
+
["monitor-10"], ["monitor-11"], ["monitor-12"],
|
|
26
|
+
["monitor-13"], ["monitor-14"], ["monitor-15"],
|
|
27
|
+
["monitor-16"], ["monitor-17"], ["monitor-18"],
|
|
28
|
+
["monitor-19"], ["monitor-20"], ["monitor-21"]
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": "monitor-1",
|
|
33
|
+
"type": "nostr-relay",
|
|
34
|
+
"name": "User 1",
|
|
35
|
+
"relayConfig": "relay-config",
|
|
36
|
+
"x": 280,
|
|
37
|
+
"y": 120,
|
|
38
|
+
"wires": [["event-router"]]
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "event-router",
|
|
42
|
+
"type": "function",
|
|
43
|
+
"name": "Event Router",
|
|
44
|
+
"func": "// Route events based on kind\nconst event = msg.payload;\n\nswitch(event.kind) {\n case 1: // Text notes\n return [msg, null, null];\n case 6: // Reposts\n return [null, msg, null];\n case 7: // Reactions\n return [null, null, msg];\n default:\n return [null, null, null];\n}",
|
|
45
|
+
"outputs": 3,
|
|
46
|
+
"x": 480,
|
|
47
|
+
"y": 120,
|
|
48
|
+
"wires": [
|
|
49
|
+
["text-filter"],
|
|
50
|
+
["repost-filter"],
|
|
51
|
+
["reaction-filter"]
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "text-filter",
|
|
56
|
+
"type": "nostr-filter",
|
|
57
|
+
"name": "Text Notes",
|
|
58
|
+
"filterType": "kind",
|
|
59
|
+
"eventKinds": [1],
|
|
60
|
+
"x": 680,
|
|
61
|
+
"y": 80,
|
|
62
|
+
"wires": [["format-text"]]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "repost-filter",
|
|
66
|
+
"type": "nostr-filter",
|
|
67
|
+
"name": "Reposts",
|
|
68
|
+
"filterType": "kind",
|
|
69
|
+
"eventKinds": [6],
|
|
70
|
+
"x": 680,
|
|
71
|
+
"y": 120,
|
|
72
|
+
"wires": [["format-repost"]]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"id": "reaction-filter",
|
|
76
|
+
"type": "nostr-filter",
|
|
77
|
+
"name": "Reactions",
|
|
78
|
+
"filterType": "kind",
|
|
79
|
+
"eventKinds": [7],
|
|
80
|
+
"x": 680,
|
|
81
|
+
"y": 160,
|
|
82
|
+
"wires": [["format-reaction"]]
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"id": "format-text",
|
|
86
|
+
"type": "function",
|
|
87
|
+
"name": "Format Text Note",
|
|
88
|
+
"func": "const event = msg.payload;\n\nconst formatted = {\n type: 'text',\n time: new Date(event.created_at * 1000).toLocaleString(),\n content: event.content,\n author: event.pubkey,\n id: event.id,\n urls: event.content.match(/https?:\\/\\/[^\\s]+/g) || [],\n tags: event.tags\n};\n\nmsg.payload = formatted;\nreturn msg;",
|
|
89
|
+
"x": 880,
|
|
90
|
+
"y": 80,
|
|
91
|
+
"wires": [["dashboard"]]
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "format-repost",
|
|
95
|
+
"type": "function",
|
|
96
|
+
"name": "Format Repost",
|
|
97
|
+
"func": "const event = msg.payload;\n\nconst formatted = {\n type: 'repost',\n time: new Date(event.created_at * 1000).toLocaleString(),\n content: event.content,\n author: event.pubkey,\n originalPost: event.tags.find(t => t[0] === 'e')?.[1],\n id: event.id\n};\n\nmsg.payload = formatted;\nreturn msg;",
|
|
98
|
+
"x": 880,
|
|
99
|
+
"y": 120,
|
|
100
|
+
"wires": [["dashboard"]]
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "format-reaction",
|
|
104
|
+
"type": "function",
|
|
105
|
+
"name": "Format Reaction",
|
|
106
|
+
"func": "const event = msg.payload;\n\nconst formatted = {\n type: 'reaction',\n time: new Date(event.created_at * 1000).toLocaleString(),\n content: event.content,\n author: event.pubkey,\n targetPost: event.tags.find(t => t[0] === 'e')?.[1],\n id: event.id\n};\n\nmsg.payload = formatted;\nreturn msg;",
|
|
107
|
+
"x": 880,
|
|
108
|
+
"y": 160,
|
|
109
|
+
"wires": [["dashboard"]]
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"id": "dashboard",
|
|
113
|
+
"type": "ui_template",
|
|
114
|
+
"name": "Interactive Dashboard",
|
|
115
|
+
"group": "Nostr Monitor",
|
|
116
|
+
"order": 1,
|
|
117
|
+
"width": "24",
|
|
118
|
+
"height": "12",
|
|
119
|
+
"format": "<div class=\"nostr-feed\">\n <div ng-repeat=\"event in msg.payload | limitTo:50\" \n class=\"event-card\" \n ng-class=\"event.type\">\n <div class=\"event-header\">\n <span class=\"event-time\">{{event.time}}</span>\n <span class=\"event-type\">{{event.type}}</span>\n </div>\n \n <div class=\"event-content\" ng-if=\"event.type === 'text'\">\n {{event.content}}\n <div class=\"event-urls\" ng-if=\"event.urls.length > 0\">\n <a ng-repeat=\"url in event.urls\" \n href=\"{{url}}\" \n target=\"_blank\">{{url}}</a>\n </div>\n </div>\n \n <div class=\"event-content\" ng-if=\"event.type === 'repost'\">\n Reposted: {{event.originalPost}}\n <div ng-if=\"event.content\">\n Comment: {{event.content}}\n </div>\n </div>\n \n <div class=\"event-content\" ng-if=\"event.type === 'reaction'\">\n Reacted to: {{event.targetPost}}\n <div class=\"reaction-content\">\n {{event.content}}\n </div>\n </div>\n \n <div class=\"event-footer\">\n <span class=\"event-id\">{{event.id}}</span>\n </div>\n </div>\n</div>\n\n<style>\n.nostr-feed {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n padding: 1rem;\n}\n\n.event-card {\n border: 1px solid #ddd;\n border-radius: 8px;\n padding: 1rem;\n background: white;\n box-shadow: 0 2px 4px rgba(0,0,0,0.1);\n}\n\n.event-card.text { border-left: 4px solid #2196F3; }\n.event-card.repost { border-left: 4px solid #4CAF50; }\n.event-card.reaction { border-left: 4px solid #FF9800; }\n\n.event-header {\n display: flex;\n justify-content: space-between;\n margin-bottom: 0.5rem;\n color: #666;\n}\n\n.event-content {\n margin: 1rem 0;\n white-space: pre-wrap;\n}\n\n.event-urls {\n margin-top: 0.5rem;\n}\n\n.event-urls a {\n display: block;\n color: #2196F3;\n margin: 0.25rem 0;\n}\n\n.event-footer {\n font-size: 0.8rem;\n color: #999;\n margin-top: 0.5rem;\n}\n</style>",
|
|
120
|
+
"storeOutMessages": true,
|
|
121
|
+
"fwdInMessages": true,
|
|
122
|
+
"resendOnRefresh": true,
|
|
123
|
+
"templateScope": "local",
|
|
124
|
+
"x": 1080,
|
|
125
|
+
"y": 120,
|
|
126
|
+
"wires": [[]]
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nostr": {
|
|
3
|
+
"relay-config": {
|
|
4
|
+
"label": {
|
|
5
|
+
"name": "Name",
|
|
6
|
+
"relay": "Relay URL",
|
|
7
|
+
"privateKey": "Private Key"
|
|
8
|
+
},
|
|
9
|
+
"placeholder": {
|
|
10
|
+
"name": "My Nostr Relay",
|
|
11
|
+
"relay": "wss://relay.damus.io",
|
|
12
|
+
"privateKey": "Enter your private key (hex format)"
|
|
13
|
+
},
|
|
14
|
+
"errors": {
|
|
15
|
+
"invalid-private-key": "Invalid private key format",
|
|
16
|
+
"connection-failed": "Failed to connect to relay",
|
|
17
|
+
"websocket-error": "WebSocket error occurred"
|
|
18
|
+
},
|
|
19
|
+
"status": {
|
|
20
|
+
"connected": "Connected to relay",
|
|
21
|
+
"disconnected": "Disconnected from relay",
|
|
22
|
+
"connecting": "Connecting to relay..."
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"relay": {
|
|
26
|
+
"label": {
|
|
27
|
+
"name": "Name",
|
|
28
|
+
"config": "Relay Config",
|
|
29
|
+
"filter": "Event Filter"
|
|
30
|
+
},
|
|
31
|
+
"placeholder": {
|
|
32
|
+
"name": "My Nostr Node",
|
|
33
|
+
"filter": "Event filter (JSON)"
|
|
34
|
+
},
|
|
35
|
+
"errors": {
|
|
36
|
+
"invalid-filter": "Invalid filter format",
|
|
37
|
+
"subscription-error": "Subscription error"
|
|
38
|
+
},
|
|
39
|
+
"status": {
|
|
40
|
+
"subscribed": "Subscribed to events",
|
|
41
|
+
"unsubscribed": "Unsubscribed from events"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"filter": {
|
|
45
|
+
"label": {
|
|
46
|
+
"name": "Name",
|
|
47
|
+
"authors": "Authors",
|
|
48
|
+
"kinds": "Event Kinds",
|
|
49
|
+
"since": "Since",
|
|
50
|
+
"until": "Until",
|
|
51
|
+
"limit": "Limit"
|
|
52
|
+
},
|
|
53
|
+
"placeholder": {
|
|
54
|
+
"name": "My Filter",
|
|
55
|
+
"authors": "Comma-separated list of pubkeys",
|
|
56
|
+
"kinds": "Comma-separated list of event kinds",
|
|
57
|
+
"since": "Unix timestamp",
|
|
58
|
+
"until": "Unix timestamp",
|
|
59
|
+
"limit": "Maximum number of events"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "node-red-contrib-nostr",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Node-RED nodes for seamless Nostr protocol integration. Features robust WebSocket handling, event filtering, and NPUB-based routing. Built with TypeScript for type safety and extensive testing. Perfect for Nostr automation flows.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "vveerrgg",
|
|
7
|
+
"url": "https://github.com/vveerrgg"
|
|
8
|
+
},
|
|
9
|
+
"type": "commonjs",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"postbuild": "for dir in nostr-filter nostr-relay nostr-relay-config nostr-npub-filter; do mkdir -p dist/nodes/$dir && cp src/nodes/$dir/$dir.html dist/nodes/$dir/; done && cp -r src/nodes/*/icons dist/nodes/ && cp -r locales dist/ && cp -r examples dist/ && cp package.json dist/",
|
|
13
|
+
"prepare": "npm run build",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest watch",
|
|
16
|
+
"test:coverage": "vitest run --coverage",
|
|
17
|
+
"test:ui": "vitest --ui",
|
|
18
|
+
"dev": "tsc -w",
|
|
19
|
+
"lint": "eslint . --ext .ts",
|
|
20
|
+
"lint:fix": "eslint . --ext .ts --fix"
|
|
21
|
+
},
|
|
22
|
+
"main": "dist/nodes/index.js",
|
|
23
|
+
"files": [
|
|
24
|
+
"dist/**/*.js",
|
|
25
|
+
"dist/**/*.d.ts",
|
|
26
|
+
"dist/**/*.html",
|
|
27
|
+
"dist/**/icons/*",
|
|
28
|
+
"locales/**/*",
|
|
29
|
+
"examples/**/*"
|
|
30
|
+
],
|
|
31
|
+
"keywords": [
|
|
32
|
+
"node-red",
|
|
33
|
+
"nostr",
|
|
34
|
+
"websocket",
|
|
35
|
+
"relay",
|
|
36
|
+
"filter",
|
|
37
|
+
"npub",
|
|
38
|
+
"network",
|
|
39
|
+
"protocol"
|
|
40
|
+
],
|
|
41
|
+
"node-red": {
|
|
42
|
+
"version": ">=2.0.0",
|
|
43
|
+
"nodes": {
|
|
44
|
+
"nostr-relay-config": "dist/nodes/nostr-relay-config/nostr-relay-config.js",
|
|
45
|
+
"nostr-filter": "dist/nodes/nostr-filter/nostr-filter.js",
|
|
46
|
+
"nostr-npub-filter": "dist/nodes/nostr-npub-filter/nostr-npub-filter.js"
|
|
47
|
+
},
|
|
48
|
+
"examples": {
|
|
49
|
+
"Monitor Jack's Posts": "examples/jack-monitor.json",
|
|
50
|
+
"Track Multiple Users": "examples/multi-user-track.json"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@noble/hashes": "^1.3.2",
|
|
55
|
+
"@noble/secp256k1": "^2.0.0",
|
|
56
|
+
"bech32": "^2.0.0",
|
|
57
|
+
"nostr-tools": "^1.17.0",
|
|
58
|
+
"nostr-websocket-utils": "^0.2.5",
|
|
59
|
+
"ws": "^8.16.0"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/node": "^20.4.5",
|
|
63
|
+
"@types/node-red": "^1.3.1",
|
|
64
|
+
"@types/ws": "^8.5.5",
|
|
65
|
+
"@typescript-eslint/eslint-plugin": "^6.2.0",
|
|
66
|
+
"@typescript-eslint/parser": "^6.2.0",
|
|
67
|
+
"@vitest/coverage-v8": "^1.1.3",
|
|
68
|
+
"@vitest/ui": "^1.1.3",
|
|
69
|
+
"eslint": "^8.45.0",
|
|
70
|
+
"node-red": "^4.0.8",
|
|
71
|
+
"node-red-node-test-helper": "^0.3.3",
|
|
72
|
+
"typescript": "^5.1.6",
|
|
73
|
+
"vitest": "^1.1.3"
|
|
74
|
+
},
|
|
75
|
+
"engines": {
|
|
76
|
+
"node": ">=14.0.0"
|
|
77
|
+
},
|
|
78
|
+
"repository": {
|
|
79
|
+
"type": "git",
|
|
80
|
+
"url": "git+https://github.com/HumanJavaEnterprise/node-red-contrib-nostr.git"
|
|
81
|
+
},
|
|
82
|
+
"bugs": {
|
|
83
|
+
"url": "https://github.com/HumanJavaEnterprise/node-red-contrib-nostr/issues"
|
|
84
|
+
},
|
|
85
|
+
"homepage": "https://github.com/HumanJavaEnterprise/node-red-contrib-nostr#readme"
|
|
86
|
+
}
|