v0.4.9 - Working on dm admin
This commit is contained in:
447
src/api.c
447
src/api.c
@@ -1,7 +1,7 @@
|
||||
// Define _GNU_SOURCE to ensure all POSIX features are available
|
||||
#define _GNU_SOURCE
|
||||
|
||||
// API module for serving embedded web content
|
||||
// API module for serving embedded web content and NIP-17 admin messaging
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
@@ -9,6 +9,18 @@
|
||||
#include <libwebsockets.h>
|
||||
#include "api.h"
|
||||
#include "embedded_web_content.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip017.h"
|
||||
#include "../nostr_core_lib/nostr_core/nip044.h"
|
||||
#include "../nostr_core_lib/nostr_core/nostr_core.h"
|
||||
#include "config.h"
|
||||
|
||||
// Forward declarations for event creation and signing
|
||||
cJSON* nostr_create_and_sign_event(int kind, const char* content, cJSON* tags,
|
||||
const unsigned char* privkey_bytes, time_t created_at);
|
||||
|
||||
// Forward declaration for stats generation
|
||||
char* generate_stats_json(void);
|
||||
|
||||
|
||||
// Forward declarations for logging functions
|
||||
void log_info(const char* message);
|
||||
@@ -16,6 +28,9 @@ void log_success(const char* message);
|
||||
void log_error(const char* message);
|
||||
void log_warning(const char* message);
|
||||
|
||||
// Forward declarations for database functions
|
||||
int store_event(cJSON* event);
|
||||
|
||||
// Handle HTTP request for embedded files (assumes GET)
|
||||
int handle_embedded_file_request(struct lws* wsi, const char* requested_uri) {
|
||||
log_info("Handling embedded file request");
|
||||
@@ -162,4 +177,434 @@ int handle_embedded_file_writeable(struct lws* wsi) {
|
||||
|
||||
log_success("Embedded file served successfully");
|
||||
return 0;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// NIP-17 GIFT WRAP ADMIN MESSAGING 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);
|
||||
log_info("NIP-17: Processing admin command from DM content");
|
||||
|
||||
// Parse DM content as JSON array of commands
|
||||
cJSON* command_array = cJSON_Parse(dm_content);
|
||||
if (!command_array || !cJSON_IsArray(command_array)) {
|
||||
strncpy(error_message, "NIP-17: DM content is not valid JSON array", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 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
|
||||
int 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 -1;
|
||||
}
|
||||
|
||||
// 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 -1;
|
||||
}
|
||||
|
||||
// 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 -1;
|
||||
}
|
||||
|
||||
// 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 -1;
|
||||
}
|
||||
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 -1;
|
||||
}
|
||||
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
|
||||
cJSON* sender_pubkey_obj = cJSON_GetObjectItem(gift_wrap_event, "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_event(success_gift_wraps[0]);
|
||||
cJSON_Delete(success_gift_wraps[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cJSON_Delete(inner_dm);
|
||||
return result;
|
||||
}
|
||||
@@ -17,4 +17,7 @@ struct embedded_file_session_data {
|
||||
// Handle HTTP request for embedded API files
|
||||
int handle_embedded_file_request(struct lws* wsi, const char* requested_uri);
|
||||
|
||||
// Generate stats JSON from database queries
|
||||
char* generate_stats_json(void);
|
||||
|
||||
#endif // API_H
|
||||
142
src/config.c
142
src/config.c
@@ -2956,21 +2956,54 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
log_info("DEBUG: NIP-44 decryption successful");
|
||||
printf(" Decrypted content: %s\n", decrypted_text);
|
||||
printf(" Decrypted length: %zu\n", strlen(decrypted_text));
|
||||
|
||||
// Parse decrypted content as JSON array
|
||||
log_info("DEBUG: Parsing decrypted content as JSON");
|
||||
decrypted_content = cJSON_Parse(decrypted_text);
|
||||
|
||||
if (!decrypted_content || !cJSON_IsArray(decrypted_content)) {
|
||||
log_error("DEBUG: Decrypted content is not valid JSON array");
|
||||
|
||||
// Parse decrypted content as inner event JSON (NIP-17)
|
||||
log_info("DEBUG: Parsing decrypted content as inner event JSON");
|
||||
cJSON* inner_event = cJSON_Parse(decrypted_text);
|
||||
|
||||
if (!inner_event || !cJSON_IsObject(inner_event)) {
|
||||
log_error("DEBUG: Decrypted content is not valid inner event JSON");
|
||||
printf(" Decrypted content type: %s\n",
|
||||
decrypted_content ? (cJSON_IsArray(decrypted_content) ? "array" : "other") : "null");
|
||||
snprintf(error_message, error_size, "error: decrypted content is not valid JSON array");
|
||||
inner_event ? (cJSON_IsObject(inner_event) ? "object" : "other") : "null");
|
||||
cJSON_Delete(inner_event);
|
||||
snprintf(error_message, error_size, "error: decrypted content is not valid inner event JSON");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("DEBUG: Decrypted content parsed successfully as JSON array");
|
||||
|
||||
log_info("DEBUG: Inner event parsed successfully");
|
||||
printf(" Inner event kind: %d\n", (int)cJSON_GetNumberValue(cJSON_GetObjectItem(inner_event, "kind")));
|
||||
|
||||
// Extract content from inner event
|
||||
cJSON* inner_content_obj = cJSON_GetObjectItem(inner_event, "content");
|
||||
if (!inner_content_obj || !cJSON_IsString(inner_content_obj)) {
|
||||
log_error("DEBUG: Inner event missing content field");
|
||||
cJSON_Delete(inner_event);
|
||||
snprintf(error_message, error_size, "error: inner event missing content field");
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* inner_content = cJSON_GetStringValue(inner_content_obj);
|
||||
log_info("DEBUG: Extracted inner content");
|
||||
printf(" Inner content: %s\n", inner_content);
|
||||
|
||||
// Parse inner content as JSON array (the command array)
|
||||
log_info("DEBUG: Parsing inner content as command JSON array");
|
||||
decrypted_content = cJSON_Parse(inner_content);
|
||||
|
||||
if (!decrypted_content || !cJSON_IsArray(decrypted_content)) {
|
||||
log_error("DEBUG: Inner content is not valid JSON array");
|
||||
printf(" Inner content type: %s\n",
|
||||
decrypted_content ? (cJSON_IsArray(decrypted_content) ? "array" : "other") : "null");
|
||||
cJSON_Delete(inner_event);
|
||||
snprintf(error_message, error_size, "error: inner content is not valid JSON array");
|
||||
return -1;
|
||||
}
|
||||
|
||||
log_info("DEBUG: Inner content parsed successfully as JSON array");
|
||||
printf(" Array size: %d\n", cJSON_GetArraySize(decrypted_content));
|
||||
|
||||
// Clean up inner event
|
||||
cJSON_Delete(inner_event);
|
||||
|
||||
// Replace event content with decrypted command array for processing
|
||||
log_info("DEBUG: Replacing event content with decrypted marker");
|
||||
@@ -2979,16 +3012,11 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
|
||||
// Create synthetic tags from decrypted command array
|
||||
log_info("DEBUG: Creating synthetic tags from decrypted command array");
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (!tags_obj) {
|
||||
log_info("DEBUG: No existing tags, creating new tags array");
|
||||
tags_obj = cJSON_CreateArray();
|
||||
cJSON_AddItemToObject(event, "tags", tags_obj);
|
||||
} else {
|
||||
log_info("DEBUG: Using existing tags array");
|
||||
printf(" Existing tags count: %d\n", cJSON_GetArraySize(tags_obj));
|
||||
}
|
||||
|
||||
printf(" Decrypted content array size: %d\n", cJSON_GetArraySize(decrypted_content));
|
||||
|
||||
// Create new tags array with command tag first
|
||||
cJSON* new_tags = cJSON_CreateArray();
|
||||
|
||||
// Add decrypted command as first tag
|
||||
if (cJSON_GetArraySize(decrypted_content) > 0) {
|
||||
log_info("DEBUG: Adding decrypted command as synthetic tag");
|
||||
@@ -2997,10 +3025,10 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
const char* command_name = cJSON_GetStringValue(first_item);
|
||||
log_info("DEBUG: Creating command tag");
|
||||
printf(" Command: %s\n", command_name ? command_name : "null");
|
||||
|
||||
|
||||
cJSON* command_tag = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(command_tag, cJSON_Duplicate(first_item, 1));
|
||||
|
||||
|
||||
// Add remaining items as tag values
|
||||
for (int i = 1; i < cJSON_GetArraySize(decrypted_content); i++) {
|
||||
cJSON* item = cJSON_GetArrayItem(decrypted_content, i);
|
||||
@@ -3013,17 +3041,31 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
cJSON_AddItemToArray(command_tag, cJSON_Duplicate(item, 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Insert at beginning of tags array
|
||||
cJSON_InsertItemInArray(tags_obj, 0, command_tag);
|
||||
log_info("DEBUG: Synthetic command tag created and inserted");
|
||||
printf(" Final tag array size: %d\n", cJSON_GetArraySize(tags_obj));
|
||||
|
||||
cJSON_AddItemToArray(new_tags, command_tag);
|
||||
log_info("DEBUG: Synthetic command tag added to new tags array");
|
||||
printf(" New tags after adding command: %d\n", cJSON_GetArraySize(new_tags));
|
||||
} else {
|
||||
log_error("DEBUG: First item in decrypted array is not a string");
|
||||
}
|
||||
} else {
|
||||
log_error("DEBUG: Decrypted array is empty");
|
||||
}
|
||||
|
||||
// Add existing tags
|
||||
cJSON* existing_tags = cJSON_GetObjectItem(event, "tags");
|
||||
if (existing_tags && cJSON_IsArray(existing_tags)) {
|
||||
printf(" Existing tags count: %d\n", cJSON_GetArraySize(existing_tags));
|
||||
cJSON* tag = NULL;
|
||||
cJSON_ArrayForEach(tag, existing_tags) {
|
||||
cJSON_AddItemToArray(new_tags, cJSON_Duplicate(tag, 1));
|
||||
}
|
||||
printf(" New tags after adding existing: %d\n", cJSON_GetArraySize(new_tags));
|
||||
}
|
||||
|
||||
// Replace event tags with new tags
|
||||
cJSON_ReplaceItemInObject(event, "tags", new_tags);
|
||||
printf(" Final tag array size: %d\n", cJSON_GetArraySize(new_tags));
|
||||
|
||||
cJSON_Delete(decrypted_content);
|
||||
} else {
|
||||
@@ -3034,19 +3076,29 @@ int handle_kind_23456_unified(cJSON* event, char* error_message, size_t error_si
|
||||
|
||||
// Parse first tag to determine action type (now from decrypted content if applicable)
|
||||
log_info("DEBUG: Parsing first tag to determine action type");
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (tags_obj && cJSON_IsArray(tags_obj)) {
|
||||
printf(" Tags array size: %d\n", cJSON_GetArraySize(tags_obj));
|
||||
for (int i = 0; i < cJSON_GetArraySize(tags_obj); i++) {
|
||||
cJSON* tag = cJSON_GetArrayItem(tags_obj, i);
|
||||
if (tag && cJSON_IsArray(tag) && cJSON_GetArraySize(tag) > 0) {
|
||||
cJSON* tag_name = cJSON_GetArrayItem(tag, 0);
|
||||
if (tag_name && cJSON_IsString(tag_name)) {
|
||||
printf(" Tag %d: %s\n", i, cJSON_GetStringValue(tag_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf(" No tags array found\n");
|
||||
}
|
||||
|
||||
const char* action_type = get_first_tag_name(event);
|
||||
if (!action_type) {
|
||||
log_error("DEBUG: Missing or invalid first tag after processing");
|
||||
cJSON* tags_obj = cJSON_GetObjectItem(event, "tags");
|
||||
if (tags_obj && cJSON_IsArray(tags_obj)) {
|
||||
printf(" Tags array size: %d\n", cJSON_GetArraySize(tags_obj));
|
||||
} else {
|
||||
printf(" No tags array found\n");
|
||||
}
|
||||
snprintf(error_message, error_size, "invalid: missing or invalid first tag");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
log_info("DEBUG: Action type determined");
|
||||
printf(" Action type: %s\n", action_type);
|
||||
|
||||
@@ -3831,12 +3883,20 @@ int handle_stats_query_unified(cJSON* event, char* error_message, size_t error_s
|
||||
|
||||
// Query time-based statistics
|
||||
cJSON* time_stats = cJSON_CreateObject();
|
||||
if (sqlite3_prepare_v2(g_db, "SELECT total_events, events_24h, events_7d, events_30d FROM time_stats_view", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
cJSON_AddNumberToObject(time_stats, "total", sqlite3_column_int64(stmt, 0));
|
||||
cJSON_AddNumberToObject(time_stats, "last_24h", sqlite3_column_int64(stmt, 1));
|
||||
cJSON_AddNumberToObject(time_stats, "last_7d", sqlite3_column_int64(stmt, 2));
|
||||
cJSON_AddNumberToObject(time_stats, "last_30d", sqlite3_column_int64(stmt, 3));
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -57,15 +57,7 @@ extern int get_config_int(const char* key, int default_value);
|
||||
// NIP-42 constants (from nostr_core_lib)
|
||||
#define NOSTR_NIP42_AUTH_EVENT_KIND 22242
|
||||
|
||||
// NIP-42 error codes (from nostr_core_lib)
|
||||
#define NOSTR_ERROR_NIP42_CHALLENGE_NOT_FOUND -200
|
||||
#define NOSTR_ERROR_NIP42_CHALLENGE_EXPIRED -201
|
||||
#define NOSTR_ERROR_NIP42_INVALID_CHALLENGE -202
|
||||
#define NOSTR_ERROR_NIP42_URL_MISMATCH -203
|
||||
#define NOSTR_ERROR_NIP42_TIME_TOLERANCE -204
|
||||
#define NOSTR_ERROR_NIP42_AUTH_EVENT_INVALID -205
|
||||
#define NOSTR_ERROR_NIP42_INVALID_RELAY_URL -206
|
||||
#define NOSTR_ERROR_NIP42_NOT_CONFIGURED -207
|
||||
// NIP-42 error codes (from nostr_core_lib - already defined in nostr_common.h)
|
||||
|
||||
// Forward declarations for NIP-42 functions (simple implementations for C-relay)
|
||||
int nostr_nip42_generate_challenge(char *challenge_buffer, size_t buffer_size);
|
||||
|
||||
284
src/websockets.c
284
src/websockets.c
@@ -68,6 +68,13 @@ int nostr_validate_unified_request(const char* json_string, size_t json_length);
|
||||
int process_admin_event_in_config(cJSON* event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
int is_authorized_admin_event(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
// Forward declarations for NIP-17 admin messaging
|
||||
int process_nip17_admin_message(cJSON* gift_wrap_event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
// Forward declarations for DM stats command handling
|
||||
int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_size, struct lws* wsi);
|
||||
|
||||
|
||||
// Forward declarations for NIP-09 deletion request handling
|
||||
int handle_deletion_request(cJSON* event, char* error_message, size_t error_size);
|
||||
|
||||
@@ -314,13 +321,38 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Check if NIP-42 authentication is required for this event kind or globally
|
||||
int auth_required = is_nip42_auth_globally_required() || is_nip42_auth_required_for_kind(event_kind);
|
||||
|
||||
// Special case: allow kind 14 DMs addressed to relay to bypass auth (admin commands)
|
||||
int bypass_auth = 0;
|
||||
if (event_kind == 14 && event_obj && cJSON_IsObject(event_obj)) {
|
||||
cJSON* tags = cJSON_GetObjectItem(event_obj, "tags");
|
||||
if (tags && cJSON_IsArray(tags)) {
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
if (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) {
|
||||
bypass_auth = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char debug_auth_msg[256];
|
||||
snprintf(debug_auth_msg, sizeof(debug_auth_msg),
|
||||
"DEBUG AUTH: auth_required=%d, pss->authenticated=%d, event_kind=%d",
|
||||
auth_required, pss ? pss->authenticated : -1, event_kind);
|
||||
"DEBUG AUTH: auth_required=%d, bypass_auth=%d, pss->authenticated=%d, event_kind=%d",
|
||||
auth_required, bypass_auth, pss ? pss->authenticated : -1, event_kind);
|
||||
log_info(debug_auth_msg);
|
||||
|
||||
if (pss && auth_required && !pss->authenticated) {
|
||||
if (pss && auth_required && !pss->authenticated && !bypass_auth) {
|
||||
if (!pss->auth_challenge_sent) {
|
||||
log_info("DEBUG AUTH: Sending NIP-42 authentication challenge");
|
||||
send_nip42_auth_challenge(wsi, pss);
|
||||
@@ -606,6 +638,78 @@ static int nostr_relay_callback(struct lws *wsi, enum lws_callback_reasons reaso
|
||||
// Admin events are processed by the admin API, not broadcast to subscriptions
|
||||
}
|
||||
}
|
||||
} else if (event_kind == 1059) {
|
||||
// Check for NIP-17 gift wrap admin messages
|
||||
log_info("DEBUG NIP17: Detected kind 1059 gift wrap event");
|
||||
|
||||
char nip17_error[512] = {0};
|
||||
int nip17_result = process_nip17_admin_message(event, nip17_error, sizeof(nip17_error), wsi);
|
||||
|
||||
if (nip17_result != 0) {
|
||||
log_error("DEBUG NIP17: NIP-17 admin message processing failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(nip17_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, nip17_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_nip17_error_msg[600];
|
||||
snprintf(debug_nip17_error_msg, sizeof(debug_nip17_error_msg),
|
||||
"DEBUG NIP17 ERROR: %.400s", nip17_error);
|
||||
log_error(debug_nip17_error_msg);
|
||||
} else {
|
||||
log_success("DEBUG NIP17: NIP-17 admin message processed successfully");
|
||||
// Store the gift wrap event in database (unlike kind 23456)
|
||||
if (store_event(event) != 0) {
|
||||
log_error("DEBUG NIP17: Failed to store gift wrap event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store gift wrap event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("DEBUG NIP17: Gift wrap event stored successfully in database");
|
||||
// Broadcast gift wrap event to matching persistent subscriptions
|
||||
int broadcast_count = broadcast_event_to_subscriptions(event);
|
||||
char debug_broadcast_msg[128];
|
||||
snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
|
||||
"DEBUG NIP17 BROADCAST: Gift wrap event broadcast to %d subscriptions", broadcast_count);
|
||||
log_info(debug_broadcast_msg);
|
||||
}
|
||||
}
|
||||
} else if (event_kind == 14) {
|
||||
// Check for DM stats commands addressed to relay
|
||||
log_info("DEBUG DM: Detected kind 14 DM event");
|
||||
|
||||
char dm_error[512] = {0};
|
||||
int dm_result = process_dm_stats_command(event, dm_error, sizeof(dm_error), wsi);
|
||||
|
||||
if (dm_result != 0) {
|
||||
log_error("DEBUG DM: DM stats command processing failed");
|
||||
result = -1;
|
||||
size_t error_len = strlen(dm_error);
|
||||
size_t copy_len = (error_len < sizeof(error_message) - 1) ? error_len : sizeof(error_message) - 1;
|
||||
memcpy(error_message, dm_error, copy_len);
|
||||
error_message[copy_len] = '\0';
|
||||
|
||||
char debug_dm_error_msg[600];
|
||||
snprintf(debug_dm_error_msg, sizeof(debug_dm_error_msg),
|
||||
"DEBUG DM ERROR: %.400s", dm_error);
|
||||
log_error(debug_dm_error_msg);
|
||||
} else {
|
||||
log_success("DEBUG DM: DM stats command processed successfully");
|
||||
// Store the DM event in database
|
||||
if (store_event(event) != 0) {
|
||||
log_error("DEBUG DM: Failed to store DM event in database");
|
||||
result = -1;
|
||||
strncpy(error_message, "error: failed to store DM event", sizeof(error_message) - 1);
|
||||
} else {
|
||||
log_info("DEBUG DM: DM event stored successfully in database");
|
||||
// Broadcast DM event to matching persistent subscriptions
|
||||
int broadcast_count = broadcast_event_to_subscriptions(event);
|
||||
char debug_broadcast_msg[128];
|
||||
snprintf(debug_broadcast_msg, sizeof(debug_broadcast_msg),
|
||||
"DEBUG DM BROADCAST: DM event broadcast to %d subscriptions", broadcast_count);
|
||||
log_info(debug_broadcast_msg);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Regular event - store in database and broadcast
|
||||
log_info("DEBUG STORAGE: Regular event - storing in database");
|
||||
@@ -1041,6 +1145,180 @@ int start_websocket_relay(int port_override, int strict_port) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Process DM stats command
|
||||
int process_dm_stats_command(cJSON* dm_event, char* error_message, size_t error_size, struct lws* wsi) {
|
||||
// Suppress unused parameter warning
|
||||
(void)wsi;
|
||||
|
||||
if (!dm_event || !error_message) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if DM is addressed to relay
|
||||
cJSON* tags = cJSON_GetObjectItem(dm_event, "tags");
|
||||
if (!tags || !cJSON_IsArray(tags)) {
|
||||
strncpy(error_message, "DM missing or invalid tags", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char* relay_pubkey = get_relay_pubkey_cached();
|
||||
if (!relay_pubkey) {
|
||||
strncpy(error_message, "Could not get relay pubkey", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Look for "p" tag with relay pubkey
|
||||
int addressed_to_relay = 0;
|
||||
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) {
|
||||
addressed_to_relay = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!addressed_to_relay) {
|
||||
// Not addressed to relay, allow normal processing
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Get sender pubkey
|
||||
cJSON* pubkey_obj = cJSON_GetObjectItem(dm_event, "pubkey");
|
||||
if (!pubkey_obj || !cJSON_IsString(pubkey_obj)) {
|
||||
strncpy(error_message, "DM missing sender pubkey", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
const char* sender_pubkey = cJSON_GetStringValue(pubkey_obj);
|
||||
|
||||
// Check if sender is admin
|
||||
const char* admin_pubkey = get_admin_pubkey_cached();
|
||||
if (!admin_pubkey || strlen(admin_pubkey) == 0 ||
|
||||
strcmp(sender_pubkey, admin_pubkey) != 0) {
|
||||
strncpy(error_message, "Unauthorized: not admin", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get relay private key for decryption
|
||||
char* relay_privkey_hex = get_relay_private_key();
|
||||
if (!relay_privkey_hex) {
|
||||
strncpy(error_message, "Could not get relay private key", 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(relay_privkey_hex);
|
||||
strncpy(error_message, "Failed to convert relay private key", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
free(relay_privkey_hex);
|
||||
|
||||
// Convert sender pubkey to bytes
|
||||
unsigned char sender_pubkey_bytes[32];
|
||||
if (nostr_hex_to_bytes(sender_pubkey, sender_pubkey_bytes, sizeof(sender_pubkey_bytes)) != 0) {
|
||||
strncpy(error_message, "Failed to convert sender pubkey", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get encrypted content
|
||||
cJSON* content_obj = cJSON_GetObjectItem(dm_event, "content");
|
||||
if (!content_obj || !cJSON_IsString(content_obj)) {
|
||||
strncpy(error_message, "DM missing content", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
const char* encrypted_content = cJSON_GetStringValue(content_obj);
|
||||
|
||||
// Decrypt content
|
||||
char decrypted_content[4096];
|
||||
int decrypt_result = nostr_nip44_decrypt(relay_privkey, sender_pubkey_bytes,
|
||||
encrypted_content, decrypted_content, sizeof(decrypted_content));
|
||||
|
||||
if (decrypt_result != NOSTR_SUCCESS) {
|
||||
char decrypt_error[256];
|
||||
snprintf(decrypt_error, sizeof(decrypt_error), "NIP-44 decryption failed: %d", decrypt_result);
|
||||
strncpy(error_message, decrypt_error, error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check if content is "stats"
|
||||
if (strcmp(decrypted_content, "stats") != 0) {
|
||||
// Not a stats command, allow normal processing
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_info("Processing DM stats command from admin");
|
||||
|
||||
// Generate stats JSON
|
||||
char* stats_json = generate_stats_json();
|
||||
if (!stats_json) {
|
||||
strncpy(error_message, "Failed to generate stats", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Encrypt stats for response
|
||||
char encrypted_response[4096];
|
||||
int encrypt_result = nostr_nip44_encrypt(relay_privkey, sender_pubkey_bytes,
|
||||
stats_json, encrypted_response, sizeof(encrypted_response));
|
||||
|
||||
free(stats_json);
|
||||
|
||||
if (encrypt_result != NOSTR_SUCCESS) {
|
||||
char encrypt_error[256];
|
||||
snprintf(encrypt_error, sizeof(encrypt_error), "NIP-44 encryption failed: %d", encrypt_result);
|
||||
strncpy(error_message, encrypt_error, error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Create DM response event
|
||||
cJSON* dm_response = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(dm_response, "id", ""); // Will be set by event creation
|
||||
cJSON_AddStringToObject(dm_response, "pubkey", relay_pubkey);
|
||||
cJSON_AddNumberToObject(dm_response, "created_at", (double)time(NULL));
|
||||
cJSON_AddNumberToObject(dm_response, "kind", 14);
|
||||
cJSON_AddStringToObject(dm_response, "content", encrypted_response);
|
||||
|
||||
// Add tags: p tag for recipient (admin)
|
||||
cJSON* response_tags = cJSON_CreateArray();
|
||||
cJSON* p_tag = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(p_tag, cJSON_CreateString("p"));
|
||||
cJSON_AddItemToArray(p_tag, cJSON_CreateString(sender_pubkey));
|
||||
cJSON_AddItemToArray(response_tags, p_tag);
|
||||
cJSON_AddItemToObject(dm_response, "tags", response_tags);
|
||||
|
||||
// Add signature placeholder
|
||||
cJSON_AddStringToObject(dm_response, "sig", ""); // Will be set by event creation/signing
|
||||
|
||||
// Store and broadcast the DM response
|
||||
int store_result = store_event(dm_response);
|
||||
if (store_result != 0) {
|
||||
cJSON_Delete(dm_response);
|
||||
strncpy(error_message, "Failed to store DM response", error_size - 1);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Broadcast to subscriptions
|
||||
int broadcast_count = broadcast_event_to_subscriptions(dm_response);
|
||||
char broadcast_msg[128];
|
||||
snprintf(broadcast_msg, sizeof(broadcast_msg),
|
||||
"DM stats response broadcast to %d subscriptions", broadcast_count);
|
||||
log_info(broadcast_msg);
|
||||
|
||||
cJSON_Delete(dm_response);
|
||||
|
||||
log_success("DM stats command processed successfully");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Handle NIP-45 COUNT message
|
||||
int handle_count_message(const char* sub_id, cJSON* filters, struct lws *wsi, struct per_session_data *pss) {
|
||||
(void)pss; // Suppress unused parameter warning
|
||||
|
||||
Reference in New Issue
Block a user