(** Send a direct message to another Matrix user. This example demonstrates: - Creating a client and logging in - Finding or creating a direct message room with another user - Sending a single text message - Proper cleanup with logout To run: {[ dune exec examples/send_dm.exe -- \ --homeserver https://matrix.org \ --username @you:matrix.org \ --password secret \ --recipient @them:matrix.org \ "Hello!" ]} Or using an environment variable for the password: {[ MATRIX_PASSWORD=secret dune exec examples/send_dm.exe -- \ --homeserver https://matrix.org \ --username @you:matrix.org \ --recipient @them:matrix.org \ "Hello!" ]} Enable E2E encryption on new rooms: {[ dune exec examples/send_dm.exe -- --encrypted \ --homeserver https://matrix.org \ ... ]} Enable debug logging: {[ dune exec examples/send_dm.exe -- -v -v \ --homeserver https://matrix.org \ ... ]} @see Direct Messaging *) open Cmdliner open Matrix_eio (** Set up logging with fmt reporter. *) let setup_log style_renderer level = Fmt_tty.setup_std_outputs ?style_renderer (); Logs.set_level level; Logs.set_reporter (Logs_fmt.reporter ()); () (** Send a direct message to a user. Finds an existing DM room or creates a new one, sends the message, and returns. *) let send_dm ~homeserver ~username ~password ~recipient ~message ~encrypted = Eio_main.run @@ fun env -> Eio.Switch.run @@ fun sw -> Logs.info (fun m -> m "Connecting to %s" homeserver); (* Create client and login *) let client = try Matrix_eio.login_password ~sw ~env ~homeserver:(Uri.of_string homeserver) ~user:username ~password () with Eio.Io (Error.E err, _) -> Logs.err (fun m -> m "Login failed: %a" Error.pp_err err); exit 1 in let my_user_id = Client.user_id client in Logs.info (fun m -> m "Logged in as %s" (Matrix_proto.Id.User_id.to_string my_user_id)); (* Parse recipient user ID *) let recipient_id = match Matrix_proto.Id.User_id.of_string recipient with | Ok id -> id | Error (`Invalid_user_id msg) -> Logs.err (fun m -> m "Invalid recipient user ID '%s': %s" recipient msg); exit 1 in Logs.info (fun m -> m "Finding or creating DM room with %s" recipient); (* Get existing DM room or create a new one. This checks m.direct account data for existing rooms first. *) let encrypted_opt = if encrypted then Some true else None in let room_id = try Rooms.get_or_create_dm client ~user_id:recipient_id ?encrypted:encrypted_opt () with Eio.Io (Error.E err, _) -> Logs.err (fun m -> m "Failed to get/create DM room: %a" Error.pp_err err); exit 1 in Logs.info (fun m -> m "Using room %s" (Matrix_proto.Id.Room_id.to_string room_id)); (* Send the message *) Logs.info (fun m -> m "Sending message..."); let event_id = try Messages.send_text client ~room_id ~body:message () with Eio.Io (Error.E err, _) -> Logs.err (fun m -> m "Failed to send message: %a" Error.pp_err err); exit 1 in Logs.app (fun m -> m "Message sent (event ID: %s)" (Matrix_proto.Id.Event_id.to_string event_id)); (* Logout *) (try Auth.logout client; Logs.info (fun m -> m "Logged out") with Eio.Io _ -> Logs.warn (fun m -> m "Logout failed (session may still be active)")); `Ok () (* Command-line argument definitions *) let homeserver = let doc = "Matrix homeserver URL (e.g., https://matrix.org)." in let env = Cmd.Env.info "MATRIX_HOMESERVER" ~doc in Arg.(required & opt (some string) None & info ["homeserver"; "s"] ~env ~docv:"URL" ~doc) let username = let doc = "Username (localpart or full @user:server)." in let env = Cmd.Env.info "MATRIX_USERNAME" ~doc in Arg.(required & opt (some string) None & info ["username"; "u"] ~env ~docv:"USER" ~doc) let password = let doc = "Password. For better security, use the $(b,MATRIX_PASSWORD) \ environment variable instead of this flag." in let env = Cmd.Env.info "MATRIX_PASSWORD" ~doc in Arg.(required & opt (some string) None & info ["password"; "p"] ~env ~docv:"PASS" ~doc) let recipient = let doc = "Recipient user ID (e.g., @user:matrix.org)." in Arg.(required & opt (some string) None & info ["recipient"; "r"] ~docv:"USER_ID" ~doc) let encrypted = let doc = "Enable end-to-end encryption on newly created rooms. \ Note: Encryption requires key management which is not fully \ implemented; messages may not be decryptable by the recipient." in Arg.(value & flag & info ["encrypted"; "e"] ~doc) let message = let doc = "Message text to send." in Arg.(required & pos 0 (some string) None & info [] ~docv:"MESSAGE" ~doc) let setup_log_term = Term.(const setup_log $ Fmt_cli.style_renderer () $ Logs_cli.level ()) let send_dm_term = let run () homeserver username password recipient message encrypted = send_dm ~homeserver ~username ~password ~recipient ~message ~encrypted in Term.(ret (const run $ setup_log_term $ homeserver $ username $ password $ recipient $ message $ encrypted)) let cmd = let doc = "Send a direct message to a Matrix user" in let man = [ `S Manpage.s_description; `P "Sends a one-off direct message to another Matrix user. Reuses an \ existing DM room if one exists, otherwise creates a new one. \ After sending, logs out."; `S Manpage.s_examples; `Pre " send_dm -s https://matrix.org -u @me:matrix.org \\\\"; `Pre " -p secret -r @them:matrix.org \"Hello!\""; `P "Using environment variables:"; `Pre " export MATRIX_HOMESERVER=https://matrix.org"; `Pre " export MATRIX_USERNAME=@me:matrix.org"; `Pre " export MATRIX_PASSWORD=secret"; `Pre " send_dm -r @them:matrix.org \"Hello!\""; `P "With E2E encryption (for new rooms):"; `Pre " send_dm --encrypted -s https://matrix.org ... \"Hello!\""; `P "With debug logging:"; `Pre " send_dm -v -v -s https://matrix.org ... \"Hello!\""; `S Manpage.s_environment; `S Manpage.s_bugs; `P "Report bugs at ."; ] in let info = Cmd.info "send_dm" ~version:"0.1.0" ~doc ~man in Cmd.v info send_dm_term let () = exit (Cmd.eval cmd)