From da2c13dcf188cc8320f26b1fc6170261926dc1f4 Mon Sep 17 00:00:00 2001 From: evgeny Date: Tue, 6 Oct 2026 21:26:37 +0200 Subject: [PATCH] Add CLI poll creation, voting and emoji reaction commands --- src/chat/chat_poll.c | 8 +++++++- tools/chatcli | 38 +++++++++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/chat/chat_poll.c b/src/chat/chat_poll.c index cdee79f7..dce0e833 100644 --- a/src/chat/chat_poll.c +++ b/src/chat/chat_poll.c @@ -351,7 +351,13 @@ int chat_feedback_command(struct UTUN_INSTANCE* inst, const char* command, char* return poll_command_error("Cannot read reactions", result); sqlite3_stmt* st = poll_field(inst, command, "$.emoji"); if (!st) return poll_command_error("Invalid emoji", result); const char* type = (const char*)sqlite3_column_text(st, 0); sqlite3_int64 emoji = sqlite3_column_int64(st, 1); - ok = type && !strcmp(type, "integer") && emoji >= 0 && emoji < CHAT_REACTION_COUNT; sqlite3_finalize(st); + if (type && !strcmp(type, "text")) { + const char* text = (const char*)sqlite3_column_text(st, 1); emoji = -1; + for (unsigned i = 0; text && i < CHAT_REACTION_COUNT; i++) + if (!strcmp(text, chat_reaction_emoji(i))) { emoji = i; break; } + } + ok = type && (!strcmp(type, "integer") || !strcmp(type, "text")) && emoji >= 0 && emoji < CHAT_REACTION_COUNT; + sqlite3_finalize(st); if (!ok) return poll_command_error("Invalid emoji", result); size_t at = 0; while (at < count && ids[at] != emoji) at++; int on = poll_field_bool(inst, command, "$.on", at == count); diff --git a/tools/chatcli b/tools/chatcli index 510d7814..4511110e 100755 --- a/tools/chatcli +++ b/tools/chatcli @@ -27,7 +27,7 @@ # dm_send send DM text message # listen interactive event listener -import sys, os, json, socket, struct +import sys, os, json, socket, struct, argparse DEFAULT_HOST = os.environ.get("CHATCLI_HOST", "127.0.0.1") DEFAULT_PORT = int(os.environ.get("CHATCLI_PORT", "9999")) @@ -148,6 +148,41 @@ def cmd_dm_messages(conv_id, count=None): def cmd_dm_send(conv_id, text): _req("dm_send", conv_id=conv_id, data=text) +def cmd_interaction(cmd, args): + parser = argparse.ArgumentParser(prog="chatcli " + cmd) + if cmd == "reactions": + parser.parse_args(args) + _req("reaction_catalog") + return + parser.add_argument("ch") + if cmd == "poll": + parser.add_argument("question") + parser.add_argument("options", nargs="+") + parser.add_argument("--multiple", action="store_true") + parser.add_argument("--custom", action="store_true") + else: + parser.add_argument("message_id") + if cmd == "vote": + parser.add_argument("choices", nargs="*", type=int, help="option IDs from feedback (start at 0)") + parser.add_argument("--custom", default="") + elif cmd == "react": + parser.add_argument("emoji", nargs="?") + parser.add_argument("--off", action="store_true") + parser.add_argument("--clear", action="store_true") + values = parser.parse_args(args) + if cmd == "poll": + _req("poll_create", ch=values.ch, question=values.question, options=values.options, + mode="multiple" if values.multiple else "single", allow_custom_answer=values.custom) + elif cmd == "vote": + _req("vote", ch=values.ch, message_id=values.message_id, choices=values.choices, custom=values.custom) + elif cmd == "react": + if not values.clear and values.emoji is None: + parser.error("specify an emoji or --clear") + emoji = int(values.emoji) if values.emoji and values.emoji.isdigit() else values.emoji + _req("react", ch=values.ch, message_id=values.message_id, emoji=emoji, on=not values.off, clear=values.clear) + else: + _req("feedback", ch=values.ch, message_id=values.message_id) + def cmd_listen(): print(f"Listening on {DEFAULT_HOST}:{DEFAULT_PORT} (Ctrl+C to quit)") s = _connect() @@ -201,6 +236,7 @@ def main(): cmd = args[0] try: if cmd == "ping": cmd_ping() + elif cmd in ("poll", "vote", "react", "feedback", "reactions"): cmd_interaction(cmd, args[1:]) elif cmd == "status": cmd_status() elif cmd == "channels": cmd_channels() elif cmd == "members": cmd_members(*args[1:2] if len(args) > 1 else (_die("usage: members "),))