diff --git a/src/chat/chat_headless_control.c b/src/chat/chat_headless_control.c index 96d79554..088f9995 100644 --- a/src/chat/chat_headless_control.c +++ b/src/chat/chat_headless_control.c @@ -13,6 +13,7 @@ #include "chat_core.h" #include "chat_core_priv.h" #include "chat_event.h" +#include "chat_poll.h" #include "chat_sync.h" #include "chat_join.h" #include "invite_link.h" @@ -30,6 +31,8 @@ #include "../../lib/ll_queue.h" #include "../../lib/u_async.h" #include "../../lib/mem.h" +#include "../../lib/strbuf.h" +#include "../../lib/json_flat.h" #include "../../lib/socket_compat.h" #include "../../lib/debug_config.h" #include "../../lib/platform_compat.h" @@ -167,11 +170,20 @@ static void cli_send_str(struct headless_client* cli, const char* s) { } static void send_response(struct headless_client* cli, int id, const char* ok_data, const char* error) { - char buf[4096]; int len; - if (error) len = snprintf(buf, sizeof(buf), "{\"id\":%d,\"ok\":false,\"error\":\"%s\"}\n", id, error); - else if (ok_data) len = snprintf(buf, sizeof(buf), "{\"id\":%d,\"ok\":true,\"data\":%s}\n", id, ok_data); - else len = snprintf(buf, sizeof(buf), "{\"id\":%d,\"ok\":true}\n", id); - if (len > 0) cli_send(cli, buf, (size_t)len); + struct strbuf out = strbuf_new(); int rc; + if (error) { + rc = strbuf_addf(&out, "{\"id\":%d,\"ok\":false,\"error\":", id); + if (rc >= 0) rc = strbuf_add_json_string(&out, error); + if (rc >= 0) rc = strbuf_addf(&out, "}\n"); + } else rc = strbuf_addf(&out, "{\"id\":%d,\"ok\":true,\"data\":%s}\n", id, ok_data ? ok_data : "null"); + if (rc < 0 || out.len + (size_t)cli->send_len > SEND_BUF_SIZE) { + DEBUG_ERROR(DEBUG_CATEGORY_HEADLESS, "headless: response rejected id=%d bytes=%zu queued=%d rc=%d", + id, out.len, cli->send_len, rc); + char failure[160]; int bytes = snprintf(failure, sizeof(failure), + "{\"id\":%d,\"ok\":false,\"error\":\"Response too large or out of memory; request fewer records\"}\n", id); + cli_send(cli, failure, bytes); + } else cli_send(cli, strbuf_str(&out), out.len); + strbuf_free(&out); } /* ── Broadcast event ── */ @@ -191,7 +203,12 @@ static void hc_on_chat_event(struct UTUN_INSTANCE* inst, int type, const uint8_t if (!hc) return; if (type == 10) return; /* skip status text */ char json[1024]; json[0] = '\0'; - if (type == 1 && data && len >= 10) { /* MSG_RECEIVED */ + if (type == CHAT_EVT_FEEDBACK_CHANGED && data && len >= 34 && len == data[0] + 34) { + uint8_t cl = data[0]; char message_id[65]; + for (unsigned i = 0; i < 32; i++) snprintf(message_id + i * 2, 3, "%02x", data[1 + cl + i]); + snprintf(json, sizeof(json), "{\"event\":\"feedback\",\"ch\":\"%.*s\",\"message_id\":\"%s\",\"kind\":%u}", + (int)cl, data + 1, message_id, data[33 + cl]); + } else if (type == 1 && data && len >= 10) { /* MSG_RECEIVED */ uint8_t cl = data[0]; uint64_t author; memcpy(&author, data + 1 + cl, 8); snprintf(json, sizeof(json), "{\"event\":\"msg\",\"ch\":\"%.*s\",\"author_id\":\"0x%016llx\"}", (int)cl, data + 1, (unsigned long long)author); @@ -794,6 +811,19 @@ static void hc_handle_radio_subscribers(struct headless_client* cli, int id, con typedef void (*hc_cmd_fn)(struct headless_client* cli, int id, const char* json); +/* Опросы и реакции используют общий командный API ядра. */ +static void hc_handle_feedback(struct headless_client* cli, int id, const char* json) { + char* result = NULL; int rc = chat_feedback_command(cli->hc->inst, json, &result); + if (rc == 0) send_response(cli, id, result, NULL); + else { + char error[256] = "Interaction failed"; + if (result && json_flat_get(result, "error", error, sizeof(error)) < 0) + DEBUG_ERROR(DEBUG_CATEGORY_HEADLESS, "headless: invalid interaction error JSON"); + send_response(cli, id, NULL, error); + } + u_free(result); +} + static const char* cmd_names[] = { "ping", "status", "channels", "members", "messages", "send", "attach", "invite", "invite_nodes", "connect", "create_channel", "invite_to", "subscribe", "dm_start", "dm_list", "dm_messages", "dm_send", "call_start", "call_accept", "call_decline", "call_hangup", "radio_on", "radio_off", "radio_subscribers", "quit", NULL }; static hc_cmd_fn cmd_handlers[] = { hc_handle_ping, hc_handle_status, hc_handle_channels, hc_handle_members, hc_handle_messages, hc_handle_send, hc_handle_attach, hc_handle_invite, hc_handle_invite_nodes, hc_handle_connect, hc_handle_create_channel, hc_handle_invite_to, hc_handle_subscribe, hc_handle_dm_start, hc_handle_dm_list, hc_handle_dm_messages, hc_handle_dm_send, hc_handle_call_start, hc_handle_call_accept, hc_handle_call_decline, hc_handle_call_hangup, hc_handle_radio_on, hc_handle_radio_off, hc_handle_radio_subscribers, hc_handle_quit }; @@ -806,6 +836,10 @@ static void hc_handle_command(struct headless_client* cli, const char* json) { size_t clen = (size_t)(ce - cs); if (clen == 0 || clen > 31) { send_response(cli, id, NULL, "invalid cmd"); return; } char cmd[32]; memcpy(cmd, cs, clen); cmd[clen] = '\0'; + if (!strcmp(cmd, "poll_create") || !strcmp(cmd, "vote") || !strcmp(cmd, "react") + || !strcmp(cmd, "feedback") || !strcmp(cmd, "reaction_catalog")) { + hc_handle_feedback(cli, id, json); return; + } for (int i = 0; cmd_names[i]; i++) { if (strcmp(cmd, cmd_names[i]) == 0) { cmd_handlers[i](cli, id, json); return; } }