Files
didactyl/src/tools/tool_nostr_post.c
T

928 lines
28 KiB
C

#define _POSIX_C_SOURCE 200809L
#include "tools_internal.h"
#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "cjson/cJSON.h"
#include "../nostr_handler.h"
static char* json_error_local(const char* msg) {
cJSON* root = cJSON_CreateObject();
if (!root) return NULL;
cJSON_AddBoolToObject(root, "success", 0);
cJSON_AddStringToObject(root, "error", msg ? msg : "unknown error");
char* out = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return out;
}
static const char* skip_ws_local(const char* p) {
while (p && *p && isspace((unsigned char)*p)) p++;
return p;
}
static char* sanitize_json_string_controls_local(const char* in) {
if (!in) return NULL;
size_t len = strlen(in);
size_t cap = (len * 2U) + 1U;
char* out = (char*)malloc(cap);
if (!out) return NULL;
int in_string = 0;
int escaping = 0;
size_t j = 0;
for (size_t i = 0; i < len; i++) {
char c = in[i];
if (escaping) {
if (j + 1U >= cap) {
free(out);
return NULL;
}
out[j++] = c;
escaping = 0;
continue;
}
if (c == '\\') {
if (j + 1U >= cap) {
free(out);
return NULL;
}
out[j++] = c;
if (in_string) escaping = 1;
continue;
}
if (c == '"') {
if (j + 1U >= cap) {
free(out);
return NULL;
}
out[j++] = c;
in_string = !in_string;
continue;
}
if (in_string && (c == '\n' || c == '\r' || c == '\t')) {
if (j + 2U >= cap) {
free(out);
return NULL;
}
out[j++] = '\\';
out[j++] = (c == '\n') ? 'n' : (c == '\r') ? 'r' : 't';
continue;
}
if (j + 1U >= cap) {
free(out);
return NULL;
}
out[j++] = c;
}
out[j] = '\0';
return out;
}
static cJSON* parse_tool_args_json_local(const char* args_json) {
const char* raw_args_json = args_json ? args_json : "{}";
raw_args_json = skip_ws_local(raw_args_json);
if (!raw_args_json || raw_args_json[0] == '\0') {
raw_args_json = "{}";
}
cJSON* args = cJSON_Parse(raw_args_json);
char* repaired_args_json = NULL;
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
if (!args) {
repaired_args_json = sanitize_json_string_controls_local(raw_args_json);
if (repaired_args_json) {
args = cJSON_Parse(repaired_args_json);
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
}
}
free(repaired_args_json);
return args;
}
static const char* find_key_start_local(const char* in, const char* key) {
if (!in || !key) return NULL;
char pattern[64];
int n = snprintf(pattern, sizeof(pattern), "\"%s\"", key);
if (n <= 0 || (size_t)n >= sizeof(pattern)) return NULL;
return strstr(in, pattern);
}
static int parse_loose_kind_local(const char* in, int* out_kind) {
if (!in || !out_kind) return -1;
const char* p = find_key_start_local(in, "kind");
if (!p) return -1;
p = strchr(p, ':');
if (!p) return -1;
p = skip_ws_local(p + 1);
if (!p || !*p) return -1;
char* end = NULL;
long v = strtol(p, &end, 10);
if (end == p) return -1;
*out_kind = (int)v;
return 0;
}
static char* parse_loose_json_string_value_local(const char* in, const char* key) {
if (!in || !key) return NULL;
const char* p = find_key_start_local(in, key);
if (!p) return NULL;
p = strchr(p, ':');
if (!p) return NULL;
p = skip_ws_local(p + 1);
if (!p || *p != '"') return NULL;
p++;
size_t max_len = strlen(p);
char* out = (char*)malloc(max_len + 1U);
if (!out) return NULL;
size_t j = 0;
int escaping = 0;
for (size_t i = 0; p[i] != '\0'; i++) {
char c = p[i];
if (escaping) {
switch (c) {
case 'n': out[j++] = '\n'; break;
case 'r': out[j++] = '\r'; break;
case 't': out[j++] = '\t'; break;
case '"': out[j++] = '"'; break;
case '\\': out[j++] = '\\'; break;
default: out[j++] = c; break;
}
escaping = 0;
continue;
}
if (c == '\\') {
escaping = 1;
continue;
}
if (c == '"') {
out[j] = '\0';
return out;
}
out[j++] = c;
}
out[j] = '\0';
return out;
}
static cJSON* parse_loose_nostr_post_args_local(const char* in) {
if (!in) return NULL;
int kind = 0;
if (parse_loose_kind_local(in, &kind) != 0) {
return NULL;
}
char* content = parse_loose_json_string_value_local(in, "content");
if (!content) {
return NULL;
}
cJSON* args = cJSON_CreateObject();
if (!args) {
free(content);
return NULL;
}
cJSON_AddNumberToObject(args, "kind", kind);
cJSON_AddStringToObject(args, "content", content);
free(content);
return args;
}
static cJSON* ensure_tags_array_local(cJSON** tags_inout) {
if (!tags_inout) return NULL;
if (*tags_inout) {
if (cJSON_IsArray(*tags_inout)) {
return *tags_inout;
}
cJSON_Delete(*tags_inout);
*tags_inout = NULL;
}
*tags_inout = cJSON_CreateArray();
return *tags_inout;
}
static int has_tag_key_local(cJSON* tags, const char* key) {
if (!tags || !cJSON_IsArray(tags) || !key) return 0;
int n = cJSON_GetArraySize(tags);
for (int i = 0; i < n; i++) {
cJSON* tag = cJSON_GetArrayItem(tags, i);
if (!tag || !cJSON_IsArray(tag)) continue;
cJSON* k = cJSON_GetArrayItem(tag, 0);
if (k && cJSON_IsString(k) && k->valuestring && strcmp(k->valuestring, key) == 0) {
return 1;
}
}
return 0;
}
static int add_string_tag_local(cJSON* tags, const char* key, const char* value) {
if (!tags || !cJSON_IsArray(tags) || !key || !value || value[0] == '\0') return -1;
cJSON* tag = cJSON_CreateArray();
if (!tag) return -1;
cJSON_AddItemToArray(tag, cJSON_CreateString(key));
cJSON_AddItemToArray(tag, cJSON_CreateString(value));
cJSON_AddItemToArray(tags, tag);
return 0;
}
static char* trim_copy_local(const char* start, size_t len) {
while (len > 0 && isspace((unsigned char)start[0])) {
start++;
len--;
}
while (len > 0 && isspace((unsigned char)start[len - 1])) {
len--;
}
char* out = (char*)malloc(len + 1U);
if (!out) return NULL;
memcpy(out, start, len);
out[len] = '\0';
return out;
}
static char* first_markdown_h1_local(const char* content) {
if (!content) return NULL;
const char* p = content;
while (*p) {
const char* line = p;
const char* nl = strchr(line, '\n');
size_t len = nl ? (size_t)(nl - line) : strlen(line);
while (len > 0 && line[len - 1] == '\r') len--;
size_t i = 0;
while (i < len && isspace((unsigned char)line[i])) i++;
if (i < len && line[i] == '#') {
size_t j = i;
while (j < len && line[j] == '#') j++;
if (j < len && isspace((unsigned char)line[j])) {
while (j < len && isspace((unsigned char)line[j])) j++;
if (j < len) return trim_copy_local(line + j, len - j);
}
}
if (!nl) break;
p = nl + 1;
}
return NULL;
}
static int is_paragraph_line_local(const char* line, size_t len) {
size_t i = 0;
while (i < len && isspace((unsigned char)line[i])) i++;
if (i >= len) return 0;
char c = line[i];
if (c == '#' || c == '>' || c == '-' || c == '*' || c == '`' || c == '|') return 0;
if (isdigit((unsigned char)c)) {
size_t j = i;
while (j < len && isdigit((unsigned char)line[j])) j++;
if (j < len && line[j] == '.') return 0;
}
return 1;
}
static char* first_markdown_paragraph_local(const char* content) {
if (!content) return NULL;
const char* p = content;
while (*p) {
const char* line = p;
const char* nl = strchr(line, '\n');
size_t len = nl ? (size_t)(nl - line) : strlen(line);
while (len > 0 && line[len - 1] == '\r') len--;
if (is_paragraph_line_local(line, len)) {
size_t cap = 1024;
size_t used = 0;
char* out = (char*)malloc(cap);
if (!out) return NULL;
const char* q = line;
const char* qnl = nl;
size_t qlen = len;
while (1) {
if (!is_paragraph_line_local(q, qlen)) break;
size_t start = 0;
while (start < qlen && isspace((unsigned char)q[start])) start++;
size_t end = qlen;
while (end > start && isspace((unsigned char)q[end - 1])) end--;
size_t part_len = end - start;
if (used + part_len + 2 >= cap) {
cap = (cap * 2U) + part_len + 16U;
char* bigger = (char*)realloc(out, cap);
if (!bigger) {
free(out);
return NULL;
}
out = bigger;
}
if (part_len > 0) {
if (used > 0) out[used++] = ' ';
memcpy(out + used, q + start, part_len);
used += part_len;
}
if (!qnl) break;
q = qnl + 1;
qnl = strchr(q, '\n');
qlen = qnl ? (size_t)(qnl - q) : strlen(q);
while (qlen > 0 && q[qlen - 1] == '\r') qlen--;
}
out[used] = '\0';
return out;
}
if (!nl) break;
p = nl + 1;
}
return NULL;
}
static char* first_markdown_image_url_local(const char* content) {
if (!content) return NULL;
const char* p = content;
while ((p = strstr(p, "![")) != NULL) {
const char* close_bracket = strchr(p + 2, ']');
if (!close_bracket || close_bracket[1] != '(') {
p += 2;
continue;
}
const char* url_start = close_bracket + 2;
const char* url_end = strchr(url_start, ')');
if (!url_end || url_end <= url_start) {
p += 2;
continue;
}
return trim_copy_local(url_start, (size_t)(url_end - url_start));
}
return NULL;
}
static char* d_tagify_string_local(const char* in, const char* fallback) {
if (!in || in[0] == '\0') {
return fallback ? strdup(fallback) : NULL;
}
size_t len = strlen(in);
char* out = (char*)malloc(len + 1U);
if (!out) return NULL;
size_t j = 0;
int prev_dash = 0;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)in[i];
if (isalnum(c)) {
out[j++] = (char)tolower(c);
prev_dash = 0;
} else if (!prev_dash && j > 0) {
out[j++] = '-';
prev_dash = 1;
}
}
while (j > 0 && out[j - 1] == '-') j--;
out[j] = '\0';
if (j == 0) {
free(out);
return fallback ? strdup(fallback) : NULL;
}
return out;
}
static void ensure_nip23_metadata_tags_local(int kind, const char* content, cJSON** tags_inout) {
if (!content || (kind != 30023 && kind != 30024) || !tags_inout) {
return;
}
cJSON* tags = ensure_tags_array_local(tags_inout);
if (!tags) return;
char* title = has_tag_key_local(tags, "title") ? NULL : first_markdown_h1_local(content);
char* summary = has_tag_key_local(tags, "summary") ? NULL : first_markdown_paragraph_local(content);
char* image = has_tag_key_local(tags, "image") ? NULL : first_markdown_image_url_local(content);
if (title) (void)add_string_tag_local(tags, "title", title);
if (summary) (void)add_string_tag_local(tags, "summary", summary);
if (image) (void)add_string_tag_local(tags, "image", image);
if (!has_tag_key_local(tags, "published_at")) {
char published_at[32];
snprintf(published_at, sizeof(published_at), "%lld", (long long)time(NULL));
(void)add_string_tag_local(tags, "published_at", published_at);
}
if (!has_tag_key_local(tags, "d")) {
char* d_tag = NULL;
if (title) {
d_tag = d_tagify_string_local(title, "long-form-note");
} else {
char* derived_title = first_markdown_h1_local(content);
d_tag = d_tagify_string_local(derived_title, "long-form-note");
free(derived_title);
}
if (d_tag) {
(void)add_string_tag_local(tags, "d", d_tag);
free(d_tag);
}
}
free(title);
free(summary);
free(image);
}
static int is_safe_relative_path_local(const char* path) {
if (!path || path[0] == '\0') return 0;
if (path[0] == '/') return 0;
if (strstr(path, "..") != NULL) return 0;
if (strchr(path, '\\') != NULL) return 0;
return 1;
}
static int build_tool_path_local(tools_context_t* ctx, const char* rel_path, char* out, size_t out_size) {
if (!ctx || !ctx->cfg || !rel_path || !out || out_size == 0) return -1;
if (!is_safe_relative_path_local(rel_path)) return -1;
const char* cwd = ctx->cfg->tools.shell.working_directory[0] != '\0'
? ctx->cfg->tools.shell.working_directory
: ".";
int n = 0;
if (strcmp(cwd, ".") == 0) {
n = snprintf(out, out_size, "%s", rel_path);
} else {
n = snprintf(out, out_size, "%s/%s", cwd, rel_path);
}
if (n < 0 || (size_t)n >= out_size) return -1;
return 0;
}
static char* read_entire_file_local(const char* file_path, size_t* out_bytes) {
if (!file_path) return NULL;
FILE* fp = fopen(file_path, "rb");
if (!fp) return NULL;
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return NULL;
}
long size = ftell(fp);
if (size < 0) {
fclose(fp);
return NULL;
}
if (fseek(fp, 0, SEEK_SET) != 0) {
fclose(fp);
return NULL;
}
char* buf = (char*)malloc((size_t)size + 1U);
if (!buf) {
fclose(fp);
return NULL;
}
size_t n = fread(buf, 1, (size_t)size, fp);
fclose(fp);
buf[n] = '\0';
if (out_bytes) *out_bytes = n;
return buf;
}
static char* basename_lowercase_dup_local(const char* path) {
if (!path || path[0] == '\0') return NULL;
const char* base = strrchr(path, '/');
base = base ? (base + 1) : path;
if (base[0] == '\0') return NULL;
char* out = strdup(base);
if (!out) return NULL;
for (size_t i = 0; out[i] != '\0'; i++) {
out[i] = (char)tolower((unsigned char)out[i]);
}
return out;
}
char* execute_nostr_post(const char* args_json) {
const char* raw_args_json = args_json ? args_json : "{}";
raw_args_json = skip_ws_local(raw_args_json);
if (!raw_args_json || raw_args_json[0] == '\0') {
raw_args_json = "{}";
}
cJSON* args = cJSON_Parse(raw_args_json);
char* repaired_args_json = NULL;
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
if (!args && raw_args_json) {
repaired_args_json = sanitize_json_string_controls_local(raw_args_json);
if (repaired_args_json) {
args = cJSON_Parse(repaired_args_json);
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
}
}
if (!args && raw_args_json) {
args = parse_loose_nostr_post_args_local(raw_args_json);
}
if (!args) {
free(repaired_args_json);
return json_error_local("invalid arguments JSON");
}
cJSON* kind = cJSON_GetObjectItemCaseSensitive(args, "kind");
cJSON* content = cJSON_GetObjectItemCaseSensitive(args, "content");
cJSON* tags = cJSON_GetObjectItemCaseSensitive(args, "tags");
if (!kind || !cJSON_IsNumber(kind) || !content || !cJSON_IsString(content) || !content->valuestring) {
cJSON_Delete(args);
free(repaired_args_json);
return json_error_local("nostr_post requires integer kind and string content");
}
if (tags && !cJSON_IsArray(tags)) {
cJSON_Delete(args);
free(repaired_args_json);
return json_error_local("nostr_post tags must be an array when provided");
}
cJSON* tags_dup = NULL;
if (tags) {
tags_dup = cJSON_Duplicate(tags, 1);
if (!tags_dup) {
cJSON_Delete(args);
free(repaired_args_json);
return json_error_local("nostr_post failed to duplicate tags");
}
}
ensure_nip23_metadata_tags_local((int)kind->valuedouble, content->valuestring, &tags_dup);
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
int rc = nostr_handler_publish_kind_event((int)kind->valuedouble,
content->valuestring,
tags_dup,
&publish_result);
cJSON_Delete(tags_dup);
cJSON_Delete(args);
free(repaired_args_json);
if (rc != 0) {
nostr_handler_publish_result_free(&publish_result);
return json_error_local("nostr_post failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
cJSON_AddStringToObject(out, "message", "nostr_post published");
cJSON_AddNumberToObject(out, "kind", publish_result.kind);
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
cJSON_AddNumberToObject(out, "relay_count", publish_result.relay_count);
cJSON_AddNumberToObject(out, "accepted_by_pool_count", publish_result.accepted_by_pool_count);
if (publish_result.note_uri[0] != '\0') cJSON_AddStringToObject(out, "note_uri", publish_result.note_uri);
if (publish_result.naddr_uri[0] != '\0') cJSON_AddStringToObject(out, "naddr_uri", publish_result.naddr_uri);
if (publish_result.d_tag[0] != '\0') cJSON_AddStringToObject(out, "d_tag", publish_result.d_tag);
cJSON* relays = cJSON_CreateArray();
if (!relays) {
nostr_handler_publish_result_free(&publish_result);
cJSON_Delete(out);
return NULL;
}
for (int i = 0; i < publish_result.relay_count; i++) {
cJSON_AddItemToArray(relays, cJSON_CreateString(publish_result.relays[i] ? publish_result.relays[i] : ""));
}
cJSON_AddItemToObject(out, "relays", relays);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
nostr_handler_publish_result_free(&publish_result);
return json;
}
char* execute_nostr_post_readme(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
cJSON* args = parse_tool_args_json_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON_Delete(args);
char readme_path[PATH_MAX];
if (build_tool_path_local(ctx, "README.md", readme_path, sizeof(readme_path)) != 0) {
return json_error_local("failed to resolve README path");
}
size_t bytes_read = 0;
char* content = read_entire_file_local(readme_path, &bytes_read);
if (!content) {
return json_error_local("failed to read README.md");
}
cJSON* tags = cJSON_CreateArray();
if (!tags) {
free(content);
return json_error_local("failed to create tags");
}
if (add_string_tag_local(tags, "d", "readme.md") != 0 ||
add_string_tag_local(tags,
"image",
"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg") != 0) {
cJSON_Delete(tags);
free(content);
return json_error_local("failed to set required tags");
}
ensure_nip23_metadata_tags_local(30023, content, &tags);
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
int rc = nostr_handler_publish_kind_event(30023, content, tags, &publish_result);
cJSON_Delete(tags);
free(content);
if (rc != 0) {
nostr_handler_publish_result_free(&publish_result);
return json_error_local("nostr_post_readme failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
cJSON_AddStringToObject(out, "message", "nostr_post_readme published");
cJSON_AddStringToObject(out, "path", readme_path);
cJSON_AddNumberToObject(out, "bytes_read", (double)bytes_read);
cJSON_AddNumberToObject(out, "kind", publish_result.kind);
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
cJSON_AddNumberToObject(out, "relay_count", publish_result.relay_count);
cJSON_AddNumberToObject(out, "accepted_by_pool_count", publish_result.accepted_by_pool_count);
if (publish_result.note_uri[0] != '\0') cJSON_AddStringToObject(out, "note_uri", publish_result.note_uri);
if (publish_result.naddr_uri[0] != '\0') cJSON_AddStringToObject(out, "naddr_uri", publish_result.naddr_uri);
if (publish_result.d_tag[0] != '\0') cJSON_AddStringToObject(out, "d_tag", publish_result.d_tag);
cJSON* relays = cJSON_CreateArray();
if (!relays) {
nostr_handler_publish_result_free(&publish_result);
cJSON_Delete(out);
return NULL;
}
for (int i = 0; i < publish_result.relay_count; i++) {
cJSON_AddItemToArray(relays, cJSON_CreateString(publish_result.relays[i] ? publish_result.relays[i] : ""));
}
cJSON_AddItemToObject(out, "relays", relays);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
nostr_handler_publish_result_free(&publish_result);
return json;
}
char* execute_nostr_file_md_to_longform_post(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable");
cJSON* args = parse_tool_args_json_local(args_json);
if (!args) return json_error_local("invalid arguments JSON");
cJSON* file = cJSON_GetObjectItemCaseSensitive(args, "file");
cJSON* title = cJSON_GetObjectItemCaseSensitive(args, "title");
cJSON* image = cJSON_GetObjectItemCaseSensitive(args, "image");
cJSON* summary = cJSON_GetObjectItemCaseSensitive(args, "summary");
if (!file || !cJSON_IsString(file) || !file->valuestring || file->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error_local("nostr_file_md_to_longform_post requires string file");
}
if (title && !cJSON_IsString(title)) {
cJSON_Delete(args);
return json_error_local("nostr_file_md_to_longform_post title must be string when provided");
}
if (image && !cJSON_IsString(image)) {
cJSON_Delete(args);
return json_error_local("nostr_file_md_to_longform_post image must be string when provided");
}
if (summary && !cJSON_IsString(summary)) {
cJSON_Delete(args);
return json_error_local("nostr_file_md_to_longform_post summary must be string when provided");
}
char file_path[PATH_MAX];
if (build_tool_path_local(ctx, file->valuestring, file_path, sizeof(file_path)) != 0) {
cJSON_Delete(args);
return json_error_local("failed to resolve markdown file path");
}
size_t bytes_read = 0;
char* content = read_entire_file_local(file_path, &bytes_read);
if (!content) {
cJSON_Delete(args);
return json_error_local("failed to read markdown file");
}
char* d_tag = basename_lowercase_dup_local(file->valuestring);
if (!d_tag || d_tag[0] == '\0') {
free(d_tag);
free(content);
cJSON_Delete(args);
return json_error_local("failed to derive d tag from filename");
}
cJSON* tags = cJSON_CreateArray();
if (!tags) {
free(d_tag);
free(content);
cJSON_Delete(args);
return json_error_local("failed to create tags");
}
if (add_string_tag_local(tags, "d", d_tag) != 0) {
free(d_tag);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error_local("failed to set d tag");
}
if (title && title->valuestring && title->valuestring[0] != '\0') {
if (add_string_tag_local(tags, "title", title->valuestring) != 0) {
free(d_tag);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error_local("failed to set title tag");
}
}
if (image && image->valuestring && image->valuestring[0] != '\0') {
if (add_string_tag_local(tags, "image", image->valuestring) != 0) {
free(d_tag);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error_local("failed to set image tag");
}
}
if (summary && summary->valuestring && summary->valuestring[0] != '\0') {
if (add_string_tag_local(tags, "summary", summary->valuestring) != 0) {
free(d_tag);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error_local("failed to set summary tag");
}
}
ensure_nip23_metadata_tags_local(30023, content, &tags);
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
int rc = nostr_handler_publish_kind_event(30023, content, tags, &publish_result);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
if (rc != 0) {
free(d_tag);
nostr_handler_publish_result_free(&publish_result);
return json_error_local("nostr_file_md_to_longform_post failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
free(d_tag);
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
cJSON_AddStringToObject(out, "message", "nostr_file_md_to_longform_post published");
cJSON_AddStringToObject(out, "path", file_path);
cJSON_AddStringToObject(out, "d_tag", d_tag);
cJSON_AddNumberToObject(out, "bytes_read", (double)bytes_read);
cJSON_AddNumberToObject(out, "kind", publish_result.kind);
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
cJSON_AddNumberToObject(out, "relay_count", publish_result.relay_count);
cJSON_AddNumberToObject(out, "accepted_by_pool_count", publish_result.accepted_by_pool_count);
if (publish_result.note_uri[0] != '\0') cJSON_AddStringToObject(out, "note_uri", publish_result.note_uri);
if (publish_result.naddr_uri[0] != '\0') cJSON_AddStringToObject(out, "naddr_uri", publish_result.naddr_uri);
cJSON* relays = cJSON_CreateArray();
if (!relays) {
free(d_tag);
nostr_handler_publish_result_free(&publish_result);
cJSON_Delete(out);
return NULL;
}
for (int i = 0; i < publish_result.relay_count; i++) {
cJSON_AddItemToArray(relays, cJSON_CreateString(publish_result.relays[i] ? publish_result.relays[i] : ""));
}
cJSON_AddItemToObject(out, "relays", relays);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
free(d_tag);
nostr_handler_publish_result_free(&publish_result);
return json;
}