Matrix protocol in OCaml, Eio specialised
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

more

+377 -192
+15
.gitignore
··· 1 + # OCaml build artifacts 1 2 _build/ 2 3 *.install 4 + *.merlin 5 + 6 + # Third-party sources (fetch locally with opam source) 7 + third_party/ 8 + 9 + # Editor and OS files 10 + .DS_Store 11 + *.swp 12 + *~ 13 + .vscode/ 14 + .idea/ 15 + 16 + # Opam local switch 17 + _opam/
+1
.ocamlformat
··· 1 + version=0.28.1
+53
.tangled/workflows/build.yml
··· 1 + when: 2 + - event: ["push", "pull_request"] 3 + branch: ["main"] 4 + 5 + engine: nixery 6 + 7 + dependencies: 8 + nixpkgs: 9 + - shell 10 + - stdenv 11 + - findutils 12 + - binutils 13 + - libunwind 14 + - ncurses 15 + - opam 16 + - git 17 + - gawk 18 + - gnupatch 19 + - gnum4 20 + - gnumake 21 + - gnutar 22 + - gnused 23 + - gnugrep 24 + - diffutils 25 + - gzip 26 + - bzip2 27 + - gcc 28 + - ocaml 29 + - pkg-config 30 + 31 + steps: 32 + - name: opam 33 + command: | 34 + opam init --disable-sandboxing -a -y 35 + - name: repo 36 + command: | 37 + opam repo add aoah https://tangled.org/anil.recoil.org/aoah-opam-repo.git 38 + - name: switch 39 + command: | 40 + opam install . --confirm-level=unsafe-yes --deps-only 41 + - name: build 42 + command: | 43 + opam exec -- dune build 44 + - name: switch-test 45 + command: | 46 + opam install . --confirm-level=unsafe-yes --deps-only --with-test 47 + - name: test 48 + command: | 49 + opam exec -- dune runtest --verbose 50 + - name: doc 51 + command: | 52 + opam install -y odoc 53 + opam exec -- dune build @doc
+11
CHANGES.md
··· 1 + ## v0.1.0 (unreleased) 2 + 3 + Initial release of ocaml-matrix, a pure OCaml Matrix SDK. 4 + 5 + ### Features 6 + 7 + - `matrix_proto`: Matrix protocol types with bidirectional JSON codecs using jsont 8 + - `matrix_client`: Matrix client SDK with session persistence and HTTP requests 9 + - `matrix_eio`: Eio-idiomatic wrapper with switches, fibres, and Eio.Io errors 10 + - `omatrix`: CLI tool with cmdliner subcommands for login, sync, and messaging 11 + - Support for encrypted room creation and direct message rooms
+15
LICENSE.md
··· 1 + ## ISC License 2 + 3 + Copyright (c) 2024-2025 Anil Madhavapeddy <anil@recoil.org> 4 + 5 + Permission to use, copy, modify, and/or distribute this software for any 6 + purpose with or without fee is hereby granted, provided that the above 7 + copyright notice and this permission notice appear in all copies. 8 + 9 + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 + REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 + AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 + INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 + LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 14 + OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 + PERFORMANCE OF THIS SOFTWARE.
+23 -39
dune-project
··· 1 1 (lang dune 3.20) 2 - (name ocaml-matrix) 2 + (name matrix) 3 3 4 4 (generate_opam_files true) 5 5 6 - (source (github matrix-org/ocaml-matrix)) 7 - (license Apache-2.0) 8 - (authors "OCaml Matrix Contributors") 9 - (maintainers "dev@matrix.org") 6 + (license ISC) 7 + (authors "Anil Madhavapeddy") 8 + (maintainers "Anil Madhavapeddy <anil@recoil.org>") 9 + (homepage "https://tangled.org/@anil.recoil.org/ocaml-matrix") 10 + (bug_reports "https://tangled.org/@anil.recoil.org/ocaml-matrix/issues") 11 + (maintenance_intent "(latest)") 10 12 11 13 (package 12 - (name ocaml-matrix) 14 + (name matrix) 13 15 (synopsis "Pure OCaml Matrix SDK") 14 - (description "A pure OCaml implementation of the Matrix client SDK") 15 - (allow_empty) 16 - (depends 17 - (ocaml (>= 5.1)) 18 - matrix_proto)) 19 - 20 - (package 21 - (name matrix_proto) 22 - (synopsis "Matrix protocol types with JSON codecs") 23 - (description "OCaml types for Matrix protocol with bidirectional JSON encoding/decoding using jsont") 16 + (description 17 + "A pure OCaml implementation of the Matrix client SDK with protocol types, \ 18 + HTTP client, and Eio integration. Provides matrix.proto for protocol types, \ 19 + matrix.client for the HTTP client, and matrix.eio for Eio-idiomatic usage.") 24 20 (depends 25 21 (ocaml (>= 5.1)) 26 22 jsont 27 23 ptime 28 - (alcotest :with-test))) 29 - 30 - (package 31 - (name matrix_client) 32 - (synopsis "Matrix client SDK for OCaml") 33 - (description "Full Matrix client SDK using requests for HTTP and jsont for JSON") 34 - (depends 35 - (ocaml (>= 5.1)) 36 - matrix_proto 37 24 requests 38 - jsont 39 25 tomlt 40 26 xdge 41 27 uri 42 28 eio 43 - ptime 44 - logs)) 45 - 46 - (package 47 - (name matrix_eio) 48 - (synopsis "Eio-idiomatic Matrix client SDK") 49 - (description "Matrix client SDK using Eio idioms: switches for resource management, Eio.Io for errors, fibres for concurrency") 50 - (depends 51 - (ocaml (>= 5.1)) 52 - matrix_client 53 - matrix_proto 54 - eio 55 - uri)) 29 + logs 30 + base64 31 + mirage-crypto 32 + mirage-crypto-ec 33 + mirage-crypto-rng 34 + digestif 35 + kdf 36 + fmt 37 + cmdliner 38 + (alcotest :with-test) 39 + (eio_main :with-test)))
+6 -6
examples/dune
··· 1 1 (executable 2 2 (name simple_bot) 3 - (libraries matrix_eio matrix_proto jsont eio_main uri)) 3 + (libraries matrix.eio matrix.proto jsont eio_main uri)) 4 4 5 5 (executable 6 6 (name send_dm) 7 - (libraries matrix_eio matrix_proto eio_main uri cmdliner logs logs.cli logs.fmt fmt.tty fmt.cli)) 7 + (libraries matrix.eio matrix.proto eio_main uri cmdliner logs logs.cli logs.fmt fmt.tty fmt.cli)) 8 8 9 9 (executable 10 10 (name omatrix) 11 11 (public_name omatrix) 12 - (package ocaml-matrix) 12 + (package matrix) 13 13 (libraries 14 - matrix_eio 15 - matrix_client 16 - matrix_proto 14 + matrix.eio 15 + matrix.client 16 + matrix.proto 17 17 eio_main 18 18 uri 19 19 xdge
+198
examples/send_dm.ml
··· 1 + (** Send a direct message to another Matrix user. 2 + 3 + This example demonstrates: 4 + - Creating a client and logging in 5 + - Finding or creating a direct message room with another user 6 + - Sending a single text message 7 + - Proper cleanup with logout 8 + 9 + To run: 10 + {[ 11 + dune exec examples/send_dm.exe -- \ 12 + --homeserver https://matrix.org \ 13 + --username @you:matrix.org \ 14 + --password secret \ 15 + --recipient @them:matrix.org \ 16 + "Hello!" 17 + ]} 18 + 19 + Or using an environment variable for the password: 20 + {[ 21 + MATRIX_PASSWORD=secret dune exec examples/send_dm.exe -- \ 22 + --homeserver https://matrix.org \ 23 + --username @you:matrix.org \ 24 + --recipient @them:matrix.org \ 25 + "Hello!" 26 + ]} 27 + 28 + Enable E2E encryption on new rooms: 29 + {[ 30 + dune exec examples/send_dm.exe -- --encrypted \ 31 + --homeserver https://matrix.org \ 32 + ... 33 + ]} 34 + 35 + Enable debug logging: 36 + {[ 37 + dune exec examples/send_dm.exe -- -v -v \ 38 + --homeserver https://matrix.org \ 39 + ... 40 + ]} 41 + 42 + @see <https://spec.matrix.org/v1.11/client-server-api/#direct-messaging> Direct Messaging *) 43 + 44 + open Cmdliner 45 + open Matrix_eio 46 + 47 + (** Set up logging with fmt reporter. *) 48 + let setup_log style_renderer level = 49 + Fmt_tty.setup_std_outputs ?style_renderer (); 50 + Logs.set_level level; 51 + Logs.set_reporter (Logs_fmt.reporter ()); 52 + () 53 + 54 + (** Send a direct message to a user. 55 + 56 + Finds an existing DM room or creates a new one, sends the message, and returns. *) 57 + let send_dm ~homeserver ~username ~password ~recipient ~message ~encrypted = 58 + Eio_main.run @@ fun env -> 59 + Eio.Switch.run @@ fun sw -> 60 + 61 + Logs.info (fun m -> m "Connecting to %s" homeserver); 62 + 63 + (* Create client and login *) 64 + let client = 65 + try 66 + Matrix_eio.login_password ~sw ~env 67 + ~homeserver:(Uri.of_string homeserver) 68 + ~user:username ~password () 69 + with Eio.Io (Error.E err, _) -> 70 + Logs.err (fun m -> m "Login failed: %a" Error.pp_err err); 71 + exit 1 72 + in 73 + 74 + let my_user_id = Client.user_id client in 75 + Logs.info (fun m -> m "Logged in as %s" 76 + (Matrix_proto.Id.User_id.to_string my_user_id)); 77 + 78 + (* Parse recipient user ID *) 79 + let recipient_id = 80 + match Matrix_proto.Id.User_id.of_string recipient with 81 + | Ok id -> id 82 + | Error (`Invalid_user_id msg) -> 83 + Logs.err (fun m -> m "Invalid recipient user ID '%s': %s" recipient msg); 84 + exit 1 85 + in 86 + 87 + Logs.info (fun m -> m "Finding or creating DM room with %s" recipient); 88 + 89 + (* Get existing DM room or create a new one. 90 + This checks m.direct account data for existing rooms first. *) 91 + let encrypted_opt = if encrypted then Some true else None in 92 + let room_id = 93 + try 94 + Rooms.get_or_create_dm client ~user_id:recipient_id ?encrypted:encrypted_opt () 95 + with Eio.Io (Error.E err, _) -> 96 + Logs.err (fun m -> m "Failed to get/create DM room: %a" Error.pp_err err); 97 + exit 1 98 + in 99 + 100 + Logs.info (fun m -> m "Using room %s" 101 + (Matrix_proto.Id.Room_id.to_string room_id)); 102 + 103 + (* Send the message *) 104 + Logs.info (fun m -> m "Sending message..."); 105 + 106 + let event_id = 107 + try 108 + Messages.send_text client ~room_id ~body:message () 109 + with Eio.Io (Error.E err, _) -> 110 + Logs.err (fun m -> m "Failed to send message: %a" Error.pp_err err); 111 + exit 1 112 + in 113 + 114 + Logs.app (fun m -> m "Message sent (event ID: %s)" 115 + (Matrix_proto.Id.Event_id.to_string event_id)); 116 + 117 + (* Logout *) 118 + (try 119 + Auth.logout client; 120 + Logs.info (fun m -> m "Logged out") 121 + with Eio.Io _ -> 122 + Logs.warn (fun m -> m "Logout failed (session may still be active)")); 123 + 124 + `Ok () 125 + 126 + (* Command-line argument definitions *) 127 + 128 + let homeserver = 129 + let doc = "Matrix homeserver URL (e.g., https://matrix.org)." in 130 + let env = Cmd.Env.info "MATRIX_HOMESERVER" ~doc in 131 + Arg.(required & opt (some string) None & 132 + info ["homeserver"; "s"] ~env ~docv:"URL" ~doc) 133 + 134 + let username = 135 + let doc = "Username (localpart or full @user:server)." in 136 + let env = Cmd.Env.info "MATRIX_USERNAME" ~doc in 137 + Arg.(required & opt (some string) None & 138 + info ["username"; "u"] ~env ~docv:"USER" ~doc) 139 + 140 + let password = 141 + let doc = "Password. For better security, use the $(b,MATRIX_PASSWORD) \ 142 + environment variable instead of this flag." in 143 + let env = Cmd.Env.info "MATRIX_PASSWORD" ~doc in 144 + Arg.(required & opt (some string) None & 145 + info ["password"; "p"] ~env ~docv:"PASS" ~doc) 146 + 147 + let recipient = 148 + let doc = "Recipient user ID (e.g., @user:matrix.org)." in 149 + Arg.(required & opt (some string) None & 150 + info ["recipient"; "r"] ~docv:"USER_ID" ~doc) 151 + 152 + let encrypted = 153 + let doc = "Enable end-to-end encryption on newly created rooms. \ 154 + Note: Encryption requires key management which is not fully \ 155 + implemented; messages may not be decryptable by the recipient." in 156 + Arg.(value & flag & info ["encrypted"; "e"] ~doc) 157 + 158 + let message = 159 + let doc = "Message text to send." in 160 + Arg.(required & pos 0 (some string) None & info [] ~docv:"MESSAGE" ~doc) 161 + 162 + let setup_log_term = 163 + Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ()) 164 + 165 + let send_dm_term = 166 + let run () homeserver username password recipient message encrypted = 167 + send_dm ~homeserver ~username ~password ~recipient ~message ~encrypted 168 + in 169 + Term.(ret (const run $ setup_log_term $ homeserver $ username $ 170 + password $ recipient $ message $ encrypted)) 171 + 172 + let cmd = 173 + let doc = "Send a direct message to a Matrix user" in 174 + let man = [ 175 + `S Manpage.s_description; 176 + `P "Sends a one-off direct message to another Matrix user. Reuses an \ 177 + existing DM room if one exists, otherwise creates a new one. \ 178 + After sending, logs out."; 179 + `S Manpage.s_examples; 180 + `Pre " send_dm -s https://matrix.org -u @me:matrix.org \\\\"; 181 + `Pre " -p secret -r @them:matrix.org \"Hello!\""; 182 + `P "Using environment variables:"; 183 + `Pre " export MATRIX_HOMESERVER=https://matrix.org"; 184 + `Pre " export MATRIX_USERNAME=@me:matrix.org"; 185 + `Pre " export MATRIX_PASSWORD=secret"; 186 + `Pre " send_dm -r @them:matrix.org \"Hello!\""; 187 + `P "With E2E encryption (for new rooms):"; 188 + `Pre " send_dm --encrypted -s https://matrix.org ... \"Hello!\""; 189 + `P "With debug logging:"; 190 + `Pre " send_dm -v -v -s https://matrix.org ... \"Hello!\""; 191 + `S Manpage.s_environment; 192 + `S Manpage.s_bugs; 193 + `P "Report bugs at <https://github.com/matrix-org/ocaml-matrix/issues>."; 194 + ] in 195 + let info = Cmd.info "send_dm" ~version:"0.1.0" ~doc ~man in 196 + Cmd.v info send_dm_term 197 + 198 + let () = exit (Cmd.eval cmd)
+2 -2
lib/matrix_client/dune
··· 1 1 (library 2 2 (name matrix_client) 3 - (public_name matrix_client) 3 + (public_name matrix.client) 4 4 (libraries 5 - matrix_proto 5 + matrix.proto 6 6 requests 7 7 jsont 8 8 jsont.bytesrw
+3 -3
lib/matrix_eio/dune
··· 1 1 (library 2 2 (name matrix_eio) 3 - (public_name matrix_eio) 3 + (public_name matrix.eio) 4 4 (libraries 5 - matrix_client 6 - matrix_proto 5 + matrix.client 6 + matrix.proto 7 7 eio 8 8 uri))
+1 -1
lib/matrix_proto/dune
··· 1 1 (library 2 2 (name matrix_proto) 3 - (public_name matrix_proto) 3 + (public_name matrix.proto) 4 4 (libraries jsont ptime))
+48
matrix.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "Pure OCaml Matrix SDK" 4 + description: 5 + "A pure OCaml implementation of the Matrix client SDK with protocol types, HTTP client, and Eio integration. Provides matrix.proto for protocol types, matrix.client for the HTTP client, and matrix.eio for Eio-idiomatic usage." 6 + maintainer: ["Anil Madhavapeddy <anil@recoil.org>"] 7 + authors: ["Anil Madhavapeddy"] 8 + license: "ISC" 9 + homepage: "https://tangled.org/@anil.recoil.org/ocaml-matrix" 10 + bug-reports: "https://tangled.org/@anil.recoil.org/ocaml-matrix/issues" 11 + depends: [ 12 + "dune" {>= "3.20"} 13 + "ocaml" {>= "5.1"} 14 + "jsont" 15 + "ptime" 16 + "requests" 17 + "tomlt" 18 + "xdge" 19 + "uri" 20 + "eio" 21 + "logs" 22 + "base64" 23 + "mirage-crypto" 24 + "mirage-crypto-ec" 25 + "mirage-crypto-rng" 26 + "digestif" 27 + "kdf" 28 + "fmt" 29 + "cmdliner" 30 + "alcotest" {with-test} 31 + "eio_main" {with-test} 32 + "odoc" {with-doc} 33 + ] 34 + build: [ 35 + ["dune" "subst"] {dev} 36 + [ 37 + "dune" 38 + "build" 39 + "-p" 40 + name 41 + "-j" 42 + jobs 43 + "@install" 44 + "@runtest" {with-test} 45 + "@doc" {with-doc} 46 + ] 47 + ] 48 + x-maintenance-intent: ["(latest)"]
-40
matrix_client.opam
··· 1 - # This file is generated by dune, edit dune-project instead 2 - opam-version: "2.0" 3 - synopsis: "Matrix client SDK for OCaml" 4 - description: 5 - "Full Matrix client SDK using requests for HTTP and jsont for JSON" 6 - maintainer: ["dev@matrix.org"] 7 - authors: ["OCaml Matrix Contributors"] 8 - license: "Apache-2.0" 9 - homepage: "https://github.com/matrix-org/ocaml-matrix" 10 - bug-reports: "https://github.com/matrix-org/ocaml-matrix/issues" 11 - depends: [ 12 - "dune" {>= "3.20"} 13 - "ocaml" {>= "5.1"} 14 - "matrix_proto" 15 - "requests" 16 - "jsont" 17 - "tomlt" 18 - "xdge" 19 - "uri" 20 - "eio" 21 - "ptime" 22 - "logs" 23 - "odoc" {with-doc} 24 - ] 25 - build: [ 26 - ["dune" "subst"] {dev} 27 - [ 28 - "dune" 29 - "build" 30 - "-p" 31 - name 32 - "-j" 33 - jobs 34 - "@install" 35 - "@runtest" {with-test} 36 - "@doc" {with-doc} 37 - ] 38 - ] 39 - dev-repo: "git+https://github.com/matrix-org/ocaml-matrix.git" 40 - x-maintenance-intent: ["(latest)"]
-35
matrix_eio.opam
··· 1 - # This file is generated by dune, edit dune-project instead 2 - opam-version: "2.0" 3 - synopsis: "Eio-idiomatic Matrix client SDK" 4 - description: 5 - "Matrix client SDK using Eio idioms: switches for resource management, Eio.Io for errors, fibres for concurrency" 6 - maintainer: ["dev@matrix.org"] 7 - authors: ["OCaml Matrix Contributors"] 8 - license: "Apache-2.0" 9 - homepage: "https://github.com/matrix-org/ocaml-matrix" 10 - bug-reports: "https://github.com/matrix-org/ocaml-matrix/issues" 11 - depends: [ 12 - "dune" {>= "3.20"} 13 - "ocaml" {>= "5.1"} 14 - "matrix_client" 15 - "matrix_proto" 16 - "eio" 17 - "uri" 18 - "odoc" {with-doc} 19 - ] 20 - build: [ 21 - ["dune" "subst"] {dev} 22 - [ 23 - "dune" 24 - "build" 25 - "-p" 26 - name 27 - "-j" 28 - jobs 29 - "@install" 30 - "@runtest" {with-test} 31 - "@doc" {with-doc} 32 - ] 33 - ] 34 - dev-repo: "git+https://github.com/matrix-org/ocaml-matrix.git" 35 - x-maintenance-intent: ["(latest)"]
-34
matrix_proto.opam
··· 1 - # This file is generated by dune, edit dune-project instead 2 - opam-version: "2.0" 3 - synopsis: "Matrix protocol types with JSON codecs" 4 - description: 5 - "OCaml types for Matrix protocol with bidirectional JSON encoding/decoding using jsont" 6 - maintainer: ["dev@matrix.org"] 7 - authors: ["OCaml Matrix Contributors"] 8 - license: "Apache-2.0" 9 - homepage: "https://github.com/matrix-org/ocaml-matrix" 10 - bug-reports: "https://github.com/matrix-org/ocaml-matrix/issues" 11 - depends: [ 12 - "dune" {>= "3.20"} 13 - "ocaml" {>= "5.1"} 14 - "jsont" 15 - "ptime" 16 - "alcotest" {with-test} 17 - "odoc" {with-doc} 18 - ] 19 - build: [ 20 - ["dune" "subst"] {dev} 21 - [ 22 - "dune" 23 - "build" 24 - "-p" 25 - name 26 - "-j" 27 - jobs 28 - "@install" 29 - "@runtest" {with-test} 30 - "@doc" {with-doc} 31 - ] 32 - ] 33 - dev-repo: "git+https://github.com/matrix-org/ocaml-matrix.git" 34 - x-maintenance-intent: ["(latest)"]
-31
ocaml-matrix.opam
··· 1 - # This file is generated by dune, edit dune-project instead 2 - opam-version: "2.0" 3 - synopsis: "Pure OCaml Matrix SDK" 4 - description: "A pure OCaml implementation of the Matrix client SDK" 5 - maintainer: ["dev@matrix.org"] 6 - authors: ["OCaml Matrix Contributors"] 7 - license: "Apache-2.0" 8 - homepage: "https://github.com/matrix-org/ocaml-matrix" 9 - bug-reports: "https://github.com/matrix-org/ocaml-matrix/issues" 10 - depends: [ 11 - "dune" {>= "3.20"} 12 - "ocaml" {>= "5.1"} 13 - "matrix_proto" 14 - "odoc" {with-doc} 15 - ] 16 - build: [ 17 - ["dune" "subst"] {dev} 18 - [ 19 - "dune" 20 - "build" 21 - "-p" 22 - name 23 - "-j" 24 - jobs 25 - "@install" 26 - "@runtest" {with-test} 27 - "@doc" {with-doc} 28 - ] 29 - ] 30 - dev-repo: "git+https://github.com/matrix-org/ocaml-matrix.git" 31 - x-maintenance-intent: ["(latest)"]
+1 -1
test/dune
··· 1 1 (executable 2 2 (name test_matrix_proto) 3 - (libraries matrix_proto jsont jsont.bytesrw)) 3 + (libraries matrix.proto jsont jsont.bytesrw)) 4 4 5 5 (rule 6 6 (alias runtest)