Matrix protocol in OCaml, Eio specialised
at main 246 lines 9.3 kB view raw
1(** Sync operations and long-polling loop. *) 2 3(* Sync parameters *) 4type params = { 5 filter : string option; 6 since : string option; 7 full_state : bool; 8 set_presence : [ `Online | `Offline | `Unavailable ] option; 9 timeout : int; 10} 11 12let default_params = { 13 filter = None; 14 since = None; 15 full_state = false; 16 set_presence = None; 17 timeout = 30000; 18} 19 20let presence_to_string = function 21 | `Online -> "online" 22 | `Offline -> "offline" 23 | `Unavailable -> "unavailable" 24 25let sync client ?(params = default_params) () = 26 let query = [] 27 |> (fun q -> match params.filter with Some f -> ("filter", f) :: q | None -> q) 28 |> (fun q -> match params.since with Some s -> ("since", s) :: q | None -> q) 29 |> (fun q -> if params.full_state then ("full_state", "true") :: q else q) 30 |> (fun q -> match params.set_presence with Some p -> ("set_presence", presence_to_string p) :: q | None -> q) 31 |> (fun q -> ("timeout", string_of_int params.timeout) :: q) 32 in 33 let query = if query = [] then None else Some query in 34 match Client.get client ~path:"/sync" ?query () with 35 | Error e -> Error e 36 | Ok body -> Client.decode_response Matrix_proto.Sync.Response.jsont body 37 38(* Sync loop *) 39type action = 40 | Continue 41 | Stop 42 | Retry_after of int 43 44type callbacks = { 45 on_sync : Matrix_proto.Sync.Response.t -> action; 46 on_error : Error.t -> action; 47} 48 49let rec sync_loop client clock params callbacks since = 50 let params = { params with since } in 51 match sync client ~params () with 52 | Error e -> 53 (match callbacks.on_error e with 54 | Continue -> sync_loop client clock params callbacks since 55 | Stop -> () 56 | Retry_after ms -> 57 Eio.Time.sleep clock (float_of_int ms /. 1000.0); 58 sync_loop client clock params callbacks since) 59 | Ok response -> 60 (match callbacks.on_sync response with 61 | Continue -> sync_loop client clock params callbacks (Some response.next_batch) 62 | Stop -> () 63 | Retry_after ms -> 64 Eio.Time.sleep clock (float_of_int ms /. 1000.0); 65 sync_loop client clock params callbacks (Some response.next_batch)) 66 67let sync_forever client ~clock ?initial_since ?(params = default_params) ~callbacks () = 68 sync_loop client clock params callbacks initial_since 69 70(* Filter types *) 71type event_filter = { 72 limit : int option; 73 not_senders : string list; 74 not_types : string list; 75 senders : string list; 76 types : string list; 77} 78 79type room_event_filter = { 80 limit : int option; 81 not_senders : string list; 82 not_types : string list; 83 senders : string list; 84 types : string list; 85 lazy_load_members : bool; 86 include_redundant_members : bool; 87 not_rooms : string list; 88 rooms : string list; 89 contains_url : bool option; 90} 91 92type room_filter = { 93 not_rooms : string list; 94 rooms : string list; 95 ephemeral : room_event_filter option; 96 include_leave : bool; 97 state : room_event_filter option; 98 timeline : room_event_filter option; 99 account_data : room_event_filter option; 100} 101 102type filter = { 103 event_fields : string list; 104 event_format : [ `Client | `Federation ]; 105 presence : event_filter option; 106 account_data : event_filter option; 107 room : room_filter option; 108} 109 110let default_event_filter = { 111 limit = None; 112 not_senders = []; 113 not_types = []; 114 senders = []; 115 types = []; 116} 117 118let default_room_event_filter = { 119 limit = None; 120 not_senders = []; 121 not_types = []; 122 senders = []; 123 types = []; 124 lazy_load_members = true; 125 include_redundant_members = false; 126 not_rooms = []; 127 rooms = []; 128 contains_url = None; 129} 130 131let default_room_filter = { 132 not_rooms = []; 133 rooms = []; 134 ephemeral = None; 135 include_leave = false; 136 state = None; 137 timeline = None; 138 account_data = None; 139} 140 141let default_filter = { 142 event_fields = []; 143 event_format = `Client; 144 presence = None; 145 account_data = None; 146 room = None; 147} 148 149(* Filter JSON codecs *) 150let event_filter_jsont : event_filter Jsont.t = 151 let open Jsont.Object in 152 map (fun limit not_senders not_types senders types -> 153 ({ limit; not_senders; not_types; senders; types } : event_filter)) 154 |> opt_mem "limit" Jsont.int ~enc:(fun (t : event_filter) -> t.limit) 155 |> mem "not_senders" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : event_filter) -> t.not_senders) 156 |> mem "not_types" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : event_filter) -> t.not_types) 157 |> mem "senders" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : event_filter) -> t.senders) 158 |> mem "types" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : event_filter) -> t.types) 159 |> finish 160 161let room_event_filter_jsont : room_event_filter Jsont.t = 162 let open Jsont.Object in 163 map (fun limit not_senders not_types senders types lazy_load_members 164 include_redundant_members not_rooms rooms contains_url -> 165 ({ limit; not_senders; not_types; senders; types; lazy_load_members; 166 include_redundant_members; not_rooms; rooms; contains_url } : room_event_filter)) 167 |> opt_mem "limit" Jsont.int ~enc:(fun (t : room_event_filter) -> t.limit) 168 |> mem "not_senders" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : room_event_filter) -> t.not_senders) 169 |> mem "not_types" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : room_event_filter) -> t.not_types) 170 |> mem "senders" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : room_event_filter) -> t.senders) 171 |> mem "types" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : room_event_filter) -> t.types) 172 |> mem "lazy_load_members" Jsont.bool ~dec_absent:false ~enc:(fun (t : room_event_filter) -> t.lazy_load_members) 173 |> mem "include_redundant_members" Jsont.bool ~dec_absent:false ~enc:(fun (t : room_event_filter) -> t.include_redundant_members) 174 |> mem "not_rooms" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : room_event_filter) -> t.not_rooms) 175 |> mem "rooms" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : room_event_filter) -> t.rooms) 176 |> opt_mem "contains_url" Jsont.bool ~enc:(fun (t : room_event_filter) -> t.contains_url) 177 |> finish 178 179let room_filter_jsont : room_filter Jsont.t = 180 let open Jsont.Object in 181 map (fun not_rooms rooms ephemeral include_leave state timeline account_data -> 182 ({ not_rooms; rooms; ephemeral; include_leave; state; timeline; account_data } : room_filter)) 183 |> mem "not_rooms" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : room_filter) -> t.not_rooms) 184 |> mem "rooms" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : room_filter) -> t.rooms) 185 |> opt_mem "ephemeral" room_event_filter_jsont ~enc:(fun (t : room_filter) -> t.ephemeral) 186 |> mem "include_leave" Jsont.bool ~dec_absent:false ~enc:(fun (t : room_filter) -> t.include_leave) 187 |> opt_mem "state" room_event_filter_jsont ~enc:(fun (t : room_filter) -> t.state) 188 |> opt_mem "timeline" room_event_filter_jsont ~enc:(fun (t : room_filter) -> t.timeline) 189 |> opt_mem "account_data" room_event_filter_jsont ~enc:(fun (t : room_filter) -> t.account_data) 190 |> finish 191 192let event_format_jsont : [ `Client | `Federation ] Jsont.t = 193 Jsont.of_of_string ~kind:"event_format" 194 ~enc:(function `Client -> "client" | `Federation -> "federation") 195 (function 196 | "client" -> Ok `Client 197 | "federation" -> Ok `Federation 198 | s -> Error ("Unknown event_format: " ^ s)) 199 200let filter_jsont : filter Jsont.t = 201 let open Jsont.Object in 202 map (fun event_fields event_format presence account_data room -> 203 ({ event_fields; event_format; presence; account_data; room } : filter)) 204 |> mem "event_fields" (Jsont.list Jsont.string) ~dec_absent:[] ~enc:(fun (t : filter) -> t.event_fields) 205 |> mem "event_format" event_format_jsont ~dec_absent:`Client ~enc:(fun (t : filter) -> t.event_format) 206 |> opt_mem "presence" event_filter_jsont ~enc:(fun (t : filter) -> t.presence) 207 |> opt_mem "account_data" event_filter_jsont ~enc:(fun (t : filter) -> t.account_data) 208 |> opt_mem "room" room_filter_jsont ~enc:(fun (t : filter) -> t.room) 209 |> finish 210 211(* Filter API *) 212type filter_response = { 213 filter_id : string; 214} [@@warning "-69"] 215 216let filter_response_jsont = 217 Jsont.Object.( 218 map (fun filter_id -> { filter_id }) 219 |> mem "filter_id" Jsont.string 220 |> finish) 221 222let create_filter client ~filter = 223 match Client.user_id client with 224 | None -> Error (Error.Network_error "Not logged in") 225 | Some user_id -> 226 let user_id_str = Matrix_proto.Id.User_id.to_string user_id in 227 let path = Printf.sprintf "/user/%s/filter" user_id_str in 228 match Client.encode_body filter_jsont filter with 229 | Error e -> Error e 230 | Ok body -> 231 match Client.post client ~path ~body () with 232 | Error e -> Error e 233 | Ok body -> 234 match Client.decode_response filter_response_jsont body with 235 | Error e -> Error e 236 | Ok resp -> Ok resp.filter_id 237 238let get_filter client ~filter_id = 239 match Client.user_id client with 240 | None -> Error (Error.Network_error "Not logged in") 241 | Some user_id -> 242 let user_id_str = Matrix_proto.Id.User_id.to_string user_id in 243 let path = Printf.sprintf "/user/%s/filter/%s" user_id_str filter_id in 244 match Client.get client ~path () with 245 | Error e -> Error e 246 | Ok body -> Client.decode_response filter_jsont body