# mx.api

A minimal-dependency client for the
[Matrix Client-Server HTTP API](https://spec.matrix.org/), suitable for
talking to a Synapse or Conduit homeserver from R. Two Imports: `curl`
and `jsonlite`. No tidyverse.

Pairs with [`mx.crypto`](https://github.com/cornball-ai/mx.crypto),
which handles Olm + Megolm; mx.api itself does no cryptography.

## Install

```r
# CRAN
install.packages("mx.api")

# GitHub (development version)
remotes::install_github("cornball-ai/mx.api")
```

## Quick start: log in, send, sync

```r
library(mx.api)

s <- mx_login(
    server   = "https://matrix.example",
    user     = "alice",
    password = "hunter2"
)

room <- mx_room_join(s, "#general:matrix.example")
mx_send(s, room, "hello from R")

batch <- mx_sync(s, timeout = 0)
str(batch$rooms$join)

mx_logout(s)
```

## Create a private room with invites

```r
room <- mx_room_create(
    s,
    name   = "project sync",
    topic  = "weekly check-in",
    preset = "private_chat",
    invite = c("@bob:matrix.example", "@carol:matrix.example")
)
mx_send(s, room, "kickoff in 5")
```

`mx_room_create` returns the new room id as a character string.
Presets: `"private_chat"`, `"trusted_private_chat"`, `"public_chat"`.

## Sending rich-text (code blocks, bold, etc.)

`mx_send`'s `body` is plain text. Matrix clients that show rich text
(Element, Cinny, Fractal, etc.) render the optional `formatted_body`
field instead, fall back to `body` when it is absent. Markdown
fences in plain `body` show as literal backticks; you have to send
HTML in `formatted_body` to get a real code block.

Pass both via the `extra` argument:

```r
plain <- "Build steps:\n\ndocker compose build\ndocker compose up -d"

html <- paste0(
    "<p>Build steps:</p>",
    "<pre><code>docker compose build\n",
    "docker compose up -d</code></pre>"
)

mx_send(
    s, room, plain,
    extra = list(
        format = "org.matrix.custom.html",
        formatted_body = html
    )
)
```

The `format` value `"org.matrix.custom.html"` is the only one the
Matrix spec defines. Supported tags are listed in the
[Matrix client-server spec, section 11.2.1.7](https://spec.matrix.org/v1.11/client-server-api/#mroommessage-msgtypes).
Code blocks use `<pre><code>…</code></pre>`; inline code is `<code>…</code>`.

## What's covered

| Area | Functions |
|---|---|
| Session | `mx_register`, `mx_login`, `mx_logout`, `mx_whoami`, `mx_session` |
| Rooms | `mx_rooms`, `mx_room_create`, `mx_room_join`, `mx_room_leave`, `mx_room_invite`, `mx_room_members`, `mx_room_name`, `mx_room_topic` |
| Messages | `mx_send`, `mx_send_event`, `mx_messages`, `mx_sync`, `mx_react`, `mx_redact`, `mx_read_receipt`, `mx_typing` |
| Room state | `mx_get_state`, `mx_set_state` |
| Media | `mx_upload` (streaming), `mx_download`, `mx_send_media` (+ `mx_send_file` / `mx_send_image` / `mx_send_audio` / `mx_send_video`), `mx_guess_mime`, `mx_media_config` |
| Profile | `mx_profile`, `mx_set_displayname`, `mx_set_avatar_url` |
| Account data | `mx_get_account_data`, `mx_set_account_data` |
| Devices | `mx_devices`, `mx_delete_device` |
| E2EE transport | `mx_keys_upload`, `mx_keys_query`, `mx_keys_claim`, `mx_send_to_device` |
| E2EE signing helper | `mx_canonical_json` |

End-to-end **cryptography** is out of scope; pair with `mx.crypto`
(or another crypto library) to sign and verify the payloads these
endpoints carry. Helpful framing:

- mx.api speaks Matrix HTTP. It does no signing, no key management,
  no key validation, and holds no state between calls.
- mx.crypto speaks Olm + Megolm. It does no HTTP.
- [mx.client](https://github.com/cornball-ai/mx.client) is the stateful
  layer that uses both: config persistence, sync cursors, and E2EE
  orchestration (its `vignette("e2ee")` walks the whole flow).

## Canonical JSON

`mx_canonical_json()` is the byte-stable encoder Matrix's signing rules
require. It is hand-rolled (not a jsonlite wrapper) so the
spec-sensitive choices — key ordering by UTF-8 byte sequence, integer
range, NaN/Inf/NA rejection, duplicate-key rejection, control-char
escaping — are visible and unit-tested rather than hidden in another
package's defaults.

```r
mx_canonical_json(list(b = 2, a = 1))
#> [1] "{\"a\":1,\"b\":2}"

mx_canonical_json(1.5)
#> Error: mx_canonical_json: non-integer number 1.5 disallowed
```

107 assertions exercise the encoder (see `inst/tinytest/test_canonical_json.R`).

## Status

**0.3.0**. Relative to 0.2.0 the delta is additive:

- Generic event and state plumbing: `mx_send_event`, `mx_set_state`,
  `mx_get_state` (needed for `m.room.encrypted` / `m.room.encryption`).
- Media messages: `mx_send_media` and the file/image/audio/video
  wrappers.
- Bot lifecycle: `mx_room_invite`, `mx_redact`, `mx_typing`,
  `mx_profile` / `mx_set_displayname` / `mx_set_avatar_url`.
- Account data: `mx_get_account_data`, `mx_set_account_data`.
- Devices: `mx_devices`, `mx_delete_device`.
- Classed error conditions (`mx_error_<ERRCODE>`) for programmatic
  handling; streamed uploads.

See `NEWS.md` for the full changelog.

## CI

GitHub Actions via [r-ci](https://github.com/eddelbuettel/r-ci); macOS
and Ubuntu runners cover every commit + PR.

## License

MIT. See `LICENSE`.
