Matrix protocol in OCaml, Eio specialised
at main 330 lines 10 kB view raw
1(** Event relations: reactions, edits, threads, and replies. *) 2 3(* Relation types *) 4type relation_type = 5 | Annotation (* m.annotation - reactions *) 6 | Reference (* m.reference - generic reference *) 7 | Replace (* m.replace - edits *) 8 | Thread (* m.thread - threads *) 9 10let relation_type_to_string = function 11 | Annotation -> "m.annotation" 12 | Reference -> "m.reference" 13 | Replace -> "m.replace" 14 | Thread -> "m.thread" 15 16let relation_type_of_string = function 17 | "m.annotation" -> Ok Annotation 18 | "m.reference" -> Ok Reference 19 | "m.replace" -> Ok Replace 20 | "m.thread" -> Ok Thread 21 | s -> Error ("Unknown relation type: " ^ s) 22 23let relation_type_jsont = 24 Jsont.of_of_string ~kind:"relation_type" 25 ~enc:relation_type_to_string 26 relation_type_of_string 27[@@warning "-32"] 28 29(* Reaction *) 30type reaction = { 31 event_id : Matrix_proto.Id.Event_id.t; 32 key : string; (* emoji or shortcode *) 33} 34 35(* Send a reaction to an event *) 36type reaction_content = { 37 relates_to : reaction_relates_to; 38} [@@warning "-69"] 39 40and reaction_relates_to = { 41 rel_type : string; 42 event_id : string; 43 key : string; 44} [@@warning "-69"] 45 46let reaction_relates_to_jsont = 47 Jsont.Object.( 48 map (fun rel_type event_id key -> { rel_type; event_id; key }) 49 |> mem "rel_type" Jsont.string 50 |> mem "event_id" Jsont.string 51 |> mem "key" Jsont.string 52 |> finish) 53 54let reaction_content_jsont = 55 Jsont.Object.( 56 map (fun relates_to -> { relates_to }) 57 |> mem "m.relates_to" reaction_relates_to_jsont 58 |> finish) 59 60let send_reaction client ~room_id ~event_id ~key = 61 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in 62 let event_id_str = Matrix_proto.Id.Event_id.to_string event_id in 63 let path = Printf.sprintf "/rooms/%s/send/m.reaction" (Uri.pct_encode room_id_str) in 64 let content = { 65 relates_to = { 66 rel_type = "m.annotation"; 67 event_id = event_id_str; 68 key; 69 } 70 } in 71 match Client.encode_body reaction_content_jsont content with 72 | Error e -> Error e 73 | Ok body -> 74 match Client.post client ~path ~body () with 75 | Error e -> Error e 76 | Ok body -> 77 match Client.decode_response Messages.send_response_jsont body with 78 | Error e -> Error e 79 | Ok resp -> Ok resp.event_id 80 81(* Edit a message *) 82type edit_content = { 83 msgtype : string; 84 body : string; 85 new_content : edit_new_content; 86 relates_to : edit_relates_to; 87} [@@warning "-69"] 88 89and edit_new_content = { 90 msgtype : string; 91 body : string; 92} [@@warning "-69"] 93 94and edit_relates_to = { 95 rel_type : string; 96 event_id : string; 97} [@@warning "-69"] 98 99let edit_new_content_jsont = 100 Jsont.Object.( 101 map (fun msgtype body -> { msgtype; body }) 102 |> mem "msgtype" Jsont.string 103 |> mem "body" Jsont.string 104 |> finish) 105 106let edit_relates_to_jsont = 107 Jsont.Object.( 108 map (fun rel_type event_id -> { rel_type; event_id }) 109 |> mem "rel_type" Jsont.string 110 |> mem "event_id" Jsont.string 111 |> finish) 112 113let edit_content_jsont = 114 Jsont.Object.( 115 map (fun msgtype body new_content relates_to -> 116 { msgtype; body; new_content; relates_to }) 117 |> mem "msgtype" Jsont.string 118 |> mem "body" Jsont.string 119 |> mem "m.new_content" edit_new_content_jsont 120 |> mem "m.relates_to" edit_relates_to_jsont 121 |> finish) 122 123let edit_message client ~room_id ~event_id ~new_body = 124 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in 125 let event_id_str = Matrix_proto.Id.Event_id.to_string event_id in 126 let path = Printf.sprintf "/rooms/%s/send/m.room.message" (Uri.pct_encode room_id_str) in 127 let content = { 128 msgtype = "m.text"; 129 body = "* " ^ new_body; (* Fallback for clients that don't support edits *) 130 new_content = { 131 msgtype = "m.text"; 132 body = new_body; 133 }; 134 relates_to = { 135 rel_type = "m.replace"; 136 event_id = event_id_str; 137 }; 138 } in 139 match Client.encode_body edit_content_jsont content with 140 | Error e -> Error e 141 | Ok body -> 142 match Client.post client ~path ~body () with 143 | Error e -> Error e 144 | Ok body -> 145 match Client.decode_response Messages.send_response_jsont body with 146 | Error e -> Error e 147 | Ok resp -> Ok resp.event_id 148 149(* Reply to a message *) 150type reply_relates_to = { 151 in_reply_to : reply_in_reply_to; 152} [@@warning "-69"] 153 154and reply_in_reply_to = { 155 event_id : string; 156} [@@warning "-69"] 157 158type reply_content = { 159 msgtype : string; 160 body : string; 161 relates_to : reply_relates_to; 162} [@@warning "-69"] 163 164let reply_in_reply_to_jsont = 165 Jsont.Object.( 166 map (fun event_id -> { event_id }) 167 |> mem "event_id" Jsont.string 168 |> finish) 169 170let reply_relates_to_jsont = 171 Jsont.Object.( 172 map (fun in_reply_to -> { in_reply_to }) 173 |> mem "m.in_reply_to" reply_in_reply_to_jsont 174 |> finish) 175 176let reply_content_jsont = 177 Jsont.Object.( 178 map (fun msgtype body relates_to -> { msgtype; body; relates_to }) 179 |> mem "msgtype" Jsont.string 180 |> mem "body" Jsont.string 181 |> mem "m.relates_to" reply_relates_to_jsont 182 |> finish) 183 184let send_reply client ~room_id ~event_id ~body = 185 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in 186 let event_id_str = Matrix_proto.Id.Event_id.to_string event_id in 187 let path = Printf.sprintf "/rooms/%s/send/m.room.message" (Uri.pct_encode room_id_str) in 188 let content = { 189 msgtype = "m.text"; 190 body; 191 relates_to = { 192 in_reply_to = { 193 event_id = event_id_str; 194 }; 195 }; 196 } in 197 match Client.encode_body reply_content_jsont content with 198 | Error e -> Error e 199 | Ok body -> 200 match Client.post client ~path ~body () with 201 | Error e -> Error e 202 | Ok resp_body -> 203 match Client.decode_response Messages.send_response_jsont resp_body with 204 | Error e -> Error e 205 | Ok resp -> Ok resp.event_id 206 207(* Thread message *) 208type thread_relates_to = { 209 rel_type : string; 210 event_id : string; 211 is_falling_back : bool; 212 in_reply_to : reply_in_reply_to option; 213} [@@warning "-69"] 214 215type thread_content = { 216 msgtype : string; 217 body : string; 218 relates_to : thread_relates_to; 219} [@@warning "-69"] 220 221let thread_relates_to_jsont = 222 Jsont.Object.( 223 map (fun rel_type event_id is_falling_back in_reply_to -> 224 { rel_type; event_id; is_falling_back; in_reply_to }) 225 |> mem "rel_type" Jsont.string 226 |> mem "event_id" Jsont.string 227 |> mem "is_falling_back" Jsont.bool ~dec_absent:true 228 |> opt_mem "m.in_reply_to" reply_in_reply_to_jsont ~enc:(fun t -> t.in_reply_to) 229 |> finish) 230 231let thread_content_jsont = 232 Jsont.Object.( 233 map (fun msgtype body relates_to -> { msgtype; body; relates_to }) 234 |> mem "msgtype" Jsont.string 235 |> mem "body" Jsont.string 236 |> mem "m.relates_to" thread_relates_to_jsont 237 |> finish) 238 239let send_in_thread client ~room_id ~thread_root_id ?reply_to_id ~body () = 240 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in 241 let thread_root_str = Matrix_proto.Id.Event_id.to_string thread_root_id in 242 let path = Printf.sprintf "/rooms/%s/send/m.room.message" (Uri.pct_encode room_id_str) in 243 let in_reply_to = match reply_to_id with 244 | Some id -> Some { event_id = Matrix_proto.Id.Event_id.to_string id } 245 | None -> None 246 in 247 let content = { 248 msgtype = "m.text"; 249 body; 250 relates_to = { 251 rel_type = "m.thread"; 252 event_id = thread_root_str; 253 is_falling_back = Option.is_none reply_to_id; 254 in_reply_to; 255 }; 256 } in 257 match Client.encode_body thread_content_jsont content with 258 | Error e -> Error e 259 | Ok body -> 260 match Client.post client ~path ~body () with 261 | Error e -> Error e 262 | Ok resp_body -> 263 match Client.decode_response Messages.send_response_jsont resp_body with 264 | Error e -> Error e 265 | Ok resp -> Ok resp.event_id 266 267(* Get relations for an event *) 268type aggregation = { 269 event_id : Matrix_proto.Id.Event_id.t; 270 origin_server_ts : int64; 271 sender : Matrix_proto.Id.User_id.t; 272} 273 274let aggregation_jsont = 275 Jsont.Object.( 276 map (fun event_id origin_server_ts sender -> 277 { event_id; origin_server_ts; sender }) 278 |> mem "event_id" Matrix_proto.Id.Event_id.jsont 279 |> mem "origin_server_ts" Jsont.int64 280 |> mem "sender" Matrix_proto.Id.User_id.jsont 281 |> finish) 282 283type relations_response = { 284 chunk : aggregation list; 285 next_batch : string option; 286 prev_batch : string option; 287} 288 289let relations_response_jsont = 290 Jsont.Object.( 291 map (fun chunk next_batch prev_batch -> 292 { chunk; next_batch; prev_batch }) 293 |> mem "chunk" (Jsont.list aggregation_jsont) ~dec_absent:[] 294 |> opt_mem "next_batch" Jsont.string ~enc:(fun t -> t.next_batch) 295 |> opt_mem "prev_batch" Jsont.string ~enc:(fun t -> t.prev_batch) 296 |> finish) 297 298let get_relations client ~room_id ~event_id ?rel_type ?event_type ?limit ?from () = 299 let room_id_str = Matrix_proto.Id.Room_id.to_string room_id in 300 let event_id_str = Matrix_proto.Id.Event_id.to_string event_id in 301 let path = match rel_type, event_type with 302 | Some rt, Some et -> 303 Printf.sprintf "/rooms/%s/relations/%s/%s/%s" 304 (Uri.pct_encode room_id_str) 305 (Uri.pct_encode event_id_str) 306 (Uri.pct_encode (relation_type_to_string rt)) 307 (Uri.pct_encode et) 308 | Some rt, None -> 309 Printf.sprintf "/rooms/%s/relations/%s/%s" 310 (Uri.pct_encode room_id_str) 311 (Uri.pct_encode event_id_str) 312 (Uri.pct_encode (relation_type_to_string rt)) 313 | None, _ -> 314 Printf.sprintf "/rooms/%s/relations/%s" 315 (Uri.pct_encode room_id_str) 316 (Uri.pct_encode event_id_str) 317 in 318 let query = 319 [] 320 |> (fun q -> match limit with Some l -> ("limit", string_of_int l) :: q | None -> q) 321 |> (fun q -> match from with Some f -> ("from", f) :: q | None -> q) 322 in 323 let query = if query = [] then None else Some query in 324 match Client.get client ~path ?query () with 325 | Error e -> Error e 326 | Ok body -> Client.decode_response relations_response_jsont body 327 328(* Get all reactions for an event *) 329let get_reactions client ~room_id ~event_id = 330 get_relations client ~room_id ~event_id ~rel_type:Annotation ~event_type:"m.reaction" ()