v0.4.11 - Fixed nasty DM bug

This commit is contained in:
Your Name
2025-10-06 10:06:24 -04:00
parent d5350d7c30
commit f6d13d4318
11 changed files with 1376 additions and 753 deletions

View File

@@ -1,6 +1,8 @@
#define _GNU_SOURCE
#include "config.h"
#include "../nostr_core_lib/nostr_core/nostr_core.h"
#include "../nostr_core_lib/nostr_core/nip017.h"
#include "../nostr_core_lib/nostr_core/nip044.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -31,6 +33,24 @@ extern int handle_auth_rule_modification_unified(cJSON* event, char* error_messa
extern const char* get_first_tag_name(cJSON* event);
extern const char* get_tag_value(cJSON* event, const char* tag_name, int value_index);
// Forward declarations for config functions
extern const char* get_relay_pubkey_cached(void);
extern char* get_relay_private_key(void);
// Forward declarations for database functions
extern int store_event(cJSON* event);
// Forward declarations for stats generation
extern char* generate_stats_json(void);
// Forward declarations for admin event processing
extern int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
// Forward declarations for NIP-17 processing
int is_nip17_gift_wrap_for_relay(cJSON* event);
int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t error_size, struct lws* wsi);
cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message, size_t error_size, struct lws* wsi);
// ================================
// DIRECT MESSAGING ADMIN SYSTEM
// ================================
@@ -177,6 +197,239 @@ int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_mes
return result;
}
// Generate stats JSON from database queries
char* generate_stats_json(void) {
extern sqlite3* g_db;
if (!g_db) {
log_error("Database not available for stats generation");
return NULL;
}
log_info("Generating stats JSON from database");
// Build response with database statistics
cJSON* response = cJSON_CreateObject();
cJSON_AddStringToObject(response, "query_type", "stats_query");
cJSON_AddNumberToObject(response, "timestamp", (double)time(NULL));
// Get database file size
extern char g_database_path[512];
struct stat db_stat;
long long db_size = 0;
if (stat(g_database_path, &db_stat) == 0) {
db_size = db_stat.st_size;
}
cJSON_AddNumberToObject(response, "database_size_bytes", db_size);
// Query total events count
sqlite3_stmt* stmt;
if (sqlite3_prepare_v2(g_db, "SELECT COUNT(*) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON_AddNumberToObject(response, "total_events", sqlite3_column_int64(stmt, 0));
}
sqlite3_finalize(stmt);
}
// Query event kinds distribution
cJSON* event_kinds = cJSON_CreateArray();
if (sqlite3_prepare_v2(g_db, "SELECT kind, count, percentage FROM event_kinds_view ORDER BY count DESC", -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON* kind_obj = cJSON_CreateObject();
cJSON_AddNumberToObject(kind_obj, "kind", sqlite3_column_int(stmt, 0));
cJSON_AddNumberToObject(kind_obj, "count", sqlite3_column_int64(stmt, 1));
cJSON_AddNumberToObject(kind_obj, "percentage", sqlite3_column_double(stmt, 2));
cJSON_AddItemToArray(event_kinds, kind_obj);
}
sqlite3_finalize(stmt);
}
cJSON_AddItemToObject(response, "event_kinds", event_kinds);
// Query time-based statistics
cJSON* time_stats = cJSON_CreateObject();
if (sqlite3_prepare_v2(g_db, "SELECT period, total_events FROM time_stats_view", -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
const char* period = (const char*)sqlite3_column_text(stmt, 0);
sqlite3_int64 count = sqlite3_column_int64(stmt, 1);
if (strcmp(period, "total") == 0) {
cJSON_AddNumberToObject(time_stats, "total", count);
} else if (strcmp(period, "24h") == 0) {
cJSON_AddNumberToObject(time_stats, "last_24h", count);
} else if (strcmp(period, "7d") == 0) {
cJSON_AddNumberToObject(time_stats, "last_7d", count);
} else if (strcmp(period, "30d") == 0) {
cJSON_AddNumberToObject(time_stats, "last_30d", count);
}
}
sqlite3_finalize(stmt);
}
cJSON_AddItemToObject(response, "time_stats", time_stats);
// Query top pubkeys
cJSON* top_pubkeys = cJSON_CreateArray();
if (sqlite3_prepare_v2(g_db, "SELECT pubkey, event_count, percentage FROM top_pubkeys_view ORDER BY event_count DESC LIMIT 10", -1, &stmt, NULL) == SQLITE_OK) {
while (sqlite3_step(stmt) == SQLITE_ROW) {
cJSON* pubkey_obj = cJSON_CreateObject();
const char* pubkey = (const char*)sqlite3_column_text(stmt, 0);
cJSON_AddStringToObject(pubkey_obj, "pubkey", pubkey ? pubkey : "");
cJSON_AddNumberToObject(pubkey_obj, "event_count", sqlite3_column_int64(stmt, 1));
cJSON_AddNumberToObject(pubkey_obj, "percentage", sqlite3_column_double(stmt, 2));
cJSON_AddItemToArray(top_pubkeys, pubkey_obj);
}
sqlite3_finalize(stmt);
}
cJSON_AddItemToObject(response, "top_pubkeys", top_pubkeys);
// Get database creation timestamp (oldest event)
if (sqlite3_prepare_v2(g_db, "SELECT MIN(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
sqlite3_int64 oldest_timestamp = sqlite3_column_int64(stmt, 0);
if (oldest_timestamp > 0) {
cJSON_AddNumberToObject(response, "database_created_at", (double)oldest_timestamp);
}
}
sqlite3_finalize(stmt);
}
// Get latest event timestamp
if (sqlite3_prepare_v2(g_db, "SELECT MAX(created_at) FROM events", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
sqlite3_int64 latest_timestamp = sqlite3_column_int64(stmt, 0);
if (latest_timestamp > 0) {
cJSON_AddNumberToObject(response, "latest_event_at", (double)latest_timestamp);
}
}
sqlite3_finalize(stmt);
}
// Convert to JSON string
char* json_string = cJSON_Print(response);
cJSON_Delete(response);
if (json_string) {
log_success("Stats JSON generated successfully");
} else {
log_error("Failed to generate stats JSON");
}
return json_string;
}
// Main NIP-17 processing function
cJSON* process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message, size_t error_size, struct lws* wsi) {
if (!gift_wrap_event || !error_message) {
return NULL;
}
// Step 1: Validate it's addressed to us
if (!is_nip17_gift_wrap_for_relay(gift_wrap_event)) {
strncpy(error_message, "NIP-17: Event is not a valid gift wrap for this relay", error_size - 1);
return NULL;
}
// Step 2: Get relay private key for decryption
char* relay_privkey_hex = get_relay_private_key();
if (!relay_privkey_hex) {
strncpy(error_message, "NIP-17: Could not get relay private key for decryption", error_size - 1);
return NULL;
}
// Convert hex private key to bytes
unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
log_error("NIP-17: Failed to convert relay private key from hex");
free(relay_privkey_hex);
strncpy(error_message, "NIP-17: Failed to convert relay private key", error_size - 1);
return NULL;
}
free(relay_privkey_hex);
// Step 3: Decrypt and parse inner event using library function
log_info("NIP-17: Attempting to decrypt gift wrap with nostr_nip17_receive_dm");
cJSON* inner_dm = nostr_nip17_receive_dm(gift_wrap_event, relay_privkey);
if (!inner_dm) {
log_error("NIP-17: nostr_nip17_receive_dm returned NULL");
// Debug: Print the gift wrap event
char* gift_wrap_debug = cJSON_Print(gift_wrap_event);
if (gift_wrap_debug) {
char debug_msg[1024];
snprintf(debug_msg, sizeof(debug_msg), "NIP-17: Gift wrap event: %.500s", gift_wrap_debug);
log_error(debug_msg);
free(gift_wrap_debug);
}
// Debug: Check if private key is valid
char privkey_hex[65];
for (int i = 0; i < 32; i++) {
sprintf(privkey_hex + (i * 2), "%02x", relay_privkey[i]);
}
privkey_hex[64] = '\0';
char privkey_msg[128];
snprintf(privkey_msg, sizeof(privkey_msg), "NIP-17: Using relay private key: %.16s...", privkey_hex);
log_info(privkey_msg);
strncpy(error_message, "NIP-17: Failed to decrypt and parse inner DM event", error_size - 1);
return NULL;
}
log_info("NIP-17: Successfully decrypted gift wrap");
// Step 4: Process admin command
int result = process_nip17_admin_command(inner_dm, error_message, error_size, wsi);
// Step 5: Create response if command was processed successfully
if (result == 0) {
// Get sender pubkey for response from the decrypted DM event
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(inner_dm, "pubkey");
if (sender_pubkey_obj && cJSON_IsString(sender_pubkey_obj)) {
const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);
// Create success response using library function
char response_content[1024];
snprintf(response_content, sizeof(response_content),
"[\"command_processed\", \"success\", \"%s\"]", "NIP-17 admin command executed");
// Get relay pubkey for creating DM event
const char* relay_pubkey = get_relay_pubkey_cached();
if (relay_pubkey) {
cJSON* success_dm = nostr_nip17_create_chat_event(
response_content, // message content
(const char**)&sender_pubkey, // recipient pubkeys
1, // num recipients
NULL, // subject (optional)
NULL, // reply_to_event_id (optional)
NULL, // reply_relay_url (optional)
relay_pubkey // sender pubkey
);
if (success_dm) {
cJSON* success_gift_wraps[1];
int send_result = nostr_nip17_send_dm(
success_dm, // dm_event
(const char**)&sender_pubkey, // recipient_pubkeys
1, // num_recipients
relay_privkey, // sender_private_key
success_gift_wraps, // gift_wraps_out
1 // max_gift_wraps
);
cJSON_Delete(success_dm);
if (send_result == 1 && success_gift_wraps[0]) {
// Store the response gift wrap in database
store_event(success_gift_wraps[0]);
// Return the response event for broadcasting
cJSON_Delete(inner_dm);
return success_gift_wraps[0];
}
}
}
}
}
cJSON_Delete(inner_dm);
return NULL;
}
// Check if decrypted content is a direct command array (DM system)
// Returns 1 if it's a valid command array, 0 if it should fall back to inner event parsing
int is_dm_command_array(const char* decrypted_content) {
@@ -199,4 +452,512 @@ int is_dm_command_array(const char* decrypted_content) {
cJSON_Delete(parsed);
return is_array;
}
// =============================================================================
// NIP-17 GIFT WRAP PROCESSING FUNCTIONS
// =============================================================================
// Check if an event is a NIP-17 gift wrap addressed to this relay
int is_nip17_gift_wrap_for_relay(cJSON* event) {
if (!event || !cJSON_IsObject(event)) {
return 0;
}
// Check kind
cJSON* kind_obj = cJSON_GetObjectItem(event, "kind");
if (!kind_obj || !cJSON_IsNumber(kind_obj) || (int)cJSON_GetNumberValue(kind_obj) != 1059) {
return 0;
}
// Check tags for "p" tag with relay pubkey
cJSON* tags = cJSON_GetObjectItem(event, "tags");
if (!tags || !cJSON_IsArray(tags)) {
return 0;
}
const char* relay_pubkey = get_relay_pubkey_cached();
if (!relay_pubkey) {
log_error("NIP-17: Could not get relay pubkey for validation");
return 0;
}
// Look for "p" tag with relay pubkey
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
cJSON* tag_value = cJSON_GetArrayItem(tag, 1);
if (tag_name && cJSON_IsString(tag_name) &&
strcmp(cJSON_GetStringValue(tag_name), "p") == 0 &&
tag_value && cJSON_IsString(tag_value) &&
strcmp(cJSON_GetStringValue(tag_value), relay_pubkey) == 0) {
return 1; // Found matching p tag
}
}
}
return 0; // No matching p tag found
}
// Process NIP-17 admin command from decrypted DM content
int process_nip17_admin_command(cJSON* dm_event, char* error_message, size_t error_size, struct lws* wsi) {
if (!dm_event || !error_message) {
return -1;
}
// Extract content from DM
cJSON* content_obj = cJSON_GetObjectItem(dm_event, "content");
if (!content_obj || !cJSON_IsString(content_obj)) {
strncpy(error_message, "NIP-17: DM missing content", error_size - 1);
return -1;
}
const char* dm_content = cJSON_GetStringValue(content_obj);
// Check if sender is admin before processing any commands
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
log_info("NIP-17: DM missing sender pubkey, treating as user DM");
return 0; // Not an error, just treat as user DM
}
const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);
// Check if sender is admin
const char* admin_pubkey = get_admin_pubkey_cached();
int is_admin = admin_pubkey && strlen(admin_pubkey) > 0 && strcmp(sender_pubkey, admin_pubkey) == 0;
log_info("NIP-17: Processing admin command from DM content");
char log_msg[256];
snprintf(log_msg, sizeof(log_msg), "NIP-17: Received DM content: '%.50s'%s", dm_content, strlen(dm_content) > 50 ? "..." : "");
log_info(log_msg);
// Parse DM content as JSON array of commands
cJSON* command_array = cJSON_Parse(dm_content);
if (!command_array || !cJSON_IsArray(command_array)) {
// If content is not a JSON array, check for plain text commands
if (is_admin) {
// Convert content to lowercase for case-insensitive matching
char content_lower[256];
size_t content_len = strlen(dm_content);
size_t copy_len = content_len < sizeof(content_lower) - 1 ? content_len : sizeof(content_lower) - 1;
memcpy(content_lower, dm_content, copy_len);
content_lower[copy_len] = '\0';
// Convert to lowercase
for (size_t i = 0; i < copy_len; i++) {
if (content_lower[i] >= 'A' && content_lower[i] <= 'Z') {
content_lower[i] = content_lower[i] + 32;
}
}
// Check for stats commands
if (strstr(content_lower, "stats") != NULL || strstr(content_lower, "statistics") != NULL) {
log_info("NIP-17: Recognized plain text 'stats' command from admin");
log_info("NIP-17: Action: Generate and send relay statistics");
// Generate stats JSON
char* stats_json = generate_stats_json();
if (!stats_json) {
log_error("NIP-17: Failed to generate stats for plain text command");
return -1;
}
// Get relay keys for signing
const char* relay_pubkey = get_relay_pubkey_cached();
char* relay_privkey_hex = get_relay_private_key();
if (!relay_pubkey || !relay_privkey_hex) {
free(stats_json);
log_error("NIP-17: Could not get relay keys for stats response");
return -1;
}
// Convert relay private key to bytes
unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
free(stats_json);
free(relay_privkey_hex);
log_error("NIP-17: Failed to convert relay private key for stats response");
return -1;
}
free(relay_privkey_hex);
// Create DM response event using library function
cJSON* dm_response = nostr_nip17_create_chat_event(
stats_json, // message content
(const char**)&sender_pubkey, // recipient pubkeys
1, // num recipients
NULL, // subject (optional)
NULL, // reply_to_event_id (optional)
NULL, // reply_relay_url (optional)
relay_pubkey // sender pubkey
);
free(stats_json);
if (!dm_response) {
log_error("NIP-17: Failed to create DM response event for stats");
return -1;
}
// Create and sign gift wrap using library function
cJSON* gift_wraps[1];
int send_result = nostr_nip17_send_dm(
dm_response, // dm_event
(const char**)&sender_pubkey, // recipient_pubkeys
1, // num_recipients
relay_privkey, // sender_private_key
gift_wraps, // gift_wraps_out
1 // max_gift_wraps
);
cJSON_Delete(dm_response);
if (send_result != 1 || !gift_wraps[0]) {
log_error("NIP-17: Failed to create and sign response gift wrap for stats");
return -1;
}
// Fix the p tag in the gift wrap - library function may use wrong pubkey
cJSON* gift_wrap_tags = cJSON_GetObjectItem(gift_wraps[0], "tags");
if (gift_wrap_tags && cJSON_IsArray(gift_wrap_tags)) {
// Find and replace the p tag with the correct user pubkey
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, gift_wrap_tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
if (tag_name && cJSON_IsString(tag_name) &&
strcmp(cJSON_GetStringValue(tag_name), "p") == 0) {
// Replace the p tag value with the correct user pubkey
cJSON_ReplaceItemInArray(tag, 1, cJSON_CreateString(sender_pubkey));
log_info("NIP-17: Fixed p tag in stats response gift wrap");
break;
}
}
}
}
// Debug print to show the response event
char* response_debug = cJSON_Print(gift_wraps[0]);
if (response_debug) {
log_info("DM Admin: Response event created");
printf(" Response event: %s\n", response_debug);
free(response_debug);
}
// Debug: Print event before storing
char* debug_before_store = cJSON_Print(gift_wraps[0]);
if (debug_before_store) {
log_info("DEBUG EVENT: Before storing in database");
printf(" Event: %s\n", debug_before_store);
free(debug_before_store);
}
// Store the gift wrap in database
int store_result = store_event(gift_wraps[0]);
cJSON_Delete(gift_wraps[0]);
if (store_result != 0) {
log_error("NIP-17: Failed to store response gift wrap for stats");
return -1;
}
log_success("NIP-17: Stats command processed successfully, response sent");
return 0;
}
// Check for config commands
else if (strstr(content_lower, "config") != NULL || strstr(content_lower, "configuration") != NULL) {
log_info("NIP-17: Recognized plain text 'config' command from admin");
log_info("NIP-17: Action: Generate and send relay configuration");
// Get relay pubkey for config response
const char* relay_pubkey = get_relay_pubkey_cached();
// Generate config JSON - for now, use a simple config summary
cJSON* config_response = cJSON_CreateObject();
cJSON_AddStringToObject(config_response, "command", "config");
cJSON_AddStringToObject(config_response, "relay_pubkey", relay_pubkey ? relay_pubkey : "unknown");
// Add some basic config values
const char* port = get_config_value("relay_port");
const char* nip42_auth = get_config_bool("nip42_auth_required_events", 0) ? "enabled" : "disabled";
const char* nip42_sub = get_config_bool("nip42_auth_required_subscriptions", 0) ? "enabled" : "disabled";
cJSON_AddStringToObject(config_response, "relay_port", port ? port : "8888");
cJSON_AddStringToObject(config_response, "nip42_auth_events", nip42_auth);
cJSON_AddStringToObject(config_response, "nip42_auth_subscriptions", nip42_sub);
char* config_json = cJSON_Print(config_response);
cJSON_Delete(config_response);
if (!config_json) {
log_error("NIP-17: Failed to generate config JSON for plain text command");
return -1;
}
// Get relay keys for signing
char* relay_privkey_hex = get_relay_private_key();
if (!relay_privkey_hex) {
free(config_json);
log_error("NIP-17: Could not get relay private key for config response");
return -1;
}
// Convert relay private key to bytes
unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
free(config_json);
free(relay_privkey_hex);
log_error("NIP-17: Failed to convert relay private key for config response");
return -1;
}
free(relay_privkey_hex);
// Create DM response event using library function
cJSON* dm_response = nostr_nip17_create_chat_event(
config_json, // message content
(const char**)&sender_pubkey, // recipient pubkeys
1, // num recipients
NULL, // subject (optional)
NULL, // reply_to_event_id (optional)
NULL, // reply_relay_url (optional)
relay_pubkey // sender pubkey (already declared above)
);
free(config_json);
if (!dm_response) {
log_error("NIP-17: Failed to create DM response event for config");
return -1;
}
// Create and sign gift wrap using library function
cJSON* gift_wraps[1];
int send_result = nostr_nip17_send_dm(
dm_response, // dm_event
(const char**)&sender_pubkey, // recipient_pubkeys
1, // num_recipients
relay_privkey, // sender_private_key
gift_wraps, // gift_wraps_out
1 // max_gift_wraps
);
cJSON_Delete(dm_response);
if (send_result != 1 || !gift_wraps[0]) {
log_error("NIP-17: Failed to create and sign response gift wrap for config");
return -1;
}
// Fix the p tag in the gift wrap - library function may use wrong pubkey
cJSON* gift_wrap_tags = cJSON_GetObjectItem(gift_wraps[0], "tags");
if (gift_wrap_tags && cJSON_IsArray(gift_wrap_tags)) {
// Find and replace the p tag with the correct user pubkey
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, gift_wrap_tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
if (tag_name && cJSON_IsString(tag_name) &&
strcmp(cJSON_GetStringValue(tag_name), "p") == 0) {
// Replace the p tag value with the correct user pubkey
cJSON_ReplaceItemInArray(tag, 1, cJSON_CreateString(sender_pubkey));
log_info("NIP-17: Fixed p tag in config response gift wrap");
break;
}
}
}
}
// Debug: Print event after p tag fix
char* debug_after_fix = cJSON_Print(gift_wraps[0]);
if (debug_after_fix) {
log_info("DEBUG EVENT: After p tag fix");
printf(" Event: %s\n", debug_after_fix);
free(debug_after_fix);
}
// Debug print to show the response event
char* response_debug = cJSON_Print(gift_wraps[0]);
if (response_debug) {
log_info("DM Admin: Response event created");
printf(" Response event: %s\n", response_debug);
free(response_debug);
}
// Store the gift wrap in database
int store_result = store_event(gift_wraps[0]);
cJSON_Delete(gift_wraps[0]);
if (store_result != 0) {
log_error("NIP-17: Failed to store response gift wrap for config");
return -1;
}
log_success("NIP-17: Config command processed successfully, response sent");
return 0;
}
else {
log_info("NIP-17: Plain text content from admin not recognized as command, treating as user DM");
return 0; // Admin sent unrecognized plain text, treat as user DM
}
} else {
// Not admin, treat as user DM
log_info("NIP-17: Content is not JSON array and sender is not admin, treating as user DM");
return 0;
}
}
// Check if this is a "stats" command
if (cJSON_GetArraySize(command_array) > 0) {
cJSON* first_item = cJSON_GetArrayItem(command_array, 0);
if (cJSON_IsString(first_item) && strcmp(cJSON_GetStringValue(first_item), "stats") == 0) {
log_info("NIP-17: Processing 'stats' command directly");
// Generate stats JSON
char* stats_json = generate_stats_json();
if (!stats_json) {
cJSON_Delete(command_array);
strncpy(error_message, "NIP-17: Failed to generate stats", error_size - 1);
return -1;
}
// Get sender pubkey for response
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) {
free(stats_json);
cJSON_Delete(command_array);
strncpy(error_message, "NIP-17: DM missing sender pubkey", error_size - 1);
return -1;
}
const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj);
// Get relay keys for signing
const char* relay_pubkey = get_relay_pubkey_cached();
char* relay_privkey_hex = get_relay_private_key();
if (!relay_pubkey || !relay_privkey_hex) {
free(stats_json);
cJSON_Delete(command_array);
if (relay_privkey_hex) free(relay_privkey_hex);
strncpy(error_message, "NIP-17: Could not get relay keys", error_size - 1);
return -1;
}
// Convert relay private key to bytes
unsigned char relay_privkey[32];
if (nostr_hex_to_bytes(relay_privkey_hex, relay_privkey, sizeof(relay_privkey)) != 0) {
free(stats_json);
free(relay_privkey_hex);
cJSON_Delete(command_array);
strncpy(error_message, "NIP-17: Failed to convert relay private key", error_size - 1);
return -1;
}
free(relay_privkey_hex);
// Create DM response event using library function
cJSON* dm_response = nostr_nip17_create_chat_event(
stats_json, // message content
(const char**)&sender_pubkey, // recipient pubkeys
1, // num recipients
NULL, // subject (optional)
NULL, // reply_to_event_id (optional)
NULL, // reply_relay_url (optional)
relay_pubkey // sender pubkey
);
free(stats_json);
if (!dm_response) {
cJSON_Delete(command_array);
strncpy(error_message, "NIP-17: Failed to create DM response event", error_size - 1);
return -1;
}
// Create and sign gift wrap using library function
cJSON* gift_wraps[1];
int send_result = nostr_nip17_send_dm(
dm_response, // dm_event
(const char**)&sender_pubkey, // recipient_pubkeys
1, // num_recipients
relay_privkey, // sender_private_key
gift_wraps, // gift_wraps_out
1 // max_gift_wraps
);
cJSON_Delete(dm_response);
if (send_result != 1 || !gift_wraps[0]) {
cJSON_Delete(command_array);
strncpy(error_message, "NIP-17: Failed to create and sign response gift wrap", error_size - 1);
return -1;
}
// Fix the p tag in the gift wrap - library function may use wrong pubkey
cJSON* gift_wrap_tags = cJSON_GetObjectItem(gift_wraps[0], "tags");
if (gift_wrap_tags && cJSON_IsArray(gift_wrap_tags)) {
// Find and replace the p tag with the correct user pubkey
cJSON* tag = NULL;
cJSON_ArrayForEach(tag, gift_wrap_tags) {
if (cJSON_IsArray(tag) && cJSON_GetArraySize(tag) >= 2) {
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
if (tag_name && cJSON_IsString(tag_name) &&
strcmp(cJSON_GetStringValue(tag_name), "p") == 0) {
// Replace the p tag value with the correct user pubkey
cJSON_ReplaceItemInArray(tag, 1, cJSON_CreateString(sender_pubkey));
log_info("NIP-17: Fixed p tag in JSON array stats response gift wrap");
break;
}
}
}
}
// Debug print to show the response event
char* response_debug = cJSON_Print(gift_wraps[0]);
if (response_debug) {
log_info("DM Admin: Response event created");
printf(" Response event: %s\n", response_debug);
free(response_debug);
}
// Store the gift wrap in database
int store_result = store_event(gift_wraps[0]);
cJSON_Delete(gift_wraps[0]);
if (store_result != 0) {
cJSON_Delete(command_array);
strncpy(error_message, "NIP-17: Failed to store response gift wrap", error_size - 1);
return -1;
}
cJSON_Delete(command_array);
log_success("NIP-17: Stats command processed successfully");
return 0;
}
}
// For other commands, delegate to existing admin processing
// Create a synthetic kind 23456 event with the DM content
cJSON* synthetic_event = cJSON_CreateObject();
cJSON_AddNumberToObject(synthetic_event, "kind", 23456);
cJSON_AddStringToObject(synthetic_event, "content", dm_content);
// Copy pubkey from DM
cJSON* pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
if (pubkey_obj && cJSON_IsString(pubkey_obj)) {
cJSON_AddStringToObject(synthetic_event, "pubkey", cJSON_GetStringValue(pubkey_obj));
}
// Copy tags from DM
cJSON* tags = cJSON_GetObjectItem(dm_event, "tags");
if (tags) {
cJSON_AddItemToObject(synthetic_event, "tags", cJSON_Duplicate(tags, 1));
}
// Process as regular admin event
int result = process_admin_event_in_config(synthetic_event, error_message, error_size, wsi);
cJSON_Delete(synthetic_event);
cJSON_Delete(command_array);
return result;
}