v0.7.42 - Fix ephemeral event storage and document monitoring system
This commit is contained in:
@@ -1,3 +1 @@
|
|||||||
src/embedded_web_content.c
|
src/embedded_web_content.c
|
||||||
src/dm_admin.c
|
|
||||||
src/dm_admin.h
|
|
||||||
65
README.md
65
README.md
@@ -195,6 +195,9 @@ All commands are sent as NIP-44 encrypted JSON arrays in the event content. The
|
|||||||
- `pow_min_difficulty`: Minimum proof-of-work difficulty
|
- `pow_min_difficulty`: Minimum proof-of-work difficulty
|
||||||
- `nip40_expiration_enabled`: Enable event expiration (`true`/`false`)
|
- `nip40_expiration_enabled`: Enable event expiration (`true`/`false`)
|
||||||
|
|
||||||
|
**Monitoring Settings:**
|
||||||
|
- `kind_24567_reporting_throttle_sec`: Minimum seconds between monitoring events (default: 5)
|
||||||
|
|
||||||
### Dynamic Configuration Updates
|
### Dynamic Configuration Updates
|
||||||
|
|
||||||
C-Relay supports **dynamic configuration updates** without requiring a restart for most settings. Configuration parameters are categorized as either **dynamic** (can be updated immediately) or **restart-required** (require relay restart to take effect).
|
C-Relay supports **dynamic configuration updates** without requiring a restart for most settings. Configuration parameters are categorized as either **dynamic** (can be updated immediately) or **restart-required** (require relay restart to take effect).
|
||||||
@@ -391,6 +394,68 @@ SELECT
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Real-time Monitoring System
|
||||||
|
|
||||||
|
C-Relay includes a subscription-based monitoring system that broadcasts real-time relay statistics using ephemeral events (kind 24567).
|
||||||
|
|
||||||
|
### Activation
|
||||||
|
|
||||||
|
The monitoring system activates automatically when clients subscribe to kind 24567 events:
|
||||||
|
|
||||||
|
```json
|
||||||
|
["REQ", "monitoring-sub", {"kinds": [24567]}]
|
||||||
|
```
|
||||||
|
|
||||||
|
For specific monitoring types, use d-tag filters:
|
||||||
|
|
||||||
|
```json
|
||||||
|
["REQ", "event-kinds-sub", {"kinds": [24567], "#d": ["event_kinds"]}]
|
||||||
|
["REQ", "time-stats-sub", {"kinds": [24567], "#d": ["time_stats"]}]
|
||||||
|
["REQ", "top-pubkeys-sub", {"kinds": [24567], "#d": ["top_pubkeys"]}]
|
||||||
|
```
|
||||||
|
|
||||||
|
When no subscriptions exist, monitoring is dormant to conserve resources.
|
||||||
|
|
||||||
|
### Monitoring Event Types
|
||||||
|
|
||||||
|
| Type | d Tag | Description |
|
||||||
|
|------|-------|-------------|
|
||||||
|
| Event Distribution | `event_kinds` | Event count by kind with percentages |
|
||||||
|
| Time Statistics | `time_stats` | Events in last 24h, 7d, 30d |
|
||||||
|
| Top Publishers | `top_pubkeys` | Top 10 pubkeys by event count |
|
||||||
|
| Active Subscriptions | `active_subscriptions` | Current subscription details (admin only) |
|
||||||
|
| Subscription Details | `subscription_details` | Detailed subscription info (admin only) |
|
||||||
|
| CPU Metrics | `cpu_metrics` | Process CPU and memory usage |
|
||||||
|
|
||||||
|
### Event Structure
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 24567,
|
||||||
|
"pubkey": "<relay_pubkey>",
|
||||||
|
"created_at": <timestamp>,
|
||||||
|
"content": "{\"data_type\":\"event_kinds\",\"timestamp\":1234567890,...}",
|
||||||
|
"tags": [
|
||||||
|
["d", "event_kinds"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
- `kind_24567_reporting_throttle_sec`: Minimum seconds between monitoring events (default: 5)
|
||||||
|
|
||||||
|
### Web Dashboard Integration
|
||||||
|
|
||||||
|
The built-in web dashboard (`/api/`) automatically subscribes to monitoring events and displays real-time statistics.
|
||||||
|
|
||||||
|
### Performance Considerations
|
||||||
|
|
||||||
|
- Monitoring events are ephemeral (not stored in database)
|
||||||
|
- Throttling prevents excessive event generation
|
||||||
|
- Automatic activation/deactivation based on subscriptions
|
||||||
|
- Minimal overhead when no clients are monitoring
|
||||||
|
|
||||||
## Direct Messaging Admin System
|
## Direct Messaging Admin System
|
||||||
|
|
||||||
In addition to the above admin API, c-relay allows the administrator to direct message the relay to get information or control some settings. As long as the administrator is signed in with any nostr client that allows sending nip-17 direct messages (DMs), they can control the relay.
|
In addition to the above admin API, c-relay allows the administrator to direct message the relay to get information or control some settings. As long as the administrator is signed in with any nostr client that allows sending nip-17 direct messages (DMs), they can control the relay.
|
||||||
|
|||||||
24
debug.log
Normal file
24
debug.log
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
=== NOSTR WebSocket Debug Log Started ===
|
||||||
|
[14:16:28.243] SEND localhost:8888: ["EVENT", {
|
||||||
|
"pubkey": "193279d1459ba1399aadb954422bf8595aa77367dccf482c682f5f208e435844",
|
||||||
|
"created_at": 1761499411,
|
||||||
|
"kind": 1059,
|
||||||
|
"tags": [["p", "4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"]],
|
||||||
|
"content": "AmWNi4P5J126kk69XH2o5mYvGj+69+Fjfr/nZx892I2z8edkwtp2IH7XAnPUqdGPu7x1xiZF1sNfr21VKThOhE54K/uQHLFydZN3acgUfX13sCeWhrvnQD0EvjvZC6QzW9DfFayYoYl+rEPYcra1/N68a+N1R7XnNcf1K/ZFh5Grcnln0H5YdXKRBhQI9aai4iFp1VGy2V0IR+6gDJGbJ7TbAbD3wgGWv1i77C03skH3RgzH+f2b7VBtm+vjKX6q7v6v8j3w1lRFE5Qh0Tqgedh3+UsnwqQta7OCzF9OyAVPK7EqKQBss5LzYSRUpcCE1vw5b7I7yeBFwU9WfnGLUW+uZxMJ2C3P4NBBrVO8UFIkBrPL2cqkoD5c8DgMLJjXGmc4EWfB4ZWb3KjbfLbgi6DVQ++cDjBbnCOPhX+/4qOnWq+gI28e/xk3cBvQtgUOkvWX3oGl3/Q33u4UGtxkFEXGfzdKHVDkR86kqf7RMZjIwTjLGpx4uov0cNmzj07hYEdoG/lJ4yA1v/GyF7viJdnnz3tE0hCZaViqSCev0rfUHWRDDMXJzJ9SS+OwpVswSG4NKvYsDhDM89BjhFs08HshTFdIh2AY45jR/16CsZM9JudH5BwqcX23wToYdZ+lrerOA0EkYb0DJUzGVe4lMpdJZoB8qXLHxMAKwKu0UEWEkeBnnZbvTGwCRbfGorxwPrnyqUCy9tzJx0GOLhRIzBmt6lki607VLDYjK97VIz0dff3fyWPAfy/yBlO2nHhVubUgpPaAjcaYNkO/iZwuP8oJkClWWmKwAQoNoxt+Ly2llrkz+Ne8oXMQdJSq416x6MLHo2JbKH8uwjx0yKG0oldLyWaz3A8OHYkJuxOi7HPVTlOOJrsjG4kMn2g97rVUXLs5v9F/StOjzxtiQWmCBtCsvK2LEEK/DzfavcJstEMxQztJjhiYRO3MJanL7lN2zu1ZHO149FJrgqGV6RQ8DDXf55yuabqHilBuUSDKpI0gl0+Efuor1my+L9J7MjJQ83aSwGizX7uXedMsGQRcvU3++Uvbw7sd2l67fb7IoYU04TPGZkIm120qwf7GAUpnDL7Lhulu/9LFMFs3UnGl9cLzY6EAJtDANHjMAoXbGbYclnoSiNW4yr3X9PBHO5o2YhIxfpTyEgLebJLOkzoziuCTpX8/MdhOhFtlIyo5B8Mbt5GDOHh4x1ZMKOl02J00Vvgui0hLw4Vri8Lz/ErPIRSlrEOB+8K5zPzJy/bD8XrOKlOwSbF5j9dsqs+8uCTC/v9YNQ0cC9wP7gVAxErQ3suJVeV7pzY+eGR051AcW7ppTs1gShhxDDaSaKdMlrkBdFDZcCJ+tomSgW56bOi45erpmk8Lcv4RrBzjBtq1hz+XSaTBAtEnGtHNH2uOn7KP/NNaD38dYkpb3N1VR3zuV67RcuPZeB+5WR9jhnLoSMGox2s=",
|
||||||
|
"id": "c6c18d902744fc0aaa4ca9172b3bcd0dde3fd7d943b41b2a39a16927ede67804",
|
||||||
|
"sig": "d67e0e914aa361c528510efd216548b6734a5fa68c46426571fbc87626bf19a9ec46e16883e7fad700f4fee5cfffd9bba03c3c08e57938fbca77a28b30a32bb7"
|
||||||
|
}]
|
||||||
|
[14:16:28.256] RECV localhost:8888: ["OK", "c6c18d902744fc0aaa4ca9172b3bcd0dde3fd7d943b41b2a39a16927ede67804", true, ""]
|
||||||
|
|
||||||
|
=== NOSTR WebSocket Debug Log Started ===
|
||||||
|
[15:01:18.592] SEND localhost:8888: ["EVENT", {
|
||||||
|
"pubkey": "ec9578ade9e74358ed35d8091d41bfa277e86d649614a8865e3725e38ebe5bc9",
|
||||||
|
"created_at": 1761502101,
|
||||||
|
"kind": 1059,
|
||||||
|
"tags": [["p", "4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"]],
|
||||||
|
"content": "AlgLnVwti8Dk2nu0e4bMrXeZiR/u+RPnA85kpts2svaFGfMByS4iap7xqdiSrXpSQPjQsix6jP9Qiy1a6rrvC6MutqTi3JfsMexLR61/ZKTK41sWTXNDTT3keH543vx3fVQH1mq+LgG4mjNzkPe0RqkYFvC8R0nxyAcCecHDxZUlmXQmAGiB5JB2GvstA4eoZLP1OI3fcLA3qaITLNRJOwRUoTYKqUkENHwz74CW0TnYDrKRZVe9zKNWQBLmtsgVoGd5CXNAVgXwmm2h0eCNIcRGnFqDHzpegpEGO+A7tvB0KJwlj4j/GmRgmnWO4pkrM2fmsTdlb5KNqe7NPuTVgYfdvld70zWpenp7jF/0psaEQEl8R7FbG2rNCv8fXtH+womvJQj4S0eUBxfvsUU1wWYmhusEzvyTfpV/nw0Er+pmAUZ2eGk7LEB2GMsJrkT+G5oohm0n+5c72iWJqW9A1eAzjR6Z21FkH4kAEJOl70fw9Xeig+s9rYk3GcKlMvj42zf7DepMXHPy62TbqUeclcm5W/semyasGP521GBuw152IN+dS67OVVmEvEJ89xhwiTeIty78enR4Gq7d1eNK+rqStdtJ7FN6kD/8gv4sFojUXyi0sIxzaSPrwI3ohOqbpEK1dTs6fmTUiyT/Buq++IhD9UwsZgz/kYpZfm1NVnWx+yTEv4I1H80FDxmMzbYnTHuIdRJFeh/NJRy9h+gXoZlZnteHkbwm1w2AejTkVnGs2Pz7aUZgC+1Za8fhtXq3Z9N3R8f8gtVFnnjRzApg5U89QrXUBS0R6F9dTqINk4qti94JWO4dYcPuudCutME5hfCYBoo+LHuRmdPKry7vSK1WgLYQsHuG+r313Ak8DhZYNbL+0d5UJ9kDFlFKaP3xLahSbEc/7u+AyuN68IyM1NwEehllxqVUsX8dsD4bZ2yW5rVjAQm9tT8Ypm73kJEb+DYVqT0WjFx88ee+HX89a9NgszWf1HE1KNQ9gWjn4eH6xbwrOkS4/v2O2tQoAd00vyPKAWly3Zlrz2cRnrSnxTZ5Lt7HtwAt6Err8MhD/w5rMLXHTBCMrroG1VfMo1OgL1YPafKDZmwVcHWacqtZiB0heRx742WipmTonqMjCOTNufdwxQcRPLLio0mtqiIrzgJqqIQenBXSa1jaG6Lvb5PCUKThbg4sSFfgssoUNKM7ytmBAe+PPmOVe11/gGaFWoQeUordbvmiCtzIPUYiKsuhfeK4I3jKvEofQU37hOam8ZxUczXvX6dgOOto002EWyCVfAzFgyey6wI+FGEbhXqlw7nB+azpqLQMJnHg7pfb1stXk3d8rjgVrRsVRJe/5KrXyZ5cd7ftJuJLxpTYmfFu6CKoUE0L5eRxXiwa16Pi0BehxOLaZteiTzttyfj+ClMKs2J/2/T1BVya1oGUW2Wg6ri/qS8oXv8bqiXBZ1/BwfI=",
|
||||||
|
"id": "ea5bb419a8efea8ee86bb8696406a70a0387a7d0ac6e60760026d1aea28b427f",
|
||||||
|
"sig": "0ffde3fd0d83c80693aa656668f2553807f8d474738ff3d9676090a5b8748a8e8e0c75a1d64963e4604046e18a806c4371a9cf2af2fd72f9db50f15bc78a4e25"
|
||||||
|
}]
|
||||||
|
[15:01:18.604] RECV localhost:8888: ["OK", "ea5bb419a8efea8ee86bb8696406a70a0387a7d0ac6e60760026d1aea28b427f", true, ""]
|
||||||
@@ -611,7 +611,14 @@ int store_event(cJSON* event) {
|
|||||||
|
|
||||||
// Classify event type
|
// Classify event type
|
||||||
event_type_t type = classify_event_kind((int)cJSON_GetNumberValue(kind));
|
event_type_t type = classify_event_kind((int)cJSON_GetNumberValue(kind));
|
||||||
|
|
||||||
|
// EPHEMERAL EVENTS (kinds 20000-29999) should NOT be stored
|
||||||
|
if (type == EVENT_TYPE_EPHEMERAL) {
|
||||||
|
DEBUG_LOG("Ephemeral event (kind %d) - broadcasting only, not storing",
|
||||||
|
(int)cJSON_GetNumberValue(kind));
|
||||||
|
return 0; // Success - event was handled but not stored
|
||||||
|
}
|
||||||
|
|
||||||
// Serialize tags to JSON (use empty array if no tags)
|
// Serialize tags to JSON (use empty array if no tags)
|
||||||
char* tags_json = NULL;
|
char* tags_json = NULL;
|
||||||
if (tags && cJSON_IsArray(tags)) {
|
if (tags && cJSON_IsArray(tags)) {
|
||||||
|
|||||||
@@ -10,10 +10,10 @@
|
|||||||
#define MAIN_H
|
#define MAIN_H
|
||||||
|
|
||||||
// Version information (auto-updated by build system)
|
// Version information (auto-updated by build system)
|
||||||
#define VERSION "v0.7.41"
|
#define VERSION "v0.7.42"
|
||||||
#define VERSION_MAJOR 0
|
#define VERSION_MAJOR 0
|
||||||
#define VERSION_MINOR 7
|
#define VERSION_MINOR 7
|
||||||
#define VERSION_PATCH 41
|
#define VERSION_PATCH 42
|
||||||
|
|
||||||
// Relay metadata (authoritative source for NIP-11 information)
|
// Relay metadata (authoritative source for NIP-11 information)
|
||||||
#define RELAY_NAME "C-Relay"
|
#define RELAY_NAME "C-Relay"
|
||||||
|
|||||||
@@ -93,16 +93,6 @@ FROM events\n\
|
|||||||
WHERE kind = 33334\n\
|
WHERE kind = 33334\n\
|
||||||
ORDER BY created_at DESC;\n\
|
ORDER BY created_at DESC;\n\
|
||||||
\n\
|
\n\
|
||||||
-- Optimization: Trigger for automatic cleanup of ephemeral events older than 1 hour\n\
|
|
||||||
CREATE TRIGGER cleanup_ephemeral_events\n\
|
|
||||||
AFTER INSERT ON events\n\
|
|
||||||
WHEN NEW.event_type = 'ephemeral'\n\
|
|
||||||
BEGIN\n\
|
|
||||||
DELETE FROM events \n\
|
|
||||||
WHERE event_type = 'ephemeral' \n\
|
|
||||||
AND first_seen < (strftime('%s', 'now') - 3600);\n\
|
|
||||||
END;\n\
|
|
||||||
\n\
|
|
||||||
-- Replaceable event handling trigger\n\
|
-- Replaceable event handling trigger\n\
|
||||||
CREATE TRIGGER handle_replaceable_events\n\
|
CREATE TRIGGER handle_replaceable_events\n\
|
||||||
AFTER INSERT ON events\n\
|
AFTER INSERT ON events\n\
|
||||||
|
|||||||
12
tests/debug.log
Normal file
12
tests/debug.log
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
=== NOSTR WebSocket Debug Log Started ===
|
||||||
|
[14:13:42.079] SEND localhost:8888: ["EVENT", {
|
||||||
|
"pubkey": "e74e808f64b82fe4671b92cdf83f6dd5f5f44dbcb67fbd0e044f34a6193e0994",
|
||||||
|
"created_at": 1761499244,
|
||||||
|
"kind": 1059,
|
||||||
|
"tags": [["p", "4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa"]],
|
||||||
|
"content": "ApTb8y2oD3/TtVCV73Szhgfh5ODlluGd5zjsH44g5BBwaGB1NshOJ/5kF/XN0TfYJKQBe07UTpnOYMZ4l2ppU6SrR8Tor+ZEiAF/kpCpa/x6LDDIvf4mueQicDKjOf8Y6oEbsxYjtFrpuSC0LLMgLaVhcZjAgVD0YQTo+8nHOzHZD5RBr305vdnrxIe4ubEficAHCpnKq9L3A46AIyb+aHjjTbSYmB061cf6hzLSnmdh5xeACExjhxwsX9ivSvqGYcDNsH1JCM8EYQyRX9xAPDBYM1yuS8PpadqMluOcqOd/FFYyjYNpFrardblPsjUzZTz/TDSLyrYFDUKNa7pWIhW1asc1ZaY0ry0AoWnbl/QyMxqBjDFXd3mJfWccYsOI/Yrx3sxbZdL+ayRlQeQuDk/M9rQkH8GN/5+GE1aN5I6eVl0F37Axc/lLuIt/AIpoTwZYAEi9j/BYGLP6sYkjUp0foz91QximOTgu8evynu+nfAv330HVkipTIGOjEZea7QNSK0Fylxs8fanHlmiqWGyfyBeoWpxGslHZVu6K9k7GC8ABEIdNRa8vlqlphPfWPCS70Lnq3LgeKOj1C3sNF9ST8g7pth/0FEZgXruzhpx/EyjsasNbdLZg3iX1QwRS0P4L341Flrztovt8npyP9ytTiukkYIQzXCX8XuWjiaUuzXiLkVazjh0Nl03ikKKu2+7nuaBB92geBjbGT76zZ6HeXBgcmC7dWn7pHhzqu+QTonZK0oCl427Fs0eXiYsILjxFFQkmk7OHXgdZF9jquNXloz5lgwY9S3xj4JyRwLN/9xfh16awxLZNEFvX10X97bXsmNMRUDrJJPkKMTSxZpvuTbd+Lx2iB++4NyGZibNa6nOWOJG9d2LwEzIcIHS0uQpEIPl7Ccz6+rmkVh9kLbB2rda2fYp9GCOcn6XbfaXZZXJM+HAQwPJgrtDiuQex0tEIcQcB9CYCN4ze9HCt1kb23TUgEDAipz/RqYP4dOCYmRZ7vaYk/irJ+iRDfnvPK0Id1TrSeo5kaVc7py2zWZRVdndpTM8RvW0SLwdldXDIv+ym/mS0L7bchoaYjoNeuTNKQ6AOoc0E7f4ySr65FUKYd2FTvIsP2Avsa3S+D0za30ensxr733l80AQlVmUPrhsgOzzjEuOW1hGlGus38X+CDDEuMSJnq3hvz/CxVtAk71Zkbyr5lc1BPi758Y4rlZFQnhaKYKv5nSFJc7GtDykv+1cwxNGC6AxGKprnYMDVxuAIFYBztFitdO5BsjWvvKzAbleszewtGfjE2NgltIJk+gQlTpWvLNxd3gvb+qHarfEv7BPnPfsKktDpEfuNMKXdJPANyACq5gXj854o/X8iO2iLm7JSdMhEQgIIyHNyLCCQdLDnqDWIfcdyIzAfRilSCwImt3CVJBGD7HoXRbwGRR3vgEBcoVPmsYzaU9vr62I=",
|
||||||
|
"id": "75c178ee47aac3ab9e984ddb85bdf9d8c68ade0d97e9cd86bb39e3110218a589",
|
||||||
|
"sig": "aba8382cc8d6ba6bba467109d2ddc19718732fe803d71e73fd2db62c1cbbb1b4527447240906e01755139067a71c75d8c03271826ca5d0226c818cb7fb495fe2"
|
||||||
|
}]
|
||||||
|
[14:13:42.083] RECV localhost:8888: ["OK", "75c178ee47aac3ab9e984ddb85bdf9d8c68ade0d97e9cd86bb39e3110218a589", true, ""]
|
||||||
BIN
tests/sendDM
Executable file
BIN
tests/sendDM
Executable file
Binary file not shown.
296
tests/sendDM.c
Normal file
296
tests/sendDM.c
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
/*
|
||||||
|
* NIP-17 Private Direct Messages - Command Line Application
|
||||||
|
*
|
||||||
|
* This example demonstrates how to send NIP-17 private direct messages
|
||||||
|
* using the Nostr Core Library.
|
||||||
|
*
|
||||||
|
* Usage:
|
||||||
|
* ./send_nip17_dm -r <recipient> -s <sender> [-R <relay>]... <message>
|
||||||
|
*
|
||||||
|
* Options:
|
||||||
|
* -r <recipient>: The recipient's public key (npub or hex)
|
||||||
|
* -s <sender>: The sender's private key (nsec or hex)
|
||||||
|
* -R <relay>: Relay URL to send to (can be specified multiple times)
|
||||||
|
* <message>: The message to send (must be the last argument)
|
||||||
|
*
|
||||||
|
* If no relays are specified, uses default relay.
|
||||||
|
* If no sender key is provided, uses a default test key.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* ./send_nip17_dm -r npub1example... -s nsec1test... -R wss://relay1.com "Hello from NIP-17!"
|
||||||
|
* ./send_nip17_dm -r 4f355bdcb7cc0af728ef3cceb9615d90684bb5b2ca5f859ab0f0b704075871aa -s aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa -R ws://localhost:8888 "config"
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define _GNU_SOURCE
|
||||||
|
#define _POSIX_C_SOURCE 200809L
|
||||||
|
|
||||||
|
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
|
||||||
|
// Default test private key (for demonstration - DO NOT USE IN PRODUCTION)
|
||||||
|
#define DEFAULT_SENDER_NSEC "nsec12kgt0dv2k2safv6s32w8f89z9uw27e68hjaa0d66c5xvk70ezpwqncd045"
|
||||||
|
|
||||||
|
// Default relay for sending DMs
|
||||||
|
#define DEFAULT_RELAY "wss://relay.laantungir.net"
|
||||||
|
|
||||||
|
// Progress callback for publishing
|
||||||
|
void publish_progress_callback(const char* relay_url, const char* status,
|
||||||
|
const char* message, int success_count,
|
||||||
|
int total_relays, int completed_relays, void* user_data) {
|
||||||
|
(void)user_data;
|
||||||
|
|
||||||
|
if (relay_url) {
|
||||||
|
printf("📡 [%s]: %s", relay_url, status);
|
||||||
|
if (message) {
|
||||||
|
printf(" - %s", message);
|
||||||
|
}
|
||||||
|
printf(" (%d/%d completed, %d successful)\n", completed_relays, total_relays, success_count);
|
||||||
|
} else {
|
||||||
|
printf("📡 PUBLISH COMPLETE: %d/%d successful\n", success_count, total_relays);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert npub or hex pubkey to hex format
|
||||||
|
*/
|
||||||
|
int convert_pubkey_to_hex(const char* input_pubkey, char* output_hex) {
|
||||||
|
// Check if it's already hex (64 characters)
|
||||||
|
if (strlen(input_pubkey) == 64) {
|
||||||
|
// Assume it's already hex
|
||||||
|
strcpy(output_hex, input_pubkey);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an npub (starts with "npub1")
|
||||||
|
if (strncmp(input_pubkey, "npub1", 5) == 0) {
|
||||||
|
// Convert npub to hex
|
||||||
|
unsigned char pubkey_bytes[32];
|
||||||
|
if (nostr_decode_npub(input_pubkey, pubkey_bytes) != 0) {
|
||||||
|
fprintf(stderr, "Error: Invalid npub format\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nostr_bytes_to_hex(pubkey_bytes, 32, output_hex);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Error: Public key must be 64-character hex or valid npub\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert nsec to private key bytes if needed
|
||||||
|
*/
|
||||||
|
int convert_nsec_to_private_key(const char* input_nsec, unsigned char* private_key) {
|
||||||
|
// Check if it's already hex (64 characters)
|
||||||
|
if (strlen(input_nsec) == 64) {
|
||||||
|
// Convert hex to bytes
|
||||||
|
if (nostr_hex_to_bytes(input_nsec, private_key, 32) != 0) {
|
||||||
|
fprintf(stderr, "Error: Invalid hex private key\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if it's an nsec (starts with "nsec1")
|
||||||
|
if (strncmp(input_nsec, "nsec1", 5) == 0) {
|
||||||
|
// Convert nsec directly to private key bytes
|
||||||
|
if (nostr_decode_nsec(input_nsec, private_key) != 0) {
|
||||||
|
fprintf(stderr, "Error: Invalid nsec format\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fprintf(stderr, "Error: Private key must be 64-character hex or valid nsec\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main function
|
||||||
|
*/
|
||||||
|
int main(int argc, char* argv[]) {
|
||||||
|
char* recipient_key = NULL;
|
||||||
|
char* sender_key = NULL;
|
||||||
|
char** relays = NULL;
|
||||||
|
int relay_count = 0;
|
||||||
|
char* message = NULL;
|
||||||
|
|
||||||
|
// Parse command line options
|
||||||
|
int opt;
|
||||||
|
while ((opt = getopt(argc, argv, "r:s:R:")) != -1) {
|
||||||
|
switch (opt) {
|
||||||
|
case 'r':
|
||||||
|
recipient_key = optarg;
|
||||||
|
break;
|
||||||
|
case 's':
|
||||||
|
sender_key = optarg;
|
||||||
|
break;
|
||||||
|
case 'R':
|
||||||
|
relays = realloc(relays, (relay_count + 1) * sizeof(char*));
|
||||||
|
relays[relay_count] = optarg;
|
||||||
|
relay_count++;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
fprintf(stderr, "Usage: %s -r <recipient> -s <sender> [-R <relay>]... <message>\n", argv[0]);
|
||||||
|
fprintf(stderr, "Options:\n");
|
||||||
|
fprintf(stderr, " -r <recipient>: The recipient's public key (npub or hex)\n");
|
||||||
|
fprintf(stderr, " -s <sender>: The sender's private key (nsec or hex)\n");
|
||||||
|
fprintf(stderr, " -R <relay>: Relay URL to send to (can be specified multiple times)\n");
|
||||||
|
fprintf(stderr, " <message>: The message to send (must be the last argument)\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for required arguments
|
||||||
|
if (!recipient_key) {
|
||||||
|
fprintf(stderr, "Error: Recipient key (-r) is required\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get message from remaining arguments
|
||||||
|
if (optind >= argc) {
|
||||||
|
fprintf(stderr, "Error: Message is required\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
message = argv[optind];
|
||||||
|
|
||||||
|
// Use default values if not provided
|
||||||
|
if (!sender_key) {
|
||||||
|
sender_key = DEFAULT_SENDER_NSEC;
|
||||||
|
}
|
||||||
|
if (relay_count == 0) {
|
||||||
|
relays = malloc(sizeof(char*));
|
||||||
|
relays[0] = DEFAULT_RELAY;
|
||||||
|
relay_count = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("🧪 NIP-17 Private Direct Message Sender\n");
|
||||||
|
printf("======================================\n\n");
|
||||||
|
|
||||||
|
// Initialize crypto
|
||||||
|
if (nostr_init() != NOSTR_SUCCESS) {
|
||||||
|
fprintf(stderr, "Failed to initialize crypto\n");
|
||||||
|
free(relays);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert recipient pubkey
|
||||||
|
char recipient_pubkey_hex[65];
|
||||||
|
if (convert_pubkey_to_hex(recipient_key, recipient_pubkey_hex) != 0) {
|
||||||
|
free(relays);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert sender private key
|
||||||
|
unsigned char sender_privkey[32];
|
||||||
|
if (convert_nsec_to_private_key(sender_key, sender_privkey) != 0) {
|
||||||
|
free(relays);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Derive sender public key for display
|
||||||
|
unsigned char sender_pubkey_bytes[32];
|
||||||
|
char sender_pubkey_hex[65];
|
||||||
|
if (nostr_ec_public_key_from_private_key(sender_privkey, sender_pubkey_bytes) != 0) {
|
||||||
|
fprintf(stderr, "Failed to derive sender public key\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
nostr_bytes_to_hex(sender_pubkey_bytes, 32, sender_pubkey_hex);
|
||||||
|
|
||||||
|
printf("📤 Sender: %s\n", sender_pubkey_hex);
|
||||||
|
printf("📥 Recipient: %s\n", recipient_pubkey_hex);
|
||||||
|
printf("💬 Message: %s\n", message);
|
||||||
|
printf("🌐 Relays: ");
|
||||||
|
for (int i = 0; i < relay_count; i++) {
|
||||||
|
printf("%s", relays[i]);
|
||||||
|
if (i < relay_count - 1) printf(", ");
|
||||||
|
}
|
||||||
|
printf("\n\n");
|
||||||
|
|
||||||
|
// Create DM event
|
||||||
|
printf("💬 Creating DM event...\n");
|
||||||
|
const char* recipient_pubkeys[] = {recipient_pubkey_hex};
|
||||||
|
cJSON* dm_event = nostr_nip17_create_chat_event(
|
||||||
|
message,
|
||||||
|
recipient_pubkeys,
|
||||||
|
1,
|
||||||
|
"NIP-17 CLI", // subject
|
||||||
|
NULL, // no reply
|
||||||
|
relays[0], // relay hint (use first relay)
|
||||||
|
sender_pubkey_hex
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!dm_event) {
|
||||||
|
fprintf(stderr, "Failed to create DM event\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("✅ Created DM event (kind 14)\n");
|
||||||
|
|
||||||
|
// Send DM (create gift wraps)
|
||||||
|
printf("🎁 Creating gift wraps...\n");
|
||||||
|
cJSON* gift_wraps[10]; // Max 10 gift wraps
|
||||||
|
int gift_wrap_count = nostr_nip17_send_dm(
|
||||||
|
dm_event,
|
||||||
|
recipient_pubkeys,
|
||||||
|
1,
|
||||||
|
sender_privkey,
|
||||||
|
gift_wraps,
|
||||||
|
10
|
||||||
|
);
|
||||||
|
|
||||||
|
cJSON_Delete(dm_event); // Original DM event no longer needed
|
||||||
|
|
||||||
|
if (gift_wrap_count <= 0) {
|
||||||
|
fprintf(stderr, "Failed to create gift wraps\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("✅ Created %d gift wrap(s)\n", gift_wrap_count);
|
||||||
|
|
||||||
|
// Publish the gift wrap to relays
|
||||||
|
printf("\n📤 Publishing gift wrap to %d relay(s)...\n", relay_count);
|
||||||
|
|
||||||
|
int success_count = 0;
|
||||||
|
publish_result_t* publish_results = synchronous_publish_event_with_progress(
|
||||||
|
(const char**)relays,
|
||||||
|
relay_count,
|
||||||
|
gift_wraps[0], // Send the first gift wrap
|
||||||
|
&success_count,
|
||||||
|
10, // 10 second timeout
|
||||||
|
publish_progress_callback,
|
||||||
|
NULL, // no user data
|
||||||
|
0, // NIP-42 disabled
|
||||||
|
NULL // no private key for auth
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!publish_results || success_count == 0) {
|
||||||
|
fprintf(stderr, "\n❌ Failed to publish gift wrap to any relay (success_count: %d/%d)\n", success_count, relay_count);
|
||||||
|
// Clean up gift wraps
|
||||||
|
for (int i = 0; i < gift_wrap_count; i++) {
|
||||||
|
cJSON_Delete(gift_wraps[i]);
|
||||||
|
}
|
||||||
|
if (publish_results) free(publish_results);
|
||||||
|
free(relays);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n✅ Successfully published NIP-17 DM to %d/%d relay(s)!\n", success_count, relay_count);
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
free(publish_results);
|
||||||
|
for (int i = 0; i < gift_wrap_count; i++) {
|
||||||
|
cJSON_Delete(gift_wraps[i]);
|
||||||
|
}
|
||||||
|
free(relays);
|
||||||
|
|
||||||
|
nostr_cleanup();
|
||||||
|
|
||||||
|
printf("\n🎉 DM sent successfully! The recipient can now decrypt it using their private key.\n");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user