Matrix protocol in OCaml, Eio specialised
at main 198 lines 6.5 kB view raw
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 44open Cmdliner 45open Matrix_eio 46 47(** Set up logging with fmt reporter. *) 48let 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. *) 57let 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 128let 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 134let 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 140let 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 147let 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 152let 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 158let message = 159 let doc = "Message text to send." in 160 Arg.(required & pos 0 (some string) None & info [] ~docv:"MESSAGE" ~doc) 161 162let setup_log_term = 163 Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ()) 164 165let 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 172let 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 198let () = exit (Cmd.eval cmd)