Compare commits
3 Commits
e7d8b49ecc
...
v0.0.13
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
918cc618a9 | ||
|
|
b9911dd4a6 | ||
|
|
9c51e261dd |
26
include/nsigner_auth.h
Normal file
26
include/nsigner_auth.h
Normal file
@@ -0,0 +1,26 @@
|
||||
#ifndef NSIGNER_AUTH_H
|
||||
#define NSIGNER_AUTH_H
|
||||
|
||||
#include "../resources/nostr_core_lib/cjson/cJSON.h"
|
||||
|
||||
#include <time.h>
|
||||
|
||||
typedef struct {
|
||||
int enabled;
|
||||
unsigned char privkey[32];
|
||||
char label[64];
|
||||
} nt_nsigner_auth_ctx_t;
|
||||
|
||||
/*
|
||||
* Build a TCP auth envelope event (kind 27235) for an n_signer JSON-RPC request.
|
||||
* Caller owns *out_auth and must cJSON_Delete() it.
|
||||
* Returns 0 on success.
|
||||
*/
|
||||
int nt_nsigner_auth_build(const char *request_id,
|
||||
const char *method,
|
||||
const cJSON *params,
|
||||
const nt_nsigner_auth_ctx_t *auth_ctx,
|
||||
time_t created_at,
|
||||
cJSON **out_auth);
|
||||
|
||||
#endif /* NSIGNER_AUTH_H */
|
||||
@@ -2,6 +2,22 @@
|
||||
#define NSIGNER_CLIENT_H
|
||||
|
||||
#include "../resources/nostr_core_lib/cjson/cJSON.h"
|
||||
#include "nsigner_auth.h"
|
||||
|
||||
/* Selector for key derivation/role in n_signer requests. */
|
||||
typedef struct {
|
||||
int has_nostr_index;
|
||||
int nostr_index;
|
||||
char role[64];
|
||||
} nt_nsigner_selector_t;
|
||||
|
||||
static inline nt_nsigner_selector_t nsigner_selector_default(void) {
|
||||
nt_nsigner_selector_t s;
|
||||
s.has_nostr_index = 1;
|
||||
s.nostr_index = 0;
|
||||
s.role[0] = '\0';
|
||||
return s;
|
||||
}
|
||||
|
||||
/* Error codes */
|
||||
#define NT_NSIGNER_OK 0
|
||||
@@ -10,6 +26,7 @@
|
||||
#define NT_NSIGNER_E_UNAUTH -3
|
||||
#define NT_NSIGNER_E_PARSE -4
|
||||
#define NT_NSIGNER_E_ERROR -5
|
||||
#define NT_NSIGNER_E_AUTH -6
|
||||
|
||||
/*
|
||||
* Enumerate running nsigner instances by reading /proc/net/unix.
|
||||
@@ -39,44 +56,102 @@ int nsigner_rpc(int fd, const cJSON *request, cJSON **response_out, int read_tim
|
||||
* socket_name: without '@' prefix.
|
||||
* role: the role selector string (e.g. "main").
|
||||
*/
|
||||
int nsigner_get_public_key(const char *socket_name, const char *role, char *out_hex_65);
|
||||
int nsigner_get_public_key(const char *socket_name, const nt_nsigner_selector_t *selector, char *out_hex_65);
|
||||
|
||||
int nsigner_sign_event(const char *socket_name, const char *role,
|
||||
int nsigner_sign_event(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *unsigned_event_json, char **signed_event_json_out);
|
||||
|
||||
int nsigner_nip04_encrypt(const char *socket_name, const char *role,
|
||||
int nsigner_nip04_encrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
|
||||
|
||||
int nsigner_nip04_decrypt(const char *socket_name, const char *role,
|
||||
int nsigner_nip04_decrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
|
||||
|
||||
int nsigner_nip44_encrypt(const char *socket_name, const char *role,
|
||||
int nsigner_nip44_encrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
|
||||
|
||||
int nsigner_nip44_decrypt(const char *socket_name, const char *role,
|
||||
int nsigner_nip44_decrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
|
||||
|
||||
/*
|
||||
* URL helpers for FIPS/TCP transport.
|
||||
* URL format: http://host:port (path/query ignored if present).
|
||||
*/
|
||||
int nsigner_get_public_key_url(const char *url, const char *role, char *out_hex_65);
|
||||
int nsigner_get_public_key_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth, char *out_hex_65);
|
||||
|
||||
int nsigner_sign_event_url(const char *url, const char *role,
|
||||
int nsigner_sign_event_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *unsigned_event_json, char **signed_event_json_out);
|
||||
|
||||
int nsigner_nip04_encrypt_url(const char *url, const char *role,
|
||||
int nsigner_nip04_encrypt_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
|
||||
|
||||
int nsigner_nip04_decrypt_url(const char *url, const char *role,
|
||||
int nsigner_nip04_decrypt_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
|
||||
|
||||
int nsigner_nip44_encrypt_url(const char *url, const char *role,
|
||||
int nsigner_nip44_encrypt_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex, const char *plaintext, char **cipher_out);
|
||||
|
||||
int nsigner_nip44_decrypt_url(const char *url, const char *role,
|
||||
int nsigner_nip44_decrypt_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex, const char *ciphertext, char **plain_out);
|
||||
|
||||
/*
|
||||
* Persistent URL session helpers.
|
||||
* Open once and reuse the fd for all RPC calls to keep a stable TCP source port.
|
||||
*/
|
||||
int nsigner_session_open_url(const char *url);
|
||||
void nsigner_session_close(int fd);
|
||||
|
||||
int nsigner_session_get_public_key(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
char *out_hex_65);
|
||||
|
||||
int nsigner_session_sign_event(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *unsigned_event_json,
|
||||
char **signed_event_json_out);
|
||||
|
||||
int nsigner_session_nip04_encrypt(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex,
|
||||
const char *plaintext,
|
||||
char **cipher_out);
|
||||
|
||||
int nsigner_session_nip04_decrypt(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex,
|
||||
const char *ciphertext,
|
||||
char **plain_out);
|
||||
|
||||
int nsigner_session_nip44_encrypt(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex,
|
||||
const char *plaintext,
|
||||
char **cipher_out);
|
||||
|
||||
int nsigner_session_nip44_decrypt(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex,
|
||||
const char *ciphertext,
|
||||
char **plain_out);
|
||||
|
||||
/*
|
||||
* qrexec helpers for cross-qube n_signer transport.
|
||||
* target_qube: destination signer qube (e.g. "nsigner-vault").
|
||||
@@ -84,39 +159,39 @@ int nsigner_nip44_decrypt_url(const char *url, const char *role,
|
||||
*/
|
||||
int nsigner_qrexec_get_public_key(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
char *out_hex_65);
|
||||
|
||||
int nsigner_qrexec_sign_event(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *unsigned_event_json,
|
||||
char **signed_event_json_out);
|
||||
|
||||
int nsigner_qrexec_nip04_encrypt(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex,
|
||||
const char *plaintext,
|
||||
char **cipher_out);
|
||||
|
||||
int nsigner_qrexec_nip04_decrypt(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex,
|
||||
const char *ciphertext,
|
||||
char **plain_out);
|
||||
|
||||
int nsigner_qrexec_nip44_encrypt(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex,
|
||||
const char *plaintext,
|
||||
char **cipher_out);
|
||||
|
||||
int nsigner_qrexec_nip44_decrypt(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex,
|
||||
const char *ciphertext,
|
||||
char **plain_out);
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
#define SIGNER_H
|
||||
|
||||
#include "../resources/nostr_core_lib/cjson/cJSON.h"
|
||||
#include "nsigner_client.h"
|
||||
|
||||
#include <time.h>
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
NT_SIGNER_LOCAL = 0,
|
||||
@@ -18,16 +20,27 @@ typedef struct {
|
||||
char qrexec_target[128];
|
||||
char qrexec_service[128];
|
||||
char endpoint_url[256];
|
||||
char role[64];
|
||||
int url_session_fd;
|
||||
nt_nsigner_selector_t selector;
|
||||
nt_nsigner_auth_ctx_t url_auth;
|
||||
} nt_signer_t;
|
||||
|
||||
extern nt_signer_t g_signer;
|
||||
|
||||
void signer_init(void);
|
||||
void signer_shutdown(void);
|
||||
int signer_init_local(void);
|
||||
int signer_init_nsigner(const char *socket_name);
|
||||
int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name);
|
||||
int signer_init_nsigner_url(const char *endpoint_url);
|
||||
void signer_set_selector(int has_index, int index, const char *role);
|
||||
int signer_init_nsigner(const char *socket_name, const nt_nsigner_selector_t *selector);
|
||||
int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name, const nt_nsigner_selector_t *selector);
|
||||
int signer_init_nsigner_url(const char *endpoint_url, const nt_nsigner_selector_t *selector);
|
||||
int signer_init_nsigner_url_with_session(const char *endpoint_url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
int session_fd);
|
||||
int signer_init_nsigner_url_with_session_auth(const char *endpoint_url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
int session_fd,
|
||||
const nt_nsigner_auth_ctx_t *auth_or_null);
|
||||
|
||||
int signer_create_and_sign(int kind,
|
||||
const char *content,
|
||||
|
||||
@@ -1,18 +1,58 @@
|
||||
#ifndef TUI_H
|
||||
#define TUI_H
|
||||
|
||||
#define TUI_KEY_RESIZE -2
|
||||
|
||||
typedef struct {
|
||||
const char *breadcrumb;
|
||||
const char *title_suffix;
|
||||
} tui_view_t;
|
||||
|
||||
/* Initialize terminal input mode (non-canonical, no echo). */
|
||||
void tui_init(void);
|
||||
|
||||
/* Restore terminal settings to their original state. */
|
||||
void tui_cleanup(void);
|
||||
|
||||
/* Install SIGWINCH handler for resize-aware redraw loops. */
|
||||
void tui_install_resize_handler(void);
|
||||
|
||||
/* Consume pending resize flag (returns 1 if a resize was pending). */
|
||||
int tui_consume_resize_flag(void);
|
||||
|
||||
/* Set app title used by anchored frame renderers. */
|
||||
void tui_set_app_title(const char *title);
|
||||
|
||||
/* Clear the screen area by printing terminal-height blank lines. */
|
||||
void tui_clear_screen(void);
|
||||
|
||||
/* Print formatted output and render ^_...^: hotkey highlights. */
|
||||
void tui_print(const char *fmt, ...);
|
||||
|
||||
/* Print full-width horizontal separator using '='. */
|
||||
void tui_print_hr(void);
|
||||
|
||||
/* Print centered text in the current terminal width. */
|
||||
void tui_print_centered(const char *text);
|
||||
|
||||
/* Render anchored top frame (separator/title/separator/breadcrumb + blank lines). */
|
||||
void tui_render_top_frame(const tui_view_t *view);
|
||||
|
||||
/* Column where centered menu block should start. */
|
||||
int tui_centered_menu_start_col(void);
|
||||
|
||||
/* Print one menu line aligned to centered menu start. */
|
||||
void tui_print_menu_item(const char *fmt, ...);
|
||||
|
||||
/* Begin canonical continuous frame and reset line counting. */
|
||||
void tui_begin_frame(const tui_view_t *view);
|
||||
|
||||
/* End frame with anchored prompt near bottom, then read line. */
|
||||
int tui_end_frame_with_prompt(const char *prompt, char *buf, int bufsize);
|
||||
|
||||
/* Render startup splash screen and wait for Enter. */
|
||||
void tui_show_splash(void);
|
||||
|
||||
/* Prompt and read a blocking input line into buf, returns length. */
|
||||
int tui_get_line(const char *prompt, char *buf, int bufsize);
|
||||
|
||||
|
||||
5
src/db.c
5
src/db.c
@@ -179,7 +179,10 @@ int db_open(void) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(db_path, sizeof(db_path), "%s/nostr.db", dir_path);
|
||||
if (snprintf(db_path, sizeof(db_path), "%s/nostr.db", dir_path) >= (int)sizeof(db_path)) {
|
||||
fprintf(stderr, "db_open: database path too long\n");
|
||||
return -1;
|
||||
}
|
||||
rc = sqlite3_open(db_path, &g_db);
|
||||
if (rc != SQLITE_OK) {
|
||||
fprintf(stderr, "db_open: sqlite open failed: %s\n", sqlite3_errmsg(g_db));
|
||||
|
||||
74
src/main.c
74
src/main.c
@@ -3,6 +3,7 @@
|
||||
#include "tui.h"
|
||||
|
||||
#include "../resources/nostr_core_lib/nostr_core/nostr_common.h"
|
||||
#include "signer.h"
|
||||
|
||||
/*
|
||||
* Canonical version is the latest git tag.
|
||||
@@ -10,8 +11,8 @@
|
||||
*/
|
||||
#define NT_VERSION_MAJOR 0
|
||||
#define NT_VERSION_MINOR 0
|
||||
#define NT_VERSION_PATCH 10
|
||||
#define NT_VERSION "v0.0.10"
|
||||
#define NT_VERSION_PATCH 13
|
||||
#define NT_VERSION "v0.0.13"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
@@ -33,8 +34,13 @@ void menu_ai(void);
|
||||
void menu_ecash(void);
|
||||
|
||||
static void menu_show_loaded_events(void) {
|
||||
tui_clear_screen();
|
||||
tui_print("LOADED EVENTS\n");
|
||||
static const tui_view_t view = {
|
||||
"> Main Menu > Loaded Events",
|
||||
NULL,
|
||||
};
|
||||
char input[8];
|
||||
|
||||
tui_begin_frame(&view);
|
||||
|
||||
tui_print("KIND 0 (metadata content):");
|
||||
tui_print("%s", g_state.kind0_json ? g_state.kind0_json : "(not loaded)");
|
||||
@@ -60,42 +66,44 @@ static void menu_show_loaded_events(void) {
|
||||
tui_print("%s", g_state.user_settings_json ? g_state.user_settings_json : "(not loaded)");
|
||||
tui_print("");
|
||||
|
||||
tui_print("\nPress Enter to return.");
|
||||
{
|
||||
char input[8];
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
}
|
||||
(void)tui_end_frame_with_prompt("Press Enter to return >", input, (int)sizeof(input));
|
||||
}
|
||||
|
||||
void menu_main(void) {
|
||||
static const tui_view_t main_view = {
|
||||
"> Main Menu",
|
||||
NULL,
|
||||
};
|
||||
char input[64];
|
||||
|
||||
while (1) {
|
||||
tui_clear_screen();
|
||||
tui_print("NOSTR TERMINAL %s - %s\n", g_state.version, g_state.user_name);
|
||||
(void)tui_consume_resize_flag();
|
||||
tui_begin_frame(&main_view);
|
||||
tui_print("User: %s", g_state.user_name);
|
||||
tui_print("");
|
||||
|
||||
if (!g_state.logged_in) {
|
||||
tui_print("^_L^:og in");
|
||||
tui_print("^_Q^:uit.");
|
||||
tui_print_menu_item("^_L^:og in");
|
||||
tui_print_menu_item("^_Q^:uit");
|
||||
} else {
|
||||
tui_print("^_W^:rite");
|
||||
tui_print("^_T^:weet");
|
||||
tui_print("^_P^:rofile");
|
||||
tui_print("^_R^:elays");
|
||||
tui_print("^_F^:ollows");
|
||||
tui_print("^_K^:ind/event dump");
|
||||
tui_print("^_N^:otifications");
|
||||
tui_print("^_B^:logs/posts");
|
||||
tui_print("^_L^:ive feeds");
|
||||
tui_print("^_M^:essage");
|
||||
tui_print("To^_d^:o");
|
||||
tui_print("^_J^:ournal");
|
||||
tui_print("^_A^:i");
|
||||
tui_print("^_E^:cash");
|
||||
tui_print("^_Q^:uit.");
|
||||
tui_print_menu_item("^_W^:rite");
|
||||
tui_print_menu_item("^_T^:weet");
|
||||
tui_print_menu_item("^_P^:rofile");
|
||||
tui_print_menu_item("^_R^:elays");
|
||||
tui_print_menu_item("^_F^:ollows");
|
||||
tui_print_menu_item("^_K^:ind/event dump");
|
||||
tui_print_menu_item("^_N^:otifications");
|
||||
tui_print_menu_item("^_B^:logs/posts");
|
||||
tui_print_menu_item("^_L^:ive feeds");
|
||||
tui_print_menu_item("^_M^:essage");
|
||||
tui_print_menu_item("To^_d^:o");
|
||||
tui_print_menu_item("^_J^:ournal");
|
||||
tui_print_menu_item("^_A^:i");
|
||||
tui_print_menu_item("^_E^:cash");
|
||||
tui_print_menu_item("^_Q^:uit");
|
||||
}
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'q' || input[0] == 'Q' || input[0] == 'x' || input[0] == 'X') {
|
||||
tui_print("\nHasta luego.\n");
|
||||
@@ -193,9 +201,15 @@ int main(int argc, char *argv[]) {
|
||||
}
|
||||
|
||||
tui_init();
|
||||
tui_install_resize_handler();
|
||||
tui_set_window_title("Nostr Terminal");
|
||||
state_init();
|
||||
snprintf(g_state.version, sizeof(g_state.version), "%s", NT_VERSION);
|
||||
{
|
||||
char app_title[64];
|
||||
snprintf(app_title, sizeof(app_title), "NOSTR TERMINAL %s", g_state.version);
|
||||
tui_set_app_title(app_title);
|
||||
}
|
||||
|
||||
if (nostr_init() != 0) {
|
||||
fprintf(stderr, "Failed to initialize nostr library.\n");
|
||||
@@ -205,9 +219,11 @@ int main(int argc, char *argv[]) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
tui_show_splash();
|
||||
menu_main();
|
||||
|
||||
nostr_cleanup();
|
||||
signer_shutdown();
|
||||
state_cleanup();
|
||||
tui_cleanup();
|
||||
db_close();
|
||||
|
||||
@@ -492,6 +492,14 @@ static char *ai_extract_response(const char *resp_json) {
|
||||
}
|
||||
|
||||
static void ai_chat(const ai_prefs_t *prefs) {
|
||||
static const tui_view_t ai_chat_view = {
|
||||
"> Main Menu > AI > Chat",
|
||||
NULL,
|
||||
};
|
||||
static const tui_view_t ai_response_view = {
|
||||
"> Main Menu > AI > Chat > Response",
|
||||
NULL,
|
||||
};
|
||||
char prompt[4096];
|
||||
cJSON *root;
|
||||
cJSON *messages;
|
||||
@@ -500,20 +508,23 @@ static void ai_chat(const ai_prefs_t *prefs) {
|
||||
char auth[4096];
|
||||
char *resp_json;
|
||||
char *answer;
|
||||
char hold[16];
|
||||
char hold[512];
|
||||
|
||||
if (!prefs) {
|
||||
return;
|
||||
}
|
||||
|
||||
tui_get_line("prompt >", prompt, (int)sizeof(prompt));
|
||||
tui_begin_frame(&ai_chat_view);
|
||||
tui_print("Model: %s", prefs->model ? prefs->model : "");
|
||||
(void)tui_end_frame_with_prompt("prompt >", prompt, (int)sizeof(prompt));
|
||||
if (prompt[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!prefs->api_key || prefs->api_key[0] == '\0') {
|
||||
tui_begin_frame(&ai_chat_view);
|
||||
tui_print("AI API key is empty. Configure settings first.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -547,23 +558,23 @@ static void ai_chat(const ai_prefs_t *prefs) {
|
||||
free(body);
|
||||
|
||||
if (!resp_json) {
|
||||
tui_begin_frame(&ai_chat_view);
|
||||
tui_print("AI request failed.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
answer = ai_extract_response(resp_json);
|
||||
free(resp_json);
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("AI RESPONSE\n");
|
||||
tui_begin_frame(&ai_response_view);
|
||||
if (answer) {
|
||||
tui_print("%s", answer);
|
||||
} else {
|
||||
tui_print("Failed to parse AI response.");
|
||||
}
|
||||
free(answer);
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
}
|
||||
|
||||
static void ai_choose_model(ai_prefs_t *prefs) {
|
||||
@@ -582,62 +593,72 @@ static void ai_choose_model(ai_prefs_t *prefs) {
|
||||
}
|
||||
|
||||
static void ai_settings(ai_prefs_t *prefs) {
|
||||
static const tui_view_t ai_settings_view = {
|
||||
"> Main Menu > AI > Settings",
|
||||
NULL,
|
||||
};
|
||||
char endpoint[512];
|
||||
char key[1024];
|
||||
char model[256];
|
||||
char hold[16];
|
||||
char hold[512];
|
||||
|
||||
if (!prefs) {
|
||||
return;
|
||||
}
|
||||
|
||||
tui_begin_frame(&ai_settings_view);
|
||||
tui_print("Leave empty to keep current value.");
|
||||
|
||||
tui_print("Current endpoint: %s", prefs->endpoint ? prefs->endpoint : "");
|
||||
tui_get_line("new endpoint >", endpoint, (int)sizeof(endpoint));
|
||||
(void)tui_end_frame_with_prompt("new endpoint >", endpoint, (int)sizeof(endpoint));
|
||||
if (endpoint[0] != '\0') {
|
||||
ai_set_string(&prefs->endpoint, endpoint);
|
||||
}
|
||||
|
||||
tui_begin_frame(&ai_settings_view);
|
||||
tui_print("Current API key: %s", (prefs->api_key && prefs->api_key[0] != '\0') ? "(set)" : "(empty)");
|
||||
tui_get_line("new api key >", key, (int)sizeof(key));
|
||||
(void)tui_end_frame_with_prompt("new api key >", key, (int)sizeof(key));
|
||||
if (key[0] != '\0') {
|
||||
ai_set_string(&prefs->api_key, key);
|
||||
}
|
||||
|
||||
tui_begin_frame(&ai_settings_view);
|
||||
tui_print("Current model: %s", prefs->model ? prefs->model : "");
|
||||
tui_get_line("new model >", model, (int)sizeof(model));
|
||||
(void)tui_end_frame_with_prompt("new model >", model, (int)sizeof(model));
|
||||
if (model[0] != '\0') {
|
||||
ai_set_string(&prefs->model, model);
|
||||
}
|
||||
|
||||
tui_begin_frame(&ai_settings_view);
|
||||
if (ai_save_prefs(prefs) != 0) {
|
||||
tui_print("Failed to save prefs.");
|
||||
} else {
|
||||
tui_print("Prefs saved.");
|
||||
}
|
||||
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
}
|
||||
|
||||
void menu_ai(void) {
|
||||
static const tui_view_t ai_view = {
|
||||
"> Main Menu > AI",
|
||||
NULL,
|
||||
};
|
||||
ai_prefs_t prefs;
|
||||
char input[16];
|
||||
char input[512];
|
||||
|
||||
ai_prefs_init(&prefs);
|
||||
(void)ai_load_prefs(&prefs);
|
||||
|
||||
while (1) {
|
||||
tui_clear_screen();
|
||||
tui_print("AI\n");
|
||||
tui_begin_frame(&ai_view);
|
||||
tui_print("Model: %s", prefs.model ? prefs.model : "");
|
||||
tui_print("Endpoint: %s", prefs.endpoint ? prefs.endpoint : "");
|
||||
tui_print("^_C^:hat");
|
||||
tui_print("^_M^:odel");
|
||||
tui_print("^_S^:ettings");
|
||||
tui_print("^_B^:ack");
|
||||
tui_print_menu_item("^_C^:hat");
|
||||
tui_print_menu_item("^_M^:odel");
|
||||
tui_print_menu_item("^_S^:ettings");
|
||||
tui_print_menu_item("^_X^:Exit");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'b' || input[0] == 'B' ||
|
||||
input[0] == 'x' || input[0] == 'X' ||
|
||||
|
||||
@@ -250,6 +250,14 @@ static int diary_cmp_desc_date(const void *a, const void *b) {
|
||||
}
|
||||
|
||||
static void diary_browse_past(void) {
|
||||
static const tui_view_t diary_past_view = {
|
||||
"> Main Menu > Diary > Past Entries",
|
||||
NULL,
|
||||
};
|
||||
static const tui_view_t diary_entry_view = {
|
||||
"> Main Menu > Diary > Past Entries > View",
|
||||
NULL,
|
||||
};
|
||||
const char **read_relays;
|
||||
int relay_count = 0;
|
||||
char filter[256];
|
||||
@@ -267,8 +275,9 @@ static void diary_browse_past(void) {
|
||||
g_state.npub_hex);
|
||||
|
||||
if (nt_query_sync(filter, read_relays, relay_count, 7000, &events, &event_count) != 0) {
|
||||
tui_begin_frame(&diary_past_view);
|
||||
tui_print("Failed to query diary entries.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -326,17 +335,17 @@ static void diary_browse_past(void) {
|
||||
}
|
||||
|
||||
while (1) {
|
||||
tui_clear_screen();
|
||||
tui_print("PAST DIARY ENTRIES\n");
|
||||
tui_begin_frame(&diary_past_view);
|
||||
for (i = 0; i < entries_count; i++) {
|
||||
tui_print("%2d - %s", i + 1, entries[i].date_d ? entries[i].date_d : "(no d-tag)");
|
||||
}
|
||||
if (entries_count == 0) {
|
||||
tui_print("No diary entries found.");
|
||||
}
|
||||
tui_print("\nEnter number to view, x to back.");
|
||||
tui_print("");
|
||||
tui_print("Enter number to view, x to back.");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
if (input[0] == 'x' || input[0] == 'X' || input[0] == 'q' || input[0] == 'Q') {
|
||||
break;
|
||||
}
|
||||
@@ -346,10 +355,11 @@ static void diary_browse_past(void) {
|
||||
if (idx < 0 || idx >= entries_count) {
|
||||
continue;
|
||||
}
|
||||
tui_clear_screen();
|
||||
tui_print("DIARY %s\n", entries[idx].date_d ? entries[idx].date_d : "");
|
||||
tui_begin_frame(&diary_entry_view);
|
||||
tui_print("Date: %s", entries[idx].date_d ? entries[idx].date_d : "");
|
||||
tui_print("");
|
||||
tui_print("%s", entries[idx].content ? entries[idx].content : "");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -357,29 +367,33 @@ static void diary_browse_past(void) {
|
||||
}
|
||||
|
||||
void menu_diary(void) {
|
||||
static const tui_view_t diary_view = {
|
||||
"> Main Menu > Diary",
|
||||
NULL,
|
||||
};
|
||||
char today_d[16];
|
||||
char *existing = NULL;
|
||||
char *edited = NULL;
|
||||
char input[16];
|
||||
|
||||
if (diary_get_today_d(today_d) != 0) {
|
||||
tui_begin_frame(&diary_view);
|
||||
tui_print("Failed to compute today's date.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
existing = diary_load_entry_plain(today_d);
|
||||
|
||||
while (1) {
|
||||
tui_clear_screen();
|
||||
tui_print("DIARY\n");
|
||||
tui_begin_frame(&diary_view);
|
||||
tui_print("Today: %s", today_d);
|
||||
tui_print("Existing entry: %s", existing ? "yes" : "no");
|
||||
tui_print("^_E^:dit today's entry");
|
||||
tui_print("^_B^:rowse past entries");
|
||||
tui_print("^_Q^:uit");
|
||||
tui_print_menu_item("^_E^:dit today's entry");
|
||||
tui_print_menu_item("^_B^:rowse past entries");
|
||||
tui_print_menu_item("^_X^:Exit");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'q' || input[0] == 'Q' ||
|
||||
input[0] == 'x' || input[0] == 'X') {
|
||||
@@ -389,8 +403,9 @@ void menu_diary(void) {
|
||||
if (input[0] == 'e' || input[0] == 'E') {
|
||||
edited = editor_launch(existing ? existing : "");
|
||||
if (!edited) {
|
||||
tui_begin_frame(&diary_view);
|
||||
tui_print("Edit canceled.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -399,7 +414,9 @@ void menu_diary(void) {
|
||||
existing = diary_strdup(edited);
|
||||
}
|
||||
free(edited);
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
tui_begin_frame(&diary_view);
|
||||
tui_print("Diary save attempted.");
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
} else if (input[0] == 'b' || input[0] == 'B') {
|
||||
diary_browse_past();
|
||||
}
|
||||
|
||||
@@ -133,6 +133,10 @@ static int dm_parse_recipient_hex(const char *input, char out_hex[65]) {
|
||||
}
|
||||
|
||||
static void menu_dm_send(void) {
|
||||
static const tui_view_t dm_send_view = {
|
||||
"> Main Menu > DM > Send",
|
||||
NULL,
|
||||
};
|
||||
char who[256];
|
||||
char msg[4096];
|
||||
char recip_hex[65] = {0};
|
||||
@@ -147,28 +151,28 @@ static void menu_dm_send(void) {
|
||||
int any_ok = 0;
|
||||
char hold[16];
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("SEND DM\n");
|
||||
|
||||
tui_get_line("recipient (npub or hex) >", who, (int)sizeof(who));
|
||||
tui_begin_frame(&dm_send_view);
|
||||
(void)tui_end_frame_with_prompt("recipient (npub or hex) >", who, (int)sizeof(who));
|
||||
if (who[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dm_parse_recipient_hex(who, recip_hex) != 0) {
|
||||
tui_begin_frame(&dm_send_view);
|
||||
tui_print("Invalid recipient. Use npub or 64-char hex pubkey.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
tui_get_line("message >", msg, (int)sizeof(msg));
|
||||
(void)tui_end_frame_with_prompt("message >", msg, (int)sizeof(msg));
|
||||
if (msg[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (signer_get_local_private_key(priv) != 0) {
|
||||
tui_begin_frame(&dm_send_view);
|
||||
tui_print("Private key unavailable for DM send.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -181,20 +185,23 @@ static void menu_dm_send(void) {
|
||||
NULL,
|
||||
g_state.npub_hex);
|
||||
if (!rumor) {
|
||||
tui_begin_frame(&dm_send_view);
|
||||
tui_print("Failed to build NIP-17 DM rumor.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
wrap_count = nostr_nip17_send_dm(rumor, recips, 1, priv, gift_wraps, 8, 0);
|
||||
cJSON_Delete(rumor);
|
||||
if (wrap_count <= 0) {
|
||||
tui_begin_frame(&dm_send_view);
|
||||
tui_print("Failed to create NIP-17 gift wrap event(s).");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
write_relays = state_get_write_relays(&relay_count);
|
||||
tui_begin_frame(&dm_send_view);
|
||||
tui_print("Publishing %d wrapped DM event(s) to %d relay(s)...", wrap_count, relay_count);
|
||||
|
||||
for (i = 0; i < wrap_count; i++) {
|
||||
@@ -225,7 +232,7 @@ static void menu_dm_send(void) {
|
||||
tui_print("DM publish failed.");
|
||||
}
|
||||
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
}
|
||||
|
||||
static char *dm_try_decrypt_kind4(const char *sender_pub_hex, const char *ciphertext) {
|
||||
@@ -397,13 +404,16 @@ static void dm_collect_nip17_kind1059(dm_item_t **items, int *items_count) {
|
||||
}
|
||||
|
||||
static void menu_dm_read_inbox(void) {
|
||||
static const tui_view_t dm_inbox_view = {
|
||||
"> Main Menu > DM > Inbox",
|
||||
NULL,
|
||||
};
|
||||
dm_item_t *items = NULL;
|
||||
int items_count = 0;
|
||||
int i;
|
||||
char hold[16];
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("READ INBOX\n");
|
||||
tui_begin_frame(&dm_inbox_view);
|
||||
tui_print("Querying kind 1059 + kind 4...");
|
||||
|
||||
dm_collect_nip17_kind1059(&items, &items_count);
|
||||
@@ -413,8 +423,7 @@ static void menu_dm_read_inbox(void) {
|
||||
qsort(items, (size_t)items_count, sizeof(dm_item_t), dm_cmp_desc_created);
|
||||
}
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("INBOX\n");
|
||||
tui_begin_frame(&dm_inbox_view);
|
||||
|
||||
for (i = 0; i < items_count; i++) {
|
||||
char sender_short[32];
|
||||
@@ -427,20 +436,23 @@ static void menu_dm_read_inbox(void) {
|
||||
}
|
||||
|
||||
dm_free_items(items, items_count);
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", hold, (int)sizeof(hold));
|
||||
}
|
||||
|
||||
void menu_dm(void) {
|
||||
static const tui_view_t dm_view = {
|
||||
"> Main Menu > DM",
|
||||
NULL,
|
||||
};
|
||||
char input[16];
|
||||
|
||||
while (1) {
|
||||
tui_clear_screen();
|
||||
tui_print("DIRECT MESSAGE\n");
|
||||
tui_print("^_S^:end DM (NIP-17)");
|
||||
tui_print("^_R^:ead inbox (kind 1059 + kind 4)");
|
||||
tui_print("^_B^:ack");
|
||||
tui_begin_frame(&dm_view);
|
||||
tui_print_menu_item("^_S^:end DM (NIP-17)");
|
||||
tui_print_menu_item("^_R^:ead inbox (kind 1059 + kind 4)");
|
||||
tui_print_menu_item("^_X^:Exit");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'b' || input[0] == 'B' ||
|
||||
input[0] == 'x' || input[0] == 'X' ||
|
||||
|
||||
159
src/menu_ecash.c
159
src/menu_ecash.c
@@ -406,12 +406,30 @@ static uint64_t ecash_wallet_total(const ecash_wallet_t *w) {
|
||||
return total;
|
||||
}
|
||||
|
||||
static void ecash_print_mint_row_responsive(int term_width, int index, const char *mint_url, uint64_t mint_total) {
|
||||
if (term_width < 90) {
|
||||
tui_print("%2d. %s", index, mint_url ? mint_url : "");
|
||||
tui_print(" %llu sats", (unsigned long long)mint_total);
|
||||
return;
|
||||
}
|
||||
|
||||
tui_print("%2d. %-48s %12llu sats",
|
||||
index,
|
||||
mint_url ? mint_url : "",
|
||||
(unsigned long long)mint_total);
|
||||
}
|
||||
|
||||
static void ecash_show_balance(const ecash_wallet_t *w) {
|
||||
static const tui_view_t ecash_balance_view = {
|
||||
"> Main Menu > Ecash > Balance",
|
||||
NULL,
|
||||
};
|
||||
int i;
|
||||
int term_width = 0;
|
||||
char hold[16];
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("ECASH BALANCE\n");
|
||||
tui_begin_frame(&ecash_balance_view);
|
||||
tui_get_terminal_size(&term_width, NULL);
|
||||
tui_print("Total: %llu sats", (unsigned long long)ecash_wallet_total(w));
|
||||
tui_print("");
|
||||
|
||||
@@ -425,30 +443,36 @@ static void ecash_show_balance(const ecash_wallet_t *w) {
|
||||
}
|
||||
}
|
||||
|
||||
tui_print("%2d. %s", i + 1, w->mints[i]);
|
||||
tui_print(" balance: %llu sats", (unsigned long long)mint_total);
|
||||
ecash_print_mint_row_responsive(term_width, i + 1, w->mints[i], mint_total);
|
||||
}
|
||||
|
||||
if (w->mint_count == 0) {
|
||||
tui_print("No mints configured.");
|
||||
}
|
||||
|
||||
tui_print("\nProof count: %d", w->proof_count);
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
tui_print("");
|
||||
tui_print("Proof count: %d", w->proof_count);
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", hold, (int)sizeof(hold));
|
||||
}
|
||||
|
||||
static void ecash_add_mint_menu(ecash_wallet_t *w) {
|
||||
static const tui_view_t ecash_add_mint_view = {
|
||||
"> Main Menu > Ecash > Add Mint",
|
||||
NULL,
|
||||
};
|
||||
char mint[512];
|
||||
char hold[16];
|
||||
|
||||
tui_get_line("mint url >", mint, (int)sizeof(mint));
|
||||
tui_begin_frame(&ecash_add_mint_view);
|
||||
(void)tui_end_frame_with_prompt("mint url >", mint, (int)sizeof(mint));
|
||||
if (mint[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
tui_begin_frame(&ecash_add_mint_view);
|
||||
if (ecash_add_mint(w, mint) != 0) {
|
||||
tui_print("Failed to add mint.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -457,30 +481,48 @@ static void ecash_add_mint_menu(ecash_wallet_t *w) {
|
||||
} else {
|
||||
tui_print("Mint added.");
|
||||
}
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
}
|
||||
|
||||
static void ecash_remove_mint_menu(ecash_wallet_t *w) {
|
||||
static const tui_view_t ecash_remove_mint_view = {
|
||||
"> Main Menu > Ecash > Remove Mint",
|
||||
NULL,
|
||||
};
|
||||
char input[64];
|
||||
char yn[8];
|
||||
int idx;
|
||||
int i;
|
||||
char hold[16];
|
||||
|
||||
if (!w || w->mint_count <= 0) {
|
||||
tui_begin_frame(&ecash_remove_mint_view);
|
||||
tui_print("No mints to remove.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
tui_begin_frame(&ecash_remove_mint_view);
|
||||
for (i = 0; i < w->mint_count; i++) {
|
||||
tui_print("%2d. %s", i + 1, w->mints[i]);
|
||||
}
|
||||
|
||||
tui_get_line("remove number >", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("remove number >", input, (int)sizeof(input));
|
||||
idx = atoi(input) - 1;
|
||||
if (idx < 0 || idx >= w->mint_count) {
|
||||
tui_begin_frame(&ecash_remove_mint_view);
|
||||
tui_print("Invalid mint number.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
tui_begin_frame(&ecash_remove_mint_view);
|
||||
tui_print("Remove mint: %s", w->mints[idx] ? w->mints[idx] : "");
|
||||
(void)tui_end_frame_with_prompt("Confirm remove [y/n] >", yn, (int)sizeof(yn));
|
||||
if (!(yn[0] == 'y' || yn[0] == 'Y')) {
|
||||
tui_begin_frame(&ecash_remove_mint_view);
|
||||
tui_print("Remove canceled.");
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -490,15 +532,20 @@ static void ecash_remove_mint_menu(ecash_wallet_t *w) {
|
||||
}
|
||||
w->mint_count--;
|
||||
|
||||
tui_begin_frame(&ecash_remove_mint_view);
|
||||
if (ecash_publish_wallet(w) != 0) {
|
||||
tui_print("Failed to publish wallet metadata.");
|
||||
} else {
|
||||
tui_print("Mint removed.");
|
||||
}
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
}
|
||||
|
||||
static void ecash_receive_token_menu(ecash_wallet_t *w) {
|
||||
static const tui_view_t ecash_import_view = {
|
||||
"> Main Menu > Ecash > Import Token",
|
||||
NULL,
|
||||
};
|
||||
char token[8192];
|
||||
cashu_decoded_token_t decoded;
|
||||
int i;
|
||||
@@ -509,21 +556,24 @@ static void ecash_receive_token_menu(ecash_wallet_t *w) {
|
||||
}
|
||||
|
||||
memset(&decoded, 0, sizeof(decoded));
|
||||
tui_get_line("paste cashu token >", token, (int)sizeof(token));
|
||||
tui_begin_frame(&ecash_import_view);
|
||||
(void)tui_end_frame_with_prompt("paste cashu token >", token, (int)sizeof(token));
|
||||
if (token[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cashu_decode_token(token, &decoded) != 0) {
|
||||
tui_begin_frame(&ecash_import_view);
|
||||
tui_print("Failed to decode token.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!decoded.mint_url || decoded.proof_count <= 0 || !decoded.proofs) {
|
||||
cashu_free_decoded_token(&decoded);
|
||||
tui_begin_frame(&ecash_import_view);
|
||||
tui_print("Token did not contain usable proofs.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -534,12 +584,13 @@ static void ecash_receive_token_menu(ecash_wallet_t *w) {
|
||||
|
||||
cashu_free_decoded_token(&decoded);
|
||||
|
||||
tui_begin_frame(&ecash_import_view);
|
||||
if (ecash_publish_wallet(w) != 0) {
|
||||
tui_print("Token imported locally, but publish failed.");
|
||||
} else {
|
||||
tui_print("Token received and wallet updated.");
|
||||
}
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
}
|
||||
|
||||
static void ecash_remove_proofs_by_global_indices(ecash_wallet_t *w, const int *indices, int nidx) {
|
||||
@@ -582,6 +633,14 @@ static void ecash_remove_proofs_by_global_indices(ecash_wallet_t *w, const int *
|
||||
}
|
||||
|
||||
static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
static const tui_view_t ecash_send_view = {
|
||||
"> Main Menu > Ecash > Send Token",
|
||||
NULL,
|
||||
};
|
||||
static const tui_view_t ecash_send_result_view = {
|
||||
"> Main Menu > Ecash > Send Token > Result",
|
||||
NULL,
|
||||
};
|
||||
char input[128];
|
||||
int mint_idx;
|
||||
uint64_t target_amount;
|
||||
@@ -602,28 +661,34 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
}
|
||||
|
||||
if (w->mint_count <= 0) {
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("No mints configured.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
for (i = 0; i < w->mint_count; i++) {
|
||||
tui_print("%2d. %s", i + 1, w->mints[i]);
|
||||
}
|
||||
|
||||
tui_get_line("mint number >", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("mint number >", input, (int)sizeof(input));
|
||||
mint_idx = atoi(input) - 1;
|
||||
if (mint_idx < 0 || mint_idx >= w->mint_count) {
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Invalid mint number.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
tui_get_line("amount (sats) >", input, (int)sizeof(input));
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Mint: %s", w->mints[mint_idx]);
|
||||
(void)tui_end_frame_with_prompt("amount (sats) >", input, (int)sizeof(input));
|
||||
target_amount = (uint64_t)strtoull(input, NULL, 10);
|
||||
if (target_amount == 0) {
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Invalid amount.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -636,8 +701,9 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
if (!next_proofs) {
|
||||
free(mint_proofs);
|
||||
free(mint_global_indices);
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Memory error.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -647,8 +713,9 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
if (!next_idx) {
|
||||
free(mint_proofs);
|
||||
free(mint_global_indices);
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Memory error.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -662,8 +729,9 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
if (mint_proof_count <= 0) {
|
||||
free(mint_proofs);
|
||||
free(mint_global_indices);
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("No proofs available for selected mint.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -671,8 +739,9 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
if (!selected_local) {
|
||||
free(mint_proofs);
|
||||
free(mint_global_indices);
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Memory error.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -686,8 +755,9 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
free(selected_local);
|
||||
free(mint_proofs);
|
||||
free(mint_global_indices);
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Could not select proofs for that amount.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -703,8 +773,9 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
free(mint_global_indices);
|
||||
free(selected_global);
|
||||
cashu_free_decoded_token(&token);
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Memory error.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -719,8 +790,9 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
free(mint_global_indices);
|
||||
free(selected_global);
|
||||
cashu_free_decoded_token(&token);
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Failed to assemble token proofs.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -734,23 +806,23 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
free(mint_global_indices);
|
||||
free(selected_global);
|
||||
cashu_free_decoded_token(&token);
|
||||
tui_begin_frame(&ecash_send_view);
|
||||
tui_print("Failed to encode token.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
return;
|
||||
}
|
||||
|
||||
ecash_remove_proofs_by_global_indices(w, selected_global, selected_count);
|
||||
(void)ecash_publish_wallet(w);
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("SEND TOKEN\n");
|
||||
tui_begin_frame(&ecash_send_result_view);
|
||||
tui_print("Requested: %llu sats", (unsigned long long)target_amount);
|
||||
tui_print("Selected: %llu sats", (unsigned long long)selected_total);
|
||||
tui_print("Token:");
|
||||
tui_print("%s", encoded);
|
||||
tui_print("");
|
||||
tui_print("Note: Mint HTTP swap/melt flows are not yet implemented in this MVP.");
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", hold, (int)sizeof(hold));
|
||||
|
||||
free(encoded);
|
||||
free(selected_local);
|
||||
@@ -761,6 +833,10 @@ static void ecash_send_token_menu(ecash_wallet_t *w) {
|
||||
}
|
||||
|
||||
void menu_ecash(void) {
|
||||
static const tui_view_t ecash_view = {
|
||||
"> Main Menu > Ecash",
|
||||
NULL,
|
||||
};
|
||||
ecash_wallet_t wallet;
|
||||
char input[16];
|
||||
|
||||
@@ -769,18 +845,17 @@ void menu_ecash(void) {
|
||||
}
|
||||
|
||||
while (1) {
|
||||
tui_clear_screen();
|
||||
tui_print("ECASH WALLET\n");
|
||||
tui_begin_frame(&ecash_view);
|
||||
tui_print("Mints: %d", wallet.mint_count);
|
||||
tui_print("Proofs: %d", wallet.proof_count);
|
||||
tui_print("^_B^:alance");
|
||||
tui_print("^_A^:dd mint");
|
||||
tui_print("^_R^:emove mint");
|
||||
tui_print("^_I^:mport token");
|
||||
tui_print("^_S^:end token");
|
||||
tui_print("E^_x^:it");
|
||||
tui_print_menu_item("^_B^:alance");
|
||||
tui_print_menu_item("^_A^:dd mint");
|
||||
tui_print_menu_item("^_R^:emove mint");
|
||||
tui_print_menu_item("^_I^:mport token");
|
||||
tui_print_menu_item("^_S^:end token");
|
||||
tui_print_menu_item("E^_x^:it");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'x' || input[0] == 'X' ||
|
||||
input[0] == 'q' || input[0] == 'Q') {
|
||||
|
||||
@@ -30,7 +30,23 @@ static int follows_publish_kind3(cJSON *event_obj) {
|
||||
8000);
|
||||
}
|
||||
|
||||
static void follows_print_row_responsive(int term_width, int index, const char *npub, const char *name) {
|
||||
if (term_width < 90) {
|
||||
tui_print("[%2d] %s", index, npub ? npub : "");
|
||||
if (name && name[0] != '\0') {
|
||||
tui_print(" %s", name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
tui_print("[%2d] %s %s", index, npub ? npub : "", name ? name : "");
|
||||
}
|
||||
|
||||
void menu_follows(void) {
|
||||
static const tui_view_t follows_view = {
|
||||
"> Main Menu > Follows",
|
||||
NULL,
|
||||
};
|
||||
cJSON *working = NULL;
|
||||
cJSON *tags = NULL;
|
||||
char input[256];
|
||||
@@ -53,10 +69,10 @@ void menu_follows(void) {
|
||||
while (1) {
|
||||
int i;
|
||||
int n;
|
||||
int term_width = 0;
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("FOLLOWS");
|
||||
tui_print("\n\n");
|
||||
tui_begin_frame(&follows_view);
|
||||
tui_get_terminal_size(&term_width, NULL);
|
||||
|
||||
n = cJSON_GetArraySize(tags);
|
||||
for (i = 0; i < n; i++) {
|
||||
@@ -81,20 +97,20 @@ void menu_follows(void) {
|
||||
}
|
||||
}
|
||||
|
||||
tui_print("[%2d] %s %s",
|
||||
i + 1,
|
||||
npub,
|
||||
(cJSON_IsString(t3) && t3->valuestring) ? t3->valuestring : "");
|
||||
follows_print_row_responsive(term_width,
|
||||
i + 1,
|
||||
npub,
|
||||
(cJSON_IsString(t3) && t3->valuestring) ? t3->valuestring : "");
|
||||
}
|
||||
|
||||
tui_print("\n\n");
|
||||
tui_print("^_R^:efresh profile");
|
||||
tui_print("^_A^:dd follow");
|
||||
tui_print("^_D^:elete follow");
|
||||
tui_print("^_P^:ost changes and exit.");
|
||||
tui_print("E^_x^:it without saving");
|
||||
tui_print("");
|
||||
tui_print_menu_item("^_R^:efresh profile");
|
||||
tui_print_menu_item("^_A^:dd follow");
|
||||
tui_print_menu_item("^_D^:elete follow");
|
||||
tui_print_menu_item("^_P^:ost changes and exit.");
|
||||
tui_print_menu_item("E^_x^:it without saving");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'a' || input[0] == 'A') {
|
||||
char who[256];
|
||||
@@ -107,7 +123,8 @@ void menu_follows(void) {
|
||||
cJSON *meta;
|
||||
cJSON *name;
|
||||
|
||||
tui_get_line("npub to add >", who, (int)sizeof(who));
|
||||
tui_begin_frame(&follows_view);
|
||||
(void)tui_end_frame_with_prompt("npub to add >", who, (int)sizeof(who));
|
||||
if (who[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
@@ -115,22 +132,26 @@ void menu_follows(void) {
|
||||
if (strncmp(who, "npub", 4) == 0) {
|
||||
unsigned char pub[32];
|
||||
if (nostr_decode_npub(who, pub) != 0) {
|
||||
tui_begin_frame(&follows_view);
|
||||
tui_print("Invalid npub.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
nostr_bytes_to_hex(pub, 32, follow_hex);
|
||||
} else if (strlen(who) == 64) {
|
||||
unsigned char pub[32];
|
||||
if (nostr_hex_to_bytes(who, pub, 32) != 0) {
|
||||
tui_begin_frame(&follows_view);
|
||||
tui_print("Invalid hex pubkey.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
snprintf(follow_hex, sizeof(follow_hex), "%s", who);
|
||||
strncpy(follow_hex, who, sizeof(follow_hex) - 1U);
|
||||
follow_hex[sizeof(follow_hex) - 1U] = '\0';
|
||||
} else {
|
||||
tui_begin_frame(&follows_view);
|
||||
tui_print("Enter npub or 64-char hex pubkey.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -140,8 +161,9 @@ void menu_follows(void) {
|
||||
g_state.read_relay_count > 0 ? g_state.read_relay_count : g_state.bootstrap_relay_count,
|
||||
7000,
|
||||
&follow_kind0) != 0 || !follow_kind0) {
|
||||
tui_begin_frame(&follows_view);
|
||||
tui_print("Can't find user. Try adding more relays.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -158,11 +180,12 @@ void menu_follows(void) {
|
||||
}
|
||||
}
|
||||
|
||||
tui_get_line((cJSON_IsString(name) && name->valuestring)
|
||||
? "Add follow [y/n] >"
|
||||
: "Add this follow [y/n] >",
|
||||
yn,
|
||||
(int)sizeof(yn));
|
||||
tui_begin_frame(&follows_view);
|
||||
(void)tui_end_frame_with_prompt((cJSON_IsString(name) && name->valuestring)
|
||||
? "Add follow [y/n] >"
|
||||
: "Add this follow [y/n] >",
|
||||
yn,
|
||||
(int)sizeof(yn));
|
||||
if (yn[0] == 'y' || yn[0] == 'Y') {
|
||||
cJSON *tag = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(tag, cJSON_CreateString("p"));
|
||||
@@ -180,13 +203,14 @@ void menu_follows(void) {
|
||||
char yn[32];
|
||||
int idx;
|
||||
|
||||
tui_get_line("# to delete >", numbuf, (int)sizeof(numbuf));
|
||||
tui_begin_frame(&follows_view);
|
||||
(void)tui_end_frame_with_prompt("# to delete >", numbuf, (int)sizeof(numbuf));
|
||||
idx = atoi(numbuf) - 1;
|
||||
if (idx < 0 || idx >= cJSON_GetArraySize(tags)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
tui_get_line("Delete follow [y/n] >", yn, (int)sizeof(yn));
|
||||
(void)tui_end_frame_with_prompt("Delete follow [y/n] >", yn, (int)sizeof(yn));
|
||||
if (yn[0] == 'y' || yn[0] == 'Y') {
|
||||
cJSON_DeleteItemFromArray(tags, idx);
|
||||
}
|
||||
@@ -201,7 +225,8 @@ void menu_follows(void) {
|
||||
cJSON *content;
|
||||
cJSON *meta;
|
||||
|
||||
tui_get_line("#>", numbuf, (int)sizeof(numbuf));
|
||||
tui_begin_frame(&follows_view);
|
||||
(void)tui_end_frame_with_prompt("# >", numbuf, (int)sizeof(numbuf));
|
||||
idx = atoi(numbuf) - 1;
|
||||
if (idx < 0 || idx >= cJSON_GetArraySize(tags)) {
|
||||
continue;
|
||||
@@ -219,8 +244,9 @@ void menu_follows(void) {
|
||||
g_state.read_relay_count > 0 ? g_state.read_relay_count : g_state.bootstrap_relay_count,
|
||||
7000,
|
||||
&follow_kind0) != 0 || !follow_kind0) {
|
||||
tui_begin_frame(&follows_view);
|
||||
tui_print("Unable to fetch profile.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -233,7 +259,8 @@ void menu_follows(void) {
|
||||
}
|
||||
}
|
||||
|
||||
tui_print("PROFILE");
|
||||
tui_begin_frame(&follows_view);
|
||||
tui_print("Profile");
|
||||
if (meta) {
|
||||
cJSON *it = NULL;
|
||||
cJSON_ArrayForEach(it, meta) {
|
||||
@@ -245,7 +272,7 @@ void menu_follows(void) {
|
||||
tui_print("No metadata content.");
|
||||
}
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
cJSON_Delete(meta);
|
||||
cJSON_Delete(ev);
|
||||
free(follow_kind0);
|
||||
@@ -256,10 +283,14 @@ void menu_follows(void) {
|
||||
free(g_state.kind3_json);
|
||||
g_state.kind3_json = new_json;
|
||||
}
|
||||
|
||||
tui_begin_frame(&follows_view);
|
||||
if (posted < 0) {
|
||||
tui_print("Follows publish failed.");
|
||||
} else {
|
||||
tui_print("Follows published.");
|
||||
}
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
break;
|
||||
} else if (input[0] == 'x' || input[0] == 'X' ||
|
||||
input[0] == 'q' || input[0] == 'Q') {
|
||||
|
||||
@@ -60,7 +60,7 @@ static void live_preview_text(const char *text, int max_len, char *buf, size_t b
|
||||
buf[0] = '\0';
|
||||
|
||||
if (!text || text[0] == '\0' || max_len <= 0) {
|
||||
snprintf(buf, buf_size, "");
|
||||
buf[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -218,6 +218,10 @@ static void live_free_authors(char **authors, int count) {
|
||||
}
|
||||
|
||||
static void menu_live_run_filter(const char *title, const char *filter_json) {
|
||||
static const tui_view_t live_feed_view = {
|
||||
"> Main Menu > Live Feeds > Stream",
|
||||
NULL,
|
||||
};
|
||||
const char **read_relays;
|
||||
int relay_count = 0;
|
||||
live_feed_ctx_t ctx;
|
||||
@@ -225,8 +229,8 @@ static void menu_live_run_filter(const char *title, const char *filter_json) {
|
||||
char input[8];
|
||||
|
||||
read_relays = state_get_read_relays(&relay_count);
|
||||
tui_clear_screen();
|
||||
tui_print("%s\n", title);
|
||||
tui_begin_frame(&live_feed_view);
|
||||
tui_print("%s", title ? title : "Live Feed");
|
||||
tui_print("Press any key to stop...");
|
||||
|
||||
memset(&ctx, 0, sizeof(ctx));
|
||||
@@ -238,27 +242,30 @@ static void menu_live_run_filter(const char *title, const char *filter_json) {
|
||||
live_event_print_cb,
|
||||
&ctx);
|
||||
|
||||
tui_print("");
|
||||
tui_begin_frame(&live_feed_view);
|
||||
if (events_received < 0) {
|
||||
tui_print("Feed stopped (failed to connect to all relays).");
|
||||
} else {
|
||||
tui_print("Feed stopped. %d events received.", events_received);
|
||||
}
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
}
|
||||
|
||||
void menu_live(void) {
|
||||
static const tui_view_t live_menu_view = {
|
||||
"> Main Menu > Live Feeds",
|
||||
NULL,
|
||||
};
|
||||
char input[16];
|
||||
|
||||
while (1) {
|
||||
tui_clear_screen();
|
||||
tui_print("LIVE FEEDS\n");
|
||||
tui_print("^_F^:ollows feed");
|
||||
tui_print("^_M^:entions");
|
||||
tui_print("^_G^:lobal firehose");
|
||||
tui_print("^_B^:ack");
|
||||
tui_begin_frame(&live_menu_view);
|
||||
tui_print_menu_item("^_F^:ollows feed");
|
||||
tui_print_menu_item("^_M^:entions");
|
||||
tui_print_menu_item("^_G^:lobal firehose");
|
||||
tui_print_menu_item("^_X^:Exit");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'b' || input[0] == 'B' ||
|
||||
input[0] == 'x' || input[0] == 'X' ||
|
||||
@@ -277,8 +284,9 @@ void menu_live(void) {
|
||||
int i;
|
||||
|
||||
if (live_extract_follow_authors(&authors, &authors_count) != 0 || authors_count <= 0) {
|
||||
tui_begin_frame(&live_menu_view);
|
||||
tui_print("No follows found in kind3 list.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
live_free_authors(authors, authors_count);
|
||||
continue;
|
||||
}
|
||||
@@ -293,8 +301,9 @@ void menu_live(void) {
|
||||
cJSON_Delete(authors_json);
|
||||
cJSON_Delete(kinds_json);
|
||||
live_free_authors(authors, authors_count);
|
||||
tui_begin_frame(&live_menu_view);
|
||||
tui_print("Failed to build follows filter.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
228
src/menu_login.c
228
src/menu_login.c
@@ -17,6 +17,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <sys/random.h>
|
||||
|
||||
#define NT_DEV_SEED_PHRASE "cube dirt movie learn depth axis ball view aunt electric finish release"
|
||||
|
||||
@@ -93,6 +94,10 @@ static int menu_publish_signed_event(int kind, const char *content, cJSON *tags)
|
||||
}
|
||||
|
||||
static void menu_add_new_user(void) {
|
||||
static const tui_view_t login_new_profile_view = {
|
||||
"> Login > New Account > Profile Setup",
|
||||
NULL,
|
||||
};
|
||||
char buf[512];
|
||||
char username[128];
|
||||
cJSON *meta = cJSON_CreateObject();
|
||||
@@ -102,28 +107,35 @@ static void menu_add_new_user(void) {
|
||||
|
||||
username[0] = '\0';
|
||||
do {
|
||||
tui_get_line("Enter a username for this account (required) >", username, (int)sizeof(username));
|
||||
tui_begin_frame(&login_new_profile_view);
|
||||
(void)tui_end_frame_with_prompt("Enter a username for this account (required) >", username, (int)sizeof(username));
|
||||
} while (username[0] == '\0');
|
||||
|
||||
cJSON_AddStringToObject(meta, "name", username);
|
||||
cJSON_AddStringToObject(meta, "displayname", username);
|
||||
|
||||
tui_get_line("Enter \"about\" info: >", buf, (int)sizeof(buf));
|
||||
tui_begin_frame(&login_new_profile_view);
|
||||
(void)tui_end_frame_with_prompt("Enter \"about\" info: >", buf, (int)sizeof(buf));
|
||||
cJSON_AddStringToObject(meta, "about", buf);
|
||||
|
||||
tui_get_line("Enter profile picture url: >", buf, (int)sizeof(buf));
|
||||
tui_begin_frame(&login_new_profile_view);
|
||||
(void)tui_end_frame_with_prompt("Enter profile picture url: >", buf, (int)sizeof(buf));
|
||||
cJSON_AddStringToObject(meta, "picture", buf);
|
||||
|
||||
tui_get_line("Enter banner picture url: >", buf, (int)sizeof(buf));
|
||||
tui_begin_frame(&login_new_profile_view);
|
||||
(void)tui_end_frame_with_prompt("Enter banner picture url: >", buf, (int)sizeof(buf));
|
||||
cJSON_AddStringToObject(meta, "banner", buf);
|
||||
|
||||
tui_get_line("Enter website url: >", buf, (int)sizeof(buf));
|
||||
tui_begin_frame(&login_new_profile_view);
|
||||
(void)tui_end_frame_with_prompt("Enter website url: >", buf, (int)sizeof(buf));
|
||||
cJSON_AddStringToObject(meta, "website", buf);
|
||||
|
||||
tui_get_line("Enter lud16: >", buf, (int)sizeof(buf));
|
||||
tui_begin_frame(&login_new_profile_view);
|
||||
(void)tui_end_frame_with_prompt("Enter lud16: >", buf, (int)sizeof(buf));
|
||||
cJSON_AddStringToObject(meta, "lud16", buf);
|
||||
|
||||
tui_get_line("Enter nip05: >", buf, (int)sizeof(buf));
|
||||
tui_begin_frame(&login_new_profile_view);
|
||||
(void)tui_end_frame_with_prompt("Enter nip05: >", buf, (int)sizeof(buf));
|
||||
cJSON_AddStringToObject(meta, "nip05", buf);
|
||||
|
||||
free(g_state.kind0_json);
|
||||
@@ -152,6 +164,34 @@ static void menu_add_new_user(void) {
|
||||
}
|
||||
|
||||
void menu_login(void) {
|
||||
static const tui_view_t login_view = {
|
||||
"> Login",
|
||||
NULL,
|
||||
};
|
||||
static const tui_view_t login_privkey_view = {
|
||||
"> Login > Private Key",
|
||||
NULL,
|
||||
};
|
||||
static const tui_view_t login_mnemonic_view = {
|
||||
"> Login > Mnemonic",
|
||||
NULL,
|
||||
};
|
||||
static const tui_view_t login_signer_local_view = {
|
||||
"> Login > Signer Local",
|
||||
NULL,
|
||||
};
|
||||
static const tui_view_t login_signer_url_view = {
|
||||
"> Login > URL Signer",
|
||||
NULL,
|
||||
};
|
||||
static const tui_view_t login_signer_qrexec_view = {
|
||||
"> Login > Qrexec Signer",
|
||||
NULL,
|
||||
};
|
||||
static const tui_view_t login_new_account_view = {
|
||||
"> Login > New Account",
|
||||
NULL,
|
||||
};
|
||||
char input[512];
|
||||
char pending_test_mnemonic[512] = {0};
|
||||
int pending_test_mnemonic_ready = 0;
|
||||
@@ -160,32 +200,35 @@ void menu_login(void) {
|
||||
unsigned char priv[32];
|
||||
unsigned char pub[32];
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("LOGIN\n");
|
||||
tui_print("^_E^:nter test/dev seed fallback");
|
||||
tui_print("^_P^:rivate key (nsec or 64-hex)");
|
||||
tui_print("^_M^:nemonic (12 words)");
|
||||
tui_print("^_S^:igner local (same qube)");
|
||||
tui_print("^_U^:RL signer (FIPS/web address)");
|
||||
tui_print("^_N^:ew account");
|
||||
tui_print("^_Q^:uit");
|
||||
(void)tui_consume_resize_flag();
|
||||
tui_begin_frame(&login_view);
|
||||
tui_print_menu_item("^_E^:nter test/dev seed fallback");
|
||||
tui_print_menu_item("^_P^:rivate key (nsec or 64-hex)");
|
||||
tui_print_menu_item("^_M^:nemonic (12 words)");
|
||||
tui_print_menu_item("^_S^:igner local (same qube)");
|
||||
tui_print_menu_item("^_U^:RL signer (FIPS/web address)");
|
||||
tui_print_menu_item("^_N^:ew account");
|
||||
tui_print_menu_item("^_Q^:uit");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (strcmp(input, "q") == 0 || strcmp(input, "x") == 0) {
|
||||
tui_print("\nHasta luego.\n");
|
||||
signer_shutdown();
|
||||
exit(0);
|
||||
}
|
||||
|
||||
if (input[0] == 'e' || input[0] == 'E') {
|
||||
input[0] = '\0';
|
||||
} else if (input[0] == 'p' || input[0] == 'P') {
|
||||
tui_get_line("Private key (nsec or 64-hex) >", input, (int)sizeof(input));
|
||||
tui_begin_frame(&login_privkey_view);
|
||||
(void)tui_end_frame_with_prompt("Private key (nsec or 64-hex) >", input, (int)sizeof(input));
|
||||
if (input[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
} else if (input[0] == 'm' || input[0] == 'M') {
|
||||
tui_get_line("Seed phrase (12 words) >", input, (int)sizeof(input));
|
||||
tui_begin_frame(&login_mnemonic_view);
|
||||
(void)tui_end_frame_with_prompt("Seed phrase (12 words) >", input, (int)sizeof(input));
|
||||
if (input[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
@@ -196,12 +239,16 @@ void menu_login(void) {
|
||||
int name_count = 0;
|
||||
char pubkey_hex[65] = {0};
|
||||
int selected = 0;
|
||||
nt_nsigner_selector_t selector = nsigner_selector_default();
|
||||
char idxbuf[32];
|
||||
int nostr_index = 0;
|
||||
|
||||
tui_begin_frame(&login_signer_local_view);
|
||||
tui_print("Searching for running n_signer instances...");
|
||||
|
||||
if (nsigner_list(&names, &name_count) != 0 || name_count == 0) {
|
||||
tui_print("No running n_signer found. Start `nsigner` and try again.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -210,31 +257,44 @@ void menu_login(void) {
|
||||
selected = 0;
|
||||
} else {
|
||||
int idx;
|
||||
char idxbuf[16];
|
||||
char pickbuf[16];
|
||||
tui_print("Found %d n_signer instances:", name_count);
|
||||
for (idx = 0; idx < name_count; idx++) {
|
||||
tui_print(" [%d] @%s", idx, names[idx]);
|
||||
}
|
||||
tui_get_line("Select (0) >", idxbuf, (int)sizeof(idxbuf));
|
||||
selected = (idxbuf[0] != '\0') ? atoi(idxbuf) : 0;
|
||||
(void)tui_end_frame_with_prompt("Select (0) >", pickbuf, (int)sizeof(pickbuf));
|
||||
selected = (pickbuf[0] != '\0') ? atoi(pickbuf) : 0;
|
||||
if (selected < 0 || selected >= name_count) {
|
||||
selected = 0;
|
||||
}
|
||||
}
|
||||
|
||||
tui_begin_frame(&login_signer_local_view);
|
||||
(void)tui_end_frame_with_prompt("Seed phrase index (0) >", idxbuf, (int)sizeof(idxbuf));
|
||||
if (idxbuf[0] != '\0') {
|
||||
nostr_index = atoi(idxbuf);
|
||||
if (nostr_index < 0) {
|
||||
nostr_index = 0;
|
||||
}
|
||||
}
|
||||
selector.has_nostr_index = 1;
|
||||
selector.nostr_index = nostr_index;
|
||||
selector.role[0] = '\0';
|
||||
|
||||
tui_begin_frame(&login_signer_local_view);
|
||||
tui_print("Waiting for n_signer approval...");
|
||||
if (nsigner_get_public_key(names[selected], "main", pubkey_hex) != 0) {
|
||||
if (nsigner_get_public_key(names[selected], &selector, pubkey_hex) != 0) {
|
||||
int i;
|
||||
tui_print("Failed to get public key from n_signer (denied or error).");
|
||||
for (i = 0; i < name_count; i++) {
|
||||
free(names[i]);
|
||||
}
|
||||
free(names);
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
signer_init_nsigner(names[selected]);
|
||||
signer_init_nsigner(names[selected], &selector);
|
||||
|
||||
snprintf(g_state.npub_hex, sizeof(g_state.npub_hex), "%s", pubkey_hex);
|
||||
{
|
||||
@@ -273,31 +333,68 @@ void menu_login(void) {
|
||||
char pubkey_hex[65] = {0};
|
||||
char endpoint_url[256];
|
||||
const char *saved_endpoint = db_get_local("nsigner_url_endpoint");
|
||||
nt_nsigner_selector_t selector = nsigner_selector_default();
|
||||
char idxbuf[32];
|
||||
int nostr_index = 0;
|
||||
int session_fd = -1;
|
||||
nt_nsigner_auth_ctx_t auth = {0};
|
||||
|
||||
snprintf(endpoint_url, sizeof(endpoint_url), "%s", (saved_endpoint && saved_endpoint[0] != '\0') ? saved_endpoint : "");
|
||||
tui_get_line("Signer URL (http://<npub>.fips:8080) >", endpoint_url, (int)sizeof(endpoint_url));
|
||||
tui_begin_frame(&login_signer_url_view);
|
||||
(void)tui_end_frame_with_prompt("Signer URL (http://<npub>.fips:8080) >", endpoint_url, (int)sizeof(endpoint_url));
|
||||
if (endpoint_url[0] == '\0') {
|
||||
if (saved_endpoint && saved_endpoint[0] != '\0') {
|
||||
snprintf(endpoint_url, sizeof(endpoint_url), "%s", saved_endpoint);
|
||||
} else {
|
||||
tui_begin_frame(&login_signer_url_view);
|
||||
tui_print("No URL provided.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
tui_begin_frame(&login_signer_url_view);
|
||||
(void)tui_end_frame_with_prompt("Seed phrase index (0) >", idxbuf, (int)sizeof(idxbuf));
|
||||
if (idxbuf[0] != '\0') {
|
||||
nostr_index = atoi(idxbuf);
|
||||
if (nostr_index < 0) {
|
||||
nostr_index = 0;
|
||||
}
|
||||
}
|
||||
selector.has_nostr_index = 1;
|
||||
selector.nostr_index = nostr_index;
|
||||
selector.role[0] = '\0';
|
||||
|
||||
tui_begin_frame(&login_signer_url_view);
|
||||
tui_print("Connecting to n_signer URL: %s", endpoint_url);
|
||||
tui_print("Waiting for n_signer approval...");
|
||||
|
||||
if (nsigner_get_public_key_url(endpoint_url, "main", pubkey_hex) != 0) {
|
||||
tui_print("Failed to get public key from n_signer URL (denied or error).");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
auth.enabled = 1;
|
||||
if (getrandom(auth.privkey, sizeof(auth.privkey), 0) != (ssize_t)sizeof(auth.privkey)) {
|
||||
tui_print("Failed to generate URL auth key.");
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
snprintf(auth.label, sizeof(auth.label), "%s", "nostr_terminal");
|
||||
|
||||
session_fd = nsigner_session_open_url(endpoint_url);
|
||||
if (session_fd < 0) {
|
||||
tui_print("Failed to open persistent connection to n_signer URL.");
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (signer_init_nsigner_url(endpoint_url) != 0) {
|
||||
if (nsigner_session_get_public_key(&session_fd, endpoint_url, &selector, &auth, pubkey_hex) != 0) {
|
||||
nsigner_session_close(session_fd);
|
||||
tui_print("Failed to get public key from n_signer URL (denied or error).");
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (signer_init_nsigner_url_with_session_auth(endpoint_url, &selector, session_fd, &auth) != 0) {
|
||||
nsigner_session_close(session_fd);
|
||||
tui_print("Failed to initialize URL signer mode.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -333,32 +430,50 @@ void menu_login(void) {
|
||||
char target_qube[128];
|
||||
char service_name[128];
|
||||
const char *saved_target = db_get_local("nsigner_qrexec_target");
|
||||
nt_nsigner_selector_t selector = nsigner_selector_default();
|
||||
char idxbuf[32];
|
||||
int nostr_index = 0;
|
||||
|
||||
snprintf(target_qube, sizeof(target_qube), "%s", (saved_target && saved_target[0] != '\0') ? saved_target : "nsigner-vault");
|
||||
snprintf(service_name, sizeof(service_name), "%s", "qubes.NsignerRpc");
|
||||
|
||||
tui_get_line("Target signer qube (nsigner-vault) >", target_qube, (int)sizeof(target_qube));
|
||||
tui_begin_frame(&login_signer_qrexec_view);
|
||||
(void)tui_end_frame_with_prompt("Target signer qube (nsigner-vault) >", target_qube, (int)sizeof(target_qube));
|
||||
if (target_qube[0] == '\0') {
|
||||
snprintf(target_qube, sizeof(target_qube), "%s", (saved_target && saved_target[0] != '\0') ? saved_target : "nsigner-vault");
|
||||
}
|
||||
|
||||
tui_get_line("Qrexec service (qubes.NsignerRpc) >", service_name, (int)sizeof(service_name));
|
||||
tui_begin_frame(&login_signer_qrexec_view);
|
||||
(void)tui_end_frame_with_prompt("Qrexec service (qubes.NsignerRpc) >", service_name, (int)sizeof(service_name));
|
||||
if (service_name[0] == '\0') {
|
||||
snprintf(service_name, sizeof(service_name), "%s", "qubes.NsignerRpc");
|
||||
}
|
||||
|
||||
tui_begin_frame(&login_signer_qrexec_view);
|
||||
(void)tui_end_frame_with_prompt("Seed phrase index (0) >", idxbuf, (int)sizeof(idxbuf));
|
||||
if (idxbuf[0] != '\0') {
|
||||
nostr_index = atoi(idxbuf);
|
||||
if (nostr_index < 0) {
|
||||
nostr_index = 0;
|
||||
}
|
||||
}
|
||||
selector.has_nostr_index = 1;
|
||||
selector.nostr_index = nostr_index;
|
||||
selector.role[0] = '\0';
|
||||
|
||||
tui_begin_frame(&login_signer_qrexec_view);
|
||||
tui_print("Calling qrexec service %s in %s...", service_name, target_qube);
|
||||
tui_print("Waiting for n_signer approval...");
|
||||
|
||||
if (nsigner_qrexec_get_public_key(target_qube, service_name, "main", pubkey_hex) != 0) {
|
||||
if (nsigner_qrexec_get_public_key(target_qube, service_name, &selector, pubkey_hex) != 0) {
|
||||
tui_print("Failed to get public key via qrexec (denied or error).");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (signer_init_nsigner_qrexec(target_qube, service_name) != 0) {
|
||||
if (signer_init_nsigner_qrexec(target_qube, service_name, &selector) != 0) {
|
||||
tui_print("Failed to initialize qrexec signer mode.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -395,27 +510,31 @@ void menu_login(void) {
|
||||
int account = 0;
|
||||
|
||||
if (nostr_generate_mnemonic_and_keys(mnemonic, sizeof(mnemonic), 0, priv, pub) != 0) {
|
||||
tui_begin_frame(&login_new_account_view);
|
||||
tui_print("Failed to generate new seed phrase.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
tui_get_line("Enter seed phrase index (0):", idxbuf, (int)sizeof(idxbuf));
|
||||
tui_begin_frame(&login_new_account_view);
|
||||
(void)tui_end_frame_with_prompt("Enter seed phrase index (0):", idxbuf, (int)sizeof(idxbuf));
|
||||
if (idxbuf[0] != '\0') {
|
||||
account = atoi(idxbuf);
|
||||
if (account < 0) {
|
||||
account = 0;
|
||||
}
|
||||
if (nostr_derive_keys_from_mnemonic(mnemonic, account, priv, pub) != 0) {
|
||||
tui_begin_frame(&login_new_account_view);
|
||||
tui_print("Failed to derive keys from generated mnemonic.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (menu_set_keys_from_private_bytes(priv, mnemonic) != 0) {
|
||||
tui_begin_frame(&login_new_account_view);
|
||||
tui_print("Could not initialize keys.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -465,8 +584,9 @@ void menu_login(void) {
|
||||
(void)state_load_user_info();
|
||||
return;
|
||||
}
|
||||
tui_begin_frame(&login_privkey_view);
|
||||
tui_print("Invalid nsecHex. Try again.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -477,8 +597,9 @@ void menu_login(void) {
|
||||
(void)state_load_user_info();
|
||||
return;
|
||||
}
|
||||
tui_begin_frame(&login_privkey_view);
|
||||
tui_print("Invalid nsec. Try again.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -499,12 +620,14 @@ void menu_login(void) {
|
||||
int account = 0;
|
||||
|
||||
if (nostr_bip39_mnemonic_validate(input) != 0) {
|
||||
tui_begin_frame(&login_mnemonic_view);
|
||||
tui_print("Invalid seed phrase. Try again.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
tui_get_line("Enter seed phrase index (0) >", idxbuf, (int)sizeof(idxbuf));
|
||||
tui_begin_frame(&login_mnemonic_view);
|
||||
(void)tui_end_frame_with_prompt("Enter seed phrase index (0) >", idxbuf, (int)sizeof(idxbuf));
|
||||
if (idxbuf[0] != '\0') {
|
||||
account = atoi(idxbuf);
|
||||
if (account < 0) {
|
||||
@@ -514,22 +637,23 @@ void menu_login(void) {
|
||||
|
||||
if (nostr_derive_keys_from_mnemonic(input, account, priv, pub) != 0 ||
|
||||
menu_set_keys_from_private_bytes(priv, input) != 0) {
|
||||
tui_begin_frame(&login_mnemonic_view);
|
||||
tui_print("Failed to derive keys from seed phrase.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
continue;
|
||||
}
|
||||
|
||||
tui_print("");
|
||||
tui_begin_frame(&login_mnemonic_view);
|
||||
tui_print("Using seed phrase: %s", g_state.seed_phrase);
|
||||
tui_print("");
|
||||
tui_get_line("Press Enter to continue >", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
|
||||
(void)state_load_user_info();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
tui_begin_frame(&login_view);
|
||||
tui_print("Invalid login input. Choose a menu command or enter valid key/mnemonic data.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,6 +169,10 @@ static int notif_cmp_desc_created(const void *a, const void *b) {
|
||||
}
|
||||
|
||||
void menu_notifications(void) {
|
||||
static const tui_view_t notifications_view = {
|
||||
"> Main Menu > Notifications",
|
||||
NULL,
|
||||
};
|
||||
const char **read_relays;
|
||||
int relay_count = 0;
|
||||
time_t now;
|
||||
@@ -194,13 +198,12 @@ void menu_notifications(void) {
|
||||
g_state.npub_hex,
|
||||
since);
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("NOTIFICATIONS\n");
|
||||
tui_begin_frame(¬ifications_view);
|
||||
tui_print("Querying %d read relay(s)...", relay_count);
|
||||
|
||||
if (nt_query_sync(filter, read_relays, relay_count, 7000, &events, &event_count) != 0) {
|
||||
tui_print("Failed to query notifications (all relays failed).");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -271,8 +274,7 @@ void menu_notifications(void) {
|
||||
qsort(items, (size_t)items_count, sizeof(notif_item_t), notif_cmp_desc_created);
|
||||
}
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("NOTIFICATIONS\n");
|
||||
tui_begin_frame(¬ifications_view);
|
||||
tui_print("%d reactions, %d replies in last 3 days", reactions, replies);
|
||||
tui_print("");
|
||||
|
||||
@@ -305,5 +307,5 @@ void menu_notifications(void) {
|
||||
|
||||
notif_free_items(items, items_count);
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
}
|
||||
|
||||
126
src/menu_posts.c
126
src/menu_posts.c
@@ -162,6 +162,26 @@ static void posts_format_date(long long created_at, char *buf, size_t buf_sz) {
|
||||
}
|
||||
}
|
||||
|
||||
static void posts_print_list_item_responsive(int index,
|
||||
int kind,
|
||||
const char *kind_label,
|
||||
const char *label,
|
||||
const char *date_buf,
|
||||
int term_width) {
|
||||
if (term_width < 80) {
|
||||
tui_print("[%2d] %s", index, label ? label : "(untitled)");
|
||||
tui_print(" %s %s", kind_label ? kind_label : "?", date_buf ? date_buf : "");
|
||||
return;
|
||||
}
|
||||
|
||||
tui_print("[%2d] (%d/%s) %s %s",
|
||||
index,
|
||||
kind,
|
||||
kind_label ? kind_label : "?",
|
||||
label ? label : "(untitled)",
|
||||
date_buf ? date_buf : "");
|
||||
}
|
||||
|
||||
static int posts_build_list(const char *filter_json, post_item_t **items_out, int *count_out) {
|
||||
const char **read_relays;
|
||||
int relay_count = 0;
|
||||
@@ -279,6 +299,10 @@ static const char *posts_kind_label(int kind) {
|
||||
}
|
||||
|
||||
static void posts_view_item(const post_item_t *it) {
|
||||
static const tui_view_t post_view = {
|
||||
"> Main Menu > Posts > View",
|
||||
NULL,
|
||||
};
|
||||
char date_buf[64];
|
||||
char input[16];
|
||||
char *decrypted = NULL;
|
||||
@@ -293,8 +317,7 @@ static void posts_view_item(const post_item_t *it) {
|
||||
decrypted = posts_try_decrypt_nip04(it->pubkey, it->content);
|
||||
}
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("POST VIEW\n");
|
||||
tui_begin_frame(&post_view);
|
||||
tui_print("id: %s", it->id);
|
||||
tui_print("kind: %d (%s)", it->kind, posts_kind_label(it->kind));
|
||||
tui_print("date: %s", date_buf);
|
||||
@@ -312,19 +335,35 @@ static void posts_view_item(const post_item_t *it) {
|
||||
}
|
||||
|
||||
free(decrypted);
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
}
|
||||
|
||||
static void posts_delete_item(const post_item_t *it) {
|
||||
static const tui_view_t post_delete = {
|
||||
"> Main Menu > Posts > Delete",
|
||||
NULL,
|
||||
};
|
||||
cJSON *tags;
|
||||
cJSON *e_tag;
|
||||
char input[16];
|
||||
char yn[8];
|
||||
int posted;
|
||||
|
||||
if (!it || !it->id) {
|
||||
return;
|
||||
}
|
||||
|
||||
tui_begin_frame(&post_delete);
|
||||
tui_print("Delete this post from relays?");
|
||||
tui_print("id: %.24s...", it->id);
|
||||
(void)tui_end_frame_with_prompt("Confirm delete [y/n] >", yn, (int)sizeof(yn));
|
||||
if (!(yn[0] == 'y' || yn[0] == 'Y')) {
|
||||
tui_begin_frame(&post_delete);
|
||||
tui_print("Delete canceled.");
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
tags = cJSON_CreateArray();
|
||||
if (!tags) {
|
||||
return;
|
||||
@@ -343,16 +382,21 @@ static void posts_delete_item(const post_item_t *it) {
|
||||
posted = publish_signed_event_with_report("Delete", 5, "", tags, 8000);
|
||||
cJSON_Delete(tags);
|
||||
|
||||
tui_begin_frame(&post_delete);
|
||||
if (posted < 0) {
|
||||
tui_print("Delete publish failed.");
|
||||
} else {
|
||||
tui_print("Deletion published.");
|
||||
}
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
}
|
||||
|
||||
static void posts_edit_item(const post_item_t *it) {
|
||||
static const tui_view_t post_edit = {
|
||||
"> Main Menu > Posts > Edit",
|
||||
NULL,
|
||||
};
|
||||
char *initial = NULL;
|
||||
char *edited = NULL;
|
||||
char *out_content = NULL;
|
||||
@@ -374,8 +418,9 @@ static void posts_edit_item(const post_item_t *it) {
|
||||
}
|
||||
|
||||
if (!initial) {
|
||||
tui_begin_frame(&post_edit);
|
||||
tui_print("Failed to prepare editor content.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -383,23 +428,24 @@ static void posts_edit_item(const post_item_t *it) {
|
||||
free(initial);
|
||||
|
||||
if (!edited) {
|
||||
tui_begin_frame(&post_edit);
|
||||
tui_print("Edit canceled.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
if (it->kind == 30024 || it->kind == 30078) {
|
||||
if (posts_encrypt_nip04_for_self(edited, &out_content) != 0) {
|
||||
tui_begin_frame(&post_edit);
|
||||
tui_print("Failed to encrypt updated content.");
|
||||
free(edited);
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
out_content = posts_strdup(edited);
|
||||
if (!out_content) {
|
||||
free(edited);
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -408,8 +454,9 @@ static void posts_edit_item(const post_item_t *it) {
|
||||
if (!tags_copy) {
|
||||
free(edited);
|
||||
free(out_content);
|
||||
tui_begin_frame(&post_edit);
|
||||
tui_print("Failed to duplicate tags.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -418,33 +465,39 @@ static void posts_edit_item(const post_item_t *it) {
|
||||
free(edited);
|
||||
free(out_content);
|
||||
|
||||
tui_begin_frame(&post_edit);
|
||||
if (posted < 0) {
|
||||
tui_print("Post update failed.");
|
||||
} else {
|
||||
tui_print("Post updated.");
|
||||
}
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
}
|
||||
|
||||
static void posts_kind_menu(const char *title, const char *filter_json) {
|
||||
static const tui_view_t posts_kind_view = {
|
||||
"> Main Menu > Posts > List",
|
||||
NULL,
|
||||
};
|
||||
post_item_t *items = NULL;
|
||||
int items_count = 0;
|
||||
char input[32];
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("%s\n", title);
|
||||
|
||||
if (posts_build_list(filter_json, &items, &items_count) != 0) {
|
||||
tui_begin_frame(&posts_kind_view);
|
||||
tui_print("%s", title ? title : "Posts");
|
||||
tui_print("Failed to query posts (all relays failed).");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
int i;
|
||||
int term_width = 0;
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("%s\n", title);
|
||||
tui_begin_frame(&posts_kind_view);
|
||||
tui_print("%s", title ? title : "Posts");
|
||||
tui_get_terminal_size(&term_width, NULL);
|
||||
for (i = 0; i < items_count; i++) {
|
||||
char date_buf[64];
|
||||
const char *label;
|
||||
@@ -454,12 +507,12 @@ static void posts_kind_menu(const char *title, const char *filter_json) {
|
||||
? items[i].title
|
||||
: ((items[i].dtag && items[i].dtag[0] != '\0') ? items[i].dtag : "(untitled)");
|
||||
|
||||
tui_print("[%2d] (%d/%s) %s %s",
|
||||
i + 1,
|
||||
items[i].kind,
|
||||
posts_kind_label(items[i].kind),
|
||||
label,
|
||||
date_buf);
|
||||
posts_print_list_item_responsive(i + 1,
|
||||
items[i].kind,
|
||||
posts_kind_label(items[i].kind),
|
||||
label,
|
||||
date_buf,
|
||||
term_width);
|
||||
}
|
||||
|
||||
if (items_count == 0) {
|
||||
@@ -467,11 +520,11 @@ static void posts_kind_menu(const char *title, const char *filter_json) {
|
||||
}
|
||||
|
||||
tui_print("");
|
||||
tui_print("^_V^:iew <n>");
|
||||
tui_print("^_E^:dit <n>");
|
||||
tui_print("^_D^:elete <n>");
|
||||
tui_print("^_B^:ack");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
tui_print_menu_item("^_V^:iew <n>");
|
||||
tui_print_menu_item("^_E^:dit <n>");
|
||||
tui_print_menu_item("^_D^:elete <n>");
|
||||
tui_print_menu_item("^_X^:Exit");
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'b' || input[0] == 'B' ||
|
||||
input[0] == 'x' || input[0] == 'X' ||
|
||||
@@ -502,19 +555,22 @@ static void posts_kind_menu(const char *title, const char *filter_json) {
|
||||
}
|
||||
|
||||
void menu_posts(void) {
|
||||
static const tui_view_t posts_view = {
|
||||
"> Main Menu > Posts",
|
||||
NULL,
|
||||
};
|
||||
char input[32];
|
||||
char filter[512];
|
||||
|
||||
while (1) {
|
||||
tui_clear_screen();
|
||||
tui_print("POSTS\n");
|
||||
tui_print("^_S^:ecret blog (kind 30024)");
|
||||
tui_print("^_P^:ublic blog (kind 30023)");
|
||||
tui_print("^_E^:ncrypted data (kind 30078)");
|
||||
tui_print("^_A^:ll posts");
|
||||
tui_print("^_B^:ack");
|
||||
tui_begin_frame(&posts_view);
|
||||
tui_print_menu_item("^_S^:ecret blog (kind 30024)");
|
||||
tui_print_menu_item("^_P^:ublic blog (kind 30023)");
|
||||
tui_print_menu_item("^_E^:ncrypted data (kind 30078)");
|
||||
tui_print_menu_item("^_A^:ll posts");
|
||||
tui_print_menu_item("^_X^:Exit");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'b' || input[0] == 'B' ||
|
||||
input[0] == 'x' || input[0] == 'X' ||
|
||||
|
||||
@@ -16,6 +16,10 @@ static int profile_publish_kind0(const char *content_json) {
|
||||
}
|
||||
|
||||
void menu_profile(void) {
|
||||
static const tui_view_t profile_view = {
|
||||
"> Main Menu > Profile",
|
||||
NULL,
|
||||
};
|
||||
char menu_sel[32];
|
||||
char edit_buf[512];
|
||||
|
||||
@@ -24,18 +28,32 @@ void menu_profile(void) {
|
||||
const char *fields[] = {"name", "about", "picture", "banner", "website", "lud16", "nip05"};
|
||||
int i;
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("PROFILE\n");
|
||||
tui_begin_frame(&profile_view);
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER) {
|
||||
tui_print("Signer: n_signer (@%s)", g_signer.socket_name);
|
||||
if (g_signer.selector.has_nostr_index) {
|
||||
tui_print("Signer selector: nostr_index=%d", g_signer.selector.nostr_index);
|
||||
} else {
|
||||
tui_print("Signer selector: role=%s", g_signer.selector.role[0] ? g_signer.selector.role : "main");
|
||||
}
|
||||
} else if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
|
||||
tui_print("Signer: n_signer qrexec");
|
||||
tui_print("Signer target: %s", g_signer.qrexec_target);
|
||||
tui_print("Signer service: %s", g_signer.qrexec_service);
|
||||
if (g_signer.selector.has_nostr_index) {
|
||||
tui_print("Signer selector: nostr_index=%d", g_signer.selector.nostr_index);
|
||||
} else {
|
||||
tui_print("Signer selector: role=%s", g_signer.selector.role[0] ? g_signer.selector.role : "main");
|
||||
}
|
||||
} else if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
|
||||
tui_print("Signer: n_signer URL");
|
||||
tui_print("Signer endpoint: %s", g_signer.endpoint_url);
|
||||
if (g_signer.selector.has_nostr_index) {
|
||||
tui_print("Signer selector: nostr_index=%d", g_signer.selector.nostr_index);
|
||||
} else {
|
||||
tui_print("Signer selector: role=%s", g_signer.selector.role[0] ? g_signer.selector.role : "main");
|
||||
}
|
||||
} else {
|
||||
tui_print("nsecHex: %s", g_state.nsec_hex);
|
||||
tui_print("nsec: %s", g_state.nsec_bech32);
|
||||
@@ -54,12 +72,12 @@ void menu_profile(void) {
|
||||
tui_print("%-18s %s", fields[i], (cJSON_IsString(v) && v->valuestring) ? v->valuestring : "");
|
||||
}
|
||||
|
||||
tui_print("\n");
|
||||
tui_print("^_M^:odify account");
|
||||
tui_print("^_P^:ost changes and exit.");
|
||||
tui_print("E^_x^:it without saving");
|
||||
tui_print("");
|
||||
tui_print_menu_item("^_M^:odify account");
|
||||
tui_print_menu_item("^_P^:ost changes and exit.");
|
||||
tui_print_menu_item("E^_x^:it without saving");
|
||||
|
||||
tui_get_line(">", menu_sel, (int)sizeof(menu_sel));
|
||||
(void)tui_end_frame_with_prompt(">", menu_sel, (int)sizeof(menu_sel));
|
||||
|
||||
if (menu_sel[0] == 'm' || menu_sel[0] == 'M') {
|
||||
for (i = 0; i < (int)(sizeof(fields) / sizeof(fields[0])); i++) {
|
||||
|
||||
@@ -95,7 +95,21 @@ static void relays_print_nip11_summary(const char *nip11_json) {
|
||||
cJSON_Delete(doc);
|
||||
}
|
||||
|
||||
static void relays_print_row_responsive(int term_width, int index, const char *url, const char *rw) {
|
||||
if (term_width < 90) {
|
||||
tui_print("[%2d] %s", index, url ? url : "");
|
||||
tui_print(" %s", rw ? rw : "");
|
||||
return;
|
||||
}
|
||||
|
||||
tui_print("[%2d] %-30s %s", index, url ? url : "", rw ? rw : "");
|
||||
}
|
||||
|
||||
void menu_relays(void) {
|
||||
static const tui_view_t relays_view = {
|
||||
"> Main Menu > Relays",
|
||||
NULL,
|
||||
};
|
||||
cJSON *working = NULL;
|
||||
cJSON *tags = NULL;
|
||||
char input[512];
|
||||
@@ -118,9 +132,10 @@ void menu_relays(void) {
|
||||
while (1) {
|
||||
int i;
|
||||
int n;
|
||||
int term_width = 0;
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("RELAYS\n");
|
||||
tui_begin_frame(&relays_view);
|
||||
tui_get_terminal_size(&term_width, NULL);
|
||||
|
||||
n = cJSON_GetArraySize(tags);
|
||||
for (i = 0; i < n; i++) {
|
||||
@@ -138,17 +153,20 @@ void menu_relays(void) {
|
||||
rw = t2->valuestring;
|
||||
}
|
||||
|
||||
tui_print("[%2d] %-30s %s", i + 1, (cJSON_IsString(t1) && t1->valuestring) ? t1->valuestring : "", rw);
|
||||
relays_print_row_responsive(term_width,
|
||||
i + 1,
|
||||
(cJSON_IsString(t1) && t1->valuestring) ? t1->valuestring : "",
|
||||
rw);
|
||||
}
|
||||
|
||||
tui_print("\n\n");
|
||||
tui_print("^_A^:dd relay");
|
||||
tui_print("^_D^:elete relay");
|
||||
tui_print("^_M^:odify relay");
|
||||
tui_print("^_P^:ost changes and exit.");
|
||||
tui_print("E^_x^:it without saving");
|
||||
tui_print("");
|
||||
tui_print_menu_item("^_A^:dd relay");
|
||||
tui_print_menu_item("^_D^:elete relay");
|
||||
tui_print_menu_item("^_M^:odify relay");
|
||||
tui_print_menu_item("^_P^:ost changes and exit.");
|
||||
tui_print_menu_item("E^_x^:it without saving");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'a' || input[0] == 'A') {
|
||||
char relay_url[256];
|
||||
@@ -157,12 +175,14 @@ void menu_relays(void) {
|
||||
char write_yn[32];
|
||||
char *nip11 = NULL;
|
||||
|
||||
tui_print("\n\nADD RELAY\n");
|
||||
tui_get_line("Relay URL >", relay_url, (int)sizeof(relay_url));
|
||||
tui_begin_frame(&relays_view);
|
||||
tui_print("Add relay");
|
||||
(void)tui_end_frame_with_prompt("Relay URL >", relay_url, (int)sizeof(relay_url));
|
||||
if (relay_url[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
tui_begin_frame(&relays_view);
|
||||
tui_print("Gathering relay data ...");
|
||||
if (nt_fetch_nip11(relay_url, &nip11) == 0 && nip11) {
|
||||
relays_print_nip11_summary(nip11);
|
||||
@@ -171,14 +191,14 @@ void menu_relays(void) {
|
||||
tui_print("NIP-11 fetch failed.");
|
||||
}
|
||||
|
||||
tui_get_line("Add relay [y/n] >", yn, (int)sizeof(yn));
|
||||
(void)tui_end_frame_with_prompt("Add relay [y/n] >", yn, (int)sizeof(yn));
|
||||
if (yn[0] == 'y' || yn[0] == 'Y') {
|
||||
cJSON *tag = cJSON_CreateArray();
|
||||
cJSON_AddItemToArray(tag, cJSON_CreateString("r"));
|
||||
cJSON_AddItemToArray(tag, cJSON_CreateString(relay_url));
|
||||
|
||||
tui_get_line("Read from relay [y/n] >", read_yn, (int)sizeof(read_yn));
|
||||
tui_get_line("Write to relay [y/n] >", write_yn, (int)sizeof(write_yn));
|
||||
(void)tui_end_frame_with_prompt("Read from relay [y/n] >", read_yn, (int)sizeof(read_yn));
|
||||
(void)tui_end_frame_with_prompt("Write to relay [y/n] >", write_yn, (int)sizeof(write_yn));
|
||||
|
||||
if ((read_yn[0] == 'n' || read_yn[0] == 'N') && (write_yn[0] == 'y' || write_yn[0] == 'Y')) {
|
||||
cJSON_AddItemToArray(tag, cJSON_CreateString("write"));
|
||||
@@ -194,7 +214,8 @@ void menu_relays(void) {
|
||||
cJSON *tag;
|
||||
cJSON *url;
|
||||
|
||||
tui_get_line("relay to delete >", numbuf, (int)sizeof(numbuf));
|
||||
tui_begin_frame(&relays_view);
|
||||
(void)tui_end_frame_with_prompt("relay to delete >", numbuf, (int)sizeof(numbuf));
|
||||
idx = atoi(numbuf) - 1;
|
||||
if (idx < 0 || idx >= cJSON_GetArraySize(tags)) {
|
||||
continue;
|
||||
@@ -202,12 +223,16 @@ void menu_relays(void) {
|
||||
|
||||
tag = cJSON_GetArrayItem(tags, idx);
|
||||
url = cJSON_GetArrayItem(tag, 1);
|
||||
tui_get_line("Delete selected relay [y/n] >", yn, (int)sizeof(yn));
|
||||
(void)tui_end_frame_with_prompt("Delete selected relay [y/n] >", yn, (int)sizeof(yn));
|
||||
if (yn[0] == 'y' || yn[0] == 'Y') {
|
||||
cJSON_DeleteItemFromArray(tags, idx);
|
||||
tui_begin_frame(&relays_view);
|
||||
if (cJSON_IsString(url) && url->valuestring) {
|
||||
tui_print("Deleted %s", url->valuestring);
|
||||
} else {
|
||||
tui_print("Deleted relay.");
|
||||
}
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", input, (int)sizeof(input));
|
||||
}
|
||||
} else if (input[0] == 'm' || input[0] == 'M') {
|
||||
char numbuf[32];
|
||||
@@ -218,7 +243,8 @@ void menu_relays(void) {
|
||||
cJSON *url;
|
||||
cJSON *new_tag;
|
||||
|
||||
tui_get_line("# relay to modify >", numbuf, (int)sizeof(numbuf));
|
||||
tui_begin_frame(&relays_view);
|
||||
(void)tui_end_frame_with_prompt("# relay to modify >", numbuf, (int)sizeof(numbuf));
|
||||
idx = atoi(numbuf) - 1;
|
||||
if (idx < 0 || idx >= cJSON_GetArraySize(tags)) {
|
||||
continue;
|
||||
@@ -234,8 +260,8 @@ void menu_relays(void) {
|
||||
cJSON_AddItemToArray(new_tag, cJSON_CreateString("r"));
|
||||
cJSON_AddItemToArray(new_tag, cJSON_CreateString(url->valuestring));
|
||||
|
||||
tui_get_line("Read from relay [y/n]?", read_yn, (int)sizeof(read_yn));
|
||||
tui_get_line("Write to relay [y/n]?", write_yn, (int)sizeof(write_yn));
|
||||
(void)tui_end_frame_with_prompt("Read from relay [y/n]?", read_yn, (int)sizeof(read_yn));
|
||||
(void)tui_end_frame_with_prompt("Write to relay [y/n]?", write_yn, (int)sizeof(write_yn));
|
||||
|
||||
if ((read_yn[0] == 'n' || read_yn[0] == 'N') && (write_yn[0] == 'y' || write_yn[0] == 'Y')) {
|
||||
cJSON_AddItemToArray(new_tag, cJSON_CreateString("write"));
|
||||
@@ -247,15 +273,20 @@ void menu_relays(void) {
|
||||
} else if (input[0] == 'p' || input[0] == 'P') {
|
||||
int posted = relays_publish_kind10002(working);
|
||||
char *new_json = cJSON_PrintUnformatted(working);
|
||||
|
||||
if (new_json) {
|
||||
free(g_state.kind10002_json);
|
||||
g_state.kind10002_json = new_json;
|
||||
state_parse_relay_list();
|
||||
}
|
||||
|
||||
tui_begin_frame(&relays_view);
|
||||
if (posted < 0) {
|
||||
tui_print("Relay list publish failed.");
|
||||
} else {
|
||||
tui_print("Relay list published.");
|
||||
}
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
break;
|
||||
} else if (input[0] == 'x' || input[0] == 'X' ||
|
||||
input[0] == 'q' || input[0] == 'Q') {
|
||||
|
||||
@@ -301,6 +301,7 @@ static void todo_toggle_item(todo_item_t *items, int count) {
|
||||
|
||||
static void todo_delete_item(todo_item_t *items, int *count) {
|
||||
char input[64];
|
||||
char yn[8];
|
||||
int idx;
|
||||
int i;
|
||||
|
||||
@@ -315,6 +316,12 @@ static void todo_delete_item(todo_item_t *items, int *count) {
|
||||
return;
|
||||
}
|
||||
|
||||
tui_get_line("Delete selected item [y/n] >", yn, (int)sizeof(yn));
|
||||
if (!(yn[0] == 'y' || yn[0] == 'Y')) {
|
||||
tui_print("Delete canceled.");
|
||||
return;
|
||||
}
|
||||
|
||||
free(items[idx].text);
|
||||
for (i = idx; i < (*count - 1); i++) {
|
||||
items[i] = items[i + 1];
|
||||
@@ -389,27 +396,37 @@ static int todo_publish(const todo_item_t *items, int count) {
|
||||
}
|
||||
|
||||
void menu_todo(void) {
|
||||
static const tui_view_t todo_view = {
|
||||
"> Main Menu > Todo",
|
||||
NULL,
|
||||
};
|
||||
todo_item_t *items = NULL;
|
||||
int count = 0;
|
||||
char input[32];
|
||||
|
||||
if (todo_load_remote(&items, &count) != 0) {
|
||||
tui_print("Failed to load remote todo list; starting empty.");
|
||||
items = NULL;
|
||||
count = 0;
|
||||
}
|
||||
|
||||
while (1) {
|
||||
tui_clear_screen();
|
||||
todo_show_list(items, count);
|
||||
tui_print("^_A^:dd item");
|
||||
tui_print("^_C^:omplete/toggle item");
|
||||
tui_print("^_D^:elete item");
|
||||
tui_print("^_R^:eorder (swap two)");
|
||||
tui_print("^_P^:ost/save changes");
|
||||
tui_print("E^_x^:it without saving");
|
||||
tui_begin_frame(&todo_view);
|
||||
if (count == 0) {
|
||||
tui_print("TODO LIST");
|
||||
tui_print("(empty)");
|
||||
tui_print("");
|
||||
} else {
|
||||
todo_show_list(items, count);
|
||||
}
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
tui_print_menu_item("^_A^:dd item");
|
||||
tui_print_menu_item("^_C^:omplete/toggle item");
|
||||
tui_print_menu_item("^_D^:elete item");
|
||||
tui_print_menu_item("^_R^:eorder (swap two)");
|
||||
tui_print_menu_item("^_P^:ost/save changes");
|
||||
tui_print_menu_item("E^_x^:it without saving");
|
||||
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 'x' || input[0] == 'X' ||
|
||||
input[0] == 'q' || input[0] == 'Q') {
|
||||
@@ -427,7 +444,9 @@ void menu_todo(void) {
|
||||
} else if (input[0] == 'p' || input[0] == 'P') {
|
||||
char hold[8];
|
||||
(void)todo_publish(items, count);
|
||||
tui_get_line(">", hold, (int)sizeof(hold));
|
||||
tui_begin_frame(&todo_view);
|
||||
tui_print("Todo publish attempted.");
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,20 +8,28 @@
|
||||
#include <string.h>
|
||||
|
||||
void menu_tweet(void) {
|
||||
static const tui_view_t tweet_view = {
|
||||
"> Main Menu > Tweet",
|
||||
NULL,
|
||||
};
|
||||
char text[4096];
|
||||
int posted;
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("TWEET\n");
|
||||
|
||||
tui_get_line("tweet >", text, (int)sizeof(text));
|
||||
tui_begin_frame(&tweet_view);
|
||||
tui_print("Compose a short post.");
|
||||
tui_print("");
|
||||
(void)tui_end_frame_with_prompt("tweet >", text, (int)sizeof(text));
|
||||
if (text[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
posted = publish_signed_event_with_report("Tweet", 1, text, NULL, 8000);
|
||||
|
||||
tui_begin_frame(&tweet_view);
|
||||
if (posted < 0) {
|
||||
tui_print("Tweet publish failed.");
|
||||
} else {
|
||||
tui_print("Tweet published.");
|
||||
}
|
||||
tui_get_line(">", text, (int)sizeof(text));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", text, (int)sizeof(text));
|
||||
}
|
||||
|
||||
@@ -39,35 +39,40 @@ static cJSON *write_make_blog_tags(const char *d_tag_value, const char *title_op
|
||||
}
|
||||
|
||||
void menu_write(void) {
|
||||
static const tui_view_t write_view = {
|
||||
"> Main Menu > Write",
|
||||
NULL,
|
||||
};
|
||||
char *content;
|
||||
char input[64];
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("WRITE\n");
|
||||
tui_begin_frame(&write_view);
|
||||
tui_print("Launching external editor...");
|
||||
|
||||
content = editor_launch(NULL);
|
||||
if (!content) {
|
||||
tui_begin_frame(&write_view);
|
||||
tui_print("No content created.");
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
tui_clear_screen();
|
||||
tui_print("WRITE RESULT\n");
|
||||
tui_begin_frame(&write_view);
|
||||
tui_print("Content length: %d bytes", (int)strlen(content));
|
||||
tui_print("\n");
|
||||
tui_print("^_T^:weet it");
|
||||
tui_print("^_B^:log post (kind 30023)");
|
||||
tui_print("^_D^:iary entry (kind 30024)");
|
||||
tui_print("^_C^:ancel/discard");
|
||||
tui_print("");
|
||||
tui_print_menu_item("^_T^:weet it");
|
||||
tui_print_menu_item("^_B^:log post (kind 30023)");
|
||||
tui_print_menu_item("^_D^:iary entry (kind 30024)");
|
||||
tui_print_menu_item("^_X^:Exit without saving");
|
||||
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt(">", input, (int)sizeof(input));
|
||||
|
||||
if (input[0] == 't' || input[0] == 'T') {
|
||||
int posted = publish_signed_event_with_report("Tweet", 1, content, NULL, 8000);
|
||||
tui_begin_frame(&write_view);
|
||||
if (posted < 0) {
|
||||
tui_print("Tweet publish failed.");
|
||||
} else {
|
||||
tui_print("Tweet published.");
|
||||
}
|
||||
} else if (input[0] == 'b' || input[0] == 'B') {
|
||||
char title[256];
|
||||
@@ -76,11 +81,14 @@ void menu_write(void) {
|
||||
cJSON *tags;
|
||||
int posted;
|
||||
|
||||
tui_get_line("title >", title, (int)sizeof(title));
|
||||
tui_get_line("slug (d tag) [empty uses title] >", slug, (int)sizeof(slug));
|
||||
tui_begin_frame(&write_view);
|
||||
(void)tui_end_frame_with_prompt("title >", title, (int)sizeof(title));
|
||||
(void)tui_end_frame_with_prompt("slug (d tag) [empty uses title] >", slug, (int)sizeof(slug));
|
||||
|
||||
d_val = (slug[0] != '\0') ? slug : title;
|
||||
tags = write_make_blog_tags(d_val, title);
|
||||
|
||||
tui_begin_frame(&write_view);
|
||||
if (!tags) {
|
||||
tui_print("Failed to build blog tags.");
|
||||
} else {
|
||||
@@ -88,6 +96,8 @@ void menu_write(void) {
|
||||
cJSON_Delete(tags);
|
||||
if (posted < 0) {
|
||||
tui_print("Blog publish failed.");
|
||||
} else {
|
||||
tui_print("Blog post published.");
|
||||
}
|
||||
}
|
||||
} else if (input[0] == 'd' || input[0] == 'D') {
|
||||
@@ -98,17 +108,18 @@ void menu_write(void) {
|
||||
int posted;
|
||||
|
||||
memset(&tmv, 0, sizeof(tmv));
|
||||
tui_begin_frame(&write_view);
|
||||
if (!localtime_r(&now, &tmv)) {
|
||||
tui_print("Failed to get local date.");
|
||||
free(content);
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
if (strftime(date_d, sizeof(date_d), "%Y%m%d", &tmv) == 0) {
|
||||
tui_print("Failed to format diary date.");
|
||||
free(content);
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,16 +131,15 @@ void menu_write(void) {
|
||||
cJSON_Delete(tags);
|
||||
if (posted < 0) {
|
||||
tui_print("Diary publish failed.");
|
||||
} else {
|
||||
tui_print("Diary entry published.");
|
||||
}
|
||||
}
|
||||
} else if (input[0] == 'c' || input[0] == 'C' ||
|
||||
input[0] == 'x' || input[0] == 'X' ||
|
||||
input[0] == 'q' || input[0] == 'Q') {
|
||||
tui_print("Discarded.");
|
||||
} else {
|
||||
tui_begin_frame(&write_view);
|
||||
tui_print("Discarded.");
|
||||
}
|
||||
|
||||
free(content);
|
||||
tui_get_line(">", input, (int)sizeof(input));
|
||||
(void)tui_end_frame_with_prompt("Press Enter to exit >", input, (int)sizeof(input));
|
||||
}
|
||||
|
||||
115
src/net.c
115
src/net.c
@@ -8,6 +8,7 @@
|
||||
#include "../resources/nostr_core_lib/nostr_core/nip042.h"
|
||||
#include "../resources/nostr_core_lib/nostr_core/utils.h"
|
||||
#include "../resources/nostr_core_lib/nostr_websocket/nostr_websocket_tls.h"
|
||||
#include "../resources/nostr_core_lib/nostr_core/nostr_core.h"
|
||||
|
||||
#include <curl/curl.h>
|
||||
#include <errno.h>
|
||||
@@ -19,7 +20,7 @@
|
||||
#include <unistd.h>
|
||||
|
||||
#define NT_SUB_ID "sub1"
|
||||
#define NT_WS_BUFFER_SIZE 65536
|
||||
#define NT_WS_BUFFER_SIZE 262144
|
||||
|
||||
static long long nt_now_ms(void) {
|
||||
struct timeval tv;
|
||||
@@ -529,7 +530,7 @@ void nt_publish_results_free(nt_publish_result_t *results, int result_count) {
|
||||
}
|
||||
|
||||
/* Query one relay and append deduplicated EVENT payloads until EOSE/timeout. */
|
||||
static int nt_query_one_relay(const char *relay_url,
|
||||
static __attribute__((unused)) int nt_query_one_relay(const char *relay_url,
|
||||
cJSON *filter,
|
||||
int timeout_ms,
|
||||
int verbose,
|
||||
@@ -685,13 +686,12 @@ int nt_query_sync_verbose(const char *filter_json,
|
||||
char ***events_out,
|
||||
int *event_count) {
|
||||
cJSON *filter = NULL;
|
||||
nostr_relay_pool_t *pool = NULL;
|
||||
cJSON **pool_events = NULL;
|
||||
int pool_event_count = 0;
|
||||
char **events = NULL;
|
||||
char **seen_ids = NULL;
|
||||
int events_n = 0;
|
||||
int events_cap = 0;
|
||||
int seen_n = 0;
|
||||
int seen_cap = 0;
|
||||
int success_relays = 0;
|
||||
int i;
|
||||
|
||||
if (!events_out || !event_count || !filter_json || !relay_urls || relay_count <= 0) {
|
||||
@@ -708,59 +708,62 @@ int nt_query_sync_verbose(const char *filter_json,
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < relay_count; i++) {
|
||||
const char *relay = relay_urls[i];
|
||||
int rc;
|
||||
const char *reason = "unknown";
|
||||
|
||||
if (!relay || relay[0] == '\0') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (verbose && phase_label) {
|
||||
fprintf(stderr, "%s: attempting relay %d/%d %s\n", phase_label, i + 1, relay_count, relay);
|
||||
}
|
||||
|
||||
rc = nt_query_one_relay(relay,
|
||||
filter,
|
||||
timeout_ms,
|
||||
verbose,
|
||||
phase_label,
|
||||
&events,
|
||||
&events_n,
|
||||
&events_cap,
|
||||
&seen_ids,
|
||||
&seen_n,
|
||||
&seen_cap,
|
||||
&reason);
|
||||
if (rc < 0) {
|
||||
if (verbose && phase_label) {
|
||||
fprintf(stderr, "%s: failed %s (%s)\n", phase_label, relay, reason ? reason : "error");
|
||||
}
|
||||
nt_free_string_array(events, events_n);
|
||||
nt_free_string_array(seen_ids, seen_n);
|
||||
cJSON_Delete(filter);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (rc > 0) {
|
||||
success_relays++;
|
||||
if (verbose && phase_label) {
|
||||
fprintf(stderr, "%s: connected %s (%s)\n", phase_label, relay, reason ? reason : "ok");
|
||||
}
|
||||
} else if (verbose && phase_label) {
|
||||
fprintf(stderr, "%s: failed %s (%s)\n", phase_label, relay, reason ? reason : "failed");
|
||||
}
|
||||
}
|
||||
|
||||
nt_free_string_array(seen_ids, seen_n);
|
||||
cJSON_Delete(filter);
|
||||
|
||||
if (success_relays == 0) {
|
||||
nt_free_string_array(events, events_n);
|
||||
pool = nostr_relay_pool_create(NULL);
|
||||
if (!pool) {
|
||||
cJSON_Delete(filter);
|
||||
fprintf(stderr, "query: failed to create relay pool\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (verbose && phase_label) {
|
||||
fprintf(stderr,
|
||||
"%s: querying %d relays via nostr_core relay pool\n",
|
||||
phase_label,
|
||||
relay_count);
|
||||
}
|
||||
|
||||
pool_events = nostr_relay_pool_query_sync(pool,
|
||||
relay_urls,
|
||||
relay_count,
|
||||
filter,
|
||||
&pool_event_count,
|
||||
timeout_ms);
|
||||
|
||||
cJSON_Delete(filter);
|
||||
filter = NULL;
|
||||
|
||||
if (!pool_events || pool_event_count <= 0) {
|
||||
nostr_relay_pool_destroy(pool);
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (i = 0; i < pool_event_count; i++) {
|
||||
char *event_str = cJSON_PrintUnformatted(pool_events[i]);
|
||||
if (!event_str || nt_push_string(&events, &events_n, &events_cap, event_str) != 0) {
|
||||
free(event_str);
|
||||
nt_free_string_array(events, events_n);
|
||||
for (i = 0; i < pool_event_count; i++) {
|
||||
cJSON_Delete(pool_events[i]);
|
||||
}
|
||||
free(pool_events);
|
||||
nostr_relay_pool_destroy(pool);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0; i < pool_event_count; i++) {
|
||||
cJSON_Delete(pool_events[i]);
|
||||
}
|
||||
free(pool_events);
|
||||
|
||||
if (verbose && phase_label) {
|
||||
fprintf(stderr,
|
||||
"%s: relay pool collected %d unique events\n",
|
||||
phase_label,
|
||||
events_n);
|
||||
}
|
||||
|
||||
nostr_relay_pool_destroy(pool);
|
||||
*events_out = events;
|
||||
*event_count = events_n;
|
||||
return 0;
|
||||
|
||||
109
src/nsigner_auth.c
Normal file
109
src/nsigner_auth.c
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "nsigner_auth.h"
|
||||
|
||||
#include "../resources/nostr_core_lib/nostr_core/nip001.h"
|
||||
#include "../resources/nostr_core_lib/nostr_core/utils.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#define NT_NSIGNER_AUTH_EVENT_KIND 27235
|
||||
|
||||
static cJSON *nt_nsigner_auth_build_tag_pair(const char *name, const char *value) {
|
||||
cJSON *pair;
|
||||
|
||||
if (!name || !value) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pair = cJSON_CreateArray();
|
||||
if (!pair) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(pair, cJSON_CreateString(name));
|
||||
cJSON_AddItemToArray(pair, cJSON_CreateString(value));
|
||||
return pair;
|
||||
}
|
||||
|
||||
static int nt_nsigner_auth_compute_params_hash_hex(const cJSON *params, char out_hex_65[65]) {
|
||||
char *params_json = NULL;
|
||||
unsigned char hash[32];
|
||||
int rc = -1;
|
||||
|
||||
if (!out_hex_65) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (params) {
|
||||
params_json = cJSON_PrintUnformatted((cJSON *)params);
|
||||
} else {
|
||||
params_json = strdup("null");
|
||||
}
|
||||
|
||||
if (!params_json) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (nostr_sha256((const unsigned char *)params_json, strlen(params_json), hash) != 0) {
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
nostr_bytes_to_hex(hash, sizeof(hash), out_hex_65);
|
||||
out_hex_65[64] = '\0';
|
||||
rc = 0;
|
||||
|
||||
cleanup:
|
||||
free(params_json);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int nt_nsigner_auth_build(const char *request_id,
|
||||
const char *method,
|
||||
const cJSON *params,
|
||||
const nt_nsigner_auth_ctx_t *auth_ctx,
|
||||
time_t created_at,
|
||||
cJSON **out_auth) {
|
||||
char body_hash_hex[65];
|
||||
cJSON *tags = NULL;
|
||||
cJSON *auth_event = NULL;
|
||||
|
||||
if (!request_id || request_id[0] == '\0' ||
|
||||
!method || method[0] == '\0' ||
|
||||
!auth_ctx || !auth_ctx->enabled ||
|
||||
!out_auth) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out_auth = NULL;
|
||||
|
||||
if (nt_nsigner_auth_compute_params_hash_hex(params, body_hash_hex) != 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
tags = cJSON_CreateArray();
|
||||
if (!tags) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
cJSON_AddItemToArray(tags, nt_nsigner_auth_build_tag_pair("nsigner_rpc", request_id));
|
||||
cJSON_AddItemToArray(tags, nt_nsigner_auth_build_tag_pair("nsigner_method", method));
|
||||
cJSON_AddItemToArray(tags, nt_nsigner_auth_build_tag_pair("nsigner_body_hash", body_hash_hex));
|
||||
|
||||
if (created_at <= 0) {
|
||||
created_at = time(NULL);
|
||||
}
|
||||
|
||||
auth_event = nostr_create_and_sign_event(NT_NSIGNER_AUTH_EVENT_KIND,
|
||||
auth_ctx->label,
|
||||
tags,
|
||||
auth_ctx->privkey,
|
||||
created_at);
|
||||
if (!auth_event) {
|
||||
cJSON_Delete(tags);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*out_auth = auth_event;
|
||||
return 0;
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
#include <netdb.h>
|
||||
#include <netinet/tcp.h>
|
||||
|
||||
static int write_all(int fd, const void *buf, size_t len) {
|
||||
const unsigned char *p = (const unsigned char *)buf;
|
||||
@@ -71,6 +72,7 @@ static int set_recv_timeout_ms(int fd, int timeout_ms) {
|
||||
static int nsigner_error_code_from_response(const cJSON *response) {
|
||||
const cJSON *error_item;
|
||||
const char *name = NULL;
|
||||
int code = 0;
|
||||
|
||||
error_item = cJSON_GetObjectItemCaseSensitive((cJSON *)response, "error");
|
||||
if (!error_item || cJSON_IsNull(error_item)) {
|
||||
@@ -81,18 +83,28 @@ static int nsigner_error_code_from_response(const cJSON *response) {
|
||||
name = error_item->valuestring;
|
||||
} else if (cJSON_IsObject(error_item)) {
|
||||
const cJSON *message_item = cJSON_GetObjectItemCaseSensitive((cJSON *)error_item, "message");
|
||||
const cJSON *code_item = cJSON_GetObjectItemCaseSensitive((cJSON *)error_item, "code");
|
||||
if (cJSON_IsString(message_item) && message_item->valuestring) {
|
||||
name = message_item->valuestring;
|
||||
}
|
||||
if (cJSON_IsNumber(code_item)) {
|
||||
code = code_item->valueint;
|
||||
}
|
||||
}
|
||||
|
||||
if (name && strcmp(name, "approval_denied") == 0) {
|
||||
if ((name && strcmp(name, "approval_denied") == 0) ||
|
||||
(name && strcmp(name, "policy_denied") == 0)) {
|
||||
return NT_NSIGNER_E_DENIED;
|
||||
}
|
||||
if (name && strcmp(name, "unauthorized") == 0) {
|
||||
return NT_NSIGNER_E_UNAUTH;
|
||||
}
|
||||
|
||||
if ((name && strncmp(name, "auth_envelope_", 14) == 0) ||
|
||||
(code >= 2010 && code <= 2017)) {
|
||||
return NT_NSIGNER_E_AUTH;
|
||||
}
|
||||
|
||||
return NT_NSIGNER_E_ERROR;
|
||||
}
|
||||
|
||||
@@ -349,6 +361,7 @@ static int nsigner_connect_url(const char *url) {
|
||||
|
||||
for (ai = res; ai; ai = ai->ai_next) {
|
||||
struct timeval tv;
|
||||
int one = 1;
|
||||
|
||||
fd = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
|
||||
if (fd < 0) {
|
||||
@@ -358,6 +371,10 @@ static int nsigner_connect_url(const char *url) {
|
||||
tv.tv_sec = 5;
|
||||
tv.tv_usec = 0;
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||
(void)setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &one, sizeof(one));
|
||||
#ifdef TCP_NODELAY
|
||||
(void)setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one));
|
||||
#endif
|
||||
|
||||
if (connect(fd, ai->ai_addr, ai->ai_addrlen) == 0) {
|
||||
break;
|
||||
@@ -448,8 +465,41 @@ int nsigner_rpc(int fd, const cJSON *request, cJSON **response_out, int read_tim
|
||||
return NT_NSIGNER_OK;
|
||||
}
|
||||
|
||||
static cJSON *nsigner_selector_to_json(const nt_nsigner_selector_t *selector) {
|
||||
nt_nsigner_selector_t fallback;
|
||||
const nt_nsigner_selector_t *sel = selector;
|
||||
cJSON *selector_obj;
|
||||
|
||||
if (!sel) {
|
||||
fallback = nsigner_selector_default();
|
||||
sel = &fallback;
|
||||
}
|
||||
|
||||
selector_obj = cJSON_CreateObject();
|
||||
if (!selector_obj) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (sel->has_nostr_index) {
|
||||
if (!cJSON_AddNumberToObject(selector_obj, "nostr_index", sel->nostr_index)) {
|
||||
cJSON_Delete(selector_obj);
|
||||
return NULL;
|
||||
}
|
||||
return selector_obj;
|
||||
}
|
||||
|
||||
if (!cJSON_AddStringToObject(selector_obj,
|
||||
"role",
|
||||
(sel->role[0] != '\0') ? sel->role : "main")) {
|
||||
cJSON_Delete(selector_obj);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return selector_obj;
|
||||
}
|
||||
|
||||
static int nsigner_call_string_method(const char *socket_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *method,
|
||||
const char **args,
|
||||
int arg_count,
|
||||
@@ -457,7 +507,7 @@ static int nsigner_call_string_method(const char *socket_name,
|
||||
int fd = -1;
|
||||
cJSON *request = NULL;
|
||||
cJSON *params = NULL;
|
||||
cJSON *role_obj = NULL;
|
||||
cJSON *selector_obj = NULL;
|
||||
cJSON *response = NULL;
|
||||
cJSON *result_item;
|
||||
int rc;
|
||||
@@ -494,15 +544,14 @@ static int nsigner_call_string_method(const char *socket_name,
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(args[i]));
|
||||
}
|
||||
|
||||
role_obj = cJSON_CreateObject();
|
||||
if (!role_obj) {
|
||||
selector_obj = nsigner_selector_to_json(selector);
|
||||
if (!selector_obj) {
|
||||
cJSON_Delete(request);
|
||||
cJSON_Delete(params);
|
||||
close(fd);
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
cJSON_AddStringToObject(role_obj, "role", (role && *role) ? role : "main");
|
||||
cJSON_AddItemToArray(params, role_obj);
|
||||
cJSON_AddItemToArray(params, selector_obj);
|
||||
|
||||
cJSON_AddItemToObject(request, "params", params);
|
||||
|
||||
@@ -525,36 +574,69 @@ static int nsigner_call_string_method(const char *socket_name,
|
||||
return *result_out ? NT_NSIGNER_OK : NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
static int nsigner_call_string_method_url(const char *url,
|
||||
const char *role,
|
||||
const char *method,
|
||||
const char **args,
|
||||
int arg_count,
|
||||
char **result_out) {
|
||||
int fd = -1;
|
||||
static int nsigner_rpc_with_retry(int *fd_inout,
|
||||
const char *url,
|
||||
const cJSON *request,
|
||||
cJSON **response_out,
|
||||
int read_timeout_ms) {
|
||||
int attempts = 0;
|
||||
|
||||
if (!fd_inout || !url || !request || !response_out) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
*response_out = NULL;
|
||||
|
||||
while (attempts < 2) {
|
||||
int rc;
|
||||
|
||||
if (*fd_inout < 0) {
|
||||
*fd_inout = nsigner_connect_url(url);
|
||||
if (*fd_inout < 0) {
|
||||
attempts++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
rc = nsigner_rpc(*fd_inout, request, response_out, read_timeout_ms);
|
||||
if (rc == NT_NSIGNER_E_IO) {
|
||||
close(*fd_inout);
|
||||
*fd_inout = -1;
|
||||
attempts++;
|
||||
continue;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
static int nsigner_call_string_method_url_session(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *method,
|
||||
const char **args,
|
||||
int arg_count,
|
||||
char **result_out) {
|
||||
cJSON *request = NULL;
|
||||
cJSON *params = NULL;
|
||||
cJSON *selector_obj = NULL;
|
||||
cJSON *response = NULL;
|
||||
cJSON *result_item;
|
||||
cJSON *auth_json = NULL;
|
||||
int rc;
|
||||
|
||||
if (!url || !method || !result_out || arg_count < 0) {
|
||||
if (!fd_inout || !url || !method || !result_out || arg_count < 0) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
(void)role;
|
||||
*result_out = NULL;
|
||||
|
||||
fd = nsigner_connect_url(url);
|
||||
if (fd < 0) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
request = cJSON_CreateObject();
|
||||
params = cJSON_CreateArray();
|
||||
if (!request || !params) {
|
||||
cJSON_Delete(request);
|
||||
cJSON_Delete(params);
|
||||
close(fd);
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
@@ -565,17 +647,34 @@ static int nsigner_call_string_method_url(const char *url,
|
||||
if (!args || !args[i]) {
|
||||
cJSON_Delete(request);
|
||||
cJSON_Delete(params);
|
||||
close(fd);
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(args[i]));
|
||||
}
|
||||
|
||||
selector_obj = nsigner_selector_to_json(selector);
|
||||
if (!selector_obj) {
|
||||
cJSON_Delete(request);
|
||||
cJSON_Delete(params);
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
cJSON_AddItemToArray(params, selector_obj);
|
||||
|
||||
cJSON_AddItemToObject(request, "params", params);
|
||||
|
||||
rc = nsigner_rpc(fd, request, &response, 60000);
|
||||
if (auth && auth->enabled) {
|
||||
if (nt_nsigner_auth_build("1", method, params, auth, 0, &auth_json) != 0 ||
|
||||
!auth_json ||
|
||||
!cJSON_AddItemToObject(request, "auth", auth_json)) {
|
||||
cJSON_Delete(auth_json);
|
||||
cJSON_Delete(request);
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
auth_json = NULL;
|
||||
}
|
||||
|
||||
rc = nsigner_rpc_with_retry(fd_inout, url, request, &response, 60000);
|
||||
cJSON_Delete(request);
|
||||
close(fd);
|
||||
if (rc != NT_NSIGNER_OK) {
|
||||
return rc;
|
||||
}
|
||||
@@ -592,6 +691,23 @@ static int nsigner_call_string_method_url(const char *url,
|
||||
return *result_out ? NT_NSIGNER_OK : NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
static int nsigner_call_string_method_url(const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *method,
|
||||
const char **args,
|
||||
int arg_count,
|
||||
char **result_out) {
|
||||
int fd = -1;
|
||||
int rc;
|
||||
|
||||
rc = nsigner_call_string_method_url_session(&fd, url, selector, auth, method, args, arg_count, result_out);
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int nsigner_qrexec_rpc(const char *target_qube,
|
||||
const char *service_name,
|
||||
const cJSON *request,
|
||||
@@ -727,14 +843,14 @@ static int nsigner_qrexec_rpc(const char *target_qube,
|
||||
|
||||
static int nsigner_qrexec_call_string_method(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *method,
|
||||
const char **args,
|
||||
int arg_count,
|
||||
char **result_out) {
|
||||
cJSON *request = NULL;
|
||||
cJSON *params = NULL;
|
||||
cJSON *role_obj = NULL;
|
||||
cJSON *selector_obj = NULL;
|
||||
cJSON *response = NULL;
|
||||
cJSON *result_item;
|
||||
int rc;
|
||||
@@ -764,14 +880,13 @@ static int nsigner_qrexec_call_string_method(const char *target_qube,
|
||||
cJSON_AddItemToArray(params, cJSON_CreateString(args[i]));
|
||||
}
|
||||
|
||||
role_obj = cJSON_CreateObject();
|
||||
if (!role_obj) {
|
||||
selector_obj = nsigner_selector_to_json(selector);
|
||||
if (!selector_obj) {
|
||||
cJSON_Delete(request);
|
||||
cJSON_Delete(params);
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
cJSON_AddStringToObject(role_obj, "role", (role && *role) ? role : "main");
|
||||
cJSON_AddItemToArray(params, role_obj);
|
||||
cJSON_AddItemToArray(params, selector_obj);
|
||||
|
||||
cJSON_AddItemToObject(request, "params", params);
|
||||
|
||||
@@ -793,7 +908,7 @@ static int nsigner_qrexec_call_string_method(const char *target_qube,
|
||||
return *result_out ? NT_NSIGNER_OK : NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
int nsigner_get_public_key(const char *socket_name, const char *role, char *out_hex_65) {
|
||||
int nsigner_get_public_key(const char *socket_name, const nt_nsigner_selector_t *selector, char *out_hex_65) {
|
||||
char *result = NULL;
|
||||
int rc;
|
||||
size_t len;
|
||||
@@ -802,7 +917,7 @@ int nsigner_get_public_key(const char *socket_name, const char *role, char *out_
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
rc = nsigner_call_string_method(socket_name, role, "get_public_key", NULL, 0, &result);
|
||||
rc = nsigner_call_string_method(socket_name, selector, "get_public_key", NULL, 0, &result);
|
||||
if (rc != NT_NSIGNER_OK) {
|
||||
return rc;
|
||||
}
|
||||
@@ -818,7 +933,7 @@ int nsigner_get_public_key(const char *socket_name, const char *role, char *out_
|
||||
return NT_NSIGNER_OK;
|
||||
}
|
||||
|
||||
int nsigner_sign_event(const char *socket_name, const char *role,
|
||||
int nsigner_sign_event(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *unsigned_event_json, char **signed_event_json_out) {
|
||||
const char *args[1];
|
||||
|
||||
@@ -827,10 +942,10 @@ int nsigner_sign_event(const char *socket_name, const char *role,
|
||||
}
|
||||
|
||||
args[0] = unsigned_event_json;
|
||||
return nsigner_call_string_method(socket_name, role, "sign_event", args, 1, signed_event_json_out);
|
||||
return nsigner_call_string_method(socket_name, selector, "sign_event", args, 1, signed_event_json_out);
|
||||
}
|
||||
|
||||
int nsigner_nip04_encrypt(const char *socket_name, const char *role,
|
||||
int nsigner_nip04_encrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
|
||||
const char *args[2];
|
||||
|
||||
@@ -840,10 +955,10 @@ int nsigner_nip04_encrypt(const char *socket_name, const char *role,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = plaintext;
|
||||
return nsigner_call_string_method(socket_name, role, "nip04_encrypt", args, 2, cipher_out);
|
||||
return nsigner_call_string_method(socket_name, selector, "nip04_encrypt", args, 2, cipher_out);
|
||||
}
|
||||
|
||||
int nsigner_nip04_decrypt(const char *socket_name, const char *role,
|
||||
int nsigner_nip04_decrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
|
||||
const char *args[2];
|
||||
|
||||
@@ -853,10 +968,10 @@ int nsigner_nip04_decrypt(const char *socket_name, const char *role,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = ciphertext;
|
||||
return nsigner_call_string_method(socket_name, role, "nip04_decrypt", args, 2, plain_out);
|
||||
return nsigner_call_string_method(socket_name, selector, "nip04_decrypt", args, 2, plain_out);
|
||||
}
|
||||
|
||||
int nsigner_nip44_encrypt(const char *socket_name, const char *role,
|
||||
int nsigner_nip44_encrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
|
||||
const char *args[2];
|
||||
|
||||
@@ -866,10 +981,10 @@ int nsigner_nip44_encrypt(const char *socket_name, const char *role,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = plaintext;
|
||||
return nsigner_call_string_method(socket_name, role, "nip44_encrypt", args, 2, cipher_out);
|
||||
return nsigner_call_string_method(socket_name, selector, "nip44_encrypt", args, 2, cipher_out);
|
||||
}
|
||||
|
||||
int nsigner_nip44_decrypt(const char *socket_name, const char *role,
|
||||
int nsigner_nip44_decrypt(const char *socket_name, const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
|
||||
const char *args[2];
|
||||
|
||||
@@ -879,19 +994,33 @@ int nsigner_nip44_decrypt(const char *socket_name, const char *role,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = ciphertext;
|
||||
return nsigner_call_string_method(socket_name, role, "nip44_decrypt", args, 2, plain_out);
|
||||
return nsigner_call_string_method(socket_name, selector, "nip44_decrypt", args, 2, plain_out);
|
||||
}
|
||||
|
||||
int nsigner_get_public_key_url(const char *url, const char *role, char *out_hex_65) {
|
||||
int nsigner_session_open_url(const char *url) {
|
||||
return nsigner_connect_url(url);
|
||||
}
|
||||
|
||||
void nsigner_session_close(int fd) {
|
||||
if (fd >= 0) {
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
int nsigner_session_get_public_key(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
char *out_hex_65) {
|
||||
char *result = NULL;
|
||||
int rc;
|
||||
size_t len;
|
||||
|
||||
if (!out_hex_65) {
|
||||
if (!fd_inout || !out_hex_65) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
rc = nsigner_call_string_method_url(url, role, "get_public_key", NULL, 0, &result);
|
||||
rc = nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "get_public_key", NULL, 0, &result);
|
||||
if (rc != NT_NSIGNER_OK) {
|
||||
return rc;
|
||||
}
|
||||
@@ -907,7 +1036,122 @@ int nsigner_get_public_key_url(const char *url, const char *role, char *out_hex_
|
||||
return NT_NSIGNER_OK;
|
||||
}
|
||||
|
||||
int nsigner_sign_event_url(const char *url, const char *role,
|
||||
int nsigner_session_sign_event(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *unsigned_event_json,
|
||||
char **signed_event_json_out) {
|
||||
const char *args[1];
|
||||
|
||||
if (!fd_inout || !url || !unsigned_event_json || !signed_event_json_out) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
args[0] = unsigned_event_json;
|
||||
return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "sign_event", args, 1, signed_event_json_out);
|
||||
}
|
||||
|
||||
int nsigner_session_nip04_encrypt(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex,
|
||||
const char *plaintext,
|
||||
char **cipher_out) {
|
||||
const char *args[2];
|
||||
|
||||
if (!fd_inout || !url || !peer_pub_hex || !plaintext || !cipher_out) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = plaintext;
|
||||
return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "nip04_encrypt", args, 2, cipher_out);
|
||||
}
|
||||
|
||||
int nsigner_session_nip04_decrypt(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex,
|
||||
const char *ciphertext,
|
||||
char **plain_out) {
|
||||
const char *args[2];
|
||||
|
||||
if (!fd_inout || !url || !peer_pub_hex || !ciphertext || !plain_out) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = ciphertext;
|
||||
return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "nip04_decrypt", args, 2, plain_out);
|
||||
}
|
||||
|
||||
int nsigner_session_nip44_encrypt(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex,
|
||||
const char *plaintext,
|
||||
char **cipher_out) {
|
||||
const char *args[2];
|
||||
|
||||
if (!fd_inout || !url || !peer_pub_hex || !plaintext || !cipher_out) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = plaintext;
|
||||
return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "nip44_encrypt", args, 2, cipher_out);
|
||||
}
|
||||
|
||||
int nsigner_session_nip44_decrypt(int *fd_inout,
|
||||
const char *url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex,
|
||||
const char *ciphertext,
|
||||
char **plain_out) {
|
||||
const char *args[2];
|
||||
|
||||
if (!fd_inout || !url || !peer_pub_hex || !ciphertext || !plain_out) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = ciphertext;
|
||||
return nsigner_call_string_method_url_session(fd_inout, url, selector, auth, "nip44_decrypt", args, 2, plain_out);
|
||||
}
|
||||
|
||||
int nsigner_get_public_key_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth, char *out_hex_65) {
|
||||
char *result = NULL;
|
||||
int rc;
|
||||
size_t len;
|
||||
|
||||
if (!out_hex_65) {
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
rc = nsigner_call_string_method_url(url, selector, auth, "get_public_key", NULL, 0, &result);
|
||||
if (rc != NT_NSIGNER_OK) {
|
||||
return rc;
|
||||
}
|
||||
|
||||
len = strlen(result);
|
||||
if (len != 64) {
|
||||
free(result);
|
||||
return NT_NSIGNER_E_PARSE;
|
||||
}
|
||||
|
||||
memcpy(out_hex_65, result, len + 1);
|
||||
free(result);
|
||||
return NT_NSIGNER_OK;
|
||||
}
|
||||
|
||||
int nsigner_sign_event_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *unsigned_event_json, char **signed_event_json_out) {
|
||||
const char *args[1];
|
||||
|
||||
@@ -916,10 +1160,11 @@ int nsigner_sign_event_url(const char *url, const char *role,
|
||||
}
|
||||
|
||||
args[0] = unsigned_event_json;
|
||||
return nsigner_call_string_method_url(url, role, "sign_event", args, 1, signed_event_json_out);
|
||||
return nsigner_call_string_method_url(url, selector, auth, "sign_event", args, 1, signed_event_json_out);
|
||||
}
|
||||
|
||||
int nsigner_nip04_encrypt_url(const char *url, const char *role,
|
||||
int nsigner_nip04_encrypt_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
|
||||
const char *args[2];
|
||||
|
||||
@@ -929,10 +1174,11 @@ int nsigner_nip04_encrypt_url(const char *url, const char *role,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = plaintext;
|
||||
return nsigner_call_string_method_url(url, role, "nip04_encrypt", args, 2, cipher_out);
|
||||
return nsigner_call_string_method_url(url, selector, auth, "nip04_encrypt", args, 2, cipher_out);
|
||||
}
|
||||
|
||||
int nsigner_nip04_decrypt_url(const char *url, const char *role,
|
||||
int nsigner_nip04_decrypt_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
|
||||
const char *args[2];
|
||||
|
||||
@@ -942,10 +1188,11 @@ int nsigner_nip04_decrypt_url(const char *url, const char *role,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = ciphertext;
|
||||
return nsigner_call_string_method_url(url, role, "nip04_decrypt", args, 2, plain_out);
|
||||
return nsigner_call_string_method_url(url, selector, auth, "nip04_decrypt", args, 2, plain_out);
|
||||
}
|
||||
|
||||
int nsigner_nip44_encrypt_url(const char *url, const char *role,
|
||||
int nsigner_nip44_encrypt_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex, const char *plaintext, char **cipher_out) {
|
||||
const char *args[2];
|
||||
|
||||
@@ -955,10 +1202,11 @@ int nsigner_nip44_encrypt_url(const char *url, const char *role,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = plaintext;
|
||||
return nsigner_call_string_method_url(url, role, "nip44_encrypt", args, 2, cipher_out);
|
||||
return nsigner_call_string_method_url(url, selector, auth, "nip44_encrypt", args, 2, cipher_out);
|
||||
}
|
||||
|
||||
int nsigner_nip44_decrypt_url(const char *url, const char *role,
|
||||
int nsigner_nip44_decrypt_url(const char *url, const nt_nsigner_selector_t *selector,
|
||||
const nt_nsigner_auth_ctx_t *auth,
|
||||
const char *peer_pub_hex, const char *ciphertext, char **plain_out) {
|
||||
const char *args[2];
|
||||
|
||||
@@ -968,12 +1216,12 @@ int nsigner_nip44_decrypt_url(const char *url, const char *role,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = ciphertext;
|
||||
return nsigner_call_string_method_url(url, role, "nip44_decrypt", args, 2, plain_out);
|
||||
return nsigner_call_string_method_url(url, selector, auth, "nip44_decrypt", args, 2, plain_out);
|
||||
}
|
||||
|
||||
int nsigner_qrexec_get_public_key(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
char *out_hex_65) {
|
||||
char *result = NULL;
|
||||
int rc;
|
||||
@@ -983,7 +1231,7 @@ int nsigner_qrexec_get_public_key(const char *target_qube,
|
||||
return NT_NSIGNER_E_IO;
|
||||
}
|
||||
|
||||
rc = nsigner_qrexec_call_string_method(target_qube, service_name, role,
|
||||
rc = nsigner_qrexec_call_string_method(target_qube, service_name, selector,
|
||||
"get_public_key", NULL, 0, &result);
|
||||
if (rc != NT_NSIGNER_OK) {
|
||||
return rc;
|
||||
@@ -1002,7 +1250,7 @@ int nsigner_qrexec_get_public_key(const char *target_qube,
|
||||
|
||||
int nsigner_qrexec_sign_event(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *unsigned_event_json,
|
||||
char **signed_event_json_out) {
|
||||
const char *args[1];
|
||||
@@ -1012,13 +1260,13 @@ int nsigner_qrexec_sign_event(const char *target_qube,
|
||||
}
|
||||
|
||||
args[0] = unsigned_event_json;
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, selector,
|
||||
"sign_event", args, 1, signed_event_json_out);
|
||||
}
|
||||
|
||||
int nsigner_qrexec_nip04_encrypt(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex,
|
||||
const char *plaintext,
|
||||
char **cipher_out) {
|
||||
@@ -1030,13 +1278,13 @@ int nsigner_qrexec_nip04_encrypt(const char *target_qube,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = plaintext;
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, selector,
|
||||
"nip04_encrypt", args, 2, cipher_out);
|
||||
}
|
||||
|
||||
int nsigner_qrexec_nip04_decrypt(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex,
|
||||
const char *ciphertext,
|
||||
char **plain_out) {
|
||||
@@ -1048,13 +1296,13 @@ int nsigner_qrexec_nip04_decrypt(const char *target_qube,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = ciphertext;
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, selector,
|
||||
"nip04_decrypt", args, 2, plain_out);
|
||||
}
|
||||
|
||||
int nsigner_qrexec_nip44_encrypt(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex,
|
||||
const char *plaintext,
|
||||
char **cipher_out) {
|
||||
@@ -1066,13 +1314,13 @@ int nsigner_qrexec_nip44_encrypt(const char *target_qube,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = plaintext;
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, selector,
|
||||
"nip44_encrypt", args, 2, cipher_out);
|
||||
}
|
||||
|
||||
int nsigner_qrexec_nip44_decrypt(const char *target_qube,
|
||||
const char *service_name,
|
||||
const char *role,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
const char *peer_pub_hex,
|
||||
const char *ciphertext,
|
||||
char **plain_out) {
|
||||
@@ -1084,6 +1332,6 @@ int nsigner_qrexec_nip44_decrypt(const char *target_qube,
|
||||
|
||||
args[0] = peer_pub_hex;
|
||||
args[1] = ciphertext;
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, role,
|
||||
return nsigner_qrexec_call_string_method(target_qube, service_name, selector,
|
||||
"nip44_decrypt", args, 2, plain_out);
|
||||
}
|
||||
|
||||
194
src/signer.c
194
src/signer.c
@@ -16,7 +16,17 @@ nt_signer_t g_signer = {
|
||||
.kind = NT_SIGNER_LOCAL,
|
||||
.socket_name = {0},
|
||||
.endpoint_url = {0},
|
||||
.role = "main",
|
||||
.url_session_fd = -1,
|
||||
.selector = {
|
||||
.has_nostr_index = 1,
|
||||
.nostr_index = 0,
|
||||
.role = {0}
|
||||
},
|
||||
.url_auth = {
|
||||
.enabled = 0,
|
||||
.privkey = {0},
|
||||
.label = {0}
|
||||
}
|
||||
};
|
||||
|
||||
static int signer_local_private_key(unsigned char out_priv[32]) {
|
||||
@@ -29,13 +39,38 @@ static int signer_local_private_key(unsigned char out_priv[32]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void signer_set_selector(int has_index, int index, const char *role) {
|
||||
g_signer.selector.has_nostr_index = has_index ? 1 : 0;
|
||||
g_signer.selector.nostr_index = (index < 0) ? 0 : index;
|
||||
if (role && role[0] != '\0') {
|
||||
snprintf(g_signer.selector.role, sizeof(g_signer.selector.role), "%s", role);
|
||||
} else {
|
||||
g_signer.selector.role[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void signer_init(void) {
|
||||
nt_nsigner_selector_t sel = nsigner_selector_default();
|
||||
|
||||
if (g_signer.url_session_fd >= 0) {
|
||||
nsigner_session_close(g_signer.url_session_fd);
|
||||
g_signer.url_session_fd = -1;
|
||||
}
|
||||
|
||||
g_signer.kind = NT_SIGNER_LOCAL;
|
||||
g_signer.socket_name[0] = '\0';
|
||||
g_signer.qrexec_target[0] = '\0';
|
||||
g_signer.qrexec_service[0] = '\0';
|
||||
g_signer.endpoint_url[0] = '\0';
|
||||
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
|
||||
signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role);
|
||||
g_signer.url_auth.enabled = 0;
|
||||
memset(g_signer.url_auth.privkey, 0, sizeof(g_signer.url_auth.privkey));
|
||||
g_signer.url_auth.label[0] = '\0';
|
||||
}
|
||||
|
||||
void signer_shutdown(void) {
|
||||
memset(g_signer.url_auth.privkey, 0, sizeof(g_signer.url_auth.privkey));
|
||||
signer_init();
|
||||
}
|
||||
|
||||
int signer_init_local(void) {
|
||||
@@ -43,7 +78,9 @@ int signer_init_local(void) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int signer_init_nsigner(const char *socket_name) {
|
||||
int signer_init_nsigner(const char *socket_name, const nt_nsigner_selector_t *selector) {
|
||||
nt_nsigner_selector_t sel = selector ? *selector : nsigner_selector_default();
|
||||
|
||||
if (!socket_name || socket_name[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
@@ -52,11 +89,17 @@ int signer_init_nsigner(const char *socket_name) {
|
||||
g_signer.qrexec_target[0] = '\0';
|
||||
g_signer.qrexec_service[0] = '\0';
|
||||
g_signer.endpoint_url[0] = '\0';
|
||||
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
|
||||
if (g_signer.url_session_fd >= 0) {
|
||||
nsigner_session_close(g_signer.url_session_fd);
|
||||
g_signer.url_session_fd = -1;
|
||||
}
|
||||
signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name) {
|
||||
int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name, const nt_nsigner_selector_t *selector) {
|
||||
nt_nsigner_selector_t sel = selector ? *selector : nsigner_selector_default();
|
||||
|
||||
if (!target_qube || target_qube[0] == '\0' || !service_name || service_name[0] == '\0') {
|
||||
return -1;
|
||||
}
|
||||
@@ -65,12 +108,24 @@ int signer_init_nsigner_qrexec(const char *target_qube, const char *service_name
|
||||
snprintf(g_signer.qrexec_target, sizeof(g_signer.qrexec_target), "%s", target_qube);
|
||||
snprintf(g_signer.qrexec_service, sizeof(g_signer.qrexec_service), "%s", service_name);
|
||||
g_signer.endpoint_url[0] = '\0';
|
||||
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
|
||||
if (g_signer.url_session_fd >= 0) {
|
||||
nsigner_session_close(g_signer.url_session_fd);
|
||||
g_signer.url_session_fd = -1;
|
||||
}
|
||||
signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int signer_init_nsigner_url(const char *endpoint_url) {
|
||||
int signer_init_nsigner_url_with_session_auth(const char *endpoint_url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
int session_fd,
|
||||
const nt_nsigner_auth_ctx_t *auth_or_null) {
|
||||
nt_nsigner_selector_t sel = selector ? *selector : nsigner_selector_default();
|
||||
|
||||
if (!endpoint_url || endpoint_url[0] == '\0') {
|
||||
if (session_fd >= 0) {
|
||||
nsigner_session_close(session_fd);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -79,10 +134,41 @@ int signer_init_nsigner_url(const char *endpoint_url) {
|
||||
g_signer.qrexec_target[0] = '\0';
|
||||
g_signer.qrexec_service[0] = '\0';
|
||||
snprintf(g_signer.endpoint_url, sizeof(g_signer.endpoint_url), "%s", endpoint_url);
|
||||
snprintf(g_signer.role, sizeof(g_signer.role), "%s", "main");
|
||||
|
||||
if (g_signer.url_session_fd >= 0 && g_signer.url_session_fd != session_fd) {
|
||||
nsigner_session_close(g_signer.url_session_fd);
|
||||
}
|
||||
g_signer.url_session_fd = session_fd;
|
||||
|
||||
if (g_signer.url_session_fd < 0) {
|
||||
g_signer.url_session_fd = nsigner_session_open_url(g_signer.endpoint_url);
|
||||
}
|
||||
|
||||
signer_set_selector(sel.has_nostr_index, sel.nostr_index, sel.role);
|
||||
|
||||
g_signer.url_auth.enabled = 0;
|
||||
memset(g_signer.url_auth.privkey, 0, sizeof(g_signer.url_auth.privkey));
|
||||
g_signer.url_auth.label[0] = '\0';
|
||||
|
||||
if (auth_or_null && auth_or_null->enabled) {
|
||||
g_signer.url_auth.enabled = 1;
|
||||
memcpy(g_signer.url_auth.privkey, auth_or_null->privkey, sizeof(g_signer.url_auth.privkey));
|
||||
snprintf(g_signer.url_auth.label, sizeof(g_signer.url_auth.label), "%s", auth_or_null->label);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int signer_init_nsigner_url_with_session(const char *endpoint_url,
|
||||
const nt_nsigner_selector_t *selector,
|
||||
int session_fd) {
|
||||
return signer_init_nsigner_url_with_session_auth(endpoint_url, selector, session_fd, NULL);
|
||||
}
|
||||
|
||||
int signer_init_nsigner_url(const char *endpoint_url, const nt_nsigner_selector_t *selector) {
|
||||
return signer_init_nsigner_url_with_session_auth(endpoint_url, selector, -1, NULL);
|
||||
}
|
||||
|
||||
int signer_get_local_private_key(unsigned char out_priv[32]) {
|
||||
if (!out_priv) {
|
||||
return -1;
|
||||
@@ -148,7 +234,7 @@ int signer_create_and_sign(int kind,
|
||||
}
|
||||
|
||||
rc = nsigner_sign_event(g_signer.socket_name,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
unsigned_json,
|
||||
signed_event_json_out);
|
||||
|
||||
@@ -199,7 +285,7 @@ int signer_create_and_sign(int kind,
|
||||
|
||||
rc = nsigner_qrexec_sign_event(g_signer.qrexec_target,
|
||||
g_signer.qrexec_service,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
unsigned_json,
|
||||
signed_event_json_out);
|
||||
|
||||
@@ -248,10 +334,12 @@ int signer_create_and_sign(int kind,
|
||||
return -1;
|
||||
}
|
||||
|
||||
rc = nsigner_sign_event_url(g_signer.endpoint_url,
|
||||
g_signer.role,
|
||||
unsigned_json,
|
||||
signed_event_json_out);
|
||||
rc = nsigner_session_sign_event(&g_signer.url_session_fd,
|
||||
g_signer.endpoint_url,
|
||||
&g_signer.selector,
|
||||
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
|
||||
unsigned_json,
|
||||
signed_event_json_out);
|
||||
|
||||
free(unsigned_json);
|
||||
cJSON_Delete(unsigned_event);
|
||||
@@ -307,7 +395,7 @@ int signer_sign_event_json(const char *unsigned_event_json, char **signed_event_
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER) {
|
||||
return nsigner_sign_event(g_signer.socket_name,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
unsigned_event_json,
|
||||
signed_event_json_out);
|
||||
}
|
||||
@@ -315,16 +403,18 @@ int signer_sign_event_json(const char *unsigned_event_json, char **signed_event_
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
|
||||
return nsigner_qrexec_sign_event(g_signer.qrexec_target,
|
||||
g_signer.qrexec_service,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
unsigned_event_json,
|
||||
signed_event_json_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
|
||||
return nsigner_sign_event_url(g_signer.endpoint_url,
|
||||
g_signer.role,
|
||||
unsigned_event_json,
|
||||
signed_event_json_out);
|
||||
return nsigner_session_sign_event(&g_signer.url_session_fd,
|
||||
g_signer.endpoint_url,
|
||||
&g_signer.selector,
|
||||
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
|
||||
unsigned_event_json,
|
||||
signed_event_json_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind != NT_SIGNER_LOCAL) {
|
||||
@@ -385,7 +475,7 @@ int signer_nip04_encrypt(const char *peer_pub_hex, const char *plaintext, char *
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER) {
|
||||
return nsigner_nip04_encrypt(g_signer.socket_name,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
peer_pub_hex,
|
||||
plaintext,
|
||||
cipher_out);
|
||||
@@ -394,18 +484,20 @@ int signer_nip04_encrypt(const char *peer_pub_hex, const char *plaintext, char *
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
|
||||
return nsigner_qrexec_nip04_encrypt(g_signer.qrexec_target,
|
||||
g_signer.qrexec_service,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
peer_pub_hex,
|
||||
plaintext,
|
||||
cipher_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
|
||||
return nsigner_nip04_encrypt_url(g_signer.endpoint_url,
|
||||
g_signer.role,
|
||||
peer_pub_hex,
|
||||
plaintext,
|
||||
cipher_out);
|
||||
return nsigner_session_nip04_encrypt(&g_signer.url_session_fd,
|
||||
g_signer.endpoint_url,
|
||||
&g_signer.selector,
|
||||
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
|
||||
peer_pub_hex,
|
||||
plaintext,
|
||||
cipher_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind != NT_SIGNER_LOCAL) {
|
||||
@@ -444,7 +536,7 @@ int signer_nip04_decrypt(const char *peer_pub_hex, const char *ciphertext, char
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER) {
|
||||
return nsigner_nip04_decrypt(g_signer.socket_name,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
peer_pub_hex,
|
||||
ciphertext,
|
||||
plain_out);
|
||||
@@ -453,18 +545,20 @@ int signer_nip04_decrypt(const char *peer_pub_hex, const char *ciphertext, char
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
|
||||
return nsigner_qrexec_nip04_decrypt(g_signer.qrexec_target,
|
||||
g_signer.qrexec_service,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
peer_pub_hex,
|
||||
ciphertext,
|
||||
plain_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
|
||||
return nsigner_nip04_decrypt_url(g_signer.endpoint_url,
|
||||
g_signer.role,
|
||||
peer_pub_hex,
|
||||
ciphertext,
|
||||
plain_out);
|
||||
return nsigner_session_nip04_decrypt(&g_signer.url_session_fd,
|
||||
g_signer.endpoint_url,
|
||||
&g_signer.selector,
|
||||
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
|
||||
peer_pub_hex,
|
||||
ciphertext,
|
||||
plain_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind != NT_SIGNER_LOCAL) {
|
||||
@@ -503,7 +597,7 @@ int signer_nip44_encrypt(const char *peer_pub_hex, const char *plaintext, char *
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER) {
|
||||
return nsigner_nip44_encrypt(g_signer.socket_name,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
peer_pub_hex,
|
||||
plaintext,
|
||||
cipher_out);
|
||||
@@ -512,18 +606,20 @@ int signer_nip44_encrypt(const char *peer_pub_hex, const char *plaintext, char *
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
|
||||
return nsigner_qrexec_nip44_encrypt(g_signer.qrexec_target,
|
||||
g_signer.qrexec_service,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
peer_pub_hex,
|
||||
plaintext,
|
||||
cipher_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
|
||||
return nsigner_nip44_encrypt_url(g_signer.endpoint_url,
|
||||
g_signer.role,
|
||||
peer_pub_hex,
|
||||
plaintext,
|
||||
cipher_out);
|
||||
return nsigner_session_nip44_encrypt(&g_signer.url_session_fd,
|
||||
g_signer.endpoint_url,
|
||||
&g_signer.selector,
|
||||
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
|
||||
peer_pub_hex,
|
||||
plaintext,
|
||||
cipher_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind != NT_SIGNER_LOCAL) {
|
||||
@@ -562,7 +658,7 @@ int signer_nip44_decrypt(const char *peer_pub_hex, const char *ciphertext, char
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER) {
|
||||
return nsigner_nip44_decrypt(g_signer.socket_name,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
peer_pub_hex,
|
||||
ciphertext,
|
||||
plain_out);
|
||||
@@ -571,18 +667,20 @@ int signer_nip44_decrypt(const char *peer_pub_hex, const char *ciphertext, char
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_QREXEC) {
|
||||
return nsigner_qrexec_nip44_decrypt(g_signer.qrexec_target,
|
||||
g_signer.qrexec_service,
|
||||
g_signer.role,
|
||||
&g_signer.selector,
|
||||
peer_pub_hex,
|
||||
ciphertext,
|
||||
plain_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind == NT_SIGNER_NSIGNER_URL) {
|
||||
return nsigner_nip44_decrypt_url(g_signer.endpoint_url,
|
||||
g_signer.role,
|
||||
peer_pub_hex,
|
||||
ciphertext,
|
||||
plain_out);
|
||||
return nsigner_session_nip44_decrypt(&g_signer.url_session_fd,
|
||||
g_signer.endpoint_url,
|
||||
&g_signer.selector,
|
||||
g_signer.url_auth.enabled ? &g_signer.url_auth : NULL,
|
||||
peer_pub_hex,
|
||||
ciphertext,
|
||||
plain_out);
|
||||
}
|
||||
|
||||
if (g_signer.kind != NT_SIGNER_LOCAL) {
|
||||
|
||||
306
src/tui.c
306
src/tui.c
@@ -1,6 +1,8 @@
|
||||
#include "tui.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
@@ -13,6 +15,36 @@ static struct termios g_original_termios;
|
||||
static bool g_termios_saved = false;
|
||||
static bool g_raw_enabled = false;
|
||||
|
||||
static volatile sig_atomic_t g_resize_pending = 0;
|
||||
|
||||
static char g_app_title[256] = "NOSTR TERMINAL";
|
||||
static int g_last_menu_start_col = 0;
|
||||
static int g_frame_line_count = 0;
|
||||
static bool g_frame_active = false;
|
||||
|
||||
static int tui_visible_len_no_markup(const char *s) {
|
||||
int len = 0;
|
||||
|
||||
if (!s) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; s[i] != '\0';) {
|
||||
if (s[i] == '^' && s[i + 1] == '_') {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
if (s[i] == '^' && s[i + 1] == ':') {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
len++;
|
||||
i++;
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
/* Write a string while converting ^_...^: segments to underline. */
|
||||
static void tui_write_with_hotkey_markup(const char *s) {
|
||||
bool in_hotkey = false;
|
||||
@@ -29,6 +61,11 @@ static void tui_write_with_hotkey_markup(const char *s) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_hotkey && s[i] == '^' && s[i + 1] == '^') {
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_hotkey && s[i] == '^' && s[i + 1] == ':') {
|
||||
fputs("\x1b[0m", stdout); /* reset */
|
||||
in_hotkey = false;
|
||||
@@ -45,6 +82,86 @@ static void tui_write_with_hotkey_markup(const char *s) {
|
||||
}
|
||||
}
|
||||
|
||||
static int tui_count_newlines(const char *s) {
|
||||
int count = 0;
|
||||
|
||||
if (!s) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; s[i] != '\0'; i++) {
|
||||
if (s[i] == '\n') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static void tui_track_lines(int lines) {
|
||||
if (g_frame_active && lines > 0) {
|
||||
g_frame_line_count += lines;
|
||||
}
|
||||
}
|
||||
|
||||
static void tui_print_repeat_char(char ch, int width) {
|
||||
if (width <= 0) {
|
||||
fputc('\n', stdout);
|
||||
tui_track_lines(1);
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < width; i++) {
|
||||
fputc(ch, stdout);
|
||||
}
|
||||
fputc('\n', stdout);
|
||||
tui_track_lines(1);
|
||||
}
|
||||
|
||||
static void tui_print_centered_text(const char *text, int width) {
|
||||
int len = 0;
|
||||
|
||||
if (!text) {
|
||||
fputc('\n', stdout);
|
||||
tui_track_lines(1);
|
||||
return;
|
||||
}
|
||||
|
||||
len = tui_visible_len_no_markup(text);
|
||||
if (width <= 0) {
|
||||
fputc('\n', stdout);
|
||||
tui_track_lines(1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (len >= width) {
|
||||
tui_write_with_hotkey_markup(text);
|
||||
fputc('\n', stdout);
|
||||
tui_track_lines(1);
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
int left = (width - len) / 2;
|
||||
int right = width - len - left;
|
||||
|
||||
for (int i = 0; i < left; i++) {
|
||||
fputc(' ', stdout);
|
||||
}
|
||||
tui_write_with_hotkey_markup(text);
|
||||
for (int i = 0; i < right; i++) {
|
||||
fputc(' ', stdout);
|
||||
}
|
||||
fputc('\n', stdout);
|
||||
tui_track_lines(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void tui_handle_sigwinch(int signum) {
|
||||
(void)signum;
|
||||
g_resize_pending = 1;
|
||||
}
|
||||
|
||||
void tui_init(void) {
|
||||
struct termios raw;
|
||||
|
||||
@@ -81,6 +198,24 @@ void tui_cleanup(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void tui_install_resize_handler(void) {
|
||||
struct sigaction sa;
|
||||
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = tui_handle_sigwinch;
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
(void)sigaction(SIGWINCH, &sa, NULL);
|
||||
}
|
||||
|
||||
int tui_consume_resize_flag(void) {
|
||||
if (g_resize_pending) {
|
||||
g_resize_pending = 0;
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tui_get_terminal_size(int *width, int *height) {
|
||||
struct winsize ws;
|
||||
int w = 80;
|
||||
@@ -103,6 +238,14 @@ void tui_get_terminal_size(int *width, int *height) {
|
||||
}
|
||||
}
|
||||
|
||||
void tui_set_app_title(const char *title) {
|
||||
if (!title || title[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
snprintf(g_app_title, sizeof(g_app_title), "%s", title);
|
||||
}
|
||||
|
||||
void tui_clear_screen(void) {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
@@ -126,6 +269,152 @@ void tui_print(const char *fmt, ...) {
|
||||
tui_write_with_hotkey_markup(buffer);
|
||||
fputc('\n', stdout);
|
||||
fflush(stdout);
|
||||
|
||||
tui_track_lines(tui_count_newlines(buffer) + 1);
|
||||
}
|
||||
|
||||
void tui_print_hr(void) {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
(void)height;
|
||||
|
||||
tui_get_terminal_size(&width, &height);
|
||||
tui_print_repeat_char('=', width);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
void tui_print_centered(const char *text) {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
(void)height;
|
||||
|
||||
tui_get_terminal_size(&width, &height);
|
||||
tui_print_centered_text(text ? text : "", width);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
int tui_centered_menu_start_col(void) {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int title_len = 0;
|
||||
(void)height;
|
||||
|
||||
tui_get_terminal_size(&width, &height);
|
||||
title_len = tui_visible_len_no_markup(g_app_title);
|
||||
|
||||
if (width <= title_len) {
|
||||
g_last_menu_start_col = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
g_last_menu_start_col = (width - title_len) / 2;
|
||||
if (g_last_menu_start_col < 0) {
|
||||
g_last_menu_start_col = 0;
|
||||
}
|
||||
|
||||
return g_last_menu_start_col;
|
||||
}
|
||||
|
||||
void tui_render_top_frame(const tui_view_t *view) {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
(void)height;
|
||||
|
||||
tui_get_terminal_size(&width, &height);
|
||||
|
||||
tui_print_repeat_char('=', width);
|
||||
tui_print_centered_text(g_app_title, width);
|
||||
tui_print_repeat_char('=', width);
|
||||
|
||||
if (view && view->breadcrumb && view->breadcrumb[0] != '\0') {
|
||||
tui_write_with_hotkey_markup(view->breadcrumb);
|
||||
} else {
|
||||
tui_write_with_hotkey_markup("> Main Menu");
|
||||
}
|
||||
fputc('\n', stdout);
|
||||
tui_track_lines(1);
|
||||
|
||||
fputc('\n', stdout);
|
||||
fputc('\n', stdout);
|
||||
tui_track_lines(2);
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
tui_centered_menu_start_col();
|
||||
}
|
||||
|
||||
void tui_print_menu_item(const char *fmt, ...) {
|
||||
char buffer[8192];
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(buffer, sizeof(buffer), fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
for (int i = 0; i < g_last_menu_start_col; i++) {
|
||||
fputc(' ', stdout);
|
||||
}
|
||||
tui_write_with_hotkey_markup(buffer);
|
||||
fputc('\n', stdout);
|
||||
fflush(stdout);
|
||||
|
||||
tui_track_lines(tui_count_newlines(buffer) + 1);
|
||||
}
|
||||
|
||||
void tui_begin_frame(const tui_view_t *view) {
|
||||
g_frame_active = true;
|
||||
g_frame_line_count = 0;
|
||||
|
||||
tui_clear_screen();
|
||||
tui_render_top_frame(view);
|
||||
}
|
||||
|
||||
int tui_end_frame_with_prompt(const char *prompt, char *buf, int bufsize) {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int filler = 0;
|
||||
|
||||
tui_get_terminal_size(&width, &height);
|
||||
|
||||
filler = (height - 1) - g_frame_line_count;
|
||||
if (filler < 0) {
|
||||
filler = 0;
|
||||
}
|
||||
|
||||
for (int i = 0; i < filler; i++) {
|
||||
fputc('\n', stdout);
|
||||
}
|
||||
|
||||
if (filler > 0) {
|
||||
fprintf(stdout, "\033[%dA\r", filler);
|
||||
}
|
||||
|
||||
for (int i = 0; i < g_last_menu_start_col; i++) {
|
||||
fputc(' ', stdout);
|
||||
}
|
||||
|
||||
fflush(stdout);
|
||||
|
||||
g_frame_active = false;
|
||||
g_frame_line_count = 0;
|
||||
|
||||
return tui_get_line(prompt ? prompt : ">", buf, bufsize);
|
||||
}
|
||||
|
||||
void tui_show_splash(void) {
|
||||
static const tui_view_t splash_view = {
|
||||
"> Splash",
|
||||
NULL,
|
||||
};
|
||||
char hold[8];
|
||||
|
||||
tui_begin_frame(&splash_view);
|
||||
tui_print("Welcome to Nostr Terminal.");
|
||||
tui_print("");
|
||||
tui_print("Continuous TUI mode is enabled.");
|
||||
tui_print("Frame redraw preserves scrollback by using newline refresh.");
|
||||
tui_print("");
|
||||
(void)tui_end_frame_with_prompt("Press Enter to continue >", hold, (int)sizeof(hold));
|
||||
}
|
||||
|
||||
int tui_get_line(const char *prompt, char *buf, int bufsize) {
|
||||
@@ -162,6 +451,9 @@ int tui_get_line(const char *prompt, char *buf, int bufsize) {
|
||||
unsigned char ch = 0;
|
||||
ssize_t n = read(STDIN_FILENO, &ch, 1);
|
||||
if (n <= 0) {
|
||||
if (n < 0 && errno == EINTR) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -203,11 +495,19 @@ int tui_get_key(void) {
|
||||
tui_init();
|
||||
}
|
||||
|
||||
if (read(STDIN_FILENO, &ch, 1) != 1) {
|
||||
while (1) {
|
||||
ssize_t n = read(STDIN_FILENO, &ch, 1);
|
||||
if (n == 1) {
|
||||
return (int)ch;
|
||||
}
|
||||
if (n < 0 && errno == EINTR) {
|
||||
if (g_resize_pending) {
|
||||
return TUI_KEY_RESIZE;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (int)ch;
|
||||
}
|
||||
|
||||
void tui_set_window_title(const char *title) {
|
||||
|
||||
Reference in New Issue
Block a user