// Note to assistants. dm_admin is only for functions relating to direct messaging #define _GNU_SOURCE #include "config.h" #include "debug.h" #include "api.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 #include #include #include #include #include #include #include // External database connection (from main.c) extern sqlite3* g_db; // Forward declarations for unified handlers extern int handle_auth_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi); extern int handle_config_query_unified(cJSON* event, const char* query_type, char* error_message, size_t error_size, struct lws* wsi); extern int handle_config_set_unified(cJSON* event, const char* config_key, const char* config_value, char* error_message, size_t error_size, struct lws* wsi); extern int handle_config_update_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi); extern int handle_system_command_unified(cJSON* event, const char* command, char* error_message, size_t error_size, struct lws* wsi); extern int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi); extern int handle_auth_rule_modification_unified(cJSON* event, char* error_message, size_t error_size, struct lws* wsi); // Forward declarations for tag parsing utilities 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 char* get_relay_private_key(void); extern const char* get_config_value(const char* key); extern int get_config_bool(const char* key, int default_value); // Forward declarations for database functions extern int store_event(cJSON* event); extern int broadcast_event_to_subscriptions(cJSON* event); // Forward declarations for stats generation (moved to api.c) extern char* generate_stats_json(void); extern char* generate_stats_text(void); extern char* generate_config_text(void); // Forward declarations for NIP-17 response sender (moved to api.c) extern int send_nip17_response(const char* sender_pubkey, const char* response_content, char* error_message, size_t error_size); // Forward declarations for config change system (moved to api.c) extern int handle_config_confirmation(const char* admin_pubkey, const char* response); extern int process_config_change_request(const char* admin_pubkey, const char* message); // Forward declarations for config change system (moved to api.c) #include "api.h" // 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); // Forward declarations for API functions (moved from dm_admin.c) extern int send_admin_response(const char* sender_pubkey, const char* response_content, const char* request_id, char* error_message, size_t error_size, struct lws* wsi); extern int validate_sql_query(const char* query, char* error_message, size_t error_size); extern char* execute_sql_query(const char* query, const char* request_id, char* error_message, size_t error_size); extern int handle_sql_query_unified(cJSON* event, const char* query, char* error_message, size_t error_size, struct lws* wsi); // ================================ // DIRECT MESSAGING ADMIN SYSTEM // ================================ // Process direct command arrays (DM control system) // This handles commands sent as direct JSON arrays, not wrapped in inner events // Note: create_relay_event is NOT supported via DMs - use Kind 23456 events only int process_dm_admin_command(cJSON* command_array, cJSON* event, char* error_message, size_t error_size, struct lws* wsi) { if (!command_array || !cJSON_IsArray(command_array) || !event) { DEBUG_ERROR("DM Admin: Invalid command array or event"); snprintf(error_message, error_size, "invalid: null command array or event"); return -1; } int array_size = cJSON_GetArraySize(command_array); if (array_size < 1) { DEBUG_ERROR("DM Admin: Empty command array"); snprintf(error_message, error_size, "invalid: empty command array"); return -1; } // Get the command type from the first element cJSON* command_item = cJSON_GetArrayItem(command_array, 0); if (!command_item || !cJSON_IsString(command_item)) { DEBUG_ERROR("DM Admin: First element is not a string command"); snprintf(error_message, error_size, "invalid: command must be a string"); return -1; } const char* command_type = cJSON_GetStringValue(command_item); // Create synthetic tags from the command array for unified handler compatibility cJSON* synthetic_tags = cJSON_CreateArray(); // Add the command as the first tag cJSON* command_tag = cJSON_CreateArray(); cJSON_AddItemToArray(command_tag, cJSON_CreateString(command_type)); // Add remaining array elements as tag parameters for (int i = 1; i < array_size; i++) { cJSON* param = cJSON_GetArrayItem(command_array, i); if (param) { if (cJSON_IsString(param)) { cJSON_AddItemToArray(command_tag, cJSON_CreateString(cJSON_GetStringValue(param))); } else { // Convert non-string parameters to strings for tag compatibility char* param_str = cJSON_Print(param); if (param_str) { // Remove quotes from JSON string representation if (param_str[0] == '"' && param_str[strlen(param_str)-1] == '"') { param_str[strlen(param_str)-1] = '\0'; cJSON_AddItemToArray(command_tag, cJSON_CreateString(param_str + 1)); } else { cJSON_AddItemToArray(command_tag, cJSON_CreateString(param_str)); } free(param_str); } } } } cJSON_AddItemToArray(synthetic_tags, command_tag); // Add existing event tags cJSON* existing_tags = cJSON_GetObjectItem(event, "tags"); if (existing_tags && cJSON_IsArray(existing_tags)) { cJSON* tag = NULL; cJSON_ArrayForEach(tag, existing_tags) { cJSON_AddItemToArray(synthetic_tags, cJSON_Duplicate(tag, 1)); } } // Temporarily replace event tags with synthetic tags cJSON_ReplaceItemInObject(event, "tags", synthetic_tags); // Route to appropriate handler based on command type int result = -1; if (strcmp(command_type, "auth_query") == 0) { const char* query_type = get_tag_value(event, "auth_query", 1); if (!query_type) { DEBUG_ERROR("DM Admin: Missing auth_query type parameter"); snprintf(error_message, error_size, "invalid: missing auth_query type"); } else { result = handle_auth_query_unified(event, query_type, error_message, error_size, wsi); } } else if (strcmp(command_type, "config_query") == 0) { const char* query_type = get_tag_value(event, "config_query", 1); if (!query_type) { DEBUG_ERROR("DM Admin: Missing config_query type parameter"); snprintf(error_message, error_size, "invalid: missing config_query type"); } else { result = handle_config_query_unified(event, query_type, error_message, error_size, wsi); } } else if (strcmp(command_type, "config_set") == 0) { const char* config_key = get_tag_value(event, "config_set", 1); const char* config_value = get_tag_value(event, "config_set", 2); if (!config_key || !config_value) { DEBUG_ERROR("DM Admin: Missing config_set parameters"); snprintf(error_message, error_size, "invalid: missing config_set key or value"); } else { result = handle_config_set_unified(event, config_key, config_value, error_message, error_size, wsi); } } else if (strcmp(command_type, "config_update") == 0) { result = handle_config_update_unified(event, error_message, error_size, wsi); } else if (strcmp(command_type, "system_command") == 0) { const char* command = get_tag_value(event, "system_command", 1); if (!command) { DEBUG_ERROR("DM Admin: Missing system_command type parameter"); snprintf(error_message, error_size, "invalid: missing system_command type"); } else { result = handle_system_command_unified(event, command, error_message, error_size, wsi); } } else if (strcmp(command_type, "stats_query") == 0) { result = handle_stats_query_unified(event, error_message, error_size, wsi); } else if (strcmp(command_type, "whitelist") == 0 || strcmp(command_type, "blacklist") == 0) { result = handle_auth_rule_modification_unified(event, error_message, error_size, wsi); } else if (strcmp(command_type, "sql_query") == 0) { const char* query = get_tag_value(event, "sql_query", 1); if (!query) { DEBUG_ERROR("DM Admin: Missing sql_query parameter"); snprintf(error_message, error_size, "invalid: missing SQL query"); } else { result = handle_sql_query_unified(event, query, error_message, error_size, wsi); } } else { DEBUG_ERROR("DM Admin: Unknown command type"); printf(" Unknown command: %s\n", command_type); snprintf(error_message, error_size, "invalid: unknown DM command type '%s'", command_type); } if (result != 0) { DEBUG_ERROR("DM Admin: Command processing failed"); } return result; } // 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; } DEBUG_INFO("DM_ADMIN: Received potential NIP-17 gift wrap event for processing"); // Step 1: Validate it's addressed to us if (!is_nip17_gift_wrap_for_relay(gift_wrap_event)) { DEBUG_INFO("DM_ADMIN: Event is not a valid gift wrap for this relay - rejecting"); strncpy(error_message, "NIP-17: Event is not a valid gift wrap for this relay", error_size - 1); return NULL; } DEBUG_INFO("DM_ADMIN: Valid NIP-17 gift wrap confirmed for this relay"); // Step 2: Get relay private key for decryption char* relay_privkey_hex = get_relay_private_key(); if (!relay_privkey_hex) { DEBUG_INFO("DM_ADMIN: Could not get relay private key for decryption"); strncpy(error_message, "NIP-17: Could not get relay private key for decryption", error_size - 1); return NULL; } DEBUG_INFO("DM_ADMIN: Retrieved relay private key for decryption"); // 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) { DEBUG_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); DEBUG_INFO("DM_ADMIN: Converted relay private key to bytes successfully"); // Step 3: Decrypt and parse inner event using library function DEBUG_INFO("DM_ADMIN: Attempting to decrypt NIP-17 gift wrap using nostr_nip17_receive_dm"); cJSON* inner_dm = nostr_nip17_receive_dm(gift_wrap_event, relay_privkey); if (!inner_dm) { DEBUG_INFO("DM_ADMIN: nostr_nip17_receive_dm returned NULL - decryption failed"); // 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); DEBUG_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'; DEBUG_INFO("DM_ADMIN: NIP-17 decryption failed - returning error"); strncpy(error_message, "NIP-17: Failed to decrypt and parse inner DM event", error_size - 1); return NULL; } DEBUG_INFO("DM_ADMIN: Successfully decrypted NIP-17 gift wrap, processing inner DM"); // Step 4: Process admin command DEBUG_INFO("DM_ADMIN: Processing decrypted admin command"); int result = process_nip17_admin_command(inner_dm, error_message, error_size, wsi); DEBUG_INFO("DM_ADMIN: Admin command processing completed with result: %d", result); // Step 5: For plain text commands (stats/config), the response is already handled // Only create a generic response for other command types that don't handle their own responses if (result == 0) { // Extract content to check if it's a plain text command cJSON* content_obj = cJSON_GetObjectItem(inner_dm, "content"); if (content_obj && cJSON_IsString(content_obj)) { const char* dm_content = cJSON_GetStringValue(content_obj); // Check if it's a plain text command that already handled its response 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; } } // If it's a plain text stats or config command, don't create additional response if (strstr(content_lower, "stats") != NULL || strstr(content_lower, "statistics") != NULL || strstr(content_lower, "config") != NULL || strstr(content_lower, "configuration") != NULL) { cJSON_Delete(inner_dm); return NULL; // No additional response needed } // Check if it's a JSON array command that might be stats cJSON* command_array = cJSON_Parse(dm_content); if (command_array && cJSON_IsArray(command_array) && 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) { cJSON_Delete(command_array); cJSON_Delete(inner_dm); return NULL; // No additional response needed } cJSON_Delete(command_array); } } } else if (result > 0) { // Command was handled and response was sent, don't create generic response cJSON_Delete(inner_dm); return NULL; // 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_config_value("relay_pubkey"); 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]; // Get timestamp delay configuration long max_delay_sec = get_config_int("nip59_timestamp_max_delay_sec", 0); 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 max_delay_sec // max_delay_sec ); 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) { if (!decrypted_content) { return 0; } // Quick check: must start with '[' for JSON array if (decrypted_content[0] != '[') { return 0; } // Try to parse as JSON array cJSON* parsed = cJSON_Parse(decrypted_content); if (!parsed) { return 0; } int is_array = cJSON_IsArray(parsed); 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_config_value("relay_pubkey"); if (!relay_pubkey) { DEBUG_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; } DEBUG_INFO("DM_ADMIN: Processing NIP-17 admin command from decrypted DM"); // Extract content from DM cJSON* content_obj = cJSON_GetObjectItem(dm_event, "content"); if (!content_obj || !cJSON_IsString(content_obj)) { DEBUG_INFO("DM_ADMIN: DM missing content field"); strncpy(error_message, "NIP-17: DM missing content", error_size - 1); return -1; } const char* dm_content = cJSON_GetStringValue(content_obj); DEBUG_INFO("DM_ADMIN: Extracted DM content: %.100s%s", dm_content, strlen(dm_content) > 100 ? "..." : ""); // 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)) { DEBUG_INFO("DM_ADMIN: 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_config_value("admin_pubkey"); int is_admin = admin_pubkey && strlen(admin_pubkey) > 0 && strcmp(sender_pubkey, admin_pubkey) == 0; DEBUG_INFO("DM_ADMIN: Sender pubkey: %.16s... (admin: %s)", sender_pubkey, is_admin ? "YES" : "NO"); // Parse DM content as JSON array of commands DEBUG_INFO("DM_ADMIN: Attempting to parse DM content as JSON command array"); cJSON* command_array = cJSON_Parse(dm_content); if (!command_array || !cJSON_IsArray(command_array)) { DEBUG_INFO("DM_ADMIN: Content is not a JSON array, checking for plain text commands"); // If content is not a JSON array, check for plain text commands if (is_admin) { DEBUG_INFO("DM_ADMIN: Processing plain text admin command"); // 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) { DEBUG_INFO("DM_ADMIN: Processing stats command"); char* stats_text = generate_stats_text(); if (!stats_text) { DEBUG_INFO("DM_ADMIN: Failed to generate stats text"); return -1; } char error_msg[256]; int result = send_nip17_response(sender_pubkey, stats_text, error_msg, sizeof(error_msg)); free(stats_text); if (result != 0) { DEBUG_ERROR(error_msg); return -1; } DEBUG_INFO("DM_ADMIN: Stats command processed successfully"); return 0; } // Check for config commands else if (strstr(content_lower, "config") != NULL || strstr(content_lower, "configuration") != NULL) { DEBUG_INFO("DM_ADMIN: Processing config command"); char* config_text = generate_config_text(); if (!config_text) { DEBUG_INFO("DM_ADMIN: Failed to generate config text"); return -1; } char error_msg[256]; int result = send_nip17_response(sender_pubkey, config_text, error_msg, sizeof(error_msg)); free(config_text); if (result != 0) { DEBUG_ERROR(error_msg); return -1; } DEBUG_INFO("DM_ADMIN: Config command processed successfully"); return 0; } // Check for status commands else if (strstr(content_lower, "status") != NULL) { DEBUG_INFO("DM_ADMIN: Processing status command"); // Create synthetic event for system_command handler cJSON* synthetic_event = cJSON_CreateObject(); cJSON_AddNumberToObject(synthetic_event, "kind", 23456); cJSON_AddStringToObject(synthetic_event, "pubkey", sender_pubkey); // Create tags array with system_command cJSON* tags = cJSON_CreateArray(); cJSON* cmd_tag = cJSON_CreateArray(); cJSON_AddItemToArray(cmd_tag, cJSON_CreateString("system_command")); cJSON_AddItemToArray(cmd_tag, cJSON_CreateString("system_status")); cJSON_AddItemToArray(tags, cmd_tag); cJSON_AddItemToObject(synthetic_event, "tags", tags); char error_msg[256]; int result = handle_system_command_unified(synthetic_event, "system_status", error_msg, sizeof(error_msg), wsi); cJSON_Delete(synthetic_event); if (result != 0) { DEBUG_ERROR(error_msg); return -1; } DEBUG_INFO("DM_ADMIN: Status command processed successfully"); return 0; } else { DEBUG_INFO("DM_ADMIN: Checking for confirmation or config change requests"); // Check if it's a confirmation response (yes/no) int confirmation_result = handle_config_confirmation(sender_pubkey, dm_content); if (confirmation_result != 0) { if (confirmation_result > 0) { DEBUG_INFO("DM_ADMIN: Configuration confirmation processed successfully"); } else if (confirmation_result == -2) { DEBUG_INFO("DM_ADMIN: No pending changes to confirm"); // No pending changes char no_pending_msg[256]; snprintf(no_pending_msg, sizeof(no_pending_msg), "❌ No Pending Changes\n" "━━━━━━━━━━━━━━━━━━━━━━\n" "\n" "You don't have any pending configuration changes to confirm.\n" "\n" "Send a configuration command first (e.g., 'auth_enabled true')." ); send_nip17_response(sender_pubkey, no_pending_msg, NULL, 0); } return 0; } // Check if it's a configuration change request int config_result = process_config_change_request(sender_pubkey, dm_content); if (config_result != 0) { if (config_result > 0) { DEBUG_INFO("DM_ADMIN: Configuration change request processed successfully"); return 1; // Return positive value to indicate response was handled } else { DEBUG_ERROR("NIP-17: Configuration change request failed"); return -1; // Return error to prevent generic success response } } DEBUG_INFO("DM_ADMIN: Unrecognized plain text admin command"); return 0; // Admin sent unrecognized plain text, treat as user DM } } else { DEBUG_INFO("DM_ADMIN: Non-admin user sent plain text - treating as user DM"); // Not admin, treat as user DM return 0; } } DEBUG_INFO("DM_ADMIN: Successfully parsed JSON command array"); // 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) { DEBUG_INFO("DM_ADMIN: Processing JSON stats command"); // Get sender pubkey for response cJSON* sender_pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey"); if (!sender_pubkey_obj || !cJSON_IsString(sender_pubkey_obj)) { cJSON_Delete(command_array); DEBUG_INFO("DM_ADMIN: DM missing sender pubkey for stats command"); strncpy(error_message, "NIP-17: DM missing sender pubkey", error_size - 1); return -1; } const char* sender_pubkey = cJSON_GetStringValue(sender_pubkey_obj); // Generate stats JSON (for JSON array commands, use JSON format) char* stats_json = generate_stats_json(); if (!stats_json) { cJSON_Delete(command_array); DEBUG_INFO("DM_ADMIN: Failed to generate stats JSON"); strncpy(error_message, "NIP-17: Failed to generate stats", error_size - 1); return -1; } char error_msg[256]; int result = send_nip17_response(sender_pubkey, stats_json, error_msg, sizeof(error_msg)); free(stats_json); cJSON_Delete(command_array); if (result != 0) { DEBUG_ERROR(error_msg); strncpy(error_message, error_msg, error_size - 1); return -1; } DEBUG_INFO("DM_ADMIN: JSON stats command processed successfully"); return 0; } } DEBUG_INFO("DM_ADMIN: Delegating to unified admin processing for command array"); // 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 DEBUG_INFO("DM_ADMIN: Processing synthetic admin event"); int result = process_admin_event_in_config(synthetic_event, error_message, error_size, wsi); cJSON_Delete(synthetic_event); cJSON_Delete(command_array); DEBUG_INFO("DM_ADMIN: Unified admin processing completed with result: %d", result); return result; }