···11type t = {
22 name : string;
33+ stream_id : int option;
34 description : string;
45 invite_only : bool;
66+ is_web_public : bool;
57 history_public_to_subscribers : bool;
88+ is_default : bool;
99+ message_retention_days : int option option;
1010+ first_message_id : int option;
1111+ date_created : float option;
1212+ stream_post_policy : int;
613}
71488-let create ~name ~description ?(invite_only = false)
99- ?(history_public_to_subscribers = true) () =
1010- { name; description; invite_only; history_public_to_subscribers }
1515+let create ~name ?stream_id ?(description = "") ?(invite_only = false)
1616+ ?(is_web_public = false) ?(history_public_to_subscribers = true)
1717+ ?(is_default = false) ?message_retention_days ?first_message_id
1818+ ?date_created ?(stream_post_policy = 1) () =
1919+ {
2020+ name;
2121+ stream_id;
2222+ description;
2323+ invite_only;
2424+ is_web_public;
2525+ history_public_to_subscribers;
2626+ is_default;
2727+ message_retention_days;
2828+ first_message_id;
2929+ date_created;
3030+ stream_post_policy;
3131+ }
11321233let name t = t.name
3434+let stream_id t = t.stream_id
1335let description t = t.description
1436let invite_only t = t.invite_only
3737+let is_web_public t = t.is_web_public
1538let history_public_to_subscribers t = t.history_public_to_subscribers
3939+let is_default t = t.is_default
4040+let message_retention_days t = t.message_retention_days
4141+let first_message_id t = t.first_message_id
4242+let date_created t = t.date_created
4343+let stream_post_policy t = t.stream_post_policy
16441745let pp fmt t =
1818- Format.fprintf fmt "Channel{name=%s, description=%s}" t.name t.description
4646+ Format.fprintf fmt "Channel{name=%s, stream_id=%s, description=%s}"
4747+ t.name
4848+ (match t.stream_id with Some id -> string_of_int id | None -> "none")
4949+ t.description
5050+5151+module Subscription = struct
5252+ type channel = t
5353+5454+ type t = {
5555+ channel : channel;
5656+ color : string option;
5757+ is_muted : bool;
5858+ pin_to_top : bool;
5959+ desktop_notifications : bool option;
6060+ audible_notifications : bool option;
6161+ push_notifications : bool option;
6262+ email_notifications : bool option;
6363+ wildcard_mentions_notify : bool option;
6464+ }
6565+6666+ let channel t = t.channel
6767+ let color t = t.color
6868+ let is_muted t = t.is_muted
6969+ let pin_to_top t = t.pin_to_top
7070+ let desktop_notifications t = t.desktop_notifications
7171+ let audible_notifications t = t.audible_notifications
7272+ let push_notifications t = t.push_notifications
7373+ let email_notifications t = t.email_notifications
7474+ let wildcard_mentions_notify t = t.wildcard_mentions_notify
7575+7676+ let jsont =
7777+ let kind = "Subscription" in
7878+ let doc = "A Zulip channel subscription" in
7979+ let make name stream_id description invite_only is_web_public
8080+ history_public_to_subscribers is_default message_retention_days
8181+ first_message_id date_created stream_post_policy color is_muted
8282+ pin_to_top desktop_notifications audible_notifications
8383+ push_notifications email_notifications wildcard_mentions_notify =
8484+ let channel =
8585+ {
8686+ name;
8787+ stream_id;
8888+ description;
8989+ invite_only;
9090+ is_web_public;
9191+ history_public_to_subscribers;
9292+ is_default;
9393+ message_retention_days;
9494+ first_message_id;
9595+ date_created;
9696+ stream_post_policy;
9797+ }
9898+ in
9999+ {
100100+ channel;
101101+ color;
102102+ is_muted;
103103+ pin_to_top;
104104+ desktop_notifications;
105105+ audible_notifications;
106106+ push_notifications;
107107+ email_notifications;
108108+ wildcard_mentions_notify;
109109+ }
110110+ in
111111+ Jsont.Object.map ~kind ~doc make
112112+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun t -> t.channel.name)
113113+ |> Jsont.Object.opt_mem "stream_id" Jsont.int ~enc:(fun t ->
114114+ t.channel.stream_id)
115115+ |> Jsont.Object.mem "description" Jsont.string
116116+ ~dec_absent:""
117117+ ~enc:(fun t -> t.channel.description)
118118+ |> Jsont.Object.mem "invite_only" Jsont.bool
119119+ ~dec_absent:false
120120+ ~enc:(fun t -> t.channel.invite_only)
121121+ |> Jsont.Object.mem "is_web_public" Jsont.bool
122122+ ~dec_absent:false
123123+ ~enc:(fun t -> t.channel.is_web_public)
124124+ |> Jsont.Object.mem "history_public_to_subscribers" Jsont.bool
125125+ ~dec_absent:true
126126+ ~enc:(fun t -> t.channel.history_public_to_subscribers)
127127+ |> Jsont.Object.mem "is_default" Jsont.bool
128128+ ~dec_absent:false
129129+ ~enc:(fun t -> t.channel.is_default)
130130+ |> Jsont.Object.opt_mem "message_retention_days" (Jsont.option Jsont.int)
131131+ ~enc:(fun t -> t.channel.message_retention_days)
132132+ |> Jsont.Object.opt_mem "first_message_id" Jsont.int ~enc:(fun t ->
133133+ t.channel.first_message_id)
134134+ |> Jsont.Object.opt_mem "date_created" Jsont.number ~enc:(fun t ->
135135+ t.channel.date_created)
136136+ |> Jsont.Object.mem "stream_post_policy" Jsont.int
137137+ ~dec_absent:1
138138+ ~enc:(fun t -> t.channel.stream_post_policy)
139139+ |> Jsont.Object.opt_mem "color" Jsont.string ~enc:color
140140+ |> Jsont.Object.mem "is_muted" Jsont.bool ~dec_absent:false ~enc:is_muted
141141+ |> Jsont.Object.mem "pin_to_top" Jsont.bool ~dec_absent:false ~enc:pin_to_top
142142+ |> Jsont.Object.opt_mem "desktop_notifications" Jsont.bool
143143+ ~enc:desktop_notifications
144144+ |> Jsont.Object.opt_mem "audible_notifications" Jsont.bool
145145+ ~enc:audible_notifications
146146+ |> Jsont.Object.opt_mem "push_notifications" Jsont.bool
147147+ ~enc:push_notifications
148148+ |> Jsont.Object.opt_mem "email_notifications" Jsont.bool
149149+ ~enc:email_notifications
150150+ |> Jsont.Object.opt_mem "wildcard_mentions_notify" Jsont.bool
151151+ ~enc:wildcard_mentions_notify
152152+ |> Jsont.Object.finish
153153+end
1915420155(* Jsont codec for channel *)
21156let jsont =
22157 let kind = "Channel" in
23158 let doc = "A Zulip channel (stream)" in
2424- let make name description invite_only history_public_to_subscribers =
2525- { name; description; invite_only; history_public_to_subscribers }
159159+ let make name stream_id description invite_only is_web_public
160160+ history_public_to_subscribers is_default message_retention_days
161161+ first_message_id date_created stream_post_policy =
162162+ {
163163+ name;
164164+ stream_id;
165165+ description;
166166+ invite_only;
167167+ is_web_public;
168168+ history_public_to_subscribers;
169169+ is_default;
170170+ message_retention_days;
171171+ first_message_id;
172172+ date_created;
173173+ stream_post_policy;
174174+ }
26175 in
27176 Jsont.Object.map ~kind ~doc make
28177 |> Jsont.Object.mem "name" Jsont.string ~enc:name
2929- |> Jsont.Object.mem "description" Jsont.string ~enc:description
3030- |> Jsont.Object.mem "invite_only" Jsont.bool ~enc:invite_only
178178+ |> Jsont.Object.opt_mem "stream_id" Jsont.int ~enc:stream_id
179179+ |> Jsont.Object.mem "description" Jsont.string ~dec_absent:"" ~enc:description
180180+ |> Jsont.Object.mem "invite_only" Jsont.bool ~dec_absent:false ~enc:invite_only
181181+ |> Jsont.Object.mem "is_web_public" Jsont.bool
182182+ ~dec_absent:false
183183+ ~enc:is_web_public
31184 |> Jsont.Object.mem "history_public_to_subscribers" Jsont.bool
185185+ ~dec_absent:true
32186 ~enc:history_public_to_subscribers
187187+ |> Jsont.Object.mem "is_default" Jsont.bool ~dec_absent:false ~enc:is_default
188188+ |> Jsont.Object.opt_mem "message_retention_days" (Jsont.option Jsont.int)
189189+ ~enc:message_retention_days
190190+ |> Jsont.Object.opt_mem "first_message_id" Jsont.int ~enc:first_message_id
191191+ |> Jsont.Object.opt_mem "date_created" Jsont.number ~enc:date_created
192192+ |> Jsont.Object.mem "stream_post_policy" Jsont.int
193193+ ~dec_absent:1
194194+ ~enc:stream_post_policy
33195 |> Jsont.Object.finish
+109-3
lib/zulip/channel.mli
···11(** Zulip channels (streams).
2233 This module represents channel/stream information from the Zulip API.
44- Use {!jsont} with Bytesrw-eio for wire serialization. *)
44+ Use {!jsont} with Bytesrw-eio for wire serialization.
55+66+ Note: Zulip uses "stream" in the API but "channel" in the UI.
77+ This library uses "channel" to match the current Zulip terminology. *)
88+99+(** {1 Channel Type} *)
510611type t
1212+(** A Zulip channel/stream. *)
1313+1414+(** {1 Construction} *)
715816val create :
917 name:string ->
1010- description:string ->
1818+ ?stream_id:int ->
1919+ ?description:string ->
1120 ?invite_only:bool ->
2121+ ?is_web_public:bool ->
1222 ?history_public_to_subscribers:bool ->
2323+ ?is_default:bool ->
2424+ ?message_retention_days:int option ->
2525+ ?first_message_id:int ->
2626+ ?date_created:float ->
2727+ ?stream_post_policy:int ->
1328 unit ->
1429 t
3030+(** Create a channel record.
3131+3232+ @param name Channel name (required)
3333+ @param stream_id Server-assigned channel ID
3434+ @param description Channel description
3535+ @param invite_only Whether the channel is private
3636+ @param is_web_public Whether the channel is web-public
3737+ @param history_public_to_subscribers Whether history is visible to new subscribers
3838+ @param is_default Whether this is a default channel for new users
3939+ @param message_retention_days Message retention policy (None = forever)
4040+ @param first_message_id ID of the first message in the channel
4141+ @param date_created Unix timestamp of creation
4242+ @param stream_post_policy Who can post (1=any, 2=admins, 3=full members, 4=moderators) *)
4343+4444+(** {1 Accessors} *)
15451646val name : t -> string
4747+(** Channel name. *)
4848+4949+val stream_id : t -> int option
5050+(** Server-assigned channel ID. *)
5151+1752val description : t -> string
5353+(** Channel description. *)
5454+1855val invite_only : t -> bool
5656+(** Whether the channel is private (invite-only). *)
5757+5858+val is_web_public : t -> bool
5959+(** Whether the channel is web-public (visible without authentication). *)
6060+1961val history_public_to_subscribers : t -> bool
6262+(** Whether new subscribers can see message history. *)
20632121-(** Jsont codec for the channel type *)
6464+val is_default : t -> bool
6565+(** Whether new users are automatically subscribed. *)
6666+6767+val message_retention_days : t -> int option option
6868+(** Message retention policy. [None] if not set (use organization default),
6969+ [Some None] for unlimited retention, [Some (Some n)] for n days. *)
7070+7171+val first_message_id : t -> int option
7272+(** ID of the first message in the channel. *)
7373+7474+val date_created : t -> float option
7575+(** Unix timestamp when the channel was created. *)
7676+7777+val stream_post_policy : t -> int
7878+(** Who can post to the channel.
7979+ 1 = any member, 2 = admins only, 3 = full members, 4 = moderators only. *)
8080+8181+(** {1 Subscription Info}
8282+8383+ When retrieved via subscriptions API, channels include additional
8484+ subscription-specific fields. *)
8585+8686+module Subscription : sig
8787+ type channel := t
8888+8989+ type t
9090+ (** A channel subscription with user-specific settings. *)
9191+9292+ val channel : t -> channel
9393+ (** The underlying channel. *)
9494+9595+ val color : t -> string option
9696+ (** User's color preference for the channel (hex string). *)
9797+9898+ val is_muted : t -> bool
9999+ (** Whether the user has muted this channel. *)
100100+101101+ val pin_to_top : t -> bool
102102+ (** Whether the channel is pinned. *)
103103+104104+ val desktop_notifications : t -> bool option
105105+ (** Desktop notification setting (None = use global default). *)
106106+107107+ val audible_notifications : t -> bool option
108108+ (** Sound notification setting. *)
109109+110110+ val push_notifications : t -> bool option
111111+ (** Push notification setting. *)
112112+113113+ val email_notifications : t -> bool option
114114+ (** Email notification setting. *)
115115+116116+ val wildcard_mentions_notify : t -> bool option
117117+ (** Whether to notify on @all/@everyone mentions. *)
118118+119119+ val jsont : t Jsont.t
120120+ (** Jsont codec for subscription. *)
121121+end
122122+123123+(** {1 JSON Codec} *)
124124+22125val jsont : t Jsont.t
126126+(** Jsont codec for the channel type. *)
127127+128128+(** {1 Pretty Printing} *)
2312924130val pp : Format.formatter -> t -> unit
+468-41
lib/zulip/channels.ml
···11-let create_channel client channel =
22- let body = Encode.to_form_urlencoded Channel.jsont channel in
33- let content_type = "application/x-www-form-urlencoded" in
11+let list client =
22+ let response_codec =
33+ Jsont.Object.(
44+ map ~kind:"StreamsResponse" (fun streams -> streams)
55+ |> mem "streams" (Jsont.list Channel.jsont) ~enc:(fun x -> x)
66+ |> finish)
77+ in
88+ let json = Client.request client ~method_:`GET ~path:"/api/v1/streams" () in
99+ match Encode.from_json response_codec json with
1010+ | Ok channels -> channels
1111+ | Error msg ->
1212+ Error.raise_with_context
1313+ (Error.make ~code:(Other "json_parse") ~message:msg ())
1414+ "parsing channels list"
1515+1616+let list_all client ?include_public ?include_web_public ?include_subscribed
1717+ ?include_all_active ?include_default ?include_owner_subscribed () =
1818+ let params =
1919+ List.filter_map Fun.id
2020+ [
2121+ Option.map (fun v -> ("include_public", string_of_bool v)) include_public;
2222+ Option.map
2323+ (fun v -> ("include_web_public", string_of_bool v))
2424+ include_web_public;
2525+ Option.map
2626+ (fun v -> ("include_subscribed", string_of_bool v))
2727+ include_subscribed;
2828+ Option.map
2929+ (fun v -> ("include_all_active", string_of_bool v))
3030+ include_all_active;
3131+ Option.map
3232+ (fun v -> ("include_default", string_of_bool v))
3333+ include_default;
3434+ Option.map
3535+ (fun v -> ("include_owner_subscribed", string_of_bool v))
3636+ include_owner_subscribed;
3737+ ]
3838+ in
3939+ let response_codec =
4040+ Jsont.Object.(
4141+ map ~kind:"StreamsResponse" (fun streams -> streams)
4242+ |> mem "streams" (Jsont.list Channel.jsont) ~enc:(fun x -> x)
4343+ |> finish)
4444+ in
4545+ let json =
4646+ Client.request client ~method_:`GET ~path:"/api/v1/streams" ~params ()
4747+ in
4848+ match Encode.from_json response_codec json with
4949+ | Ok channels -> channels
5050+ | Error msg ->
5151+ Error.raise_with_context
5252+ (Error.make ~code:(Other "json_parse") ~message:msg ())
5353+ "parsing channels list"
5454+5555+let get_id client ~name =
5656+ let encoded_name = Uri.pct_encode name in
5757+ let response_codec =
5858+ Jsont.Object.(
5959+ map ~kind:"StreamIdResponse" (fun id -> id)
6060+ |> mem "stream_id" Jsont.int ~enc:(fun x -> x)
6161+ |> finish)
6262+ in
6363+ let json =
6464+ Client.request client ~method_:`GET
6565+ ~path:("/api/v1/get_stream_id?stream=" ^ encoded_name)
6666+ ()
6767+ in
6868+ match Encode.from_json response_codec json with
6969+ | Ok id -> id
7070+ | Error msg ->
7171+ Error.raise_with_context
7272+ (Error.make ~code:(Other "json_parse") ~message:msg ())
7373+ "getting stream id for %s" name
7474+7575+let get_by_id client ~stream_id =
7676+ let response_codec =
7777+ Jsont.Object.(
7878+ map ~kind:"StreamResponse" (fun stream -> stream)
7979+ |> mem "stream" Channel.jsont ~enc:(fun x -> x)
8080+ |> finish)
8181+ in
8282+ let json =
8383+ Client.request client ~method_:`GET
8484+ ~path:("/api/v1/streams/" ^ string_of_int stream_id)
8585+ ()
8686+ in
8787+ match Encode.from_json response_codec json with
8888+ | Ok channel -> channel
8989+ | Error msg ->
9090+ Error.raise_with_context
9191+ (Error.make ~code:(Other "json_parse") ~message:msg ())
9292+ "getting stream %d" stream_id
9393+9494+type create_options = {
9595+ name : string;
9696+ description : string option;
9797+ invite_only : bool option;
9898+ is_web_public : bool option;
9999+ history_public_to_subscribers : bool option;
100100+ message_retention_days : int option option;
101101+ can_remove_subscribers_group : int option;
102102+}
103103+104104+let create client opts =
105105+ let make_string s = Jsont.String (s, Jsont.Meta.none) in
106106+ let subs =
107107+ Jsont.Array
108108+ ([
109109+ Jsont.Object
110110+ (List.filter_map Fun.id
111111+ [
112112+ Some (("name", Jsont.Meta.none), make_string opts.name);
113113+ Option.map (fun d -> (("description", Jsont.Meta.none), make_string d)) opts.description;
114114+ ], Jsont.Meta.none);
115115+ ], Jsont.Meta.none)
116116+ in
117117+ let params =
118118+ [ ("subscriptions", Encode.to_json_string Jsont.json subs) ]
119119+ @ List.filter_map Fun.id
120120+ [
121121+ Option.map
122122+ (fun v -> ("invite_only", string_of_bool v))
123123+ opts.invite_only;
124124+ Option.map
125125+ (fun v -> ("is_web_public", string_of_bool v))
126126+ opts.is_web_public;
127127+ Option.map
128128+ (fun v -> ("history_public_to_subscribers", string_of_bool v))
129129+ opts.history_public_to_subscribers;
130130+ ]
131131+ in
132132+ let response_codec =
133133+ Jsont.Object.(
134134+ map ~kind:"CreateResponse" (fun created -> created)
135135+ |> mem "subscribed" Jsont.json ~enc:(fun _ -> Jsont.Object ([], Jsont.Meta.none))
136136+ |> finish)
137137+ in
138138+ let json =
139139+ Client.request client ~method_:`POST ~path:"/api/v1/users/me/subscriptions"
140140+ ~params ()
141141+ in
142142+ ignore (Encode.from_json response_codec json);
143143+ (* Return the stream_id - we need to look it up *)
144144+ get_id client ~name:opts.name
145145+146146+let create_simple client ~name ?description ?invite_only () =
147147+ create client
148148+ {
149149+ name;
150150+ description;
151151+ invite_only;
152152+ is_web_public = None;
153153+ history_public_to_subscribers = None;
154154+ message_retention_days = None;
155155+ can_remove_subscribers_group = None;
156156+ }
157157+158158+let update client ~stream_id ?description ?new_name ?is_private ?is_web_public
159159+ ?history_public_to_subscribers ?message_retention_days ?stream_post_policy
160160+ () =
161161+ let params =
162162+ List.filter_map Fun.id
163163+ [
164164+ Option.map (fun v -> ("description", v)) description;
165165+ Option.map (fun v -> ("new_name", v)) new_name;
166166+ Option.map (fun v -> ("is_private", string_of_bool v)) is_private;
167167+ Option.map (fun v -> ("is_web_public", string_of_bool v)) is_web_public;
168168+ Option.map
169169+ (fun v -> ("history_public_to_subscribers", string_of_bool v))
170170+ history_public_to_subscribers;
171171+ Option.map
172172+ (fun v ->
173173+ ( "message_retention_days",
174174+ match v with None -> "unlimited" | Some d -> string_of_int d ))
175175+ message_retention_days;
176176+ Option.map
177177+ (fun v -> ("stream_post_policy", string_of_int v))
178178+ stream_post_policy;
179179+ ]
180180+ in
4181 let _response =
55- Client.request client ~method_:`POST ~path:"/api/v1/streams" ~body
66- ~content_type ()
182182+ Client.request client ~method_:`PATCH
183183+ ~path:("/api/v1/streams/" ^ string_of_int stream_id)
184184+ ~params ()
7185 in
8186 ()
91871010-let delete client ~name =
1111- let encoded_name = Uri.pct_encode name in
188188+let delete client ~stream_id =
12189 let _response =
13190 Client.request client ~method_:`DELETE
1414- ~path:("/api/v1/streams/" ^ encoded_name)
191191+ ~path:("/api/v1/streams/" ^ string_of_int stream_id)
15192 ()
16193 in
17194 ()
181951919-let list client =
2020- (* Define response codec *)
196196+let archive = delete
197197+198198+let add_default client ~stream_id =
199199+ let params = [ ("stream_id", string_of_int stream_id) ] in
200200+ let _response =
201201+ Client.request client ~method_:`POST ~path:"/api/v1/default_streams" ~params
202202+ ()
203203+ in
204204+ ()
205205+206206+let remove_default client ~stream_id =
207207+ let params = [ ("stream_id", string_of_int stream_id) ] in
208208+ let _response =
209209+ Client.request client ~method_:`DELETE ~path:"/api/v1/default_streams"
210210+ ~params ()
211211+ in
212212+ ()
213213+214214+type subscription_request = {
215215+ name : string;
216216+ color : string option;
217217+ description : string option;
218218+}
219219+220220+let subscribe client ~subscriptions ?principals ?authorization_errors_fatal
221221+ ?announce ?invite_only ?history_public_to_subscribers () =
222222+ let make_string s = Jsont.String (s, Jsont.Meta.none) in
223223+ let subs_json =
224224+ Jsont.Array
225225+ (List.map
226226+ (fun s ->
227227+ Jsont.Object
228228+ (List.filter_map Fun.id
229229+ [
230230+ Some (("name", Jsont.Meta.none), make_string s.name);
231231+ Option.map (fun c -> (("color", Jsont.Meta.none), make_string c)) s.color;
232232+ Option.map (fun d -> (("description", Jsont.Meta.none), make_string d)) s.description;
233233+ ], Jsont.Meta.none))
234234+ subscriptions, Jsont.Meta.none)
235235+ in
236236+ let params =
237237+ [ ("subscriptions", Encode.to_json_string Jsont.json subs_json) ]
238238+ @ List.filter_map Fun.id
239239+ [
240240+ Option.map
241241+ (fun p ->
242242+ ( "principals",
243243+ match p with
244244+ | `Emails emails ->
245245+ Encode.to_json_string (Jsont.list Jsont.string) emails
246246+ | `User_ids ids ->
247247+ Encode.to_json_string (Jsont.list Jsont.int) ids ))
248248+ principals;
249249+ Option.map
250250+ (fun v -> ("authorization_errors_fatal", string_of_bool v))
251251+ authorization_errors_fatal;
252252+ Option.map (fun v -> ("announce", string_of_bool v)) announce;
253253+ Option.map (fun v -> ("invite_only", string_of_bool v)) invite_only;
254254+ Option.map
255255+ (fun v -> ("history_public_to_subscribers", string_of_bool v))
256256+ history_public_to_subscribers;
257257+ ]
258258+ in
259259+ Client.request client ~method_:`POST ~path:"/api/v1/users/me/subscriptions"
260260+ ~params ()
261261+262262+let subscribe_simple client ~channels =
263263+ let subscriptions =
264264+ List.map (fun name -> { name; color = None; description = None }) channels
265265+ in
266266+ let _ = subscribe client ~subscriptions () in
267267+ ()
268268+269269+let unsubscribe client ~subscriptions ?principals () =
270270+ let params =
271271+ [ ("subscriptions", Encode.to_json_string (Jsont.list Jsont.string) subscriptions) ]
272272+ @ List.filter_map Fun.id
273273+ [
274274+ Option.map
275275+ (fun p ->
276276+ ( "principals",
277277+ match p with
278278+ | `Emails emails ->
279279+ Encode.to_json_string (Jsont.list Jsont.string) emails
280280+ | `User_ids ids ->
281281+ Encode.to_json_string (Jsont.list Jsont.int) ids ))
282282+ principals;
283283+ ]
284284+ in
285285+ Client.request client ~method_:`DELETE ~path:"/api/v1/users/me/subscriptions"
286286+ ~params ()
287287+288288+let unsubscribe_simple client ~channels =
289289+ let _ = unsubscribe client ~subscriptions:channels () in
290290+ ()
291291+292292+let get_subscriptions client =
21293 let response_codec =
22294 Jsont.Object.(
2323- map ~kind:"StreamsResponse" (fun streams -> streams)
2424- |> mem "streams" (Jsont.list Channel.jsont) ~enc:(fun x -> x)
295295+ map ~kind:"SubscriptionsResponse" (fun subs -> subs)
296296+ |> mem "subscriptions" (Jsont.list Channel.Subscription.jsont)
297297+ ~enc:(fun x -> x)
25298 |> finish)
26299 in
2727- let json = Client.request client ~method_:`GET ~path:"/api/v1/streams" () in
300300+ let json =
301301+ Client.request client ~method_:`GET ~path:"/api/v1/users/me/subscriptions"
302302+ ()
303303+ in
28304 match Encode.from_json response_codec json with
2929- | Ok channels -> channels
305305+ | Ok subs -> subs
30306 | Error msg ->
31307 Error.raise_with_context
32308 (Error.make ~code:(Other "json_parse") ~message:msg ())
3333- "parsing channels list"
3434-3535-(* Request types with jsont codecs *)
3636-module Subscribe_request = struct
3737- type t = { subscriptions : string list }
309309+ "parsing subscriptions"
383103939- let codec =
311311+let get_subscription_status client ~user_id ~stream_id =
312312+ let response_codec =
40313 Jsont.Object.(
4141- map ~kind:"SubscribeRequest" (fun subscriptions -> { subscriptions })
4242- |> mem "subscriptions" (Jsont.list Jsont.string)
4343- ~enc:(fun r -> r.subscriptions)
314314+ map ~kind:"SubscriptionStatusResponse" (fun status -> status)
315315+ |> mem "is_subscribed" Jsont.bool ~enc:(fun x -> x)
44316 |> finish)
4545-end
317317+ in
318318+ let json =
319319+ Client.request client ~method_:`GET
320320+ ~path:
321321+ ("/api/v1/users/" ^ string_of_int user_id ^ "/subscriptions/"
322322+ ^ string_of_int stream_id)
323323+ ()
324324+ in
325325+ match Encode.from_json response_codec json with
326326+ | Ok status -> status
327327+ | Error msg ->
328328+ Error.raise_with_context
329329+ (Error.make ~code:(Other "json_parse") ~message:msg ())
330330+ "checking subscription status"
463314747-module Unsubscribe_request = struct
4848- type t = { delete : string list }
332332+let update_subscription_settings client ~stream_id ?color ?is_muted ?pin_to_top
333333+ ?desktop_notifications ?audible_notifications ?push_notifications
334334+ ?email_notifications ?wildcard_mentions_notify () =
335335+ let data =
336336+ [
337337+ {|[{"stream_id":|}
338338+ ^ string_of_int stream_id
339339+ ^ (List.filter_map Fun.id
340340+ [
341341+ Option.map (fun v -> Printf.sprintf {|,"property":"color","value":"%s"|} v) color;
342342+ Option.map
343343+ (fun v -> Printf.sprintf {|,"property":"is_muted","value":%b|} v)
344344+ is_muted;
345345+ Option.map
346346+ (fun v -> Printf.sprintf {|,"property":"pin_to_top","value":%b|} v)
347347+ pin_to_top;
348348+ Option.map
349349+ (fun v ->
350350+ Printf.sprintf {|,"property":"desktop_notifications","value":%b|} v)
351351+ desktop_notifications;
352352+ Option.map
353353+ (fun v ->
354354+ Printf.sprintf {|,"property":"audible_notifications","value":%b|} v)
355355+ audible_notifications;
356356+ Option.map
357357+ (fun v ->
358358+ Printf.sprintf {|,"property":"push_notifications","value":%b|} v)
359359+ push_notifications;
360360+ Option.map
361361+ (fun v ->
362362+ Printf.sprintf {|,"property":"email_notifications","value":%b|} v)
363363+ email_notifications;
364364+ Option.map
365365+ (fun v ->
366366+ Printf.sprintf
367367+ {|,"property":"wildcard_mentions_notify","value":%b|} v)
368368+ wildcard_mentions_notify;
369369+ ]
370370+ |> String.concat "")
371371+ ^ "}]";
372372+ ]
373373+ in
374374+ let params = [ ("subscription_data", String.concat "" data) ] in
375375+ let _response =
376376+ Client.request client ~method_:`POST
377377+ ~path:"/api/v1/users/me/subscriptions/properties" ~params ()
378378+ in
379379+ ()
493805050- let codec =
381381+module Topic = struct
382382+ type t = { name : string; max_id : int }
383383+384384+ let name t = t.name
385385+ let max_id t = t.max_id
386386+387387+ let jsont =
51388 Jsont.Object.(
5252- map ~kind:"UnsubscribeRequest" (fun delete -> { delete })
5353- |> mem "delete" (Jsont.list Jsont.string) ~enc:(fun r -> r.delete)
389389+ map ~kind:"Topic" (fun name max_id -> { name; max_id })
390390+ |> mem "name" Jsont.string ~enc:name
391391+ |> mem "max_id" Jsont.int ~enc:max_id
54392 |> finish)
55393end
563945757-let subscribe client ~channels =
5858- let req = Subscribe_request.{ subscriptions = channels } in
5959- let body = Encode.to_form_urlencoded Subscribe_request.codec req in
6060- let content_type = "application/x-www-form-urlencoded" in
395395+let get_topics client ~stream_id =
396396+ let response_codec =
397397+ Jsont.Object.(
398398+ map ~kind:"TopicsResponse" (fun topics -> topics)
399399+ |> mem "topics" (Jsont.list Topic.jsont) ~enc:(fun x -> x)
400400+ |> finish)
401401+ in
402402+ let json =
403403+ Client.request client ~method_:`GET
404404+ ~path:("/api/v1/users/me/" ^ string_of_int stream_id ^ "/topics")
405405+ ()
406406+ in
407407+ match Encode.from_json response_codec json with
408408+ | Ok topics -> topics
409409+ | Error msg ->
410410+ Error.raise_with_context
411411+ (Error.make ~code:(Other "json_parse") ~message:msg ())
412412+ "getting topics for stream %d" stream_id
413413+414414+let delete_topic client ~stream_id ~topic =
415415+ let params = [ ("topic_name", topic) ] in
61416 let _response =
6262- Client.request client ~method_:`POST ~path:"/api/v1/users/me/subscriptions"
6363- ~body ~content_type ()
417417+ Client.request client ~method_:`POST
418418+ ~path:("/api/v1/streams/" ^ string_of_int stream_id ^ "/delete_topic")
419419+ ~params ()
64420 in
65421 ()
664226767-let unsubscribe client ~channels =
6868- let req = Unsubscribe_request.{ delete = channels } in
6969- let body = Encode.to_form_urlencoded Unsubscribe_request.codec req in
7070- let content_type = "application/x-www-form-urlencoded" in
423423+type mute_op = Mute | Unmute
424424+425425+let set_topic_mute client ~stream_id ~topic ~op =
426426+ let params =
427427+ [
428428+ ("stream_id", string_of_int stream_id);
429429+ ("topic", topic);
430430+ ("op", match op with Mute -> "add" | Unmute -> "remove");
431431+ ]
432432+ in
71433 let _response =
7272- Client.request client ~method_:`DELETE
7373- ~path:"/api/v1/users/me/subscriptions" ~body ~content_type ()
434434+ Client.request client ~method_:`PATCH
435435+ ~path:"/api/v1/users/me/subscriptions/muted_topics" ~params ()
74436 in
75437 ()
438438+439439+let get_muted_topics client =
440440+ let response_codec =
441441+ Jsont.Object.(
442442+ map ~kind:"MutedTopicsResponse" (fun topics -> topics)
443443+ |> mem "muted_topics"
444444+ (Jsont.list
445445+ (Jsont.Object.(
446446+ map ~kind:"MutedTopic" (fun stream_id topic _ts ->
447447+ (stream_id, topic))
448448+ |> mem "stream_id" Jsont.int ~enc:fst
449449+ |> mem "topic_name" Jsont.string ~enc:snd
450450+ |> mem "date_muted" Jsont.int ~dec_absent:0 ~enc:(fun _ -> 0)
451451+ |> finish)))
452452+ ~enc:(fun x -> x)
453453+ |> finish)
454454+ in
455455+ let json =
456456+ Client.request client ~method_:`GET
457457+ ~path:"/api/v1/users/me/subscriptions/muted_topics" ()
458458+ in
459459+ match Encode.from_json response_codec json with
460460+ | Ok topics -> topics
461461+ | Error msg ->
462462+ Error.raise_with_context
463463+ (Error.make ~code:(Other "json_parse") ~message:msg ())
464464+ "getting muted topics"
465465+466466+let get_subscribers client ~stream_id =
467467+ let response_codec =
468468+ Jsont.Object.(
469469+ map ~kind:"SubscribersResponse" (fun subs -> subs)
470470+ |> mem "subscribers" (Jsont.list Jsont.int) ~enc:(fun x -> x)
471471+ |> finish)
472472+ in
473473+ let json =
474474+ Client.request client ~method_:`GET
475475+ ~path:("/api/v1/streams/" ^ string_of_int stream_id ^ "/members")
476476+ ()
477477+ in
478478+ match Encode.from_json response_codec json with
479479+ | Ok subs -> subs
480480+ | Error msg ->
481481+ Error.raise_with_context
482482+ (Error.make ~code:(Other "json_parse") ~message:msg ())
483483+ "getting subscribers for stream %d" stream_id
484484+485485+let get_email_address client ~stream_id =
486486+ let response_codec =
487487+ Jsont.Object.(
488488+ map ~kind:"EmailAddressResponse" (fun email -> email)
489489+ |> mem "email" Jsont.string ~enc:(fun x -> x)
490490+ |> finish)
491491+ in
492492+ let json =
493493+ Client.request client ~method_:`GET
494494+ ~path:("/api/v1/streams/" ^ string_of_int stream_id ^ "/email_address")
495495+ ()
496496+ in
497497+ match Encode.from_json response_codec json with
498498+ | Ok email -> email
499499+ | Error msg ->
500500+ Error.raise_with_context
501501+ (Error.make ~code:(Other "json_parse") ~message:msg ())
502502+ "getting email address for stream %d" stream_id
+216-10
lib/zulip/channels.mli
···33 All functions raise [Eio.Io] with [Error.E error] on failure.
44 Context is automatically added indicating the operation being performed. *)
5566-val create_channel : Client.t -> Channel.t -> unit
77-(** Create a new channel (stream).
66+(** {1 Listing Channels} *)
77+88+val list : Client.t -> Channel.t list
99+(** List all channels in the organization.
1010+ @raise Eio.Io on failure *)
1111+1212+val list_all :
1313+ Client.t ->
1414+ ?include_public:bool ->
1515+ ?include_web_public:bool ->
1616+ ?include_subscribed:bool ->
1717+ ?include_all_active:bool ->
1818+ ?include_default:bool ->
1919+ ?include_owner_subscribed:bool ->
2020+ unit ->
2121+ Channel.t list
2222+(** List channels with filtering options.
2323+2424+ @param include_public Include public channels (default: true)
2525+ @param include_web_public Include web-public channels
2626+ @param include_subscribed Include subscribed channels
2727+ @param include_all_active Include all active channels (admin only)
2828+ @param include_default Include default channels
2929+ @param include_owner_subscribed Include channels the owner is subscribed to
3030+ @raise Eio.Io on failure *)
3131+3232+(** {1 Channel Lookup} *)
3333+3434+val get_id : Client.t -> name:string -> int
3535+(** Get the stream ID for a channel by name.
3636+ @raise Eio.Io if the channel doesn't exist or on failure *)
3737+3838+val get_by_id : Client.t -> stream_id:int -> Channel.t
3939+(** Get channel information by ID.
4040+ @raise Eio.Io on failure *)
4141+4242+(** {1 Creating Channels} *)
4343+4444+(** Options for creating a channel. *)
4545+type create_options = {
4646+ name : string; (** Channel name (required) *)
4747+ description : string option; (** Channel description *)
4848+ invite_only : bool option; (** Whether the channel is private *)
4949+ is_web_public : bool option; (** Whether the channel is web-public *)
5050+ history_public_to_subscribers : bool option;
5151+ (** Whether history is visible to new subscribers *)
5252+ message_retention_days : int option option;
5353+ (** Message retention (None = default, Some None = forever) *)
5454+ can_remove_subscribers_group : int option;
5555+ (** User group that can remove subscribers *)
5656+}
5757+5858+val create : Client.t -> create_options -> int
5959+(** Create a new channel.
6060+ @return The stream_id of the created channel
6161+ @raise Eio.Io on failure *)
6262+6363+val create_simple :
6464+ Client.t -> name:string -> ?description:string -> ?invite_only:bool -> unit -> int
6565+(** Create a new channel with common options.
6666+ @return The stream_id of the created channel
867 @raise Eio.Io on failure *)
9681010-val delete : Client.t -> name:string -> unit
1111-(** Delete a channel by name.
6969+(** {1 Updating Channels} *)
7070+7171+val update :
7272+ Client.t ->
7373+ stream_id:int ->
7474+ ?description:string ->
7575+ ?new_name:string ->
7676+ ?is_private:bool ->
7777+ ?is_web_public:bool ->
7878+ ?history_public_to_subscribers:bool ->
7979+ ?message_retention_days:int option ->
8080+ ?stream_post_policy:int ->
8181+ unit ->
8282+ unit
8383+(** Update channel properties.
1284 @raise Eio.Io on failure *)
13851414-val list : Client.t -> Channel.t list
1515-(** List all channels in the organization.
8686+(** {1 Deleting Channels} *)
8787+8888+val delete : Client.t -> stream_id:int -> unit
8989+(** Delete a channel by ID.
1690 @raise Eio.Io on failure *)
17911818-val subscribe : Client.t -> channels:string list -> unit
1919-(** Subscribe to one or more channels.
9292+val archive : Client.t -> stream_id:int -> unit
9393+(** Archive a channel (soft delete).
2094 @raise Eio.Io on failure *)
21952222-val unsubscribe : Client.t -> channels:string list -> unit
2323-(** Unsubscribe from one or more channels.
9696+(** {1 Default Channels} *)
9797+9898+val add_default : Client.t -> stream_id:int -> unit
9999+(** Add a channel to the list of default channels for new users.
100100+ @raise Eio.Io on failure *)
101101+102102+val remove_default : Client.t -> stream_id:int -> unit
103103+(** Remove a channel from the list of default channels.
104104+ @raise Eio.Io on failure *)
105105+106106+(** {1 Subscriptions} *)
107107+108108+(** Subscription request for a single channel. *)
109109+type subscription_request = {
110110+ name : string; (** Channel name *)
111111+ color : string option; (** Color preference (hex string) *)
112112+ description : string option; (** Description (for new channels) *)
113113+}
114114+115115+val subscribe :
116116+ Client.t ->
117117+ subscriptions:subscription_request list ->
118118+ ?principals:[ `Emails of string list | `User_ids of int list ] ->
119119+ ?authorization_errors_fatal:bool ->
120120+ ?announce:bool ->
121121+ ?invite_only:bool ->
122122+ ?history_public_to_subscribers:bool ->
123123+ unit ->
124124+ Jsont.json
125125+(** Subscribe users to channels.
126126+127127+ @param subscriptions List of channels to subscribe to
128128+ @param principals Users to subscribe (default: current user)
129129+ @param authorization_errors_fatal Whether to abort on permission errors
130130+ @param announce Whether to announce new subscriptions
131131+ @param invite_only For new channels: whether they should be private
132132+ @param history_public_to_subscribers For new channels: history visibility
133133+ @return JSON with "subscribed", "already_subscribed", and "unauthorized" fields
134134+ @raise Eio.Io on failure *)
135135+136136+val subscribe_simple : Client.t -> channels:string list -> unit
137137+(** Subscribe the current user to channels by name.
138138+ @raise Eio.Io on failure *)
139139+140140+val unsubscribe :
141141+ Client.t ->
142142+ subscriptions:string list ->
143143+ ?principals:[ `Emails of string list | `User_ids of int list ] ->
144144+ unit ->
145145+ Jsont.json
146146+(** Unsubscribe users from channels.
147147+148148+ @param subscriptions List of channel names to unsubscribe from
149149+ @param principals Users to unsubscribe (default: current user)
150150+ @return JSON with "removed" and "not_removed" fields
151151+ @raise Eio.Io on failure *)
152152+153153+val unsubscribe_simple : Client.t -> channels:string list -> unit
154154+(** Unsubscribe the current user from channels by name.
155155+ @raise Eio.Io on failure *)
156156+157157+val get_subscriptions : Client.t -> Channel.Subscription.t list
158158+(** Get the current user's channel subscriptions.
159159+ @raise Eio.Io on failure *)
160160+161161+val get_subscription_status : Client.t -> user_id:int -> stream_id:int -> bool
162162+(** Check if a user is subscribed to a channel.
163163+ @raise Eio.Io on failure *)
164164+165165+val update_subscription_settings :
166166+ Client.t ->
167167+ stream_id:int ->
168168+ ?color:string ->
169169+ ?is_muted:bool ->
170170+ ?pin_to_top:bool ->
171171+ ?desktop_notifications:bool ->
172172+ ?audible_notifications:bool ->
173173+ ?push_notifications:bool ->
174174+ ?email_notifications:bool ->
175175+ ?wildcard_mentions_notify:bool ->
176176+ unit ->
177177+ unit
178178+(** Update subscription settings for a channel.
179179+ @raise Eio.Io on failure *)
180180+181181+(** {1 Topics} *)
182182+183183+(** A topic within a channel. *)
184184+module Topic : sig
185185+ type t
186186+187187+ val name : t -> string
188188+ (** Topic name. *)
189189+190190+ val max_id : t -> int
191191+ (** ID of the latest message in the topic. *)
192192+193193+ val jsont : t Jsont.t
194194+end
195195+196196+val get_topics : Client.t -> stream_id:int -> Topic.t list
197197+(** Get all topics in a channel.
198198+ @raise Eio.Io on failure *)
199199+200200+val delete_topic : Client.t -> stream_id:int -> topic:string -> unit
201201+(** Delete a topic and all its messages.
202202+ Requires admin privileges.
203203+ @raise Eio.Io on failure *)
204204+205205+(** {1 Topic Muting} *)
206206+207207+type mute_op =
208208+ | Mute (** Mute the topic *)
209209+ | Unmute (** Unmute the topic *)
210210+211211+val set_topic_mute :
212212+ Client.t -> stream_id:int -> topic:string -> op:mute_op -> unit
213213+(** Mute or unmute a topic.
214214+ @raise Eio.Io on failure *)
215215+216216+val get_muted_topics : Client.t -> (int * string) list
217217+(** Get list of muted topics as (stream_id, topic_name) pairs.
218218+ @raise Eio.Io on failure *)
219219+220220+(** {1 Subscribers} *)
221221+222222+val get_subscribers : Client.t -> stream_id:int -> int list
223223+(** Get list of user IDs subscribed to a channel.
224224+ @raise Eio.Io on failure *)
225225+226226+(** {1 Email Address} *)
227227+228228+val get_email_address : Client.t -> stream_id:int -> string
229229+(** Get the email address for posting to a channel.
24230 @raise Eio.Io on failure *)
+2-1
lib/zulip/dune
···11(library
22 (public_name zulip)
33 (name zulip)
44- (libraries eio requests jsont jsont.bytesrw uri base64 logs))44+ (libraries eio requests jsont jsont.bytesrw uri base64 logs)
55+ (modules_without_implementation presence server typing user_group))
+98-20
lib/zulip/event_queue.ml
···3344module Log = (val Logs.src_log src : Logs.LOG)
5566-type t = { id : string }
66+type t = { id : string; mutable last_event_id : int }
7788(* Request/response codecs *)
99module Register_request = struct
1010 type t = { event_types : string list option }
11111212- let codec =
1212+ let _codec =
1313 Jsont.Object.(
1414 map ~kind:"RegisterRequest" (fun event_types -> { event_types })
1515 |> opt_mem "event_types" (Jsont.list Jsont.string)
···1818end
19192020module Register_response = struct
2121- type t = { queue_id : string }
2121+ type t = { queue_id : string; last_event_id : int }
22222323 let codec =
2424 Jsont.Object.(
2525- map ~kind:"RegisterResponse" (fun queue_id -> { queue_id })
2525+ map ~kind:"RegisterResponse" (fun queue_id last_event_id ->
2626+ { queue_id; last_event_id })
2627 |> mem "queue_id" Jsont.string ~enc:(fun r -> r.queue_id)
2828+ |> mem "last_event_id" Jsont.int ~dec_absent:(-1) ~enc:(fun r ->
2929+ r.last_event_id)
2730 |> finish)
2831end
29323030-let register client ?event_types () =
3333+let register client ?event_types ?narrow ?all_public_streams ?include_subscribers
3434+ ?client_capabilities ?fetch_event_types ?client_gravatar ?slim_presence () =
3135 let event_types_str =
3236 Option.map (List.map Event_type.to_string) event_types
3337 in
3434- let req = Register_request.{ event_types = event_types_str } in
3535- let body = Encode.to_form_urlencoded Register_request.codec req in
3636- let content_type = "application/x-www-form-urlencoded" in
3838+ let fetch_event_types_str =
3939+ Option.map (List.map Event_type.to_string) fetch_event_types
4040+ in
4141+ let params =
4242+ List.filter_map Fun.id
4343+ [
4444+ Option.map
4545+ (fun types ->
4646+ ("event_types", Encode.to_json_string (Jsont.list Jsont.string) types))
4747+ event_types_str;
4848+ Option.map
4949+ (fun n -> ("narrow", Encode.to_json_string Narrow.list_jsont n))
5050+ narrow;
5151+ Option.map
5252+ (fun v -> ("all_public_streams", string_of_bool v))
5353+ all_public_streams;
5454+ Option.map
5555+ (fun v -> ("include_subscribers", string_of_bool v))
5656+ include_subscribers;
5757+ Option.map
5858+ (fun v -> ("client_capabilities", Encode.to_json_string Jsont.json v))
5959+ client_capabilities;
6060+ Option.map
6161+ (fun types ->
6262+ ( "fetch_event_types",
6363+ Encode.to_json_string (Jsont.list Jsont.string) types ))
6464+ fetch_event_types_str;
6565+ Option.map
6666+ (fun v -> ("client_gravatar", string_of_bool v))
6767+ client_gravatar;
6868+ Option.map (fun v -> ("slim_presence", string_of_bool v)) slim_presence;
6969+ ]
7070+ in
37713872 (match event_types_str with
3973 | Some types ->
···4276 | None -> ());
43774478 let json =
4545- Client.request client ~method_:`POST ~path:"/api/v1/register" ~body
4646- ~content_type ()
7979+ Client.request client ~method_:`POST ~path:"/api/v1/register" ~params ()
4780 in
4881 match Encode.from_json Register_response.codec json with
4949- | Ok response -> { id = response.queue_id }
8282+ | Ok response ->
8383+ { id = response.queue_id; last_event_id = response.last_event_id }
5084 | Error msg ->
5185 Error.raise_with_context
5286 (Error.make ~code:(Other "json_parse") ~message:msg ())
5387 "parsing register response"
54885589let id t = t.id
9090+let last_event_id t = t.last_event_id
56915792(* Events response codec - events field is optional (may not be present) *)
5893module Events_response = struct
5994 type t = { events : Event.t list }
60956196 let codec =
6262- (* Use keep_unknown pattern to handle the whole object and extract events manually *)
6397 let make raw_json =
6498 match raw_json with
6599 | Jsont.Object (fields, _) ->
66100 let assoc = List.map (fun ((k, _), v) -> (k, v)) fields in
67101 (match List.assoc_opt "events" assoc with
68102 | Some (Jsont.Array (items, _)) ->
6969- (* Parse each event, skipping failures *)
70103 let events =
71104 List.fold_left
72105 (fun acc item ->
···87120 |> Jsont.Object.finish
88121end
891229090-let get_events t client ?last_event_id () =
123123+let get_events t client ?last_event_id ?dont_block () =
124124+ let event_id =
125125+ match last_event_id with Some id -> id | None -> t.last_event_id
126126+ in
91127 let params =
9292- [ ("queue_id", t.id) ]
9393- @
9494- match last_event_id with
9595- | None -> []
9696- | Some event_id -> [ ("last_event_id", string_of_int event_id) ]
128128+ [ ("queue_id", t.id); ("last_event_id", string_of_int event_id) ]
129129+ @ (match dont_block with
130130+ | Some true -> [ ("dont_block", "true") ]
131131+ | _ -> [])
97132 in
98133 let json =
99134 Client.request client ~method_:`GET ~path:"/api/v1/events" ~params ()
···102137 | Ok response ->
103138 Log.debug (fun m ->
104139 m "Got %d events from API" (List.length response.events));
140140+ (* Update internal last_event_id *)
141141+ (match response.events with
142142+ | [] -> ()
143143+ | events ->
144144+ let max_id =
145145+ List.fold_left (fun acc e -> max acc (Event.id e)) event_id events
146146+ in
147147+ t.last_event_id <- max_id);
105148 response.events
106149 | Error msg ->
107150 Log.warn (fun m -> m "Failed to parse events response: %s" msg);
···116159 in
117160 ()
118161119119-let pp fmt t = Format.fprintf fmt "EventQueue{id=%s}" t.id
162162+let call_on_each_event client ?event_types ?narrow ~callback () =
163163+ let queue = register client ?event_types ?narrow () in
164164+ let rec loop () =
165165+ let events = get_events queue client () in
166166+ List.iter
167167+ (fun event ->
168168+ (* Filter out heartbeat events *)
169169+ match Event.type_ event with Event_type.Heartbeat -> () | _ -> callback event)
170170+ events;
171171+ loop ()
172172+ in
173173+ loop ()
174174+175175+let call_on_each_message client ?narrow ~callback () =
176176+ call_on_each_event client ~event_types:[ Event_type.Message ] ?narrow
177177+ ~callback:(fun event ->
178178+ match Event.type_ event with
179179+ | Event_type.Message -> callback (Event.data event)
180180+ | _ -> ())
181181+ ()
182182+183183+let events t client =
184184+ let rec next () =
185185+ let events = get_events t client ~dont_block:true () in
186186+ match events with
187187+ | [] ->
188188+ (* No events available, wait a bit and try again *)
189189+ Seq.Cons (List.hd (get_events t client ()), next)
190190+ | e :: rest ->
191191+ (* Return events one by one *)
192192+ let rest_seq = List.to_seq rest in
193193+ Seq.Cons (e, fun () -> Seq.append rest_seq next ())
194194+ in
195195+ next
196196+197197+let pp fmt t = Format.fprintf fmt "EventQueue{id=%s, last_event_id=%d}" t.id t.last_event_id
+99-5
lib/zulip/event_queue.mli
···11(** Event queue for receiving Zulip events in real-time.
2233+ Event queues provide real-time notifications of changes in Zulip.
44+ Register a queue to receive events, then poll for updates.
55+36 All functions raise [Eio.Io] with [Error.E error] on failure. *)
4788+(** {1 Event Queue Type} *)
99+510type t
1111+(** An opaque event queue handle. *)
61277-val register : Client.t -> ?event_types:Event_type.t list -> unit -> t
1313+(** {1 Registration} *)
1414+1515+val register :
1616+ Client.t ->
1717+ ?event_types:Event_type.t list ->
1818+ ?narrow:Narrow.t list ->
1919+ ?all_public_streams:bool ->
2020+ ?include_subscribers:bool ->
2121+ ?client_capabilities:Jsont.json ->
2222+ ?fetch_event_types:Event_type.t list ->
2323+ ?client_gravatar:bool ->
2424+ ?slim_presence:bool ->
2525+ unit ->
2626+ t
827(** Register a new event queue with the server.
99- @param event_types Optional list of event types to subscribe to
2828+2929+ @param event_types Event types to subscribe to (default: all)
3030+ @param narrow Narrow filter for message events
3131+ @param all_public_streams Include events from all public streams
3232+ @param include_subscribers Include subscriber lists in stream events
3333+ @param client_capabilities Client capability flags
3434+ @param fetch_event_types Types to return initial data for
3535+ @param client_gravatar Whether client handles gravatar URLs
3636+ @param slim_presence Use compact presence format
1037 @raise Eio.Io on failure *)
3838+3939+(** {1 Queue Properties} *)
11401241val id : t -> string
1342(** Get the queue ID. *)
14431515-val get_events : t -> Client.t -> ?last_event_id:int -> unit -> Event.t list
4444+val last_event_id : t -> int
4545+(** Get the last event ID received. *)
4646+4747+(** {1 Polling for Events} *)
4848+4949+val get_events :
5050+ t -> Client.t -> ?last_event_id:int -> ?dont_block:bool -> unit -> Event.t list
1651(** Get events from the queue.
1717- @param last_event_id Optional event ID to resume from
1818- @raise Eio.Io on failure *)
5252+5353+ @param last_event_id Event ID to resume from (default: use queue's state)
5454+ @param dont_block Return immediately even if no events (default: long-poll)
5555+ @raise Eio.Io on failure
5656+5757+ Note: This function updates the queue's internal last_event_id. *)
5858+5959+(** {1 Cleanup} *)
19602061val delete : t -> Client.t -> unit
2162(** Delete the event queue from the server.
2263 @raise Eio.Io on failure *)
6464+6565+(** {1 High-Level Event Processing}
6666+6767+ These functions provide convenient callback-based patterns for
6868+ processing events. They handle queue management and reconnection
6969+ automatically. *)
7070+7171+val call_on_each_event :
7272+ Client.t ->
7373+ ?event_types:Event_type.t list ->
7474+ ?narrow:Narrow.t list ->
7575+ callback:(Event.t -> unit) ->
7676+ unit ->
7777+ unit
7878+(** Process events with a callback.
7979+8080+ Registers a queue and continuously polls for events, calling the
8181+ callback for each event. Automatically handles reconnection if
8282+ the queue expires.
8383+8484+ This function runs indefinitely until cancelled via [Eio.Cancel].
8585+8686+ @param event_types Event types to subscribe to
8787+ @param narrow Narrow filter for message events
8888+ @param callback Function called for each event
8989+9090+ Note: Heartbeat events are filtered out automatically. *)
9191+9292+val call_on_each_message :
9393+ Client.t ->
9494+ ?narrow:Narrow.t list ->
9595+ callback:(Jsont.json -> unit) ->
9696+ unit ->
9797+ unit
9898+(** Process message events with a callback.
9999+100100+ Convenience wrapper around [call_on_each_event] that filters
101101+ for message events and extracts the message data.
102102+103103+ @param narrow Narrow filter for messages
104104+ @param callback Function called with each message's JSON data *)
105105+106106+(** {1 Event Stream}
107107+108108+ For use with Eio's streaming patterns. *)
109109+110110+val events : t -> Client.t -> Event.t Seq.t
111111+(** Create a lazy sequence of events from the queue.
112112+ The sequence polls the server as needed.
113113+114114+ Note: This sequence is infinite - use [Seq.take] or similar to limit. *)
115115+116116+(** {1 Pretty Printing} *)
2311724118val pp : Format.formatter -> t -> unit
···11-type t =
22- | Message
33- | Subscription
44- | User_activity
55- | Other of string
11+(** Zulip event types.
22+33+ This module defines the event types that can be received from the
44+ Zulip event queue. These correspond to the "type" field in event
55+ objects returned by the /events endpoint. *)
66+77+(** {1 Event Types} *)
88+99+type t =
1010+ | Message (** New message received *)
1111+ | Heartbeat (** Keep-alive heartbeat (internal protocol) *)
1212+ | Presence (** User presence update *)
1313+ | Typing (** User typing notification *)
1414+ | Reaction (** Emoji reaction added/removed *)
1515+ | Subscription (** Stream subscription change *)
1616+ | Stream (** Stream created/deleted/updated *)
1717+ | Realm (** Realm settings changed *)
1818+ | Realm_user (** User added/removed/updated in realm *)
1919+ | Realm_emoji (** Custom emoji added/removed *)
2020+ | Realm_linkifiers (** Linkifier rules changed *)
2121+ | User_group (** User group modified *)
2222+ | User_status (** User status (away/active) changed *)
2323+ | Update_message (** Message edited *)
2424+ | Delete_message (** Message deleted *)
2525+ | Update_message_flags (** Message flags (read/starred) changed *)
2626+ | Restart (** Server restart notification *)
2727+ | Other of string (** Unknown/custom event type *)
2828+2929+(** {1 Conversion} *)
630731val to_string : t -> string
3232+(** Convert an event type to its wire format string. *)
3333+834val of_string : string -> t
99-val pp : Format.formatter -> t -> unit3535+(** Parse an event type from its wire format string.
3636+ Unknown types are wrapped in [Other]. *)
3737+3838+(** {1 Pretty Printing} *)
3939+4040+val pp : Format.formatter -> t -> unit
4141+4242+(** {1 JSON Codec} *)
4343+4444+val jsont : t Jsont.t
+58
lib/zulip/message_flag.ml
···11+type modifiable = [ `Read | `Starred | `Collapsed ]
22+33+type t =
44+ [ modifiable
55+ | `Mentioned
66+ | `Wildcard_mentioned
77+ | `Has_alert_word
88+ | `Historical
99+ ]
1010+1111+let to_string = function
1212+ | `Read -> "read"
1313+ | `Starred -> "starred"
1414+ | `Collapsed -> "collapsed"
1515+ | `Mentioned -> "mentioned"
1616+ | `Wildcard_mentioned -> "wildcard_mentioned"
1717+ | `Has_alert_word -> "has_alert_word"
1818+ | `Historical -> "historical"
1919+2020+let of_string = function
2121+ | "read" -> Some `Read
2222+ | "starred" -> Some `Starred
2323+ | "collapsed" -> Some `Collapsed
2424+ | "mentioned" -> Some `Mentioned
2525+ | "wildcard_mentioned" -> Some `Wildcard_mentioned
2626+ | "has_alert_word" -> Some `Has_alert_word
2727+ | "historical" -> Some `Historical
2828+ | _ -> None
2929+3030+let modifiable_of_string = function
3131+ | "read" -> Some `Read
3232+ | "starred" -> Some `Starred
3333+ | "collapsed" -> Some `Collapsed
3434+ | _ -> None
3535+3636+type op = Add | Remove
3737+3838+let op_to_string = function Add -> "add" | Remove -> "remove"
3939+4040+let pp fmt t = Format.fprintf fmt "%s" (to_string t)
4141+4242+let jsont =
4343+ let encode t = to_string t in
4444+ let decode s =
4545+ match of_string s with
4646+ | Some t -> t
4747+ | None -> failwith ("Unknown message flag: " ^ s)
4848+ in
4949+ Jsont.string |> Jsont.map ~dec:decode ~enc:encode
5050+5151+let modifiable_jsont =
5252+ let encode t = to_string t in
5353+ let decode s =
5454+ match modifiable_of_string s with
5555+ | Some t -> t
5656+ | None -> failwith ("Unknown modifiable message flag: " ^ s)
5757+ in
5858+ Jsont.string |> Jsont.map ~dec:decode ~enc:encode
+51
lib/zulip/message_flag.mli
···11+(** Message flags in Zulip.
22+33+ Message flags indicate read/unread status, starred messages,
44+ mentions, and other message properties. *)
55+66+(** {1 Flag Types} *)
77+88+type modifiable =
99+ [ `Read (** Message has been read *)
1010+ | `Starred (** Message is starred/bookmarked *)
1111+ | `Collapsed (** Message content is collapsed *)
1212+ ]
1313+(** Flags that can be directly modified by the user. *)
1414+1515+type t =
1616+ [ modifiable
1717+ | `Mentioned (** User was @-mentioned in the message *)
1818+ | `Wildcard_mentioned (** User was mentioned via @all/@everyone *)
1919+ | `Has_alert_word (** Message contains one of user's alert words *)
2020+ | `Historical (** Message predates user joining the stream *)
2121+ ]
2222+(** All possible message flags. *)
2323+2424+(** {1 Conversion} *)
2525+2626+val to_string : [< t ] -> string
2727+(** Convert a flag to its wire format string. *)
2828+2929+val of_string : string -> t option
3030+(** Parse a flag from its wire format string. *)
3131+3232+val modifiable_of_string : string -> modifiable option
3333+(** Parse a modifiable flag from its wire format string. *)
3434+3535+(** {1 Flag Update Operations} *)
3636+3737+type op =
3838+ | Add (** Add the flag to messages *)
3939+ | Remove (** Remove the flag from messages *)
4040+4141+val op_to_string : op -> string
4242+4343+(** {1 Pretty Printing} *)
4444+4545+val pp : Format.formatter -> t -> unit
4646+4747+(** {1 JSON Codec} *)
4848+4949+val jsont : t Jsont.t
5050+5151+val modifiable_jsont : modifiable Jsont.t
+187-27
lib/zulip/messages.ml
···11let send client message =
22- (* Use form-urlencoded encoding for the message *)
32 let body = Encode.to_form_urlencoded Message.jsont message in
43 let content_type = "application/x-www-form-urlencoded" in
54 let response =
···1312 (Error.make ~code:(Other "json_parse") ~message:msg ())
1413 "parsing message response"
15141616-let edit client ~message_id ?content ?topic () =
1515+let get client ~message_id =
1616+ Client.request client ~method_:`GET
1717+ ~path:("/api/v1/messages/" ^ string_of_int message_id)
1818+ ()
1919+2020+let get_raw client ~message_id =
2121+ let response_codec =
2222+ Jsont.Object.(
2323+ map ~kind:"RawMessageResponse" (fun content -> content)
2424+ |> mem "raw_content" Jsont.string ~enc:(fun x -> x)
2525+ |> finish)
2626+ in
2727+ let json =
2828+ Client.request client ~method_:`GET
2929+ ~path:("/api/v1/messages/" ^ string_of_int message_id)
3030+ ~params:[ ("apply_markdown", "false") ]
3131+ ()
3232+ in
3333+ match Encode.from_json response_codec json with
3434+ | Ok content -> content
3535+ | Error msg ->
3636+ Error.raise_with_context
3737+ (Error.make ~code:(Other "json_parse") ~message:msg ())
3838+ "getting raw message %d" message_id
3939+4040+type anchor = Newest | Oldest | First_unread | Message_id of int
4141+4242+let anchor_to_string = function
4343+ | Newest -> "newest"
4444+ | Oldest -> "oldest"
4545+ | First_unread -> "first_unread"
4646+ | Message_id id -> string_of_int id
4747+4848+let get_messages client ?anchor ?num_before ?num_after ?narrow ?include_anchor
4949+ () =
5050+ let params =
5151+ List.filter_map Fun.id
5252+ [
5353+ Option.map (fun a -> ("anchor", anchor_to_string a)) anchor;
5454+ Option.map (fun n -> ("num_before", string_of_int n)) num_before;
5555+ Option.map (fun n -> ("num_after", string_of_int n)) num_after;
5656+ Option.map
5757+ (fun n -> ("narrow", Encode.to_json_string Narrow.list_jsont n))
5858+ narrow;
5959+ Option.map
6060+ (fun v -> ("include_anchor", string_of_bool v))
6161+ include_anchor;
6262+ ]
6363+ in
6464+ Client.request client ~method_:`GET ~path:"/api/v1/messages" ~params ()
6565+6666+let check_messages_match_narrow client ~message_ids ~narrow =
6767+ let params =
6868+ [
6969+ ("msg_ids", Encode.to_json_string (Jsont.list Jsont.int) message_ids);
7070+ ("narrow", Encode.to_json_string Narrow.list_jsont narrow);
7171+ ]
7272+ in
7373+ Client.request client ~method_:`GET ~path:"/api/v1/messages/matches_narrow"
7474+ ~params ()
7575+7676+let get_history client ~message_id =
7777+ Client.request client ~method_:`GET
7878+ ~path:("/api/v1/messages/" ^ string_of_int message_id ^ "/history")
7979+ ()
8080+8181+type propagate_mode = Change_one | Change_later | Change_all
8282+8383+let propagate_mode_to_string = function
8484+ | Change_one -> "change_one"
8585+ | Change_later -> "change_later"
8686+ | Change_all -> "change_all"
8787+8888+let edit client ~message_id ?content ?topic ?stream_id ?propagate_mode
8989+ ?send_notification_to_old_thread ?send_notification_to_new_thread () =
1790 let params =
1818- (("message_id", string_of_int message_id)
1919- :: (match content with Some c -> [ ("content", c) ] | None -> []))
2020- @ match topic with Some t -> [ ("topic", t) ] | None -> []
9191+ List.filter_map Fun.id
9292+ [
9393+ Option.map (fun c -> ("content", c)) content;
9494+ Option.map (fun t -> ("topic", t)) topic;
9595+ Option.map (fun id -> ("stream_id", string_of_int id)) stream_id;
9696+ Option.map
9797+ (fun m -> ("propagate_mode", propagate_mode_to_string m))
9898+ propagate_mode;
9999+ Option.map
100100+ (fun v -> ("send_notification_to_old_thread", string_of_bool v))
101101+ send_notification_to_old_thread;
102102+ Option.map
103103+ (fun v -> ("send_notification_to_new_thread", string_of_bool v))
104104+ send_notification_to_new_thread;
105105+ ]
21106 in
22107 let _response =
23108 Client.request client ~method_:`PATCH
···34119 in
35120 ()
361213737-let get client ~message_id =
3838- Client.request client ~method_:`GET
3939- ~path:("/api/v1/messages/" ^ string_of_int message_id)
4040- ()
122122+let update_flags client ~messages ~op ~flag =
123123+ let op_str = Message_flag.op_to_string op in
124124+ let flag_str = Message_flag.to_string flag in
125125+ let params =
126126+ [
127127+ ("messages", Encode.to_json_string (Jsont.list Jsont.int) messages);
128128+ ("op", op_str);
129129+ ("flag", flag_str);
130130+ ]
131131+ in
132132+ let _response =
133133+ Client.request client ~method_:`POST ~path:"/api/v1/messages/flags" ~params
134134+ ()
135135+ in
136136+ ()
411374242-let get_messages client ?anchor ?num_before ?num_after ?narrow () =
138138+let mark_all_as_read client =
139139+ let _response =
140140+ Client.request client ~method_:`POST ~path:"/api/v1/mark_all_as_read" ()
141141+ in
142142+ ()
143143+144144+let mark_stream_as_read client ~stream_id =
145145+ let params = [ ("stream_id", string_of_int stream_id) ] in
146146+ let _response =
147147+ Client.request client ~method_:`POST ~path:"/api/v1/mark_stream_as_read"
148148+ ~params ()
149149+ in
150150+ ()
151151+152152+let mark_topic_as_read client ~stream_id ~topic =
43153 let params =
4444- ((match anchor with Some a -> [ ("anchor", a) ] | None -> [])
4545- @ (match num_before with
4646- | Some n -> [ ("num_before", string_of_int n) ]
4747- | None -> [])
4848- @ (match num_after with
4949- | Some n -> [ ("num_after", string_of_int n) ]
5050- | None -> []))
5151- @
5252- match narrow with
5353- | Some n ->
5454- List.mapi (fun i s -> ("narrow[" ^ string_of_int i ^ "]", s)) n
5555- | None -> []
154154+ [ ("stream_id", string_of_int stream_id); ("topic_name", topic) ]
155155+ in
156156+ let _response =
157157+ Client.request client ~method_:`POST ~path:"/api/v1/mark_topic_as_read"
158158+ ~params ()
56159 in
5757- Client.request client ~method_:`GET ~path:"/api/v1/messages" ~params ()
160160+ ()
161161+162162+type emoji_type = Unicode_emoji | Realm_emoji | Zulip_extra_emoji
163163+164164+let emoji_type_to_string = function
165165+ | Unicode_emoji -> "unicode_emoji"
166166+ | Realm_emoji -> "realm_emoji"
167167+ | Zulip_extra_emoji -> "zulip_extra_emoji"
581685959-let add_reaction client ~message_id ~emoji_name =
6060- let params = [ ("emoji_name", emoji_name) ] in
169169+let add_reaction client ~message_id ~emoji_name ?emoji_code ?reaction_type () =
170170+ let params =
171171+ [ ("emoji_name", emoji_name) ]
172172+ @ List.filter_map Fun.id
173173+ [
174174+ Option.map (fun c -> ("emoji_code", c)) emoji_code;
175175+ Option.map
176176+ (fun t -> ("reaction_type", emoji_type_to_string t))
177177+ reaction_type;
178178+ ]
179179+ in
61180 let _response =
62181 Client.request client ~method_:`POST
63182 ~path:("/api/v1/messages/" ^ string_of_int message_id ^ "/reactions")
···65184 in
66185 ()
671866868-let remove_reaction client ~message_id ~emoji_name =
6969- let params = [ ("emoji_name", emoji_name) ] in
187187+let remove_reaction client ~message_id ~emoji_name ?emoji_code ?reaction_type ()
188188+ =
189189+ let params =
190190+ [ ("emoji_name", emoji_name) ]
191191+ @ List.filter_map Fun.id
192192+ [
193193+ Option.map (fun c -> ("emoji_code", c)) emoji_code;
194194+ Option.map
195195+ (fun t -> ("reaction_type", emoji_type_to_string t))
196196+ reaction_type;
197197+ ]
198198+ in
70199 let _response =
71200 Client.request client ~method_:`DELETE
72201 ~path:("/api/v1/messages/" ^ string_of_int message_id ^ "/reactions")
···74203 in
75204 ()
76205206206+let render client ~content =
207207+ let params = [ ("content", content) ] in
208208+ let response_codec =
209209+ Jsont.Object.(
210210+ map ~kind:"RenderResponse" (fun rendered -> rendered)
211211+ |> mem "rendered" Jsont.string ~enc:(fun x -> x)
212212+ |> finish)
213213+ in
214214+ let json =
215215+ Client.request client ~method_:`POST ~path:"/api/v1/messages/render" ~params
216216+ ()
217217+ in
218218+ match Encode.from_json response_codec json with
219219+ | Ok rendered -> rendered
220220+ | Error msg ->
221221+ Error.raise_with_context
222222+ (Error.make ~code:(Other "json_parse") ~message:msg ())
223223+ "rendering message"
224224+77225let upload_file _client ~filename:_ =
78226 (* TODO: Implement file upload using multipart/form-data *)
79227 Error.raise
80228 (Error.make ~code:(Other "not_implemented")
81229 ~message:"File upload not yet implemented" ())
230230+231231+let get_scheduled client =
232232+ Client.request client ~method_:`GET ~path:"/api/v1/scheduled_messages" ()
233233+234234+let delete_scheduled client ~scheduled_message_id =
235235+ let _response =
236236+ Client.request client ~method_:`DELETE
237237+ ~path:
238238+ ("/api/v1/scheduled_messages/" ^ string_of_int scheduled_message_id)
239239+ ()
240240+ in
241241+ ()
+154-20
lib/zulip/messages.mli
···33 All functions raise [Eio.Io] with [Error.E error] on failure.
44 Context is automatically added indicating the operation being performed. *)
5566+(** {1 Sending Messages} *)
77+68val send : Client.t -> Message.t -> Message_response.t
79(** Send a message.
810 @raise Eio.Io on failure *)
9111010-val edit :
1111- Client.t -> message_id:int -> ?content:string -> ?topic:string -> unit -> unit
1212-(** Edit a message's content or topic.
1313- @raise Eio.Io on failure *)
1414-1515-val delete : Client.t -> message_id:int -> unit
1616-(** Delete a message.
1717- @raise Eio.Io on failure *)
1212+(** {1 Reading Messages} *)
18131914val get : Client.t -> message_id:int -> Jsont.json
2015(** Get a single message by ID.
1616+ Returns the full message object.
2117 @raise Eio.Io on failure *)
22181919+val get_raw : Client.t -> message_id:int -> string
2020+(** Get the raw Markdown content of a message.
2121+ @raise Eio.Io on failure *)
2222+2323+(** Anchor points for message pagination. *)
2424+type anchor =
2525+ | Newest (** Start from the newest message *)
2626+ | Oldest (** Start from the oldest message *)
2727+ | First_unread (** Start from first unread message *)
2828+ | Message_id of int (** Start from a specific message ID *)
2929+2330val get_messages :
2431 Client.t ->
2525- ?anchor:string ->
3232+ ?anchor:anchor ->
2633 ?num_before:int ->
2734 ?num_after:int ->
2828- ?narrow:string list ->
3535+ ?narrow:Narrow.t list ->
3636+ ?include_anchor:bool ->
2937 unit ->
3038 Jsont.json
3139(** Get multiple messages with optional filtering.
4040+4141+ @param anchor Where to start fetching (default: [Newest])
4242+ @param num_before Number of messages before anchor (default: 0)
4343+ @param num_after Number of messages after anchor (default: 0)
4444+ @param narrow Filter criteria (see {!Narrow})
4545+ @param include_anchor Include the anchor message in results
3246 @raise Eio.Io on failure *)
33473434-val add_reaction : Client.t -> message_id:int -> emoji_name:string -> unit
4848+val check_messages_match_narrow :
4949+ Client.t -> message_ids:int list -> narrow:Narrow.t list -> Jsont.json
5050+(** Check if messages match a narrow filter.
5151+ Returns which of the given messages match the narrow.
5252+ @raise Eio.Io on failure *)
5353+5454+(** {1 Message History} *)
5555+5656+val get_history : Client.t -> message_id:int -> Jsont.json
5757+(** Get the edit history of a message.
5858+ @raise Eio.Io on failure *)
5959+6060+(** {1 Editing Messages} *)
6161+6262+(** Propagation mode for topic/stream changes. *)
6363+type propagate_mode =
6464+ | Change_one (** Only change this message *)
6565+ | Change_later (** Change this and subsequent messages *)
6666+ | Change_all (** Change all messages in the topic *)
6767+6868+val edit :
6969+ Client.t ->
7070+ message_id:int ->
7171+ ?content:string ->
7272+ ?topic:string ->
7373+ ?stream_id:int ->
7474+ ?propagate_mode:propagate_mode ->
7575+ ?send_notification_to_old_thread:bool ->
7676+ ?send_notification_to_new_thread:bool ->
7777+ unit ->
7878+ unit
7979+(** Edit a message's content, topic, or stream.
8080+8181+ @param content New message content
8282+ @param topic New topic name
8383+ @param stream_id Move message to this stream
8484+ @param propagate_mode How to handle topic/stream changes for other messages
8585+ @param send_notification_to_old_thread Notify in old location
8686+ @param send_notification_to_new_thread Notify in new location
8787+ @raise Eio.Io on failure *)
8888+8989+(** {1 Deleting Messages} *)
9090+9191+val delete : Client.t -> message_id:int -> unit
9292+(** Delete a message.
9393+ @raise Eio.Io on failure *)
9494+9595+(** {1 Message Flags} *)
9696+9797+val update_flags :
9898+ Client.t ->
9999+ messages:int list ->
100100+ op:Message_flag.op ->
101101+ flag:Message_flag.modifiable ->
102102+ unit
103103+(** Update flags on a list of messages.
104104+105105+ @param messages List of message IDs to update
106106+ @param op Whether to add or remove the flag
107107+ @param flag The flag to update
108108+ @raise Eio.Io on failure
109109+110110+ {b Example:}
111111+ {[
112112+ (* Mark messages as read *)
113113+ Messages.update_flags client
114114+ ~messages:[123; 456; 789]
115115+ ~op:Add
116116+ ~flag:`Read
117117+ ]} *)
118118+119119+val mark_all_as_read : Client.t -> unit
120120+(** Mark all messages as read.
121121+ @raise Eio.Io on failure *)
122122+123123+val mark_stream_as_read : Client.t -> stream_id:int -> unit
124124+(** Mark all messages in a stream as read.
125125+ @raise Eio.Io on failure *)
126126+127127+val mark_topic_as_read : Client.t -> stream_id:int -> topic:string -> unit
128128+(** Mark all messages in a topic as read.
129129+ @raise Eio.Io on failure *)
130130+131131+(** {1 Reactions} *)
132132+133133+(** Type of emoji for reactions. *)
134134+type emoji_type =
135135+ | Unicode_emoji (** Standard Unicode emoji *)
136136+ | Realm_emoji (** Custom organization emoji *)
137137+ | Zulip_extra_emoji (** Zulip-specific emoji *)
138138+139139+val add_reaction :
140140+ Client.t ->
141141+ message_id:int ->
142142+ emoji_name:string ->
143143+ ?emoji_code:string ->
144144+ ?reaction_type:emoji_type ->
145145+ unit ->
146146+ unit
35147(** Add an emoji reaction to a message.
361483737- @param client The Zulip client
3838- @param message_id The message ID to react to
3939- @param emoji_name The emoji name (e.g., "thumbs_up", "heart", "rocket")
149149+ @param emoji_name The emoji name (e.g., "thumbs_up", "heart")
150150+ @param emoji_code The emoji code (optional, required for realm emoji)
151151+ @param reaction_type The type of emoji (default: [Unicode_emoji])
40152 @raise Eio.Io on failure
4115342154 {b Example:}
43155 {[
4444- Messages.add_reaction client ~message_id:12345 ~emoji_name:"thumbs_up"
156156+ Messages.add_reaction client ~message_id:12345 ~emoji_name:"thumbs_up" ()
45157 ]} *)
461584747-val remove_reaction : Client.t -> message_id:int -> emoji_name:string -> unit
159159+val remove_reaction :
160160+ Client.t ->
161161+ message_id:int ->
162162+ emoji_name:string ->
163163+ ?emoji_code:string ->
164164+ ?reaction_type:emoji_type ->
165165+ unit ->
166166+ unit
48167(** Remove an emoji reaction from a message.
168168+ @raise Eio.Io on failure *)
491695050- @param client The Zulip client
5151- @param message_id The message ID
5252- @param emoji_name The emoji name to remove
170170+(** {1 Rendering} *)
171171+172172+val render : Client.t -> content:string -> string
173173+(** Render message content as HTML.
174174+ Useful for previewing how a message will appear.
175175+ @return The rendered HTML
53176 @raise Eio.Io on failure *)
177177+178178+(** {1 File Uploads} *)
5417955180val upload_file : Client.t -> filename:string -> string
56181(** Upload a file to Zulip.
571825858- @param client The Zulip client
59183 @param filename The path to the file to upload
60184 @return The Zulip URL for the uploaded file
61185 @raise Eio.Io on failure
···67191 ~content:("Check out this image: " ^ uri) () in
68192 Messages.send client msg
69193 ]} *)
194194+195195+(** {1 Scheduled Messages} *)
196196+197197+val get_scheduled : Client.t -> Jsont.json
198198+(** Get all scheduled messages for the current user.
199199+ @raise Eio.Io on failure *)
200200+201201+val delete_scheduled : Client.t -> scheduled_message_id:int -> unit
202202+(** Delete a scheduled message.
203203+ @raise Eio.Io on failure *)
+116
lib/zulip/narrow.ml
···11+type t = {
22+ operator : string;
33+ operand : [ `String of string | `Int of int | `Strings of string list ];
44+ negated : bool;
55+}
66+77+let make ?(negated = false) operator operand = { operator; operand; negated }
88+99+let stream name = make "stream" (`String name)
1010+let stream_id id = make "stream" (`Int id)
1111+let topic name = make "topic" (`String name)
1212+let channel = stream
1313+1414+let sender email = make "sender" (`String email)
1515+let sender_id id = make "sender" (`Int id)
1616+1717+type is_operand =
1818+ [ `Alerted | `Dm | `Mentioned | `Private | `Resolved | `Starred | `Unread ]
1919+2020+let is_operand_to_string = function
2121+ | `Alerted -> "alerted"
2222+ | `Dm -> "dm"
2323+ | `Mentioned -> "mentioned"
2424+ | `Private -> "private"
2525+ | `Resolved -> "resolved"
2626+ | `Starred -> "starred"
2727+ | `Unread -> "unread"
2828+2929+let is operand = make "is" (`String (is_operand_to_string operand))
3030+3131+type has_operand = [ `Attachment | `Image | `Link | `Reaction ]
3232+3333+let has_operand_to_string = function
3434+ | `Attachment -> "attachment"
3535+ | `Image -> "image"
3636+ | `Link -> "link"
3737+ | `Reaction -> "reaction"
3838+3939+let has operand = make "has" (`String (has_operand_to_string operand))
4040+4141+let search query = make "search" (`String query)
4242+4343+let id msg_id = make "id" (`Int msg_id)
4444+let near msg_id = make "near" (`Int msg_id)
4545+4646+let dm emails = make "dm" (`Strings emails)
4747+let dm_including email = make "dm-including" (`String email)
4848+let group_pm_with = dm_including
4949+5050+let not_ filter = { filter with negated = true }
5151+5252+let to_json filters =
5353+ let meta = Jsont.Meta.none in
5454+ let make_string s = Jsont.String (s, meta) in
5555+ let make_member name value = ((name, meta), value) in
5656+ Jsont.Array
5757+ (List.map
5858+ (fun f ->
5959+ let operand_json =
6060+ match f.operand with
6161+ | `String s -> make_string s
6262+ | `Int i -> Jsont.Number (float_of_int i, meta)
6363+ | `Strings ss -> Jsont.Array (List.map make_string ss, meta)
6464+ in
6565+ let fields =
6666+ [ make_member "operator" (make_string f.operator);
6767+ make_member "operand" operand_json ]
6868+ in
6969+ let fields =
7070+ if f.negated then make_member "negated" (Jsont.Bool (true, meta)) :: fields else fields
7171+ in
7272+ Jsont.Object (fields, meta))
7373+ filters, meta)
7474+7575+let operand_to_json = function
7676+ | `String s -> Jsont.String (s, Jsont.Meta.none)
7777+ | `Int i -> Jsont.Number (float_of_int i, Jsont.Meta.none)
7878+ | `Strings ss ->
7979+ Jsont.Array
8080+ (List.map (fun s -> Jsont.String (s, Jsont.Meta.none)) ss, Jsont.Meta.none)
8181+8282+let operand_of_json = function
8383+ | Jsont.String (s, _) -> `String s
8484+ | Jsont.Number (f, _) -> `Int (int_of_float f)
8585+ | Jsont.Array (items, _) ->
8686+ `Strings
8787+ (List.filter_map
8888+ (function Jsont.String (s, _) -> Some s | _ -> None)
8989+ items)
9090+ | _ -> `String ""
9191+9292+let operand_jsont =
9393+ Jsont.json |> Jsont.map ~dec:operand_of_json ~enc:operand_to_json
9494+9595+let jsont =
9696+ let kind = "Narrow" in
9797+ let doc = "A narrow filter" in
9898+ let make operator operand negated = { operator; operand; negated } in
9999+ Jsont.Object.map ~kind ~doc make
100100+ |> Jsont.Object.mem "operator" Jsont.string ~enc:(fun t -> t.operator)
101101+ |> Jsont.Object.mem "operand" operand_jsont ~enc:(fun t -> t.operand)
102102+ |> Jsont.Object.mem "negated" Jsont.bool ~dec_absent:false ~enc:(fun t ->
103103+ t.negated)
104104+ |> Jsont.Object.finish
105105+106106+let list_jsont = Jsont.list jsont
107107+108108+let pp fmt t =
109109+ let neg = if t.negated then "-" else "" in
110110+ let operand =
111111+ match t.operand with
112112+ | `String s -> s
113113+ | `Int i -> string_of_int i
114114+ | `Strings ss -> String.concat "," ss
115115+ in
116116+ Format.fprintf fmt "%s%s:%s" neg t.operator operand
+112
lib/zulip/narrow.mli
···11+(** Type-safe narrow filters for message queries.
22+33+ Narrow filters constrain which messages are returned by the
44+ [Messages.get_messages] endpoint. This module provides a type-safe
55+ interface for constructing these filters.
66+77+ Example:
88+ {[
99+ let narrow = Narrow.[
1010+ stream "general";
1111+ topic "greetings";
1212+ is `Unread;
1313+ ] in
1414+ Messages.get_messages client ~narrow ()
1515+ ]} *)
1616+1717+(** {1 Filter Type} *)
1818+1919+type t
2020+(** A single narrow filter clause. *)
2121+2222+(** {1 Stream/Channel Filters} *)
2323+2424+val stream : string -> t
2525+(** [stream name] filters to messages in the given stream/channel. *)
2626+2727+val stream_id : int -> t
2828+(** [stream_id id] filters to messages in the stream with the given ID. *)
2929+3030+val topic : string -> t
3131+(** [topic name] filters to messages with the given topic/subject. *)
3232+3333+val channel : string -> t
3434+(** Alias for [stream]. *)
3535+3636+(** {1 Sender Filters} *)
3737+3838+val sender : string -> t
3939+(** [sender email] filters to messages from the given sender. *)
4040+4141+val sender_id : int -> t
4242+(** [sender_id id] filters to messages from the sender with the given user ID. *)
4343+4444+(** {1 Message Property Filters} *)
4545+4646+type is_operand =
4747+ [ `Alerted (** Messages containing alert words *)
4848+ | `Dm (** Direct messages (private messages) *)
4949+ | `Mentioned (** Messages where user is mentioned *)
5050+ | `Private (** Alias for [`Dm] *)
5151+ | `Resolved (** Topics marked as resolved *)
5252+ | `Starred (** Starred messages *)
5353+ | `Unread (** Unread messages *)
5454+ ]
5555+5656+val is : is_operand -> t
5757+(** [is operand] filters by message property. *)
5858+5959+type has_operand =
6060+ [ `Attachment (** Messages with file attachments *)
6161+ | `Image (** Messages containing images *)
6262+ | `Link (** Messages containing links *)
6363+ | `Reaction (** Messages with emoji reactions *)
6464+ ]
6565+6666+val has : has_operand -> t
6767+(** [has operand] filters to messages that have the given content type. *)
6868+6969+(** {1 Search} *)
7070+7171+val search : string -> t
7272+(** [search query] full-text search within messages. *)
7373+7474+(** {1 Message ID Filters} *)
7575+7676+val id : int -> t
7777+(** [id msg_id] filters to the specific message with the given ID. *)
7878+7979+val near : int -> t
8080+(** [near msg_id] centers results around the given message ID. *)
8181+8282+(** {1 Direct Message Filters} *)
8383+8484+val dm : string list -> t
8585+(** [dm emails] filters to direct messages with exactly these participants. *)
8686+8787+val dm_including : string -> t
8888+(** [dm_including email] filters to direct messages that include this user. *)
8989+9090+val group_pm_with : string -> t
9191+(** [group_pm_with email] filters to group DMs including this user (deprecated, use [dm_including]). *)
9292+9393+(** {1 Negation} *)
9494+9595+val not_ : t -> t
9696+(** [not_ filter] negates a filter.
9797+ Example: [not_ (stream "general")] excludes the "general" stream. *)
9898+9999+(** {1 Encoding} *)
100100+101101+val to_json : t list -> Jsont.json
102102+(** Encode a list of filters to JSON for the API request. *)
103103+104104+val jsont : t Jsont.t
105105+(** Jsont codec for a single filter. *)
106106+107107+val list_jsont : t list Jsont.t
108108+(** Jsont codec for a list of filters. *)
109109+110110+(** {1 Pretty Printing} *)
111111+112112+val pp : Format.formatter -> t -> unit
+75
lib/zulip/presence.mli
···11+(** User presence information for the Zulip API.
22+33+ Track online/offline status of users in the organization. *)
44+55+(** {1 Presence Types} *)
66+77+type status =
88+ | Active (** User is currently active *)
99+ | Idle (** User is idle *)
1010+ | Offline (** User is offline *)
1111+1212+type client_presence = {
1313+ status : status;
1414+ timestamp : float;
1515+ client : string; (** Client name (e.g., "website", "ZulipMobile") *)
1616+ pushable : bool; (** Whether push notifications can be sent *)
1717+}
1818+(** Presence information from a single client. *)
1919+2020+type user_presence = {
2121+ aggregated : client_presence option; (** Aggregated presence across clients *)
2222+ clients : (string * client_presence) list; (** Per-client presence *)
2323+}
2424+(** A user's presence information. *)
2525+2626+(** {1 Querying Presence} *)
2727+2828+val get_user : Client.t -> user_id:int -> user_presence
2929+(** Get presence information for a specific user.
3030+ @raise Eio.Io on failure *)
3131+3232+val get_user_by_email : Client.t -> email:string -> user_presence
3333+(** Get presence information for a user by email.
3434+ @raise Eio.Io on failure *)
3535+3636+val get_all : Client.t -> (int * user_presence) list
3737+(** Get presence information for all users in the organization.
3838+ Returns a list of (user_id, presence) pairs.
3939+ @raise Eio.Io on failure *)
4040+4141+(** {1 Updating Presence} *)
4242+4343+val update :
4444+ Client.t ->
4545+ status:status ->
4646+ ?ping_only:bool ->
4747+ ?new_user_input:bool ->
4848+ unit ->
4949+ unit
5050+(** Update the current user's presence status.
5151+5252+ @param status The presence status to set
5353+ @param ping_only Only send a ping without changing status
5454+ @param new_user_input Whether there was new user input
5555+ @raise Eio.Io on failure *)
5656+5757+(** {1 JSON Codecs} *)
5858+5959+val status_jsont : status Jsont.t
6060+6161+val client_presence_jsont : client_presence Jsont.t
6262+6363+val user_presence_jsont : user_presence Jsont.t
6464+6565+(** {1 Conversion} *)
6666+6767+val status_to_string : status -> string
6868+6969+val status_of_string : string -> status option
7070+7171+(** {1 Pretty Printing} *)
7272+7373+val pp_status : Format.formatter -> status -> unit
7474+7575+val pp_user_presence : Format.formatter -> user_presence -> unit
+176
lib/zulip/server.mli
···11+(** Server information and settings for the Zulip API.
22+33+ This module provides access to server-level information including
44+ version, feature level, and authentication methods. *)
55+66+(** {1 Server Settings} *)
77+88+type authentication_method = {
99+ password : bool; (** Password authentication enabled *)
1010+ dev : bool; (** Development authentication enabled *)
1111+ email : bool; (** Email authentication enabled *)
1212+ ldap : bool; (** LDAP authentication enabled *)
1313+ remoteuser : bool; (** Remote user authentication enabled *)
1414+ github : bool; (** GitHub OAuth enabled *)
1515+ azuread : bool; (** Azure AD OAuth enabled *)
1616+ gitlab : bool; (** GitLab OAuth enabled *)
1717+ apple : bool; (** Apple OAuth enabled *)
1818+ google : bool; (** Google OAuth enabled *)
1919+ saml : bool; (** SAML SSO enabled *)
2020+ openid_connect : bool; (** OpenID Connect enabled *)
2121+}
2222+(** Enabled authentication methods on the server. *)
2323+2424+type external_authentication_method = {
2525+ name : string; (** Method name *)
2626+ display_name : string; (** Display name for UI *)
2727+ display_icon : string option; (** Icon URL *)
2828+ login_url : string; (** Login URL *)
2929+ signup_url : string; (** Signup URL *)
3030+}
3131+(** External authentication method configuration. *)
3232+3333+type t = {
3434+ zulip_version : string; (** Server version string *)
3535+ zulip_feature_level : int; (** API feature level *)
3636+ zulip_merge_base : string option; (** Git merge base (for dev servers) *)
3737+ push_notifications_enabled : bool; (** Push notifications available *)
3838+ is_incompatible : bool; (** Client incompatible with server *)
3939+ email_auth_enabled : bool; (** Email auth enabled *)
4040+ require_email_format_usernames : bool; (** Usernames must be emails *)
4141+ realm_uri : string; (** Organization URL *)
4242+ realm_name : string; (** Organization name *)
4343+ realm_icon : string; (** Organization icon URL *)
4444+ realm_description : string; (** Organization description *)
4545+ realm_web_public_access_enabled : bool; (** Web public access enabled *)
4646+ authentication_methods : authentication_method;
4747+ external_authentication_methods : external_authentication_method list;
4848+}
4949+(** Server settings response. *)
5050+5151+val get_settings : Client.t -> t
5252+(** Get server settings.
5353+ @raise Eio.Io on failure *)
5454+5555+val get_settings_json : Client.t -> Jsont.json
5656+(** Get server settings as raw JSON.
5757+ @raise Eio.Io on failure *)
5858+5959+(** {1 Feature Level Checks} *)
6060+6161+val feature_level : Client.t -> int
6262+(** Get the server's feature level.
6363+ Useful for checking API compatibility.
6464+ @raise Eio.Io on failure *)
6565+6666+val supports_feature : Client.t -> level:int -> bool
6767+(** Check if the server supports a given feature level.
6868+ @raise Eio.Io on failure *)
6969+7070+(** {1 Linkifiers} *)
7171+7272+type linkifier = {
7373+ id : int; (** Linkifier ID *)
7474+ pattern : string; (** Regex pattern *)
7575+ url_template : string; (** URL template *)
7676+}
7777+(** A realm linkifier (auto-link rule). *)
7878+7979+val get_linkifiers : Client.t -> linkifier list
8080+(** Get all linkifiers for the organization.
8181+ @raise Eio.Io on failure *)
8282+8383+val add_linkifier : Client.t -> pattern:string -> url_template:string -> int
8484+(** Add a new linkifier.
8585+ @return The ID of the created linkifier
8686+ @raise Eio.Io on failure *)
8787+8888+val update_linkifier :
8989+ Client.t -> filter_id:int -> pattern:string -> url_template:string -> unit
9090+(** Update an existing linkifier.
9191+ @raise Eio.Io on failure *)
9292+9393+val delete_linkifier : Client.t -> filter_id:int -> unit
9494+(** Delete a linkifier.
9595+ @raise Eio.Io on failure *)
9696+9797+(** {1 Custom Emoji} *)
9898+9999+type emoji = {
100100+ id : string; (** Emoji ID *)
101101+ name : string; (** Emoji name *)
102102+ source_url : string; (** Source image URL *)
103103+ deactivated : bool; (** Whether emoji is deactivated *)
104104+ author_id : int option; (** User ID of uploader *)
105105+}
106106+(** A custom realm emoji. *)
107107+108108+val get_emoji : Client.t -> emoji list
109109+(** Get all custom emoji for the organization.
110110+ @raise Eio.Io on failure *)
111111+112112+val upload_emoji : Client.t -> name:string -> filename:string -> unit
113113+(** Upload a new custom emoji.
114114+ @raise Eio.Io on failure *)
115115+116116+val deactivate_emoji : Client.t -> name:string -> unit
117117+(** Deactivate a custom emoji.
118118+ @raise Eio.Io on failure *)
119119+120120+(** {1 Profile Fields} *)
121121+122122+type profile_field_type =
123123+ | Short_text (** Single line text *)
124124+ | Long_text (** Multi-line text *)
125125+ | Choice (** Select from options *)
126126+ | Date (** Date picker *)
127127+ | Link (** URL *)
128128+ | User (** User reference *)
129129+ | External_account (** External account link *)
130130+ | Pronouns (** Pronouns field *)
131131+132132+type profile_field = {
133133+ id : int;
134134+ field_type : profile_field_type;
135135+ order : int;
136136+ name : string;
137137+ hint : string;
138138+ field_data : Jsont.json;
139139+ display_in_profile_summary : bool option;
140140+}
141141+(** A custom profile field definition. *)
142142+143143+val get_profile_fields : Client.t -> profile_field list
144144+(** Get all custom profile fields.
145145+ @raise Eio.Io on failure *)
146146+147147+val create_profile_field :
148148+ Client.t ->
149149+ field_type:profile_field_type ->
150150+ name:string ->
151151+ ?hint:string ->
152152+ ?field_data:Jsont.json ->
153153+ unit ->
154154+ int
155155+(** Create a new custom profile field.
156156+ @return The ID of the created field
157157+ @raise Eio.Io on failure *)
158158+159159+val update_profile_field :
160160+ Client.t ->
161161+ field_id:int ->
162162+ ?name:string ->
163163+ ?hint:string ->
164164+ ?field_data:Jsont.json ->
165165+ unit ->
166166+ unit
167167+(** Update a custom profile field.
168168+ @raise Eio.Io on failure *)
169169+170170+val delete_profile_field : Client.t -> field_id:int -> unit
171171+(** Delete a custom profile field.
172172+ @raise Eio.Io on failure *)
173173+174174+val reorder_profile_fields : Client.t -> order:int list -> unit
175175+(** Reorder custom profile fields.
176176+ @raise Eio.Io on failure *)
+44
lib/zulip/typing.mli
···11+(** Typing notifications for the Zulip API.
22+33+ Send typing start/stop notifications to indicate that the user
44+ is composing a message. *)
55+66+(** {1 Typing Status Operations} *)
77+88+type op =
99+ | Start (** User started typing *)
1010+ | Stop (** User stopped typing *)
1111+1212+(** {1 Direct Messages} *)
1313+1414+val set_dm :
1515+ Client.t -> op:op -> user_ids:int list -> unit
1616+(** Set typing status for a direct message conversation.
1717+1818+ @param op Whether typing has started or stopped
1919+ @param user_ids List of user IDs in the conversation
2020+ @raise Eio.Io on failure *)
2121+2222+(** {1 Channel Messages} *)
2323+2424+val set_channel :
2525+ Client.t -> op:op -> stream_id:int -> topic:string -> unit
2626+(** Set typing status in a channel topic.
2727+2828+ @param op Whether typing has started or stopped
2929+ @param stream_id The channel's stream ID
3030+ @param topic The topic name
3131+ @raise Eio.Io on failure *)
3232+3333+(** {1 Legacy API} *)
3434+3535+val set :
3636+ Client.t ->
3737+ op:op ->
3838+ to_:[ `User_ids of int list | `Stream of int * string ] ->
3939+ unit
4040+(** Set typing status (unified interface).
4141+4242+ @param op Whether typing has started or stopped
4343+ @param to_ Either user IDs for DM or (stream_id, topic) for channel
4444+ @raise Eio.Io on failure *)
···33 This module represents user information from the Zulip API.
44 Use {!jsont} with Bytesrw-eio for wire serialization. *)
5566+(** {1 User Type} *)
77+68type t
99+(** A Zulip user. *)
1010+1111+(** {1 Construction} *)
712813val create :
914 email:string ->
···1217 ?delivery_email:string ->
1318 ?is_active:bool ->
1419 ?is_admin:bool ->
2020+ ?is_owner:bool ->
2121+ ?is_guest:bool ->
2222+ ?is_billing_admin:bool ->
1523 ?is_bot:bool ->
2424+ ?bot_type:int ->
2525+ ?bot_owner_id:int ->
2626+ ?avatar_url:string ->
2727+ ?avatar_version:int ->
2828+ ?timezone:string ->
2929+ ?date_joined:string ->
3030+ ?role:int ->
1631 unit ->
1732 t
18333434+(** {1 Accessors} *)
3535+1936val email : t -> string
3737+(** User's email address (may be delivery_email if visible). *)
3838+2039val full_name : t -> string
4040+(** User's full display name. *)
4141+2142val user_id : t -> int option
4343+(** Server-assigned user ID. *)
4444+2245val delivery_email : t -> string option
4646+(** User's actual email (if visible to current user). *)
4747+2348val is_active : t -> bool
4949+(** Whether the user account is active. *)
5050+2451val is_admin : t -> bool
5252+(** Whether the user is an organization admin. *)
5353+5454+val is_owner : t -> bool
5555+(** Whether the user is an organization owner. *)
5656+5757+val is_guest : t -> bool
5858+(** Whether the user is a guest. *)
5959+6060+val is_billing_admin : t -> bool
6161+(** Whether the user is a billing admin. *)
6262+2563val is_bot : t -> bool
6464+(** Whether the user is a bot. *)
6565+6666+val bot_type : t -> int option
6767+(** Bot type (1=generic, 2=incoming webhook, 3=outgoing webhook, 4=embedded). *)
26682727-(** Jsont codec for the user type *)
6969+val bot_owner_id : t -> int option
7070+(** User ID of the bot's owner. *)
7171+7272+val avatar_url : t -> string option
7373+(** URL for the user's avatar. *)
7474+7575+val avatar_version : t -> int option
7676+(** Version number of the avatar (for cache busting). *)
7777+7878+val timezone : t -> string option
7979+(** User's timezone string. *)
8080+8181+val date_joined : t -> string option
8282+(** ISO 8601 datetime when user joined. *)
8383+8484+val role : t -> int option
8585+(** User's role (100=owner, 200=admin, 300=moderator, 400=member, 600=guest). *)
8686+8787+(** {1 Role Constants} *)
8888+8989+val role_owner : int
9090+val role_admin : int
9191+val role_moderator : int
9292+val role_member : int
9393+val role_guest : int
9494+9595+(** {1 JSON Codec} *)
9696+2897val jsont : t Jsont.t
9898+(** Jsont codec for the user type. *)
9999+100100+(** {1 Pretty Printing} *)
2910130102val pp : Format.formatter -> t -> unit
+100
lib/zulip/user_group.mli
···11+(** User groups for the Zulip API.
22+33+ User groups allow organizing users and setting permissions. *)
44+55+(** {1 User Group Type} *)
66+77+type t = {
88+ id : int; (** Group ID *)
99+ name : string; (** Group name *)
1010+ description : string; (** Group description *)
1111+ members : int list; (** User IDs of group members *)
1212+ direct_subgroup_ids : int list; (** IDs of direct subgroups *)
1313+ is_system_group : bool; (** Whether this is a system-managed group *)
1414+ can_mention_group : int; (** Group ID that can mention this group *)
1515+}
1616+(** A user group. *)
1717+1818+(** {1 Listing Groups} *)
1919+2020+val list : Client.t -> t list
2121+(** Get all user groups in the organization.
2222+ @raise Eio.Io on failure *)
2323+2424+(** {1 Creating Groups} *)
2525+2626+val create :
2727+ Client.t ->
2828+ name:string ->
2929+ description:string ->
3030+ members:int list ->
3131+ ?can_mention_group:int ->
3232+ unit ->
3333+ int
3434+(** Create a new user group.
3535+ @return The ID of the created group
3636+ @raise Eio.Io on failure *)
3737+3838+(** {1 Updating Groups} *)
3939+4040+val update :
4141+ Client.t ->
4242+ group_id:int ->
4343+ ?name:string ->
4444+ ?description:string ->
4545+ ?can_mention_group:int ->
4646+ unit ->
4747+ unit
4848+(** Update a user group's properties.
4949+ @raise Eio.Io on failure *)
5050+5151+val update_members :
5252+ Client.t -> group_id:int -> ?add:int list -> ?remove:int list -> unit -> unit
5353+(** Update user group membership.
5454+5555+ @param add User IDs to add to the group
5656+ @param remove User IDs to remove from the group
5757+ @raise Eio.Io on failure *)
5858+5959+val update_subgroups :
6060+ Client.t -> group_id:int -> ?add:int list -> ?remove:int list -> unit -> unit
6161+(** Update user group subgroups.
6262+6363+ @param add Subgroup IDs to add
6464+ @param remove Subgroup IDs to remove
6565+ @raise Eio.Io on failure *)
6666+6767+(** {1 Deleting Groups} *)
6868+6969+val delete : Client.t -> group_id:int -> unit
7070+(** Delete a user group.
7171+ @raise Eio.Io on failure *)
7272+7373+(** {1 Membership Queries} *)
7474+7575+val get_members : Client.t -> group_id:int -> int list
7676+(** Get the members of a user group.
7777+ @raise Eio.Io on failure *)
7878+7979+val is_member : Client.t -> group_id:int -> user_id:int -> bool
8080+(** Check if a user is a member of a group.
8181+ @raise Eio.Io on failure *)
8282+8383+(** {1 Subgroup Queries} *)
8484+8585+val get_subgroups : Client.t -> group_id:int -> int list
8686+(** Get the direct subgroups of a user group.
8787+ @raise Eio.Io on failure *)
8888+8989+val is_subgroup : Client.t -> group_id:int -> subgroup_id:int -> bool
9090+(** Check if a group is a subgroup of another.
9191+ @raise Eio.Io on failure *)
9292+9393+(** {1 JSON Codec} *)
9494+9595+val jsont : t Jsont.t
9696+(** Jsont codec for user groups. *)
9797+9898+(** {1 Pretty Printing} *)
9999+100100+val pp : Format.formatter -> t -> unit
+365-36
lib/zulip/users.ml
···11let list client =
22- (* Define response codec *)
32 let response_codec =
43 Jsont.Object.(
54 map ~kind:"UsersResponse" (fun members -> members)
···1413 (Error.make ~code:(Other "json_parse") ~message:msg ())
1514 "parsing users list"
16151717-let get client ~email =
1818- (* Define a codec for the response that wraps the user in a "user" field *)
1919- let user_response_codec =
1616+let list_all client ?client_gravatar ?include_custom_profile_fields () =
1717+ let params =
1818+ List.filter_map Fun.id
1919+ [
2020+ Option.map
2121+ (fun v -> ("client_gravatar", string_of_bool v))
2222+ client_gravatar;
2323+ Option.map
2424+ (fun v -> ("include_custom_profile_fields", string_of_bool v))
2525+ include_custom_profile_fields;
2626+ ]
2727+ in
2828+ let response_codec =
2029 Jsont.Object.(
2121- map ~kind:"UserResponse" (fun user -> user)
2222- |> mem "user" User.jsont ~enc:(fun x -> x)
3030+ map ~kind:"UsersResponse" (fun members -> members)
3131+ |> mem "members" (Jsont.list User.jsont) ~enc:(fun x -> x)
2332 |> finish)
2433 in
2534 let json =
3535+ Client.request client ~method_:`GET ~path:"/api/v1/users" ~params ()
3636+ in
3737+ match Encode.from_json response_codec json with
3838+ | Ok users -> users
3939+ | Error msg ->
4040+ Error.raise_with_context
4141+ (Error.make ~code:(Other "json_parse") ~message:msg ())
4242+ "parsing users list"
4343+4444+let user_response_codec =
4545+ Jsont.Object.(
4646+ map ~kind:"UserResponse" (fun user -> user)
4747+ |> mem "user" User.jsont ~enc:(fun x -> x)
4848+ |> finish)
4949+5050+let get client ~email =
5151+ let json =
2652 Client.request client ~method_:`GET ~path:("/api/v1/users/" ^ email) ()
2753 in
2854 match Encode.from_json user_response_codec json with
2955 | Ok user -> user
3030- | Error _ ->
3131- (* Fallback: try parsing the whole response as a user *)
3232- (match Encode.from_json User.jsont json with
5656+ | Error _ -> (
5757+ match Encode.from_json User.jsont json with
3358 | Ok user -> user
3459 | Error msg ->
3560 Error.raise_with_context
3661 (Error.make ~code:(Other "json_parse") ~message:msg ())
3762 "parsing user %s" email)
38633939-let get_by_id client ~user_id =
4040- (* Define a codec for the response that wraps the user in a "user" field *)
4141- let user_response_codec =
4242- Jsont.Object.(
4343- map ~kind:"UserResponse" (fun user -> user)
4444- |> mem "user" User.jsont ~enc:(fun x -> x)
4545- |> finish)
6464+let get_by_id client ~user_id ?include_custom_profile_fields () =
6565+ let params =
6666+ List.filter_map Fun.id
6767+ [
6868+ Option.map
6969+ (fun v -> ("include_custom_profile_fields", string_of_bool v))
7070+ include_custom_profile_fields;
7171+ ]
4672 in
4773 let json =
4874 Client.request client ~method_:`GET
4975 ~path:("/api/v1/users/" ^ string_of_int user_id)
5050- ()
7676+ ~params ()
5177 in
5278 match Encode.from_json user_response_codec json with
5379 | Ok user -> user
5454- | Error _ ->
5555- (* Fallback: try parsing the whole response as a user *)
5656- (match Encode.from_json User.jsont json with
8080+ | Error _ -> (
8181+ match Encode.from_json User.jsont json with
5782 | Ok user -> user
5883 | Error msg ->
5984 Error.raise_with_context
6085 (Error.make ~code:(Other "json_parse") ~message:msg ())
6186 "parsing user id %d" user_id)
62876363-(* Request type for create_user *)
6464-module Create_user_request = struct
6565- type t = { email : string; full_name : string }
8888+let me client =
8989+ let json = Client.request client ~method_:`GET ~path:"/api/v1/users/me" () in
9090+ match Encode.from_json User.jsont json with
9191+ | Ok user -> user
9292+ | Error msg ->
9393+ Error.raise_with_context
9494+ (Error.make ~code:(Other "json_parse") ~message:msg ())
9595+ "parsing current user"
9696+9797+let me_pointer client =
9898+ let response_codec =
9999+ Jsont.Object.(
100100+ map ~kind:"PointerResponse" (fun pointer -> pointer)
101101+ |> mem "pointer" Jsont.int ~enc:(fun x -> x)
102102+ |> finish)
103103+ in
104104+ let json =
105105+ Client.request client ~method_:`GET ~path:"/api/v1/users/me/pointer" ()
106106+ in
107107+ match Encode.from_json response_codec json with
108108+ | Ok pointer -> pointer
109109+ | Error msg ->
110110+ Error.raise_with_context
111111+ (Error.make ~code:(Other "json_parse") ~message:msg ())
112112+ "getting pointer"
113113+114114+let update_me_pointer client ~pointer =
115115+ let params = [ ("pointer", string_of_int pointer) ] in
116116+ let _response =
117117+ Client.request client ~method_:`POST ~path:"/api/v1/users/me/pointer"
118118+ ~params ()
119119+ in
120120+ ()
121121+122122+let create client ~email ~full_name ~password =
123123+ let params =
124124+ [ ("email", email); ("full_name", full_name); ("password", password) ]
125125+ in
126126+ let _response =
127127+ Client.request client ~method_:`POST ~path:"/api/v1/users" ~params ()
128128+ in
129129+ ()
130130+131131+let update client ~user_id ?full_name ?role () =
132132+ let params =
133133+ List.filter_map Fun.id
134134+ [
135135+ Option.map (fun v -> ("full_name", v)) full_name;
136136+ Option.map (fun v -> ("role", string_of_int v)) role;
137137+ ]
138138+ in
139139+ let _response =
140140+ Client.request client ~method_:`PATCH
141141+ ~path:("/api/v1/users/" ^ string_of_int user_id)
142142+ ~params ()
143143+ in
144144+ ()
145145+146146+let deactivate client ~user_id =
147147+ let _response =
148148+ Client.request client ~method_:`DELETE
149149+ ~path:("/api/v1/users/" ^ string_of_int user_id)
150150+ ()
151151+ in
152152+ ()
153153+154154+let reactivate client ~user_id =
155155+ let _response =
156156+ Client.request client ~method_:`POST
157157+ ~path:("/api/v1/users/" ^ string_of_int user_id ^ "/reactivate")
158158+ ()
159159+ in
160160+ ()
161161+162162+let get_alert_words client =
163163+ let response_codec =
164164+ Jsont.Object.(
165165+ map ~kind:"AlertWordsResponse" (fun words -> words)
166166+ |> mem "alert_words" (Jsont.list Jsont.string) ~enc:(fun x -> x)
167167+ |> finish)
168168+ in
169169+ let json =
170170+ Client.request client ~method_:`GET ~path:"/api/v1/users/me/alert_words" ()
171171+ in
172172+ match Encode.from_json response_codec json with
173173+ | Ok words -> words
174174+ | Error msg ->
175175+ Error.raise_with_context
176176+ (Error.make ~code:(Other "json_parse") ~message:msg ())
177177+ "getting alert words"
178178+179179+let add_alert_words client ~words =
180180+ let params =
181181+ [ ("alert_words", Encode.to_json_string (Jsont.list Jsont.string) words) ]
182182+ in
183183+ let response_codec =
184184+ Jsont.Object.(
185185+ map ~kind:"AlertWordsResponse" (fun words -> words)
186186+ |> mem "alert_words" (Jsont.list Jsont.string) ~enc:(fun x -> x)
187187+ |> finish)
188188+ in
189189+ let json =
190190+ Client.request client ~method_:`POST ~path:"/api/v1/users/me/alert_words"
191191+ ~params ()
192192+ in
193193+ match Encode.from_json response_codec json with
194194+ | Ok words -> words
195195+ | Error msg ->
196196+ Error.raise_with_context
197197+ (Error.make ~code:(Other "json_parse") ~message:msg ())
198198+ "adding alert words"
199199+200200+let remove_alert_words client ~words =
201201+ let params =
202202+ [ ("alert_words", Encode.to_json_string (Jsont.list Jsont.string) words) ]
203203+ in
204204+ let response_codec =
205205+ Jsont.Object.(
206206+ map ~kind:"AlertWordsResponse" (fun words -> words)
207207+ |> mem "alert_words" (Jsont.list Jsont.string) ~enc:(fun x -> x)
208208+ |> finish)
209209+ in
210210+ let json =
211211+ Client.request client ~method_:`DELETE ~path:"/api/v1/users/me/alert_words"
212212+ ~params ()
213213+ in
214214+ match Encode.from_json response_codec json with
215215+ | Ok words -> words
216216+ | Error msg ->
217217+ Error.raise_with_context
218218+ (Error.make ~code:(Other "json_parse") ~message:msg ())
219219+ "removing alert words"
220220+221221+type status_emoji = {
222222+ emoji_name : string;
223223+ emoji_code : string option;
224224+ reaction_type : string option;
225225+}
226226+227227+let get_status client ~user_id =
228228+ Client.request client ~method_:`GET
229229+ ~path:("/api/v1/users/" ^ string_of_int user_id ^ "/status")
230230+ ()
662316767- let codec =
232232+let update_status client ?status_text ?away ?emoji () =
233233+ let params =
234234+ List.filter_map Fun.id
235235+ [
236236+ Option.map (fun v -> ("status_text", v)) status_text;
237237+ Option.map (fun v -> ("away", string_of_bool v)) away;
238238+ Option.map (fun e -> ("emoji_name", e.emoji_name)) emoji;
239239+ Option.bind emoji (fun e ->
240240+ Option.map (fun c -> ("emoji_code", c)) e.emoji_code);
241241+ Option.bind emoji (fun e ->
242242+ Option.map (fun t -> ("reaction_type", t)) e.reaction_type);
243243+ ]
244244+ in
245245+ let _response =
246246+ Client.request client ~method_:`POST ~path:"/api/v1/users/me/status" ~params
247247+ ()
248248+ in
249249+ ()
250250+251251+let get_settings client =
252252+ Client.request client ~method_:`GET ~path:"/api/v1/settings" ()
253253+254254+let update_settings client ?full_name ?email ?old_password ?new_password
255255+ ?twenty_four_hour_time ?dense_mode ?starred_message_counts
256256+ ?fluid_layout_width ?high_contrast_mode ?color_scheme ?translate_emoticons
257257+ ?default_language ?default_view ?escape_navigates_to_default_view
258258+ ?left_side_userlist ?emojiset ?demote_inactive_streams ?timezone
259259+ ?enable_stream_desktop_notifications ?enable_stream_email_notifications
260260+ ?enable_stream_push_notifications ?enable_stream_audible_notifications
261261+ ?notification_sound ?enable_desktop_notifications ?enable_sounds
262262+ ?email_notifications_batching_period_seconds
263263+ ?enable_offline_email_notifications ?enable_offline_push_notifications
264264+ ?enable_online_push_notifications ?enable_digest_emails
265265+ ?enable_marketing_emails ?enable_login_emails
266266+ ?message_content_in_email_notifications
267267+ ?pm_content_in_desktop_notifications ?wildcard_mentions_notify
268268+ ?desktop_icon_count_display ?realm_name_in_notifications ?presence_enabled
269269+ ?enter_sends () =
270270+ let params =
271271+ List.filter_map Fun.id
272272+ [
273273+ Option.map (fun v -> ("full_name", v)) full_name;
274274+ Option.map (fun v -> ("email", v)) email;
275275+ Option.map (fun v -> ("old_password", v)) old_password;
276276+ Option.map (fun v -> ("new_password", v)) new_password;
277277+ Option.map
278278+ (fun v -> ("twenty_four_hour_time", string_of_bool v))
279279+ twenty_four_hour_time;
280280+ Option.map (fun v -> ("dense_mode", string_of_bool v)) dense_mode;
281281+ Option.map
282282+ (fun v -> ("starred_message_counts", string_of_bool v))
283283+ starred_message_counts;
284284+ Option.map
285285+ (fun v -> ("fluid_layout_width", string_of_bool v))
286286+ fluid_layout_width;
287287+ Option.map
288288+ (fun v -> ("high_contrast_mode", string_of_bool v))
289289+ high_contrast_mode;
290290+ Option.map (fun v -> ("color_scheme", string_of_int v)) color_scheme;
291291+ Option.map
292292+ (fun v -> ("translate_emoticons", string_of_bool v))
293293+ translate_emoticons;
294294+ Option.map (fun v -> ("default_language", v)) default_language;
295295+ Option.map (fun v -> ("default_view", v)) default_view;
296296+ Option.map
297297+ (fun v -> ("escape_navigates_to_default_view", string_of_bool v))
298298+ escape_navigates_to_default_view;
299299+ Option.map
300300+ (fun v -> ("left_side_userlist", string_of_bool v))
301301+ left_side_userlist;
302302+ Option.map (fun v -> ("emojiset", v)) emojiset;
303303+ Option.map
304304+ (fun v -> ("demote_inactive_streams", string_of_int v))
305305+ demote_inactive_streams;
306306+ Option.map (fun v -> ("timezone", v)) timezone;
307307+ Option.map
308308+ (fun v -> ("enable_stream_desktop_notifications", string_of_bool v))
309309+ enable_stream_desktop_notifications;
310310+ Option.map
311311+ (fun v -> ("enable_stream_email_notifications", string_of_bool v))
312312+ enable_stream_email_notifications;
313313+ Option.map
314314+ (fun v -> ("enable_stream_push_notifications", string_of_bool v))
315315+ enable_stream_push_notifications;
316316+ Option.map
317317+ (fun v -> ("enable_stream_audible_notifications", string_of_bool v))
318318+ enable_stream_audible_notifications;
319319+ Option.map (fun v -> ("notification_sound", v)) notification_sound;
320320+ Option.map
321321+ (fun v -> ("enable_desktop_notifications", string_of_bool v))
322322+ enable_desktop_notifications;
323323+ Option.map (fun v -> ("enable_sounds", string_of_bool v)) enable_sounds;
324324+ Option.map
325325+ (fun v ->
326326+ ("email_notifications_batching_period_seconds", string_of_int v))
327327+ email_notifications_batching_period_seconds;
328328+ Option.map
329329+ (fun v -> ("enable_offline_email_notifications", string_of_bool v))
330330+ enable_offline_email_notifications;
331331+ Option.map
332332+ (fun v -> ("enable_offline_push_notifications", string_of_bool v))
333333+ enable_offline_push_notifications;
334334+ Option.map
335335+ (fun v -> ("enable_online_push_notifications", string_of_bool v))
336336+ enable_online_push_notifications;
337337+ Option.map
338338+ (fun v -> ("enable_digest_emails", string_of_bool v))
339339+ enable_digest_emails;
340340+ Option.map
341341+ (fun v -> ("enable_marketing_emails", string_of_bool v))
342342+ enable_marketing_emails;
343343+ Option.map
344344+ (fun v -> ("enable_login_emails", string_of_bool v))
345345+ enable_login_emails;
346346+ Option.map
347347+ (fun v -> ("message_content_in_email_notifications", string_of_bool v))
348348+ message_content_in_email_notifications;
349349+ Option.map
350350+ (fun v -> ("pm_content_in_desktop_notifications", string_of_bool v))
351351+ pm_content_in_desktop_notifications;
352352+ Option.map
353353+ (fun v -> ("wildcard_mentions_notify", string_of_bool v))
354354+ wildcard_mentions_notify;
355355+ Option.map
356356+ (fun v -> ("desktop_icon_count_display", string_of_int v))
357357+ desktop_icon_count_display;
358358+ Option.map
359359+ (fun v -> ("realm_name_in_notifications", string_of_bool v))
360360+ realm_name_in_notifications;
361361+ Option.map
362362+ (fun v -> ("presence_enabled", string_of_bool v))
363363+ presence_enabled;
364364+ Option.map (fun v -> ("enter_sends", string_of_bool v)) enter_sends;
365365+ ]
366366+ in
367367+ Client.request client ~method_:`PATCH ~path:"/api/v1/settings" ~params ()
368368+369369+let get_attachments client =
370370+ Client.request client ~method_:`GET ~path:"/api/v1/attachments" ()
371371+372372+let delete_attachment client ~attachment_id =
373373+ let _response =
374374+ Client.request client ~method_:`DELETE
375375+ ~path:("/api/v1/attachments/" ^ string_of_int attachment_id)
376376+ ()
377377+ in
378378+ ()
379379+380380+let get_muted_users client =
381381+ let response_codec =
68382 Jsont.Object.(
6969- map ~kind:"CreateUserRequest" (fun email full_name -> { email; full_name })
7070- |> mem "email" Jsont.string ~enc:(fun r -> r.email)
7171- |> mem "full_name" Jsont.string ~enc:(fun r -> r.full_name)
383383+ map ~kind:"MutedUsersResponse" (fun users -> users)
384384+ |> mem "muted_users"
385385+ (Jsont.list
386386+ (Jsont.Object.(
387387+ map ~kind:"MutedUser" (fun id _ts -> id)
388388+ |> mem "id" Jsont.int ~enc:(fun x -> x)
389389+ |> mem "timestamp" Jsont.int ~dec_absent:0 ~enc:(fun _ -> 0)
390390+ |> finish)))
391391+ ~enc:(fun x -> x)
72392 |> finish)
7373-end
393393+ in
394394+ let json =
395395+ Client.request client ~method_:`GET ~path:"/api/v1/users/me/muted_users" ()
396396+ in
397397+ match Encode.from_json response_codec json with
398398+ | Ok users -> users
399399+ | Error msg ->
400400+ Error.raise_with_context
401401+ (Error.make ~code:(Other "json_parse") ~message:msg ())
402402+ "getting muted users"
744037575-let create_user client ~email ~full_name =
7676- let req = Create_user_request.{ email; full_name } in
7777- let body = Encode.to_form_urlencoded Create_user_request.codec req in
7878- let content_type = "application/x-www-form-urlencoded" in
404404+let mute_user client ~user_id =
79405 let _response =
8080- Client.request client ~method_:`POST ~path:"/api/v1/users" ~body
8181- ~content_type ()
406406+ Client.request client ~method_:`POST
407407+ ~path:("/api/v1/users/me/muted_users/" ^ string_of_int user_id)
408408+ ()
82409 in
83410 ()
844118585-let deactivate client ~email =
412412+let unmute_user client ~user_id =
86413 let _response =
8787- Client.request client ~method_:`DELETE ~path:("/api/v1/users/" ^ email) ()
414414+ Client.request client ~method_:`DELETE
415415+ ~path:("/api/v1/users/me/muted_users/" ^ string_of_int user_id)
416416+ ()
88417 in
89418 ()
+176-4
lib/zulip/users.mli
···33 All functions raise [Eio.Io] with [Error.E error] on failure.
44 Context is automatically added indicating the operation being performed. *)
5566+(** {1 Listing Users} *)
77+68val list : Client.t -> User.t list
79(** List all users in the organization.
810 @raise Eio.Io on failure *)
9111212+val list_all :
1313+ Client.t ->
1414+ ?client_gravatar:bool ->
1515+ ?include_custom_profile_fields:bool ->
1616+ unit ->
1717+ User.t list
1818+(** List all users with additional options.
1919+2020+ @param client_gravatar Whether to include gravatar URLs
2121+ @param include_custom_profile_fields Whether to include custom profile data
2222+ @raise Eio.Io on failure *)
2323+2424+(** {1 User Lookup} *)
2525+1026val get : Client.t -> email:string -> User.t
1127(** Get a user by email address.
1228 @raise Eio.Io on failure *)
13291414-val get_by_id : Client.t -> user_id:int -> User.t
3030+val get_by_id :
3131+ Client.t -> user_id:int -> ?include_custom_profile_fields:bool -> unit -> User.t
1532(** Get a user by their numeric ID.
1633 @raise Eio.Io on failure *)
17341818-val create_user : Client.t -> email:string -> full_name:string -> unit
3535+(** {1 Current User} *)
3636+3737+val me : Client.t -> User.t
3838+(** Get the currently authenticated user's profile.
3939+ @raise Eio.Io on failure *)
4040+4141+val me_pointer : Client.t -> int
4242+(** Get the current user's message read pointer.
4343+ @raise Eio.Io on failure *)
4444+4545+val update_me_pointer : Client.t -> pointer:int -> unit
4646+(** Update the current user's message read pointer.
4747+ @raise Eio.Io on failure *)
4848+4949+(** {1 Creating Users} *)
5050+5151+val create :
5252+ Client.t ->
5353+ email:string ->
5454+ full_name:string ->
5555+ password:string ->
5656+ unit
1957(** Create a new user.
2058 @raise Eio.Io on failure *)
21592222-val deactivate : Client.t -> email:string -> unit
2323-(** Deactivate a user account.
6060+(** {1 Updating Users} *)
6161+6262+val update :
6363+ Client.t ->
6464+ user_id:int ->
6565+ ?full_name:string ->
6666+ ?role:int ->
6767+ unit ->
6868+ unit
6969+(** Update a user's profile.
7070+ @raise Eio.Io on failure *)
7171+7272+(** {1 Deactivating/Reactivating Users} *)
7373+7474+val deactivate : Client.t -> user_id:int -> unit
7575+(** Deactivate a user account by user ID.
7676+ @raise Eio.Io on failure *)
7777+7878+val reactivate : Client.t -> user_id:int -> unit
7979+(** Reactivate a deactivated user account.
8080+ @raise Eio.Io on failure *)
8181+8282+(** {1 Alert Words} *)
8383+8484+val get_alert_words : Client.t -> string list
8585+(** Get the current user's alert words.
8686+ @raise Eio.Io on failure *)
8787+8888+val add_alert_words : Client.t -> words:string list -> string list
8989+(** Add alert words for the current user.
9090+ @return The updated list of alert words
9191+ @raise Eio.Io on failure *)
9292+9393+val remove_alert_words : Client.t -> words:string list -> string list
9494+(** Remove alert words for the current user.
9595+ @return The updated list of alert words
9696+ @raise Eio.Io on failure *)
9797+9898+(** {1 User Status} *)
9999+100100+(** User status types. *)
101101+type status_emoji = {
102102+ emoji_name : string;
103103+ emoji_code : string option;
104104+ reaction_type : string option;
105105+}
106106+107107+val get_status : Client.t -> user_id:int -> Jsont.json
108108+(** Get a user's status.
109109+ @raise Eio.Io on failure *)
110110+111111+val update_status :
112112+ Client.t ->
113113+ ?status_text:string ->
114114+ ?away:bool ->
115115+ ?emoji:status_emoji ->
116116+ unit ->
117117+ unit
118118+(** Update the current user's status.
119119+ @raise Eio.Io on failure *)
120120+121121+(** {1 User Settings} *)
122122+123123+val get_settings : Client.t -> Jsont.json
124124+(** Get the current user's settings.
125125+ @raise Eio.Io on failure *)
126126+127127+val update_settings :
128128+ Client.t ->
129129+ ?full_name:string ->
130130+ ?email:string ->
131131+ ?old_password:string ->
132132+ ?new_password:string ->
133133+ ?twenty_four_hour_time:bool ->
134134+ ?dense_mode:bool ->
135135+ ?starred_message_counts:bool ->
136136+ ?fluid_layout_width:bool ->
137137+ ?high_contrast_mode:bool ->
138138+ ?color_scheme:int ->
139139+ ?translate_emoticons:bool ->
140140+ ?default_language:string ->
141141+ ?default_view:string ->
142142+ ?escape_navigates_to_default_view:bool ->
143143+ ?left_side_userlist:bool ->
144144+ ?emojiset:string ->
145145+ ?demote_inactive_streams:int ->
146146+ ?timezone:string ->
147147+ ?enable_stream_desktop_notifications:bool ->
148148+ ?enable_stream_email_notifications:bool ->
149149+ ?enable_stream_push_notifications:bool ->
150150+ ?enable_stream_audible_notifications:bool ->
151151+ ?notification_sound:string ->
152152+ ?enable_desktop_notifications:bool ->
153153+ ?enable_sounds:bool ->
154154+ ?email_notifications_batching_period_seconds:int ->
155155+ ?enable_offline_email_notifications:bool ->
156156+ ?enable_offline_push_notifications:bool ->
157157+ ?enable_online_push_notifications:bool ->
158158+ ?enable_digest_emails:bool ->
159159+ ?enable_marketing_emails:bool ->
160160+ ?enable_login_emails:bool ->
161161+ ?message_content_in_email_notifications:bool ->
162162+ ?pm_content_in_desktop_notifications:bool ->
163163+ ?wildcard_mentions_notify:bool ->
164164+ ?desktop_icon_count_display:int ->
165165+ ?realm_name_in_notifications:bool ->
166166+ ?presence_enabled:bool ->
167167+ ?enter_sends:bool ->
168168+ unit ->
169169+ Jsont.json
170170+(** Update the current user's settings.
171171+ @return JSON with the updated settings
172172+ @raise Eio.Io on failure *)
173173+174174+(** {1 Attachments} *)
175175+176176+val get_attachments : Client.t -> Jsont.json
177177+(** Get all attachments uploaded by the current user.
178178+ @raise Eio.Io on failure *)
179179+180180+val delete_attachment : Client.t -> attachment_id:int -> unit
181181+(** Delete an attachment.
182182+ @raise Eio.Io on failure *)
183183+184184+(** {1 Muted Users} *)
185185+186186+val get_muted_users : Client.t -> int list
187187+(** Get the list of muted user IDs.
188188+ @raise Eio.Io on failure *)
189189+190190+val mute_user : Client.t -> user_id:int -> unit
191191+(** Mute a user.
192192+ @raise Eio.Io on failure *)
193193+194194+val unmute_user : Client.t -> user_id:int -> unit
195195+(** Unmute a user.
24196 @raise Eio.Io on failure *)