From 65f85a1b28c13c5bee7e7bdaeb279e4084285076 Mon Sep 17 00:00:00 2001 From: Valerie Liu <79415174+ValwareIRC@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:24:19 +0000 Subject: [PATCH] JSON-RPC: Add message.* (PR #327 from Valware) * message.send_privmsg * message.send_notice * message.send_numeric * message.send_standard_reply --- doc/conf/modules.default.conf | 1 + src/modules/rpc/Makefile.in | 2 +- src/modules/rpc/message.c | 176 ++++++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+), 1 deletion(-) create mode 100644 src/modules/rpc/message.c diff --git a/doc/conf/modules.default.conf b/doc/conf/modules.default.conf index 7d94c5f23..7bff4cb8f 100644 --- a/doc/conf/modules.default.conf +++ b/doc/conf/modules.default.conf @@ -265,6 +265,7 @@ loadmodule "rpc/name_ban"; loadmodule "rpc/spamfilter"; loadmodule "rpc/log"; loadmodule "rpc/whowas"; +loadmodule "rpc/message"; /*** Other ***/ // These are modules that don't fit in any of the previous sections diff --git a/src/modules/rpc/Makefile.in b/src/modules/rpc/Makefile.in index 5a4b098ee..feb4edfc1 100644 --- a/src/modules/rpc/Makefile.in +++ b/src/modules/rpc/Makefile.in @@ -34,7 +34,7 @@ INCLUDES = ../../include/channel.h \ R_MODULES= \ rpc.so stats.so user.so server.so channel.so server_ban.so \ server_ban_exception.so name_ban.so spamfilter.so \ - log.so whowas.so + log.so whowas.so message.so MODULES=$(R_MODULES) MODULEFLAGS=@MODULEFLAGS@ diff --git a/src/modules/rpc/message.c b/src/modules/rpc/message.c new file mode 100644 index 000000000..08c7c96b6 --- /dev/null +++ b/src/modules/rpc/message.c @@ -0,0 +1,176 @@ +/* message.* RPC calls + * (C) Copyright 2025 Valware and the UnrealIRCd team + * License: GPLv2 or later + */ + +#include "unrealircd.h" + +ModuleHeader MOD_HEADER += { + "rpc/message", + "1.0.0", + "message.* RPC calls", + "UnrealIRCd Team", + "unrealircd-6", +}; + +void rpc_send_privmsgnotice(json_t *request, json_t *params, Client *client, int is_notice) +{ + json_t *result; + Client *acptr; + const char *nick, *message; + + REQUIRE_PARAM_STRING("nick", nick); + REQUIRE_PARAM_STRING("message", message); + if (!(acptr = find_user(nick, NULL))) + { + rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found"); + return; + } + + sendto_one(acptr, NULL, ":%s %s %s :%s", me.name, is_notice ? "NOTICE" : "PRIVMSG", acptr->name, message); + result = json_boolean(1); + rpc_response(client, request, result); + json_decref(result); + return; +} + +/* Forward declarations */ +RPC_CALL_FUNC(rpc_message_privmsg); +RPC_CALL_FUNC(rpc_message_notice); +RPC_CALL_FUNC(rpc_message_numeric); +RPC_CALL_FUNC(rpc_message_standardreply); + +MOD_INIT() +{ + RPCHandlerInfo r; + + MARK_AS_OFFICIAL_MODULE(modinfo); + + memset(&r, 0, sizeof(r)); + r.method = "message.send_privmsg"; + r.loglevel = ULOG_DEBUG; + r.call = rpc_message_privmsg; + if (!RPCHandlerAdd(modinfo->handle, &r)) + { + config_error("[rpc/message] Could not register RPC handler"); + return MOD_FAILED; + } + + memset(&r, 0, sizeof(r)); + r.method = "message.send_notice"; + r.loglevel = ULOG_DEBUG; + r.call = rpc_message_notice; + if (!RPCHandlerAdd(modinfo->handle, &r)) + { + config_error("[rpc/message] Could not register RPC handler"); + return MOD_FAILED; + } + + memset(&r, 0, sizeof(r)); + r.method = "message.send_numeric"; + r.call = rpc_message_numeric; + if (!RPCHandlerAdd(modinfo->handle, &r)) + { + config_error("[rpc/message] Could not register RPC handler"); + return MOD_FAILED; + } + + memset(&r, 0, sizeof(r)); + r.method = "message.send_standard_reply"; + r.call = rpc_message_standardreply; + if (!RPCHandlerAdd(modinfo->handle, &r)) + { + config_error("[rpc/message] Could not register RPC handler"); + return MOD_FAILED; + } + + return MOD_SUCCESS; +} + +MOD_LOAD() +{ + return MOD_SUCCESS; +} + +MOD_UNLOAD() +{ + return MOD_SUCCESS; +} + +RPC_CALL_FUNC(rpc_message_privmsg) +{ + rpc_send_privmsgnotice(request, params, client, 0); +} + +RPC_CALL_FUNC(rpc_message_notice) +{ + rpc_send_privmsgnotice(request, params, client, 1); +} + +RPC_CALL_FUNC(rpc_message_numeric) +{ + json_t *result; + Client *acptr; + const char *nick, *message; + int numeric; + + REQUIRE_PARAM_STRING("nick", nick); + REQUIRE_PARAM_INTEGER("numeric", numeric); + REQUIRE_PARAM_STRING("message", message); + if (!(acptr = find_user(nick, NULL))) + { + rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found"); + return; + } + if (numeric < 1 || numeric > 999) + { + rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Numeric out of range"); + return; + } + + sendnumericfmt(acptr, numeric, "%s", message); + result = json_boolean(1); + rpc_response(client, request, result); + json_decref(result); +} + +/* We don't ever include the "command" field because this + * will never be in response to a user-initiated command, + * so we just use "*" as the command. + * https://ircv3.net/specs/extensions/standard-replies +*/ +RPC_CALL_FUNC(rpc_message_standardreply) +{ + json_t *result; + Client *acptr; + const char *nick, *type, *code, *context, *description; + + REQUIRE_PARAM_STRING("nick", nick); + REQUIRE_PARAM_STRING("type", type); + REQUIRE_PARAM_STRING("code", code); + OPTIONAL_PARAM_STRING("context", context); + REQUIRE_PARAM_STRING("description", description); + + if (!(acptr = find_user(nick, NULL))) + { + rpc_error(client, request, JSON_RPC_ERROR_NOT_FOUND, "Nickname not found"); + return; + } + + if (strcasecmp(type, "FAIL") && strcasecmp(type, "WARN") && strcasecmp(type, "NOTE")) + { + rpc_error(client, request, JSON_RPC_ERROR_INVALID_PARAMS, "Invalid type, must be one of FAIL, WARN, NOTE"); + return; + } + + if (context) + sendto_one(acptr, NULL, ":%s %s * %s %s :%s", me.name, type, code, context, description); + + else + sendto_one(acptr, NULL, ":%s %s * %s :%s", me.name, type, code, description); + + result = json_boolean(1); + rpc_response(client, request, result); + json_decref(result); +}