From 4c3320f7472241a50c5491a86376d4d68a3e3078 Mon Sep 17 00:00:00 2001 From: TroyHernandez Date: Wed, 10 Jun 2026 01:27:48 -0500 Subject: [PATCH] Add mx_send_event() and mx_set_state() generic senders mx_send hardcodes m.room.message and mx_react hardcodes m.reaction, with no way to send other event types. Add mx_send_event() (arbitrary room event, e.g. m.room.encrypted for E2EE) and mx_set_state() (arbitrary state event, e.g. m.room.encryption). Both thin PUTs mirroring mx_send. Needed by mx.client's end-to-end encryption transport. --- DESCRIPTION | 2 +- NAMESPACE | 2 ++ R/messages.R | 67 ++++++++++++++++++++++++++++++++++++++++ man/mx.api-package.Rd | 15 +++------ man/mx_canonical_json.Rd | 3 ++ man/mx_send_event.Rd | 32 +++++++++++++++++++ man/mx_set_state.Rd | 32 +++++++++++++++++++ 7 files changed, 142 insertions(+), 11 deletions(-) create mode 100644 man/mx_send_event.Rd create mode 100644 man/mx_set_state.Rd diff --git a/DESCRIPTION b/DESCRIPTION index ac4d4dd..4ad549a 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: mx.api Type: Package Title: Minimal Matrix Client-Server API -Version: 0.2.0 +Version: 0.2.0.1 Date: 2026-05-13 Authors@R: c( person("Troy", "Hernandez", role = c("aut", "cre"), diff --git a/NAMESPACE b/NAMESPACE index e658387..6ee347d 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -19,8 +19,10 @@ export(mx_room_name) export(mx_room_topic) export(mx_rooms) export(mx_send) +export(mx_send_event) export(mx_send_to_device) export(mx_session) +export(mx_set_state) export(mx_sync) export(mx_upload) export(mx_whoami) diff --git a/R/messages.R b/R/messages.R index 43e7f76..1199f4d 100644 --- a/R/messages.R +++ b/R/messages.R @@ -32,6 +32,73 @@ mx_send <- function(session, room_id, body, msgtype = "m.text", extra = NULL) { resp$event_id } +#' Send an arbitrary room event +#' +#' Generic counterpart to \code{\link{mx_send}} for event types other than +#' \code{m.room.message}, such as \code{m.room.encrypted}. The content is +#' sent verbatim. +#' +#' @param session An "mx_session" object. +#' @param room_id Character. The room ID. +#' @param event_type Character. The event type, e.g. +#' \code{"m.room.encrypted"}. +#' @param content List. The event content, sent as-is. +#' @param txn_id Character or NULL. Transaction id (generated if NULL). +#' @return The event ID of the sent event. +#' @examples +#' \dontrun{ +#' mx_send_event(s, "!abc:example", "m.room.encrypted", encrypted_content) +#' } +#' @export +mx_send_event <- function(session, room_id, event_type, content, + txn_id = NULL) { + if (is.null(txn_id)) { + txn_id <- mx_txn_id() + } + path <- sprintf( + "/_matrix/client/v3/rooms/%s/send/%s/%s", + mx_encode_id(room_id), mx_encode_id(event_type), + mx_encode_id(txn_id) + ) + resp <- mx_http( + session$server, "PUT", path, + body = content, token = session$token + ) + resp$event_id +} + +#' Set a room state event +#' +#' Generic state setter, e.g. to mark a room encrypted by putting an +#' \code{m.room.encryption} event. +#' +#' @param session An "mx_session" object. +#' @param room_id Character. The room ID. +#' @param event_type Character. State event type, e.g. +#' \code{"m.room.encryption"}. +#' @param content List. The state content, sent as-is. +#' @param state_key Character. State key (default empty string). +#' @return The event ID of the state event. +#' @examples +#' \dontrun{ +#' mx_set_state(s, "!abc:example", "m.room.encryption", +#' list(algorithm = "m.megolm.v1.aes-sha2")) +#' } +#' @export +mx_set_state <- function(session, room_id, event_type, content, + state_key = "") { + path <- sprintf( + "/_matrix/client/v3/rooms/%s/state/%s/%s", + mx_encode_id(room_id), mx_encode_id(event_type), + mx_encode_id(state_key) + ) + resp <- mx_http( + session$server, "PUT", path, + body = content, token = session$token + ) + resp$event_id +} + #' Fetch historical messages from a room #' #' Thin wrapper over the /rooms/{id}/messages endpoint. diff --git a/man/mx.api-package.Rd b/man/mx.api-package.Rd index 6dff33a..f777040 100644 --- a/man/mx.api-package.Rd +++ b/man/mx.api-package.Rd @@ -3,13 +3,8 @@ \name{mx.api-package} \alias{mx.api} \alias{mx.api-package} -\title{mx.api: Minimal Matrix Client-Server API} -\description{ -Base-R bindings for the Matrix Client-Server HTTP API, suitable for -talking to a Synapse homeserver. Two dependencies: curl and jsonlite. -End-to-end encryption is out of scope; use unencrypted rooms or a -separate crypto package. -} -\author{ -\strong{Maintainer}: Troy Hernandez \email{troy@cornball.ai} -} +\title{\packageTitle{mx.api}} +\description{\packageDescription{mx.api}} +\section{Package Content}{\packageIndices{mx.api}} +\author{\packageAuthor{mx.api}} +\section{Maintainer}{\packageMaintainer{mx.api}} diff --git a/man/mx_canonical_json.Rd b/man/mx_canonical_json.Rd index 6b6196d..f885f19 100644 --- a/man/mx_canonical_json.Rd +++ b/man/mx_canonical_json.Rd @@ -22,11 +22,14 @@ strings, integers only (no floats, no exponents, no decimal places, within \code{[-(2^53)+1, (2^53)-1]}), and rejection of NaN, Inf, NA values, and NA or duplicate object keys. The output is the exact byte sequence to feed into an ed25519 signer. +} +\details{ R named lists become JSON objects; unnamed lists and length > 1 atomic vectors become arrays. Length-1 atomics become scalars. To force a length-1 value to encode as an array, wrap it in a single-element \code{list(...)} or in \code{I()} (AsIs values are always encoded as arrays, names dropped, mirroring jsonlite). + } \examples{ mx_canonical_json(list(b = 2, a = 1)) diff --git a/man/mx_send_event.Rd b/man/mx_send_event.Rd new file mode 100644 index 0000000..b910fdc --- /dev/null +++ b/man/mx_send_event.Rd @@ -0,0 +1,32 @@ +% tinyrox says don't edit this manually, but it can't stop you! +\name{mx_send_event} +\alias{mx_send_event} +\title{Send an arbitrary room event} +\usage{ +mx_send_event(session, room_id, event_type, content, txn_id = NULL) +} +\arguments{ +\item{session}{An "mx_session" object.} + +\item{room_id}{Character. The room ID.} + +\item{event_type}{Character. The event type, e.g. +\code{"m.room.encrypted"}.} + +\item{content}{List. The event content, sent as-is.} + +\item{txn_id}{Character or NULL. Transaction id (generated if NULL).} +} +\value{ +The event ID of the sent event. +} +\description{ +Generic counterpart to \code{\link{mx_send}} for event types other than +\code{m.room.message}, such as \code{m.room.encrypted}. The content is +sent verbatim. +} +\examples{ +\dontrun{ +mx_send_event(s, "!abc:example", "m.room.encrypted", encrypted_content) +} +} diff --git a/man/mx_set_state.Rd b/man/mx_set_state.Rd new file mode 100644 index 0000000..5c514b5 --- /dev/null +++ b/man/mx_set_state.Rd @@ -0,0 +1,32 @@ +% tinyrox says don't edit this manually, but it can't stop you! +\name{mx_set_state} +\alias{mx_set_state} +\title{Set a room state event} +\usage{ +mx_set_state(session, room_id, event_type, content, state_key = "") +} +\arguments{ +\item{session}{An "mx_session" object.} + +\item{room_id}{Character. The room ID.} + +\item{event_type}{Character. State event type, e.g. +\code{"m.room.encryption"}.} + +\item{content}{List. The state content, sent as-is.} + +\item{state_key}{Character. State key (default empty string).} +} +\value{ +The event ID of the state event. +} +\description{ +Generic state setter, e.g. to mark a room encrypted by putting an +\code{m.room.encryption} event. +} +\examples{ +\dontrun{ +mx_set_state(s, "!abc:example", "m.room.encryption", + list(algorithm = "m.megolm.v1.aes-sha2")) +} +}