···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Apple Mail flag colors.
77+88+ See {{:https://datatracker.ietf.org/doc/draft-ietf-mailmaint-messageflag-mailboxattribute#section-3}
99+ draft-ietf-mailmaint-messageflag-mailboxattribute Section 3}.
1010+1111+ The Apple Mail flag color encoding uses three keywords to represent
1212+ colors as a 3-bit pattern:
1313+ - [$MailFlagBit0]: bit 0
1414+ - [$MailFlagBit1]: bit 1
1515+ - [$MailFlagBit2]: bit 2
1616+1717+ Bit patterns (bit0, bit1, bit2):
1818+ - Red: (false, false, false) = 000
1919+ - Orange: (true, false, false) = 100
2020+ - Yellow: (false, true, false) = 010
2121+ - Green: (true, true, false) = 110
2222+ - Blue: (false, false, true) = 001
2323+ - Purple: (true, false, true) = 101
2424+ - Gray: (false, true, true) = 011
2525+ - 111: undefined *)
2626+2727+type t =
2828+ | Red (** Bit pattern: 000 *)
2929+ | Orange (** Bit pattern: 100 *)
3030+ | Yellow (** Bit pattern: 010 *)
3131+ | Green (** Bit pattern: 110 *)
3232+ | Blue (** Bit pattern: 001 *)
3333+ | Purple (** Bit pattern: 101 *)
3434+ | Gray (** Bit pattern: 011 *)
3535+3636+let to_bits = function
3737+ | Red -> (false, false, false) (* 000 *)
3838+ | Orange -> (true, false, false) (* 100 *)
3939+ | Yellow -> (false, true, false) (* 010 *)
4040+ | Green -> (true, true, false) (* 110 *)
4141+ | Blue -> (false, false, true) (* 001 *)
4242+ | Purple -> (true, false, true) (* 101 *)
4343+ | Gray -> (false, true, true) (* 011 *)
4444+4545+let of_bits = function
4646+ | (false, false, false) -> Some Red (* 000 *)
4747+ | (true, false, false) -> Some Orange (* 100 *)
4848+ | (false, true, false) -> Some Yellow (* 010 *)
4949+ | (true, true, false) -> Some Green (* 110 *)
5050+ | (false, false, true) -> Some Blue (* 001 *)
5151+ | (true, false, true) -> Some Purple (* 101 *)
5252+ | (false, true, true) -> Some Gray (* 011 *)
5353+ | (true, true, true) -> None (* 111 - undefined *)
5454+5555+let to_keywords = function
5656+ | Red -> []
5757+ | Orange -> [ `MailFlagBit0 ]
5858+ | Yellow -> [ `MailFlagBit1 ]
5959+ | Green -> [ `MailFlagBit0; `MailFlagBit1 ]
6060+ | Blue -> [ `MailFlagBit2 ]
6161+ | Purple -> [ `MailFlagBit0; `MailFlagBit2 ]
6262+ | Gray -> [ `MailFlagBit1; `MailFlagBit2 ]
6363+6464+let of_keywords (keywords : [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list) =
6565+ let has k = List.exists (fun x -> x = k) keywords in
6666+ let bit0 = has `MailFlagBit0 in
6767+ let bit1 = has `MailFlagBit1 in
6868+ let bit2 = has `MailFlagBit2 in
6969+ (* If no bits are set, we cannot distinguish between "no flag color"
7070+ and "Red" (which is 000). Return None to indicate ambiguity. *)
7171+ if not bit0 && not bit1 && not bit2 then None
7272+ else of_bits (bit0, bit1, bit2)
7373+7474+let of_keywords_default_red (keywords : [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list) =
7575+ let has k = List.exists (fun x -> x = k) keywords in
7676+ let bit0 = has `MailFlagBit0 in
7777+ let bit1 = has `MailFlagBit1 in
7878+ let bit2 = has `MailFlagBit2 in
7979+ of_bits (bit0, bit1, bit2)
8080+8181+let to_string = function
8282+ | Red -> "red"
8383+ | Orange -> "orange"
8484+ | Yellow -> "yellow"
8585+ | Green -> "green"
8686+ | Blue -> "blue"
8787+ | Purple -> "purple"
8888+ | Gray -> "gray"
8989+9090+let of_string s =
9191+ match String.lowercase_ascii s with
9292+ | "red" -> Some Red
9393+ | "orange" -> Some Orange
9494+ | "yellow" -> Some Yellow
9595+ | "green" -> Some Green
9696+ | "blue" -> Some Blue
9797+ | "purple" -> Some Purple
9898+ | "gray" | "grey" -> Some Gray
9999+ | _ -> None
100100+101101+let pp ppf color = Format.pp_print_string ppf (to_string color)
+72
mail-flag/lib/flag_color.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Apple Mail flag colors.
77+88+ This module implements the Apple Mail flag color encoding using the
99+ [$MailFlagBit0], [$MailFlagBit1], and [$MailFlagBit2] keywords.
1010+1111+ See {{:https://datatracker.ietf.org/doc/draft-ietf-mailmaint-messageflag-mailboxattribute#section-3}
1212+ draft-ietf-mailmaint-messageflag-mailboxattribute Section 3}.
1313+1414+ Colors are encoded as a 3-bit pattern where each bit corresponds to
1515+ a keyword:
1616+ - Bit 0: [$MailFlagBit0]
1717+ - Bit 1: [$MailFlagBit1]
1818+ - Bit 2: [$MailFlagBit2]
1919+2020+ The bit patterns are:
2121+ - Red: 000 (no bits set)
2222+ - Orange: 100 (bit 0 only)
2323+ - Yellow: 010 (bit 1 only)
2424+ - Green: 110 (bits 0 and 1)
2525+ - Blue: 001 (bit 2 only)
2626+ - Purple: 101 (bits 0 and 2)
2727+ - Gray: 011 (bits 1 and 2)
2828+ - 111: undefined (all bits set) *)
2929+3030+type t =
3131+ | Red (** Bit pattern: 000 *)
3232+ | Orange (** Bit pattern: 100 *)
3333+ | Yellow (** Bit pattern: 010 *)
3434+ | Green (** Bit pattern: 110 *)
3535+ | Blue (** Bit pattern: 001 *)
3636+ | Purple (** Bit pattern: 101 *)
3737+ | Gray (** Bit pattern: 011 *)
3838+3939+val to_bits : t -> bool * bool * bool
4040+(** [to_bits color] converts [color] to a [(bit0, bit1, bit2)] tuple
4141+ representing which [$MailFlagBit*] keywords should be set.
4242+4343+ Example: [to_bits Green] returns [(true, true, false)]. *)
4444+4545+val of_bits : bool * bool * bool -> t option
4646+(** [of_bits (bit0, bit1, bit2)] converts a bit pattern to a color.
4747+ Returns [None] for the undefined pattern [(true, true, true)] (111). *)
4848+4949+val to_keywords : t -> [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list
5050+(** [to_keywords color] returns the list of keyword bits that should be
5151+ set for [color]. Red returns an empty list since no bits are needed. *)
5252+5353+val of_keywords : [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list -> t option
5454+(** [of_keywords keywords] extracts a color from a list of keyword bits.
5555+ Returns [None] if the pattern is 111 (undefined) or if no bits are
5656+ present in the list (which would indicate no flag color is set,
5757+ rather than Red). Use {!of_keywords_default_red} if you want to
5858+ treat an empty list as Red. *)
5959+6060+val of_keywords_default_red : [ `MailFlagBit0 | `MailFlagBit1 | `MailFlagBit2 ] list -> t option
6161+(** [of_keywords_default_red keywords] is like {!of_keywords} but treats
6262+ an empty keyword list as Red. Returns [None] only for pattern 111. *)
6363+6464+val pp : Format.formatter -> t -> unit
6565+(** [pp ppf color] pretty-prints the color name to [ppf]. *)
6666+6767+val to_string : t -> string
6868+(** [to_string color] returns the lowercase color name. *)
6969+7070+val of_string : string -> t option
7171+(** [of_string s] parses a color name (case-insensitive).
7272+ Returns [None] if [s] is not a valid color name. *)
+70
mail-flag/lib/imap_wire.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** IMAP Wire Format Conversion
77+88+ Implementation of IMAP wire format conversion for message flags and
99+ mailbox attributes. See {{:https://datatracker.ietf.org/doc/html/rfc9051#section-2.3.2}RFC 9051 Section 2.3.2}. *)
1010+1111+type flag =
1212+ | System of [ `Seen | `Answered | `Flagged | `Deleted | `Draft ]
1313+ | Keyword of Keyword.t
1414+1515+(** Check if a string represents an IMAP system flag.
1616+ Returns the system flag variant if recognized, None otherwise. *)
1717+let parse_system_flag s =
1818+ let s = String.lowercase_ascii s in
1919+ (* Remove backslash prefix if present *)
2020+ let s = if String.length s > 0 && s.[0] = '\\' then
2121+ String.sub s 1 (String.length s - 1)
2222+ else s
2323+ in
2424+ match s with
2525+ | "seen" -> Some `Seen
2626+ | "answered" -> Some `Answered
2727+ | "flagged" -> Some `Flagged
2828+ | "deleted" -> Some `Deleted
2929+ | "draft" -> Some `Draft
3030+ | _ -> None
3131+3232+let flag_of_string s =
3333+ match parse_system_flag s with
3434+ | Some sys -> System sys
3535+ | None -> Keyword (Keyword.of_string s)
3636+3737+let flag_to_string = function
3838+ | System `Seen -> "\\Seen"
3939+ | System `Answered -> "\\Answered"
4040+ | System `Flagged -> "\\Flagged"
4141+ | System `Deleted -> "\\Deleted"
4242+ | System `Draft -> "\\Draft"
4343+ | Keyword k -> Keyword.to_imap_string k
4444+4545+let flags_of_keywords keywords =
4646+ List.map (fun k ->
4747+ match k with
4848+ | `Seen -> System `Seen
4949+ | `Answered -> System `Answered
5050+ | `Flagged -> System `Flagged
5151+ | `Deleted -> System `Deleted
5252+ | `Draft -> System `Draft
5353+ | other -> Keyword other
5454+ ) keywords
5555+5656+let keywords_of_flags flags =
5757+ List.map (fun flag ->
5858+ match flag with
5959+ | System `Seen -> `Seen
6060+ | System `Answered -> `Answered
6161+ | System `Flagged -> `Flagged
6262+ | System `Deleted -> `Deleted
6363+ | System `Draft -> `Draft
6464+ | Keyword k -> k
6565+ ) flags
6666+6767+let attr_of_string = Mailbox_attr.of_string
6868+let attr_to_string = Mailbox_attr.to_string
6969+7070+let pp_flag ppf flag = Fmt.string ppf (flag_to_string flag)
+106
mail-flag/lib/imap_wire.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** IMAP Wire Format Conversion
77+88+ Converts between mail-flag types and IMAP protocol format.
99+ See {{:https://datatracker.ietf.org/doc/html/rfc9051#section-2.3.2}RFC 9051 Section 2.3.2}
1010+ for the flag format specification.
1111+1212+ {2 IMAP Flag Format}
1313+1414+ IMAP uses two types of message flags:
1515+ - {b System flags} prefixed with backslash: [\Seen], [\Answered], [\Flagged], [\Deleted], [\Draft]
1616+ - {b Keywords} prefixed with dollar sign: [$Forwarded], [$Junk], etc.
1717+1818+ This module handles the conversion between the internal {!Keyword.t} representation
1919+ and the IMAP wire format. *)
2020+2121+(** {1 Message Flags} *)
2222+2323+(** IMAP message flag - either system flag or keyword.
2424+2525+ System flags are the five flags defined in
2626+ {{:https://datatracker.ietf.org/doc/html/rfc9051#section-2.3.2}RFC 9051 Section 2.3.2}:
2727+ [\Seen], [\Answered], [\Flagged], [\Deleted], [\Draft].
2828+2929+ Keywords are user-defined or server-defined flags that start with [$]. *)
3030+type flag =
3131+ | System of [ `Seen | `Answered | `Flagged | `Deleted | `Draft ]
3232+ | Keyword of Keyword.t
3333+3434+val flag_of_string : string -> flag
3535+(** [flag_of_string s] parses an IMAP flag string.
3636+3737+ System flags are recognized with or without the backslash prefix,
3838+ case-insensitively. Keywords are parsed using {!Keyword.of_string}.
3939+4040+ Examples:
4141+ - ["\\Seen"] -> [System `Seen]
4242+ - ["Seen"] -> [System `Seen]
4343+ - ["$forwarded"] -> [Keyword `Forwarded]
4444+ - ["$custom"] -> [Keyword (`Custom "custom")] *)
4545+4646+val flag_to_string : flag -> string
4747+(** [flag_to_string flag] converts a flag to IMAP wire format.
4848+4949+ System flags are returned with backslash prefix.
5050+ Keywords are returned with dollar sign prefix.
5151+5252+ Examples:
5353+ - [System `Seen] -> ["\\Seen"]
5454+ - [Keyword `Forwarded] -> ["$Forwarded"] *)
5555+5656+val flags_of_keywords : Keyword.t list -> flag list
5757+(** [flags_of_keywords keywords] converts a list of keywords to IMAP flags.
5858+5959+ Keywords that correspond to IMAP system flags ([`Seen], [`Answered],
6060+ [`Flagged], [`Deleted], [`Draft]) are converted to [System] flags.
6161+ All other keywords remain as [Keyword] flags.
6262+6363+ Example:
6464+ {[
6565+ flags_of_keywords [`Seen; `Forwarded; `Custom "label"]
6666+ (* returns [System `Seen; Keyword `Forwarded; Keyword (`Custom "label")] *)
6767+ ]} *)
6868+6969+val keywords_of_flags : flag list -> Keyword.t list
7070+(** [keywords_of_flags flags] converts IMAP flags to keywords.
7171+7272+ System flags are converted to their corresponding standard keywords.
7373+ Keyword flags are returned as-is.
7474+7575+ Example:
7676+ {[
7777+ keywords_of_flags [System `Seen; Keyword `Forwarded]
7878+ (* returns [`Seen; `Forwarded] *)
7979+ ]} *)
8080+8181+(** {1 Mailbox Attributes} *)
8282+8383+val attr_of_string : string -> Mailbox_attr.t
8484+(** [attr_of_string s] parses an IMAP mailbox attribute.
8585+8686+ Delegates to {!Mailbox_attr.of_string}. The input may optionally
8787+ include the leading backslash. Parsing is case-insensitive.
8888+8989+ Examples:
9090+ - ["\\Drafts"] -> [`Drafts]
9191+ - ["HasChildren"] -> [`HasChildren] *)
9292+9393+val attr_to_string : Mailbox_attr.t -> string
9494+(** [attr_to_string attr] converts an attribute to IMAP wire format.
9595+9696+ Delegates to {!Mailbox_attr.to_string}. Returns the attribute with
9797+ leading backslash prefix.
9898+9999+ Examples:
100100+ - [`Drafts] -> ["\\Drafts"]
101101+ - [`HasChildren] -> ["\\HasChildren"] *)
102102+103103+(** {1 Pretty Printing} *)
104104+105105+val pp_flag : Format.formatter -> flag -> unit
106106+(** [pp_flag ppf flag] pretty-prints a flag in IMAP wire format. *)
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** JMAP Wire Format Conversion
77+88+ Converts between mail-flag types and JMAP JSON format.
99+ See {{:https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.1}RFC 8621 Section 4.1.1}
1010+ for the keywords format specification.
1111+1212+ {2 JMAP Keywords Format}
1313+1414+ In JMAP, message keywords are represented as a JSON object where each key
1515+ is a keyword string (with [$] prefix for standard keywords) and the value
1616+ is always [true]:
1717+1818+ {v
1919+ {
2020+ "$seen": true,
2121+ "$flagged": true,
2222+ "$forwarded": true,
2323+ "my-custom-label": true
2424+ }
2525+ v}
2626+2727+ Keywords with [false] values are simply absent from the object. This module
2828+ provides conversion functions between the internal {!Keyword.t} list
2929+ representation and the association list format used for JSON encoding. *)
3030+3131+(** {1 Keywords as JSON} *)
3232+3333+val keywords_to_assoc : Keyword.t list -> (string * bool) list
3434+(** [keywords_to_assoc keywords] converts a keyword list to JMAP keywords
3535+ object entries.
3636+3737+ Each keyword is converted to a [(string, true)] pair using
3838+ {!Keyword.to_string} for the string representation.
3939+4040+ Example:
4141+ {[
4242+ keywords_to_assoc [`Seen; `Flagged; `Custom "label"]
4343+ (* returns [("$seen", true); ("$flagged", true); ("label", true)] *)
4444+ ]} *)
4545+4646+val keywords_of_assoc : (string * bool) list -> Keyword.t list
4747+(** [keywords_of_assoc assoc] parses JMAP keywords from object entries.
4848+4949+ Only entries with [true] value are included in the result.
5050+ Entries with [false] value are ignored (they represent the absence
5151+ of the keyword).
5252+5353+ Example:
5454+ {[
5555+ keywords_of_assoc [("$seen", true); ("$draft", false); ("label", true)]
5656+ (* returns [`Seen; `Custom "label"] *)
5757+ ]} *)
5858+5959+(** {1 Mailbox Roles} *)
6060+6161+val role_to_string : Mailbox_attr.special_use -> string
6262+(** [role_to_string role] converts a special-use attribute to JMAP role string.
6363+6464+ JMAP roles are lowercase strings without any prefix.
6565+6666+ Examples:
6767+ - [`Drafts] -> ["drafts"]
6868+ - [`Inbox] -> ["inbox"]
6969+ - [`Junk] -> ["junk"] *)
7070+7171+val role_of_string : string -> Mailbox_attr.special_use option
7272+(** [role_of_string s] parses a JMAP role string into a special-use attribute.
7373+7474+ Returns [None] if the role string is not recognized. The input should
7575+ be lowercase as per JMAP conventions, but parsing is case-insensitive.
7676+7777+ Examples:
7878+ - ["drafts"] -> [Some `Drafts]
7979+ - ["inbox"] -> [Some `Inbox]
8080+ - ["unknown"] -> [None] *)
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Unified Message Keywords for IMAP and JMAP
77+88+ This module provides a unified representation of message keywords that
99+ works across both IMAP ({{:https://datatracker.ietf.org/doc/html/rfc9051}RFC 9051})
1010+ and JMAP ({{:https://datatracker.ietf.org/doc/html/rfc8621}RFC 8621}) protocols.
1111+1212+ {2 Keyword Types}
1313+1414+ Keywords are organized into categories based on their specification:
1515+ - {!standard}: Core flags from RFC 8621 Section 4.1.1 that map to IMAP system flags
1616+ - {!spam}: Spam-related keywords for junk mail handling
1717+ - {!extended}: Extended keywords from draft-ietf-mailmaint
1818+ - {!flag_bit}: Apple Mail flag color bits
1919+2020+ {2 Protocol Mapping}
2121+2222+ IMAP system flags ([\Seen], [\Answered], etc.) map to JMAP keywords
2323+ ([$seen], [$answered], etc.). The {!to_string} and {!to_imap_string}
2424+ functions handle these conversions. *)
2525+2626+(** {1 Keyword Types} *)
2727+2828+(** Standard keywords per {{:https://datatracker.ietf.org/doc/html/rfc8621#section-4.1.1}RFC 8621 Section 4.1.1}.
2929+3030+ These keywords have direct mappings to IMAP system flags defined in
3131+ {{:https://datatracker.ietf.org/doc/html/rfc9051#section-2.3.2}RFC 9051 Section 2.3.2}. *)
3232+type standard = [
3333+ | `Seen (** Message has been read. Maps to IMAP [\Seen]. *)
3434+ | `Answered (** Message has been answered. Maps to IMAP [\Answered]. *)
3535+ | `Flagged (** Message is flagged/starred. Maps to IMAP [\Flagged]. *)
3636+ | `Draft (** Message is a draft. Maps to IMAP [\Draft]. *)
3737+ | `Deleted (** Message marked for deletion. IMAP only, maps to [\Deleted]. *)
3838+ | `Forwarded (** Message has been forwarded. JMAP [$forwarded] keyword. *)
3939+]
4040+4141+(** Spam-related keywords for junk mail handling.
4242+4343+ These keywords help mail clients and servers coordinate spam filtering
4444+ decisions across protocol boundaries. *)
4545+type spam = [
4646+ | `Phishing (** Message is a phishing attempt. JMAP [$phishing]. *)
4747+ | `Junk (** Message is spam/junk. JMAP [$junk]. *)
4848+ | `NotJunk (** Message explicitly marked as not junk. JMAP [$notjunk]. *)
4949+]
5050+5151+(** Extended keywords per draft-ietf-mailmaint.
5252+5353+ These keywords provide additional metadata for enhanced mail client features
5454+ beyond basic read/reply tracking. *)
5555+type extended = [
5656+ | `HasAttachment (** Message has attachments. *)
5757+ | `HasNoAttachment (** Message has no attachments. Mutually exclusive with [`HasAttachment]. *)
5858+ | `Memo (** Message is a memo. *)
5959+ | `HasMemo (** Message has an associated memo. *)
6060+ | `CanUnsubscribe (** Message has unsubscribe capability (List-Unsubscribe header). *)
6161+ | `Unsubscribed (** User has unsubscribed from this sender. *)
6262+ | `Muted (** Thread is muted. Mutually exclusive with [`Followed]. *)
6363+ | `Followed (** Thread is followed. Mutually exclusive with [`Muted]. *)
6464+ | `AutoSent (** Message was sent automatically. *)
6565+ | `Imported (** Message was imported from another source. *)
6666+ | `IsTrusted (** Sender is trusted. *)
6767+ | `MaskedEmail (** Message was sent to a masked email address. *)
6868+ | `New (** Message is new (not yet processed by client). *)
6969+ | `Notify (** User should be notified about this message. *)
7070+]
7171+7272+(** Apple Mail flag color bits.
7373+7474+ Apple Mail uses a 3-bit encoding for flag colors. The color is determined
7575+ by the combination of bits set. See {!flag_color_of_keywords} for the
7676+ mapping. *)
7777+type flag_bit = [
7878+ | `MailFlagBit0 (** Bit 0 of Apple Mail flag color encoding. *)
7979+ | `MailFlagBit1 (** Bit 1 of Apple Mail flag color encoding. *)
8080+ | `MailFlagBit2 (** Bit 2 of Apple Mail flag color encoding. *)
8181+]
8282+8383+(** Unified keyword type combining all keyword categories.
8484+8585+ Use [`Custom s] for server-specific or application-specific keywords
8686+ not covered by the standard categories. *)
8787+type t = [ standard | spam | extended | flag_bit | `Custom of string ]
8888+8989+(** {1 Conversion Functions} *)
9090+9191+val of_string : string -> t
9292+(** [of_string s] parses a keyword string.
9393+9494+ Handles both JMAP format ([$seen]) and bare format ([seen]).
9595+ Parsing is case-insensitive for known keywords.
9696+9797+ Examples:
9898+ - ["$seen"] -> [`Seen]
9999+ - ["seen"] -> [`Seen]
100100+ - ["SEEN"] -> [`Seen]
101101+ - ["\\Seen"] -> [`Seen] (IMAP system flag format)
102102+ - ["my-custom-flag"] -> [`Custom "my-custom-flag"] *)
103103+104104+val to_string : t -> string
105105+(** [to_string k] converts a keyword to canonical JMAP format.
106106+107107+ Standard and extended keywords are returned with [$] prefix in lowercase.
108108+ Apple Mail flag bits preserve their mixed case.
109109+ Custom keywords are returned as-is.
110110+111111+ Examples:
112112+ - [`Seen] -> ["$seen"]
113113+ - [`MailFlagBit0] -> ["$MailFlagBit0"]
114114+ - [`Custom "foo"] -> ["foo"] *)
115115+116116+val to_imap_string : t -> string
117117+(** [to_imap_string k] converts a keyword to IMAP format.
118118+119119+ Standard keywords that map to IMAP system flags use backslash prefix.
120120+ Other keywords use [$] prefix with appropriate casing.
121121+122122+ Examples:
123123+ - [`Seen] -> ["\\Seen"]
124124+ - [`Deleted] -> ["\\Deleted"]
125125+ - [`Forwarded] -> ["$Forwarded"]
126126+ - [`MailFlagBit0] -> ["$MailFlagBit0"] *)
127127+128128+(** {1 Predicates} *)
129129+130130+val is_standard : t -> bool
131131+(** [is_standard k] returns [true] if [k] maps to an IMAP system flag.
132132+133133+ The standard keywords are: [`Seen], [`Answered], [`Flagged], [`Draft],
134134+ and [`Deleted]. Note that [`Forwarded] is {i not} an IMAP system flag. *)
135135+136136+val is_mutually_exclusive : t -> t -> bool
137137+(** [is_mutually_exclusive k1 k2] returns [true] if keywords [k1] and [k2]
138138+ cannot both be set on the same message.
139139+140140+ Mutually exclusive pairs:
141141+ - [`HasAttachment] and [`HasNoAttachment]
142142+ - [`Junk] and [`NotJunk]
143143+ - [`Muted] and [`Followed] *)
144144+145145+(** {1 Pretty Printing} *)
146146+147147+val pp : Format.formatter -> t -> unit
148148+(** [pp ppf k] pretty-prints keyword [k] in JMAP format. *)
149149+150150+val equal : t -> t -> bool
151151+(** [equal k1 k2] tests equality of keywords. *)
152152+153153+val compare : t -> t -> int
154154+(** [compare k1 k2] provides total ordering on keywords. *)
155155+156156+(** {1 Apple Mail Flag Colors} *)
157157+158158+(** Apple Mail flag colors encoded as 3-bit values. *)
159159+type flag_color = [
160160+ | `Red (** No bits set *)
161161+ | `Orange (** Bit 0 only *)
162162+ | `Yellow (** Bit 1 only *)
163163+ | `Green (** All bits set *)
164164+ | `Blue (** Bit 2 only *)
165165+ | `Purple (** Bits 0 and 2 *)
166166+ | `Gray (** Bits 1 and 2 *)
167167+]
168168+169169+val flag_color_of_keywords : t list -> flag_color option
170170+(** [flag_color_of_keywords keywords] extracts the Apple Mail flag color
171171+ from a list of keywords.
172172+173173+ Returns [None] if bits 0 and 1 are set but not bit 2 (invalid encoding). *)
174174+175175+val flag_color_to_keywords : flag_color -> t list
176176+(** [flag_color_to_keywords color] returns the keyword bits needed to
177177+ represent the given flag color. *)
+149
mail-flag/lib/mailbox_attr.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Implementation of unified mailbox attributes and roles. *)
77+88+type list_attr = [
99+ | `Noinferiors
1010+ | `Noselect
1111+ | `Marked
1212+ | `Unmarked
1313+ | `Subscribed
1414+ | `HasChildren
1515+ | `HasNoChildren
1616+ | `NonExistent
1717+ | `Remote
1818+]
1919+2020+type special_use = [
2121+ | `All
2222+ | `Archive
2323+ | `Drafts
2424+ | `Flagged
2525+ | `Important
2626+ | `Inbox
2727+ | `Junk
2828+ | `Sent
2929+ | `Subscribed
3030+ | `Trash
3131+ | `Snoozed
3232+ | `Scheduled
3333+ | `Memos
3434+]
3535+3636+type t = [ list_attr | special_use | `Extension of string ]
3737+3838+(** Normalize attribute string by removing backslash prefix and converting to lowercase. *)
3939+let normalize s =
4040+ let s = String.lowercase_ascii s in
4141+ if String.length s > 0 && s.[0] = '\\' then
4242+ String.sub s 1 (String.length s - 1)
4343+ else
4444+ s
4545+4646+let of_string s =
4747+ match normalize s with
4848+ (* LIST attributes *)
4949+ | "noinferiors" -> `Noinferiors
5050+ | "noselect" -> `Noselect
5151+ | "marked" -> `Marked
5252+ | "unmarked" -> `Unmarked
5353+ | "subscribed" -> `Subscribed
5454+ | "haschildren" -> `HasChildren
5555+ | "hasnochildren" -> `HasNoChildren
5656+ | "nonexistent" -> `NonExistent
5757+ | "remote" -> `Remote
5858+ (* Special-use roles *)
5959+ | "all" -> `All
6060+ | "archive" -> `Archive
6161+ | "drafts" -> `Drafts
6262+ | "flagged" -> `Flagged
6363+ | "important" -> `Important
6464+ | "inbox" -> `Inbox
6565+ | "junk" | "spam" -> `Junk
6666+ | "sent" -> `Sent
6767+ | "trash" -> `Trash
6868+ | "snoozed" -> `Snoozed
6969+ | "scheduled" -> `Scheduled
7070+ | "memos" -> `Memos
7171+ | other -> `Extension other
7272+7373+let to_string = function
7474+ (* LIST attributes *)
7575+ | `Noinferiors -> "\\Noinferiors"
7676+ | `Noselect -> "\\Noselect"
7777+ | `Marked -> "\\Marked"
7878+ | `Unmarked -> "\\Unmarked"
7979+ | `Subscribed -> "\\Subscribed"
8080+ | `HasChildren -> "\\HasChildren"
8181+ | `HasNoChildren -> "\\HasNoChildren"
8282+ | `NonExistent -> "\\NonExistent"
8383+ | `Remote -> "\\Remote"
8484+ (* Special-use roles *)
8585+ | `All -> "\\All"
8686+ | `Archive -> "\\Archive"
8787+ | `Drafts -> "\\Drafts"
8888+ | `Flagged -> "\\Flagged"
8989+ | `Important -> "\\Important"
9090+ | `Inbox -> "\\Inbox"
9191+ | `Junk -> "\\Junk"
9292+ | `Sent -> "\\Sent"
9393+ | `Trash -> "\\Trash"
9494+ | `Snoozed -> "\\Snoozed"
9595+ | `Scheduled -> "\\Scheduled"
9696+ | `Memos -> "\\Memos"
9797+ | `Extension s ->
9898+ if String.length s > 0 && s.[0] = '\\' then s
9999+ else "\\" ^ s
100100+101101+let to_jmap_role = function
102102+ (* Special-use roles have JMAP equivalents *)
103103+ | `All -> Some "all"
104104+ | `Archive -> Some "archive"
105105+ | `Drafts -> Some "drafts"
106106+ | `Flagged -> Some "flagged"
107107+ | `Important -> Some "important"
108108+ | `Inbox -> Some "inbox"
109109+ | `Junk -> Some "junk"
110110+ | `Sent -> Some "sent"
111111+ | `Trash -> Some "trash"
112112+ | `Snoozed -> Some "snoozed"
113113+ | `Scheduled -> Some "scheduled"
114114+ | `Memos -> Some "memos"
115115+ (* LIST attributes and extensions have no JMAP role *)
116116+ | `Noinferiors | `Noselect | `Marked | `Unmarked | `Subscribed
117117+ | `HasChildren | `HasNoChildren | `NonExistent | `Remote
118118+ | `Extension _ -> None
119119+120120+let of_jmap_role s =
121121+ match String.lowercase_ascii s with
122122+ | "all" -> Some `All
123123+ | "archive" -> Some `Archive
124124+ | "drafts" -> Some `Drafts
125125+ | "flagged" -> Some `Flagged
126126+ | "important" -> Some `Important
127127+ | "inbox" -> Some `Inbox
128128+ | "junk" -> Some `Junk
129129+ | "sent" -> Some `Sent
130130+ | "trash" -> Some `Trash
131131+ | "snoozed" -> Some `Snoozed
132132+ | "scheduled" -> Some `Scheduled
133133+ | "memos" -> Some `Memos
134134+ | "subscribed" -> Some `Subscribed
135135+ | _ -> None
136136+137137+let is_special_use = function
138138+ | `All | `Archive | `Drafts | `Flagged | `Important | `Inbox
139139+ | `Junk | `Sent | `Trash | `Snoozed | `Scheduled | `Memos -> true
140140+ | `Subscribed -> true (* Also a JMAP role *)
141141+ | `Noinferiors | `Noselect | `Marked | `Unmarked
142142+ | `HasChildren | `HasNoChildren | `NonExistent | `Remote
143143+ | `Extension _ -> false
144144+145145+let is_selectable = function
146146+ | `Noselect | `NonExistent -> false
147147+ | _ -> true
148148+149149+let pp ppf attr = Fmt.string ppf (to_string attr)
+188
mail-flag/lib/mailbox_attr.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** Unified Mailbox Attributes and Roles
77+88+ This module provides a unified representation of mailbox attributes
99+ across IMAP and JMAP protocols. It combines IMAP LIST response attributes
1010+ ({{:https://www.rfc-editor.org/rfc/rfc9051#section-7.2.2}RFC 9051 Section 7.2.2}),
1111+ special-use mailbox flags ({{:https://www.rfc-editor.org/rfc/rfc6154}RFC 6154}),
1212+ and JMAP mailbox roles ({{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621}).
1313+1414+ {2 References}
1515+ - {{:https://www.rfc-editor.org/rfc/rfc9051}RFC 9051} - IMAP4rev2
1616+ - {{:https://www.rfc-editor.org/rfc/rfc6154}RFC 6154} - IMAP LIST Extension for Special-Use Mailboxes
1717+ - {{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258} - IMAP4 LIST Command Extensions
1818+ - {{:https://www.rfc-editor.org/rfc/rfc8457}RFC 8457} - IMAP \$Important Keyword and \Important Special-Use Attribute
1919+ - {{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621} - JMAP for Mail *)
2020+2121+(** {1 IMAP LIST Attributes}
2222+2323+ Attributes returned in IMAP LIST responses per
2424+ {{:https://www.rfc-editor.org/rfc/rfc9051#section-7.2.2}RFC 9051 Section 7.2.2}. *)
2525+2626+type list_attr = [
2727+ | `Noinferiors
2828+ (** [\Noinferiors] - No child mailboxes are possible under this mailbox.
2929+ The mailbox cannot have inferior (child) mailboxes, either because the
3030+ underlying storage doesn't support it or because the mailbox name is at
3131+ the hierarchy depth limit for this mailbox store. *)
3232+ | `Noselect
3333+ (** [\Noselect] - This mailbox cannot be selected. It exists only to hold
3434+ child mailboxes and is not a valid destination for messages. Stratum
3535+ only, not a real mailbox. *)
3636+ | `Marked
3737+ (** [\Marked] - The mailbox has been marked "interesting" by the server.
3838+ This typically indicates the mailbox contains new messages since the
3939+ last time it was selected. *)
4040+ | `Unmarked
4141+ (** [\Unmarked] - The mailbox is not "interesting". The mailbox does not
4242+ contain new messages since the last time it was selected. *)
4343+ | `Subscribed
4444+ (** [\Subscribed] - The mailbox is subscribed. Returned when the
4545+ SUBSCRIBED selection option is specified or implied. *)
4646+ | `HasChildren
4747+ (** [\HasChildren] - The mailbox has child mailboxes. Part of the
4848+ CHILDREN return option ({{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258}). *)
4949+ | `HasNoChildren
5050+ (** [\HasNoChildren] - The mailbox has no child mailboxes. Part of the
5151+ CHILDREN return option ({{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258}). *)
5252+ | `NonExistent
5353+ (** [\NonExistent] - The mailbox name does not refer to an existing mailbox.
5454+ This attribute is returned when a mailbox is part of the hierarchy but
5555+ doesn't actually exist ({{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258}).
5656+ Implies [\Noselect]. *)
5757+ | `Remote
5858+ (** [\Remote] - The mailbox is located on a remote server.
5959+ ({{:https://www.rfc-editor.org/rfc/rfc5258}RFC 5258}) *)
6060+]
6161+6262+(** {1 Special-Use Roles}
6363+6464+ Special-use mailbox roles per {{:https://www.rfc-editor.org/rfc/rfc6154}RFC 6154}
6565+ and {{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621}. These identify
6666+ mailboxes with specific purposes. *)
6767+6868+type special_use = [
6969+ | `All
7070+ (** [\All] - A virtual mailbox containing all messages in the user's
7171+ message store. Implementations may omit some messages. *)
7272+ | `Archive
7373+ (** [\Archive] - A mailbox used to archive messages. The meaning of
7474+ "archived" may vary by server. *)
7575+ | `Drafts
7676+ (** [\Drafts] - A mailbox used to hold draft messages, typically messages
7777+ being composed but not yet sent. *)
7878+ | `Flagged
7979+ (** [\Flagged] - A virtual mailbox containing all messages marked with
8080+ the [\Flagged] flag. *)
8181+ | `Important
8282+ (** [\Important] - A mailbox used to hold messages deemed important to
8383+ the user. ({{:https://www.rfc-editor.org/rfc/rfc8457}RFC 8457}) *)
8484+ | `Inbox
8585+ (** [inbox] - The user's inbox. This is a JMAP role
8686+ ({{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621}) without a direct
8787+ IMAP special-use equivalent since INBOX is always special in IMAP. *)
8888+ | `Junk
8989+ (** [\Junk] - A mailbox used to hold messages that have been identified
9090+ as spam or junk mail. Also known as "Spam" folder. *)
9191+ | `Sent
9292+ (** [\Sent] - A mailbox used to hold copies of messages that have been
9393+ sent. *)
9494+ | `Subscribed
9595+ (** [subscribed] - A JMAP virtual mailbox role
9696+ ({{:https://www.rfc-editor.org/rfc/rfc8621}RFC 8621}) representing
9797+ all subscribed mailboxes. *)
9898+ | `Trash
9999+ (** [\Trash] - A mailbox used to hold messages that have been deleted or
100100+ marked for deletion. *)
101101+ | `Snoozed
102102+ (** [snoozed] - A mailbox for messages that have been snoozed until a
103103+ later time. (draft-ietf-mailmaint-special-use-extensions) *)
104104+ | `Scheduled
105105+ (** [scheduled] - A mailbox for messages scheduled to be sent at a
106106+ future time. (draft-ietf-mailmaint-special-use-extensions) *)
107107+ | `Memos
108108+ (** [memos] - A mailbox for memo/note messages.
109109+ (draft-ietf-mailmaint-special-use-extensions) *)
110110+]
111111+112112+(** {1 Unified Attribute Type} *)
113113+114114+type t = [ list_attr | special_use | `Extension of string ]
115115+(** The unified mailbox attribute type combining LIST attributes, special-use
116116+ roles, and server-specific extensions. Extensions are represented with
117117+ their original string form (without leading backslash if present). *)
118118+119119+(** {1 Conversion Functions} *)
120120+121121+val of_string : string -> t
122122+(** [of_string s] parses a mailbox attribute from its IMAP wire format.
123123+ The input may optionally include the leading backslash. Parsing is
124124+ case-insensitive. Unknown attributes are returned as [`Extension s].
125125+126126+ Examples:
127127+ - [of_string "\\Drafts"] returns [`Drafts]
128128+ - [of_string "drafts"] returns [`Drafts]
129129+ - [of_string "\\X-Custom"] returns [`Extension "X-Custom"] *)
130130+131131+val to_string : t -> string
132132+(** [to_string attr] converts an attribute to its IMAP wire format with
133133+ the leading backslash prefix for standard attributes.
134134+135135+ Examples:
136136+ - [to_string `Drafts] returns ["\\Drafts"]
137137+ - [to_string `HasChildren] returns ["\\HasChildren"]
138138+ - [to_string (`Extension "X-Custom")] returns ["\\X-Custom"] *)
139139+140140+val to_jmap_role : t -> string option
141141+(** [to_jmap_role attr] converts a special-use attribute to its JMAP role
142142+ string (lowercase). Returns [None] for LIST attributes that don't
143143+ correspond to JMAP roles.
144144+145145+ Examples:
146146+ - [to_jmap_role `Drafts] returns [Some "drafts"]
147147+ - [to_jmap_role `Inbox] returns [Some "inbox"]
148148+ - [to_jmap_role `Noselect] returns [None] *)
149149+150150+val of_jmap_role : string -> special_use option
151151+(** [of_jmap_role s] parses a JMAP role string into a special-use attribute.
152152+ Returns [None] if the role string is not recognized. The input should
153153+ be lowercase as per JMAP conventions.
154154+155155+ Examples:
156156+ - [of_jmap_role "drafts"] returns [Some `Drafts]
157157+ - [of_jmap_role "inbox"] returns [Some `Inbox]
158158+ - [of_jmap_role "unknown"] returns [None] *)
159159+160160+(** {1 Predicates} *)
161161+162162+val is_special_use : t -> bool
163163+(** [is_special_use attr] returns [true] if the attribute is a special-use
164164+ role (as opposed to a LIST attribute or extension).
165165+166166+ Examples:
167167+ - [is_special_use `Drafts] returns [true]
168168+ - [is_special_use `Noselect] returns [false]
169169+ - [is_special_use (`Extension "x")] returns [false] *)
170170+171171+val is_selectable : t -> bool
172172+(** [is_selectable attr] returns [false] if the attribute indicates the
173173+ mailbox cannot be selected. This is [true] for [`Noselect] and
174174+ [`NonExistent] attributes, and [false] for all others.
175175+176176+ Note: A mailbox may have multiple attributes. To determine if a mailbox
177177+ is selectable, check that no attribute returns [false] from this function.
178178+179179+ Examples:
180180+ - [is_selectable `Noselect] returns [false]
181181+ - [is_selectable `NonExistent] returns [false]
182182+ - [is_selectable `Drafts] returns [true]
183183+ - [is_selectable `HasChildren] returns [true] *)
184184+185185+(** {1 Pretty Printing} *)
186186+187187+val pp : Format.formatter -> t -> unit
188188+(** [pp ppf attr] pretty-prints the attribute in IMAP wire format. *)
+26
mail-flag/mail-flag.opam
···11+# This file is generated by dune, edit dune-project instead
22+opam-version: "2.0"
33+synopsis: "Unified message flags and mailbox attributes for IMAP/JMAP"
44+description:
55+ "Type-safe message keywords, system flags, and mailbox attributes for email protocols. Supports RFC 9051 (IMAP4rev2), RFC 8621 (JMAP Mail), RFC 6154 (Special-Use), and draft-ietf-mailmaint extensions."
66+depends: [
77+ "dune" {>= "3.0"}
88+ "ocaml" {>= "5.0"}
99+ "fmt" {>= "0.9"}
1010+ "alcotest" {with-test}
1111+ "odoc" {with-doc}
1212+]
1313+build: [
1414+ ["dune" "subst"] {dev}
1515+ [
1616+ "dune"
1717+ "build"
1818+ "-p"
1919+ name
2020+ "-j"
2121+ jobs
2222+ "@install"
2323+ "@runtest" {with-test}
2424+ "@doc" {with-doc}
2525+ ]
2626+]
···1313module Com : sig
1414 module Atproto : sig
1515 module Repo : sig
1616- module StrongRef : sig
1717-1818-type main = {
1919- cid : string;
2020- uri : string;
2121-}
2222-2323-(** Jsont codec for {!type:main}. *)
2424-val main_jsont : main Jsont.t
2525-2626- end
2716 module Defs : sig
28172918type commit_meta = {
···33223423(** Jsont codec for {!type:commit_meta}. *)
3524val commit_meta_jsont : commit_meta Jsont.t
2525+2626+ end
2727+ module GetRecord : sig
2828+(** Get a single record from a repository. Does not require auth. *)
2929+3030+(** Query/procedure parameters. *)
3131+type params = {
3232+ cid : string option; (** The CID of the version of the record. If not specified, then return the most recent version. *)
3333+ collection : string; (** The NSID of the record collection. *)
3434+ repo : string; (** The handle or DID of the repo. *)
3535+ rkey : string; (** The Record Key. *)
3636+}
3737+3838+(** Jsont codec for {!type:params}. *)
3939+val params_jsont : params Jsont.t
4040+4141+4242+type output = {
4343+ cid : string option;
4444+ uri : string;
4545+ value : Jsont.json;
4646+}
4747+4848+(** Jsont codec for {!type:output}. *)
4949+val output_jsont : output Jsont.t
36503751 end
3852 module ListRecords : sig
···7084val output_jsont : output Jsont.t
71857286 end
7373- module GetRecord : sig
7474-(** Get a single record from a repository. Does not require auth. *)
7575-7676-(** Query/procedure parameters. *)
7777-type params = {
7878- cid : string option; (** The CID of the version of the record. If not specified, then return the most recent version. *)
7979- collection : string; (** The NSID of the record collection. *)
8080- repo : string; (** The handle or DID of the repo. *)
8181- rkey : string; (** The Record Key. *)
8282-}
8383-8484-(** Jsont codec for {!type:params}. *)
8585-val params_jsont : params Jsont.t
8686-8787+ module StrongRef : sig
87888888-type output = {
8989- cid : string option;
8989+type main = {
9090+ cid : string;
9091 uri : string;
9191- value : Jsont.json;
9292}
93939494-(** Jsont codec for {!type:output}. *)
9595-val output_jsont : output Jsont.t
9494+(** Jsont codec for {!type:main}. *)
9595+val main_jsont : main Jsont.t
96969797 end
9898 module PutRecord : sig
···124124val output_jsont : output Jsont.t
125125126126 end
127127- module DeleteRecord : sig
128128-(** Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS. *)
127127+ module CreateRecord : sig
128128+(** Create a single new repository record. Requires auth, implemented by PDS. *)
129129130130131131type input = {
132132 collection : string; (** The NSID of the record collection. *)
133133+ record : Jsont.json; (** The record itself. Must contain a $type field. *)
133134 repo : string; (** The handle or DID of the repo (aka, current account). *)
134134- rkey : string; (** The Record Key. *)
135135+ rkey : string option; (** The Record Key. *)
135136 swap_commit : string option; (** Compare and swap with the previous commit by CID. *)
136136- swap_record : string option; (** Compare and swap with the previous record by CID. *)
137137+ validate : bool option; (** Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons. *)
137138}
138139139140(** Jsont codec for {!type:input}. *)
···141142142143143144type output = {
145145+ cid : string;
144146 commit : Defs.commit_meta option;
147147+ uri : string;
148148+ validation_status : string option;
145149}
146150147151(** Jsont codec for {!type:output}. *)
148152val output_jsont : output Jsont.t
149153150154 end
151151- module CreateRecord : sig
152152-(** Create a single new repository record. Requires auth, implemented by PDS. *)
155155+ module DeleteRecord : sig
156156+(** Delete a repository record, or ensure it doesn't exist. Requires auth, implemented by PDS. *)
153157154158155159type input = {
156160 collection : string; (** The NSID of the record collection. *)
157157- record : Jsont.json; (** The record itself. Must contain a $type field. *)
158161 repo : string; (** The handle or DID of the repo (aka, current account). *)
159159- rkey : string option; (** The Record Key. *)
162162+ rkey : string; (** The Record Key. *)
160163 swap_commit : string option; (** Compare and swap with the previous commit by CID. *)
161161- validate : bool option; (** Can be set to 'false' to skip Lexicon schema validation of record data, 'true' to require it, or leave unset to validate only for known Lexicons. *)
164164+ swap_record : string option; (** Compare and swap with the previous record by CID. *)
162165}
163166164167(** Jsont codec for {!type:input}. *)
···166169167170168171type output = {
169169- cid : string;
170172 commit : Defs.commit_meta option;
171171- uri : string;
172172- validation_status : string option;
173173}
174174175175(** Jsont codec for {!type:output}. *)
···12121313module Com : sig
1414 module Atproto : sig
1515+ module Moderation : sig
1616+ module Defs : sig
1717+(** Tag describing a type of subject that might be reported. *)
1818+1919+type subject_type = string
2020+val subject_type_jsont : subject_type Jsont.t
2121+2222+(** Direct violation of server rules, laws, terms of service. Prefer new lexicon definition `tools.ozone.report.defs#reasonRuleOther`. *)
2323+2424+type reason_violation = string
2525+val reason_violation_jsont : reason_violation Jsont.t
2626+2727+2828+type reason_type = string
2929+val reason_type_jsont : reason_type Jsont.t
3030+3131+(** Spam: frequent unwanted promotion, replies, mentions. Prefer new lexicon definition `tools.ozone.report.defs#reasonMisleadingSpam`. *)
3232+3333+type reason_spam = string
3434+val reason_spam_jsont : reason_spam Jsont.t
3535+3636+(** Unwanted or mislabeled sexual content. Prefer new lexicon definition `tools.ozone.report.defs#reasonSexualUnlabeled`. *)
3737+3838+type reason_sexual = string
3939+val reason_sexual_jsont : reason_sexual Jsont.t
4040+4141+(** Rude, harassing, explicit, or otherwise unwelcoming behavior. Prefer new lexicon definition `tools.ozone.report.defs#reasonHarassmentOther`. *)
4242+4343+type reason_rude = string
4444+val reason_rude_jsont : reason_rude Jsont.t
4545+4646+(** Reports not falling under another report category. Prefer new lexicon definition `tools.ozone.report.defs#reasonOther`. *)
4747+4848+type reason_other = string
4949+val reason_other_jsont : reason_other Jsont.t
5050+5151+(** Misleading identity, affiliation, or content. Prefer new lexicon definition `tools.ozone.report.defs#reasonMisleadingOther`. *)
5252+5353+type reason_misleading = string
5454+val reason_misleading_jsont : reason_misleading Jsont.t
5555+5656+(** Appeal a previously taken moderation action *)
5757+5858+type reason_appeal = string
5959+val reason_appeal_jsont : reason_appeal Jsont.t
6060+6161+ end
6262+ end
1563 module Label : sig
1664 module Defs : sig
1765(** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. *)
···9314194142 end
95143 end
9696- module Moderation : sig
9797- module Defs : sig
9898-(** Tag describing a type of subject that might be reported. *)
9999-100100-type subject_type = string
101101-val subject_type_jsont : subject_type Jsont.t
102102-103103-(** Direct violation of server rules, laws, terms of service. Prefer new lexicon definition `tools.ozone.report.defs#reasonRuleOther`. *)
104104-105105-type reason_violation = string
106106-val reason_violation_jsont : reason_violation Jsont.t
107107-108108-109109-type reason_type = string
110110-val reason_type_jsont : reason_type Jsont.t
111111-112112-(** Spam: frequent unwanted promotion, replies, mentions. Prefer new lexicon definition `tools.ozone.report.defs#reasonMisleadingSpam`. *)
113113-114114-type reason_spam = string
115115-val reason_spam_jsont : reason_spam Jsont.t
116116-117117-(** Unwanted or mislabeled sexual content. Prefer new lexicon definition `tools.ozone.report.defs#reasonSexualUnlabeled`. *)
118118-119119-type reason_sexual = string
120120-val reason_sexual_jsont : reason_sexual Jsont.t
121121-122122-(** Rude, harassing, explicit, or otherwise unwelcoming behavior. Prefer new lexicon definition `tools.ozone.report.defs#reasonHarassmentOther`. *)
123123-124124-type reason_rude = string
125125-val reason_rude_jsont : reason_rude Jsont.t
126126-127127-(** Reports not falling under another report category. Prefer new lexicon definition `tools.ozone.report.defs#reasonOther`. *)
128128-129129-type reason_other = string
130130-val reason_other_jsont : reason_other Jsont.t
131131-132132-(** Misleading identity, affiliation, or content. Prefer new lexicon definition `tools.ozone.report.defs#reasonMisleadingOther`. *)
133133-134134-type reason_misleading = string
135135-val reason_misleading_jsont : reason_misleading Jsont.t
136136-137137-(** Appeal a previously taken moderation action *)
138138-139139-type reason_appeal = string
140140-val reason_appeal_jsont : reason_appeal Jsont.t
141141-142142- end
143143- end
144144 end
145145end
146146module App : sig
147147 module Bsky : sig
148148- module AuthManageLabelerService : sig
148148+ module AuthFullApp : sig
149149150150type main = unit
151151val main_jsont : main Jsont.t
···157157val main_jsont : main Jsont.t
158158159159 end
160160- module AuthManageModeration : sig
161161-162162-type main = unit
163163-val main_jsont : main Jsont.t
164164-165165- end
166166- module Richtext : sig
167167- module Facet : sig
168168-(** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). *)
169169-170170-type tag = {
171171- tag : string;
172172-}
173173-174174-(** Jsont codec for {!type:tag}. *)
175175-val tag_jsont : tag Jsont.t
176176-177177-(** Facet feature for mention of another account. The text is usually a handle, including a '\@' prefix, but the facet reference is a DID. *)
178178-179179-type mention = {
180180- did : string;
181181-}
182182-183183-(** Jsont codec for {!type:mention}. *)
184184-val mention_jsont : mention Jsont.t
185185-186186-(** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. *)
187187-188188-type link = {
189189- uri : string;
190190-}
191191-192192-(** Jsont codec for {!type:link}. *)
193193-val link_jsont : link Jsont.t
194194-195195-(** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. *)
196196-197197-type byte_slice = {
198198- byte_end : int;
199199- byte_start : int;
200200-}
201201-202202-(** Jsont codec for {!type:byte_slice}. *)
203203-val byte_slice_jsont : byte_slice Jsont.t
204204-205205-(** Annotation of a sub-string within rich text. *)
206206-207207-type main = {
208208- features : Jsont.json list;
209209- index : byte_slice;
210210-}
211211-212212-(** Jsont codec for {!type:main}. *)
213213-val main_jsont : main Jsont.t
214214-215215- end
216216- end
217160 module AuthManageFeedDeclarations : sig
218161219162type main = unit
220163val main_jsont : main Jsont.t
221164222165 end
223223- module AuthFullApp : sig
166166+ module AuthManageNotifications : sig
224167225168type main = unit
226169val main_jsont : main Jsont.t
227170228171 end
229229- module AuthManageNotifications : sig
172172+ module AuthManageModeration : sig
230173231174type main = unit
232175val main_jsont : main Jsont.t
···238181val main_jsont : main Jsont.t
239182240183 end
241241- module Ageassurance : sig
242242- module Defs : sig
243243-(** The status of the Age Assurance process. *)
244244-245245-type status = string
246246-val status_jsont : status Jsont.t
247247-248248-(** Additional metadata needed to compute Age Assurance state client-side. *)
249249-250250-type state_metadata = {
251251- account_created_at : string option; (** The account creation timestamp. *)
252252-}
253253-254254-(** Jsont codec for {!type:state_metadata}. *)
255255-val state_metadata_jsont : state_metadata Jsont.t
256256-257257-(** Object used to store Age Assurance data in stash. *)
258258-259259-type event = {
260260- access : string; (** The access level granted based on Age Assurance data we've processed. *)
261261- attempt_id : string; (** The unique identifier for this instance of the Age Assurance flow, in UUID format. *)
262262- complete_ip : string option; (** The IP address used when completing the Age Assurance flow. *)
263263- complete_ua : string option; (** The user agent used when completing the Age Assurance flow. *)
264264- country_code : string; (** The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow. *)
265265- created_at : string; (** The date and time of this write operation. *)
266266- email : string option; (** The email used for Age Assurance. *)
267267- init_ip : string option; (** The IP address used when initiating the Age Assurance flow. *)
268268- init_ua : string option; (** The user agent used when initiating the Age Assurance flow. *)
269269- region_code : string option; (** The ISO 3166-2 region code provided when beginning the Age Assurance flow. *)
270270- status : string; (** The status of the Age Assurance process. *)
271271-}
272272-273273-(** Jsont codec for {!type:event}. *)
274274-val event_jsont : event Jsont.t
275275-276276-(** The Age Assurance configuration for a specific region. *)
277277-278278-type config_region = {
279279- country_code : string; (** The ISO 3166-1 alpha-2 country code this configuration applies to. *)
280280- min_access_age : int; (** The minimum age (as a whole integer) required to use Bluesky in this region. *)
281281- region_code : string option; (** The ISO 3166-2 region code this configuration applies to. If omitted, the configuration applies to the entire country. *)
282282- rules : Jsont.json list; (** The ordered list of Age Assurance rules that apply to this region. Rules should be applied in order, and the first matching rule determines the access level granted. The rules array should always include a default rule as the last item. *)
283283-}
284284-285285-(** Jsont codec for {!type:config_region}. *)
286286-val config_region_jsont : config_region Jsont.t
287287-288288-(** The access level granted based on Age Assurance data we've processed. *)
289289-290290-type access = string
291291-val access_jsont : access Jsont.t
292292-293293-(** The user's computed Age Assurance state. *)
294294-295295-type state = {
296296- access : access;
297297- last_initiated_at : string option; (** The timestamp when this state was last updated. *)
298298- status : status;
299299-}
300300-301301-(** Jsont codec for {!type:state}. *)
302302-val state_jsont : state Jsont.t
303303-304304-(** Age Assurance rule that applies if the user has declared themselves under a certain age. *)
305305-306306-type config_region_rule_if_declared_under_age = {
307307- access : access;
308308- age : int; (** The age threshold as a whole integer. *)
309309-}
310310-311311-(** Jsont codec for {!type:config_region_rule_if_declared_under_age}. *)
312312-val config_region_rule_if_declared_under_age_jsont : config_region_rule_if_declared_under_age Jsont.t
313313-314314-(** Age Assurance rule that applies if the user has declared themselves equal-to or over a certain age. *)
315315-316316-type config_region_rule_if_declared_over_age = {
317317- access : access;
318318- age : int; (** The age threshold as a whole integer. *)
319319-}
320320-321321-(** Jsont codec for {!type:config_region_rule_if_declared_over_age}. *)
322322-val config_region_rule_if_declared_over_age_jsont : config_region_rule_if_declared_over_age Jsont.t
323323-324324-(** Age Assurance rule that applies if the user has been assured to be under a certain age. *)
325325-326326-type config_region_rule_if_assured_under_age = {
327327- access : access;
328328- age : int; (** The age threshold as a whole integer. *)
329329-}
330330-331331-(** Jsont codec for {!type:config_region_rule_if_assured_under_age}. *)
332332-val config_region_rule_if_assured_under_age_jsont : config_region_rule_if_assured_under_age Jsont.t
333333-334334-(** Age Assurance rule that applies if the user has been assured to be equal-to or over a certain age. *)
335335-336336-type config_region_rule_if_assured_over_age = {
337337- access : access;
338338- age : int; (** The age threshold as a whole integer. *)
339339-}
340340-341341-(** Jsont codec for {!type:config_region_rule_if_assured_over_age}. *)
342342-val config_region_rule_if_assured_over_age_jsont : config_region_rule_if_assured_over_age Jsont.t
343343-344344-(** Age Assurance rule that applies if the account is older than a certain date. *)
345345-346346-type config_region_rule_if_account_older_than = {
347347- access : access;
348348- date : string; (** The date threshold as a datetime string. *)
349349-}
350350-351351-(** Jsont codec for {!type:config_region_rule_if_account_older_than}. *)
352352-val config_region_rule_if_account_older_than_jsont : config_region_rule_if_account_older_than Jsont.t
353353-354354-(** Age Assurance rule that applies if the account is equal-to or newer than a certain date. *)
355355-356356-type config_region_rule_if_account_newer_than = {
357357- access : access;
358358- date : string; (** The date threshold as a datetime string. *)
359359-}
360360-361361-(** Jsont codec for {!type:config_region_rule_if_account_newer_than}. *)
362362-val config_region_rule_if_account_newer_than_jsont : config_region_rule_if_account_newer_than Jsont.t
363363-364364-(** Age Assurance rule that applies by default. *)
365365-366366-type config_region_rule_default = {
367367- access : access;
368368-}
369369-370370-(** Jsont codec for {!type:config_region_rule_default}. *)
371371-val config_region_rule_default_jsont : config_region_rule_default Jsont.t
372372-373373-374374-type config = {
375375- regions : config_region list; (** The per-region Age Assurance configuration. *)
376376-}
377377-378378-(** Jsont codec for {!type:config}. *)
379379-val config_jsont : config Jsont.t
380380-381381- end
382382- module Begin : sig
383383-(** Initiate Age Assurance for an account. *)
384384-385385-386386-type input = {
387387- country_code : string; (** An ISO 3166-1 alpha-2 code of the user's location. *)
388388- email : string; (** The user's email address to receive Age Assurance instructions. *)
389389- language : string; (** The user's preferred language for communication during the Age Assurance process. *)
390390- region_code : string option; (** An optional ISO 3166-2 code of the user's region or state within the country. *)
391391-}
392392-393393-(** Jsont codec for {!type:input}. *)
394394-val input_jsont : input Jsont.t
395395-396396-397397-type output = Defs.state
398398-399399-(** Jsont codec for {!type:output}. *)
400400-val output_jsont : output Jsont.t
401401-402402- end
403403- module GetState : sig
404404-(** Returns server-computed Age Assurance state, if available, and any additional metadata needed to compute Age Assurance state client-side. *)
405405-406406-(** Query/procedure parameters. *)
407407-type params = {
408408- country_code : string;
409409- region_code : string option;
410410-}
411411-412412-(** Jsont codec for {!type:params}. *)
413413-val params_jsont : params Jsont.t
414414-415415-416416-type output = {
417417- metadata : Defs.state_metadata;
418418- state : Defs.state;
419419-}
420420-421421-(** Jsont codec for {!type:output}. *)
422422-val output_jsont : output Jsont.t
423423-424424- end
425425- module GetConfig : sig
426426-(** Returns Age Assurance configuration for use on the client. *)
427427-428428-429429-type output = Defs.config
430430-431431-(** Jsont codec for {!type:output}. *)
432432-val output_jsont : output Jsont.t
433433-434434- end
435435- end
436436- module Labeler : sig
437437- module Defs : sig
438438-439439-type labeler_viewer_state = {
440440- like : string option;
441441-}
442442-443443-(** Jsont codec for {!type:labeler_viewer_state}. *)
444444-val labeler_viewer_state_jsont : labeler_viewer_state Jsont.t
445445-446446-447447-type labeler_policies = {
448448- label_value_definitions : Com.Atproto.Label.Defs.label_value_definition list option; (** Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler. *)
449449- label_values : Com.Atproto.Label.Defs.label_value list; (** The label values which this labeler publishes. May include global or custom labels. *)
450450-}
451451-452452-(** Jsont codec for {!type:labeler_policies}. *)
453453-val labeler_policies_jsont : labeler_policies Jsont.t
454454-455455-456456-type labeler_view_detailed = {
457457- cid : string;
458458- creator : Jsont.json;
459459- indexed_at : string;
460460- labels : Com.Atproto.Label.Defs.label list option;
461461- like_count : int option;
462462- policies : Jsont.json;
463463- reason_types : Com.Atproto.Moderation.Defs.reason_type list option; (** The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. *)
464464- subject_collections : string list option; (** Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. *)
465465- subject_types : Com.Atproto.Moderation.Defs.subject_type list option; (** The set of subject types (account, record, etc) this service accepts reports on. *)
466466- uri : string;
467467- viewer : Jsont.json option;
468468-}
469469-470470-(** Jsont codec for {!type:labeler_view_detailed}. *)
471471-val labeler_view_detailed_jsont : labeler_view_detailed Jsont.t
472472-473473-474474-type labeler_view = {
475475- cid : string;
476476- creator : Jsont.json;
477477- indexed_at : string;
478478- labels : Com.Atproto.Label.Defs.label list option;
479479- like_count : int option;
480480- uri : string;
481481- viewer : Jsont.json option;
482482-}
483483-484484-(** Jsont codec for {!type:labeler_view}. *)
485485-val labeler_view_jsont : labeler_view Jsont.t
486486-487487- end
488488- module Service : sig
489489-(** A declaration of the existence of labeler service. *)
490490-491491-type main = {
492492- created_at : string;
493493- labels : Com.Atproto.Label.Defs.self_labels option;
494494- policies : Jsont.json;
495495- reason_types : Com.Atproto.Moderation.Defs.reason_type list option; (** The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. *)
496496- subject_collections : string list option; (** Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. *)
497497- subject_types : Com.Atproto.Moderation.Defs.subject_type list option; (** The set of subject types (account, record, etc) this service accepts reports on. *)
498498-}
499499-500500-(** Jsont codec for {!type:main}. *)
501501-val main_jsont : main Jsont.t
502502-503503- end
504504- module GetServices : sig
505505-(** Get information about a list of labeler services. *)
506506-507507-(** Query/procedure parameters. *)
508508-type params = {
509509- detailed : bool option;
510510- dids : string list;
511511-}
512512-513513-(** Jsont codec for {!type:params}. *)
514514-val params_jsont : params Jsont.t
515515-516516-517517-type output = {
518518- views : Jsont.json list;
519519-}
520520-521521-(** Jsont codec for {!type:output}. *)
522522-val output_jsont : output Jsont.t
523523-524524- end
525525- end
526526- module AuthCreatePosts : sig
184184+ module AuthManageLabelerService : sig
527185528186type main = unit
529187val main_jsont : main Jsont.t
530188531189 end
532532- module Video : sig
533533- module GetUploadLimits : sig
534534-(** Get video upload limits for the authenticated user. *)
535535-536536-537537-type output = {
538538- can_upload : bool;
539539- error : string option;
540540- message : string option;
541541- remaining_daily_bytes : int option;
542542- remaining_daily_videos : int option;
543543-}
544544-545545-(** Jsont codec for {!type:output}. *)
546546-val output_jsont : output Jsont.t
547547-548548- end
549549- module Defs : sig
550550-551551-type job_status = {
552552- blob : Atp.Blob_ref.t option;
553553- did : string;
554554- error : string option;
555555- job_id : string;
556556- message : string option;
557557- progress : int option; (** Progress within the current processing state. *)
558558- state : string; (** The state of the video processing job. All values not listed as a known value indicate that the job is in process. *)
559559-}
560560-561561-(** Jsont codec for {!type:job_status}. *)
562562-val job_status_jsont : job_status Jsont.t
563563-564564- end
565565- module UploadVideo : sig
566566-(** Upload a video to be processed then stored on the PDS. *)
567567-568568-569569-type input = unit
570570-val input_jsont : input Jsont.t
571571-572572-573573-type output = {
574574- job_status : Defs.job_status;
575575-}
576576-577577-(** Jsont codec for {!type:output}. *)
578578-val output_jsont : output Jsont.t
579579-580580- end
581581- module GetJobStatus : sig
582582-(** Get status details for a video processing job. *)
190190+ module Notification : sig
191191+ module GetUnreadCount : sig
192192+(** Count the number of unread notifications for the requesting account. Requires auth. *)
583193584194(** Query/procedure parameters. *)
585195type params = {
586586- job_id : string;
196196+ priority : bool option;
197197+ seen_at : string option;
587198}
588199589200(** Jsont codec for {!type:params}. *)
···591202592203593204type output = {
594594- job_status : Defs.job_status;
205205+ count : int;
595206}
596207597208(** Jsont codec for {!type:output}. *)
598209val output_jsont : output Jsont.t
599210600211 end
601601- end
602602- module Embed : sig
603603- module External : sig
604604-605605-type view_external = {
606606- description : string;
607607- thumb : string option;
608608- title : string;
609609- uri : string;
610610-}
611611-612612-(** Jsont codec for {!type:view_external}. *)
613613-val view_external_jsont : view_external Jsont.t
614614-615615-616616-type external_ = {
617617- description : string;
618618- thumb : Atp.Blob_ref.t option;
619619- title : string;
620620- uri : string;
621621-}
622622-623623-(** Jsont codec for {!type:external_}. *)
624624-val external__jsont : external_ Jsont.t
625625-626626-627627-type view = {
628628- external_ : Jsont.json;
629629-}
630630-631631-(** Jsont codec for {!type:view}. *)
632632-val view_jsont : view Jsont.t
633633-634634-(** A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). *)
635635-636636-type main = {
637637- external_ : Jsont.json;
638638-}
639639-640640-(** Jsont codec for {!type:main}. *)
641641-val main_jsont : main Jsont.t
642642-643643- end
644644- module Defs : sig
645645-(** width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit. *)
646646-647647-type aspect_ratio = {
648648- height : int;
649649- width : int;
650650-}
651651-652652-(** Jsont codec for {!type:aspect_ratio}. *)
653653-val aspect_ratio_jsont : aspect_ratio Jsont.t
654654-655655- end
656656- module Images : sig
657657-658658-type view_image = {
659659- alt : string; (** Alt text description of the image, for accessibility. *)
660660- aspect_ratio : Jsont.json option;
661661- fullsize : string; (** Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. *)
662662- thumb : string; (** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. *)
663663-}
664664-665665-(** Jsont codec for {!type:view_image}. *)
666666-val view_image_jsont : view_image Jsont.t
667667-668668-669669-type image = {
670670- alt : string; (** Alt text description of the image, for accessibility. *)
671671- aspect_ratio : Jsont.json option;
672672- image : Atp.Blob_ref.t;
673673-}
674674-675675-(** Jsont codec for {!type:image}. *)
676676-val image_jsont : image Jsont.t
677677-678678-679679-type view = {
680680- images : Jsont.json list;
681681-}
682682-683683-(** Jsont codec for {!type:view}. *)
684684-val view_jsont : view Jsont.t
685685-686686-687687-type main = {
688688- images : Jsont.json list;
689689-}
690690-691691-(** Jsont codec for {!type:main}. *)
692692-val main_jsont : main Jsont.t
693693-694694- end
695695- module Video : sig
696696-697697-type view = {
698698- alt : string option;
699699- aspect_ratio : Jsont.json option;
700700- cid : string;
701701- playlist : string;
702702- thumbnail : string option;
703703-}
704704-705705-(** Jsont codec for {!type:view}. *)
706706-val view_jsont : view Jsont.t
707707-708708-709709-type caption = {
710710- file : Atp.Blob_ref.t;
711711- lang : string;
712712-}
713713-714714-(** Jsont codec for {!type:caption}. *)
715715-val caption_jsont : caption Jsont.t
716716-717717-718718-type main = {
719719- alt : string option; (** Alt text description of the video, for accessibility. *)
720720- aspect_ratio : Jsont.json option;
721721- captions : Jsont.json list option;
722722- video : Atp.Blob_ref.t; (** The mp4 video file. May be up to 100mb, formerly limited to 50mb. *)
723723-}
724724-725725-(** Jsont codec for {!type:main}. *)
726726-val main_jsont : main Jsont.t
727727-728728- end
729729- module RecordWithMedia : sig
730730-731731-type view = {
732732- media : Jsont.json;
733733- record : Jsont.json;
734734-}
735735-736736-(** Jsont codec for {!type:view}. *)
737737-val view_jsont : view Jsont.t
738738-739739-740740-type main = {
741741- media : Jsont.json;
742742- record : Jsont.json;
743743-}
744744-745745-(** Jsont codec for {!type:main}. *)
746746-val main_jsont : main Jsont.t
747747-748748- end
749749- module Record : sig
750750-751751-type view_record = {
752752- author : Jsont.json;
753753- cid : string;
754754- embeds : Jsont.json list option;
755755- indexed_at : string;
756756- labels : Com.Atproto.Label.Defs.label list option;
757757- like_count : int option;
758758- quote_count : int option;
759759- reply_count : int option;
760760- repost_count : int option;
761761- uri : string;
762762- value : Jsont.json; (** The record data itself. *)
763763-}
764764-765765-(** Jsont codec for {!type:view_record}. *)
766766-val view_record_jsont : view_record Jsont.t
767767-768768-769769-type view_not_found = {
770770- not_found : bool;
771771- uri : string;
772772-}
773773-774774-(** Jsont codec for {!type:view_not_found}. *)
775775-val view_not_found_jsont : view_not_found Jsont.t
776776-777777-778778-type view_detached = {
779779- detached : bool;
780780- uri : string;
781781-}
782782-783783-(** Jsont codec for {!type:view_detached}. *)
784784-val view_detached_jsont : view_detached Jsont.t
785785-786786-787787-type view_blocked = {
788788- author : Jsont.json;
789789- blocked : bool;
790790- uri : string;
791791-}
792792-793793-(** Jsont codec for {!type:view_blocked}. *)
794794-val view_blocked_jsont : view_blocked Jsont.t
795795-796796-797797-type view = {
798798- record : Jsont.json;
799799-}
800800-801801-(** Jsont codec for {!type:view}. *)
802802-val view_jsont : view Jsont.t
803803-804804-805805-type main = {
806806- record : Com.Atproto.Repo.StrongRef.main;
807807-}
808808-809809-(** Jsont codec for {!type:main}. *)
810810-val main_jsont : main Jsont.t
811811-812812- end
813813- end
814814- module Notification : sig
815212 module UpdateSeen : sig
816213(** Notify server that the requesting account has seen notifications. Requires auth. *)
817214···824221val input_jsont : input Jsont.t
825222826223 end
827827- module RegisterPush : sig
828828-(** Register to receive push notifications, via a specified service, for the requesting account. Requires auth. *)
829829-830830-831831-type input = {
832832- age_restricted : bool option; (** Set to true when the actor is age restricted *)
833833- app_id : string;
834834- platform : string;
835835- service_did : string;
836836- token : string;
837837-}
838838-839839-(** Jsont codec for {!type:input}. *)
840840-val input_jsont : input Jsont.t
841841-842842- end
843224 module ListNotifications : sig
844225845226type notification = {
···883264val output_jsont : output Jsont.t
884265885266 end
886886- module GetUnreadCount : sig
887887-(** Count the number of unread notifications for the requesting account. Requires auth. *)
267267+ module Declaration : sig
268268+(** A declaration of the user's choices related to notifications that can be produced by them. *)
888269889889-(** Query/procedure parameters. *)
890890-type params = {
891891- priority : bool option;
892892- seen_at : string option;
270270+type main = {
271271+ allow_subscriptions : string; (** A declaration of the user's preference for allowing activity subscriptions from other users. Absence of a record implies 'followers'. *)
893272}
894273895895-(** Jsont codec for {!type:params}. *)
896896-val params_jsont : params Jsont.t
274274+(** Jsont codec for {!type:main}. *)
275275+val main_jsont : main Jsont.t
276276+277277+ end
278278+ module PutPreferences : sig
279279+(** Set notification-related preferences for an account. Requires auth. *)
897280898281899899-type output = {
900900- count : int;
282282+type input = {
283283+ priority : bool;
901284}
902285903903-(** Jsont codec for {!type:output}. *)
904904-val output_jsont : output Jsont.t
286286+(** Jsont codec for {!type:input}. *)
287287+val input_jsont : input Jsont.t
905288906289 end
907907- module UnregisterPush : sig
908908-(** The inverse of registerPush - inform a specified service that push notifications should no longer be sent to the given token for the requesting account. Requires auth. *)
290290+ module RegisterPush : sig
291291+(** Register to receive push notifications, via a specified service, for the requesting account. Requires auth. *)
909292910293911294type input = {
295295+ age_restricted : bool option; (** Set to true when the actor is age restricted *)
912296 app_id : string;
913297 platform : string;
914298 service_did : string;
···919303val input_jsont : input Jsont.t
920304921305 end
922922- module PutPreferences : sig
923923-(** Set notification-related preferences for an account. Requires auth. *)
306306+ module UnregisterPush : sig
307307+(** The inverse of registerPush - inform a specified service that push notifications should no longer be sent to the given token for the requesting account. Requires auth. *)
924308925309926310type input = {
927927- priority : bool;
311311+ app_id : string;
312312+ platform : string;
313313+ service_did : string;
314314+ token : string;
928315}
929316930317(** Jsont codec for {!type:input}. *)
···10043911005392(** Jsont codec for {!type:preferences}. *)
1006393val preferences_jsont : preferences Jsont.t
10071007-10081008- end
10091009- module Declaration : sig
10101010-(** A declaration of the user's choices related to notifications that can be produced by them. *)
10111011-10121012-type main = {
10131013- allow_subscriptions : string; (** A declaration of the user's preference for allowing activity subscriptions from other users. Absence of a record implies 'followers'. *)
10141014-}
10151015-10161016-(** Jsont codec for {!type:main}. *)
10171017-val main_jsont : main Jsont.t
10183941019395 end
1020396 module ListActivitySubscriptions : sig
···11124881113489 end
1114490 end
491491+ module Labeler : sig
492492+ module Defs : sig
493493+494494+type labeler_viewer_state = {
495495+ like : string option;
496496+}
497497+498498+(** Jsont codec for {!type:labeler_viewer_state}. *)
499499+val labeler_viewer_state_jsont : labeler_viewer_state Jsont.t
500500+501501+502502+type labeler_policies = {
503503+ label_value_definitions : Com.Atproto.Label.Defs.label_value_definition list option; (** Label values created by this labeler and scoped exclusively to it. Labels defined here will override global label definitions for this labeler. *)
504504+ label_values : Com.Atproto.Label.Defs.label_value list; (** The label values which this labeler publishes. May include global or custom labels. *)
505505+}
506506+507507+(** Jsont codec for {!type:labeler_policies}. *)
508508+val labeler_policies_jsont : labeler_policies Jsont.t
509509+510510+511511+type labeler_view_detailed = {
512512+ cid : string;
513513+ creator : Jsont.json;
514514+ indexed_at : string;
515515+ labels : Com.Atproto.Label.Defs.label list option;
516516+ like_count : int option;
517517+ policies : Jsont.json;
518518+ reason_types : Com.Atproto.Moderation.Defs.reason_type list option; (** The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. *)
519519+ subject_collections : string list option; (** Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. *)
520520+ subject_types : Com.Atproto.Moderation.Defs.subject_type list option; (** The set of subject types (account, record, etc) this service accepts reports on. *)
521521+ uri : string;
522522+ viewer : Jsont.json option;
523523+}
524524+525525+(** Jsont codec for {!type:labeler_view_detailed}. *)
526526+val labeler_view_detailed_jsont : labeler_view_detailed Jsont.t
527527+528528+529529+type labeler_view = {
530530+ cid : string;
531531+ creator : Jsont.json;
532532+ indexed_at : string;
533533+ labels : Com.Atproto.Label.Defs.label list option;
534534+ like_count : int option;
535535+ uri : string;
536536+ viewer : Jsont.json option;
537537+}
538538+539539+(** Jsont codec for {!type:labeler_view}. *)
540540+val labeler_view_jsont : labeler_view Jsont.t
541541+542542+ end
543543+ module Service : sig
544544+(** A declaration of the existence of labeler service. *)
545545+546546+type main = {
547547+ created_at : string;
548548+ labels : Com.Atproto.Label.Defs.self_labels option;
549549+ policies : Jsont.json;
550550+ reason_types : Com.Atproto.Moderation.Defs.reason_type list option; (** The set of report reason 'codes' which are in-scope for this service to review and action. These usually align to policy categories. If not defined (distinct from empty array), all reason types are allowed. *)
551551+ subject_collections : string list option; (** Set of record types (collection NSIDs) which can be reported to this service. If not defined (distinct from empty array), default is any record type. *)
552552+ subject_types : Com.Atproto.Moderation.Defs.subject_type list option; (** The set of subject types (account, record, etc) this service accepts reports on. *)
553553+}
554554+555555+(** Jsont codec for {!type:main}. *)
556556+val main_jsont : main Jsont.t
557557+558558+ end
559559+ module GetServices : sig
560560+(** Get information about a list of labeler services. *)
561561+562562+(** Query/procedure parameters. *)
563563+type params = {
564564+ detailed : bool option;
565565+ dids : string list;
566566+}
567567+568568+(** Jsont codec for {!type:params}. *)
569569+val params_jsont : params Jsont.t
570570+571571+572572+type output = {
573573+ views : Jsont.json list;
574574+}
575575+576576+(** Jsont codec for {!type:output}. *)
577577+val output_jsont : output Jsont.t
578578+579579+ end
580580+ end
1115581 module Actor : sig
1116582 module Status : sig
1117583(** Advertises an account as currently offering live content. *)
···1126592 duration_minutes : int option; (** The duration of the status in minutes. Applications can choose to impose minimum and maximum limits. *)
1127593 embed : Jsont.json option; (** An optional embed associated with the status. *)
1128594 status : string; (** The status for the account. *)
11291129-}
11301130-11311131-(** Jsont codec for {!type:main}. *)
11321132-val main_jsont : main Jsont.t
11331133-11341134- end
11351135- module Profile : sig
11361136-(** A declaration of a Bluesky account profile. *)
11371137-11381138-type main = {
11391139- avatar : Atp.Blob_ref.t option; (** Small image to be displayed next to posts from account. AKA, 'profile picture' *)
11401140- banner : Atp.Blob_ref.t option; (** Larger horizontal image to display behind profile view. *)
11411141- created_at : string option;
11421142- description : string option; (** Free-form profile description text. *)
11431143- display_name : string option;
11441144- joined_via_starter_pack : Com.Atproto.Repo.StrongRef.main option;
11451145- labels : Com.Atproto.Label.Defs.self_labels option; (** Self-label values, specific to the Bluesky application, on the overall account. *)
11461146- pinned_post : Com.Atproto.Repo.StrongRef.main option;
11471147- pronouns : string option; (** Free-form pronouns text. *)
11481148- website : string option;
1149595}
11505961151597(** Jsont codec for {!type:main}. *)
···1515961val known_followers_jsont : known_followers Jsont.t
15169621517963 end
15181518- module GetPreferences : sig
15191519-(** Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth. *)
964964+ module Profile : sig
965965+(** A declaration of a Bluesky account profile. *)
152096615211521-(** Query/procedure parameters. *)
15221522-type params = unit
15231523-15241524-(** Jsont codec for {!type:params}. *)
15251525-val params_jsont : params Jsont.t
15261526-15271527-15281528-type output = {
15291529- preferences : Jsont.json;
967967+type main = {
968968+ avatar : Atp.Blob_ref.t option; (** Small image to be displayed next to posts from account. AKA, 'profile picture' *)
969969+ banner : Atp.Blob_ref.t option; (** Larger horizontal image to display behind profile view. *)
970970+ created_at : string option;
971971+ description : string option; (** Free-form profile description text. *)
972972+ display_name : string option;
973973+ joined_via_starter_pack : Com.Atproto.Repo.StrongRef.main option;
974974+ labels : Com.Atproto.Label.Defs.self_labels option; (** Self-label values, specific to the Bluesky application, on the overall account. *)
975975+ pinned_post : Com.Atproto.Repo.StrongRef.main option;
976976+ pronouns : string option; (** Free-form pronouns text. *)
977977+ website : string option;
1530978}
153197915321532-(** Jsont codec for {!type:output}. *)
15331533-val output_jsont : output Jsont.t
980980+(** Jsont codec for {!type:main}. *)
981981+val main_jsont : main Jsont.t
15349821535983 end
15361536- module SearchActorsTypeahead : sig
15371537-(** Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth. *)
984984+ module GetProfiles : sig
985985+(** Get detailed profile views of multiple actors. *)
15389861539987(** Query/procedure parameters. *)
1540988type params = {
15411541- limit : int option;
15421542- q : string option; (** Search query prefix; not a full query string. *)
15431543- term : string option; (** DEPRECATED: use 'q' instead. *)
989989+ actors : string list;
1544990}
15459911546992(** Jsont codec for {!type:params}. *)
···154899415499951550996type output = {
15511551- actors : Jsont.json list;
997997+ profiles : Jsont.json list;
1552998}
155399915541000(** Jsont codec for {!type:output}. *)
15551001val output_jsont : output Jsont.t
1556100215571003 end
15581558- module GetProfile : sig
15591559-(** Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth. *)
10041004+ module GetPreferences : sig
10051005+(** Get private preferences attached to the current account. Expected use is synchronization between multiple devices, and import/export during account migration. Requires auth. *)
1560100615611007(** Query/procedure parameters. *)
15621562-type params = {
15631563- actor : string; (** Handle or DID of account to fetch profile of. *)
15641564-}
10081008+type params = unit
1565100915661010(** Jsont codec for {!type:params}. *)
15671011val params_jsont : params Jsont.t
156810121569101315701570-type output = Jsont.json
10141014+type output = {
10151015+ preferences : Jsont.json;
10161016+}
1571101715721018(** Jsont codec for {!type:output}. *)
15731019val output_jsont : output Jsont.t
1574102015751021 end
15761576- module SearchActors : sig
15771577-(** Find actors (profiles) matching search criteria. Does not require auth. *)
10221022+ module GetSuggestions : sig
10231023+(** Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding. *)
1578102415791025(** Query/procedure parameters. *)
15801026type params = {
15811027 cursor : string option;
15821028 limit : int option;
15831583- q : string option; (** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. *)
15841584- term : string option; (** DEPRECATED: use 'q' instead. *)
15851029}
1586103015871031(** Jsont codec for {!type:params}. *)
···15911035type output = {
15921036 actors : Jsont.json list;
15931037 cursor : string option;
10381038+ rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
15941039}
1595104015961041(** Jsont codec for {!type:output}. *)
15971042val output_jsont : output Jsont.t
1598104315991044 end
16001600- module GetSuggestions : sig
16011601-(** Get a list of suggested actors. Expected use is discovery of accounts to follow during new account onboarding. *)
10451045+ module GetProfile : sig
10461046+(** Get detailed profile view of an actor. Does not require auth, but contains relevant metadata with auth. *)
1602104716031048(** Query/procedure parameters. *)
16041049type params = {
16051605- cursor : string option;
16061606- limit : int option;
10501050+ actor : string; (** Handle or DID of account to fetch profile of. *)
16071051}
1608105216091053(** Jsont codec for {!type:params}. *)
16101054val params_jsont : params Jsont.t
161110551612105616131613-type output = {
16141614- actors : Jsont.json list;
16151615- cursor : string option;
16161616- rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
16171617-}
10571057+type output = Jsont.json
1618105816191059(** Jsont codec for {!type:output}. *)
16201060val output_jsont : output Jsont.t
1621106116221062 end
16231623- module GetProfiles : sig
16241624-(** Get detailed profile views of multiple actors. *)
10631063+ module SearchActors : sig
10641064+(** Find actors (profiles) matching search criteria. Does not require auth. *)
1625106516261066(** Query/procedure parameters. *)
16271067type params = {
16281628- actors : string list;
10681068+ cursor : string option;
10691069+ limit : int option;
10701070+ q : string option; (** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. *)
10711071+ term : string option; (** DEPRECATED: use 'q' instead. *)
16291072}
1630107316311074(** Jsont codec for {!type:params}. *)
···163310761634107716351078type output = {
16361636- profiles : Jsont.json list;
10791079+ actors : Jsont.json list;
10801080+ cursor : string option;
16371081}
1638108216391083(** Jsont codec for {!type:output}. *)
···16521096val input_jsont : input Jsont.t
1653109716541098 end
16551655- end
16561656- module Contact : sig
16571657- module Defs : sig
10991099+ module SearchActorsTypeahead : sig
11001100+(** Find actor suggestions for a prefix search term. Expected use is for auto-completion during text field entry. Does not require auth. *)
1658110116591659-type sync_status = {
16601660- matches_count : int; (** Number of existing contact matches resulting of the user imports and of their imported contacts having imported the user. Matches stop being counted when the user either follows the matched contact or dismisses the match. *)
16611661- synced_at : string; (** Last date when contacts where imported. *)
11021102+(** Query/procedure parameters. *)
11031103+type params = {
11041104+ limit : int option;
11051105+ q : string option; (** Search query prefix; not a full query string. *)
11061106+ term : string option; (** DEPRECATED: use 'q' instead. *)
16621107}
1663110816641664-(** Jsont codec for {!type:sync_status}. *)
16651665-val sync_status_jsont : sync_status Jsont.t
11091109+(** Jsont codec for {!type:params}. *)
11101110+val params_jsont : params Jsont.t
1666111116671667-(** A stash object to be sent via bsync representing a notification to be created. *)
1668111216691669-type notification = {
16701670- from : string; (** The DID of who this notification comes from. *)
16711671- to_ : string; (** The DID of who this notification should go to. *)
16721672-}
16731673-16741674-(** Jsont codec for {!type:notification}. *)
16751675-val notification_jsont : notification Jsont.t
16761676-16771677-(** Associates a profile with the positional index of the contact import input in the call to `app.bsky.contact.importContacts`, so clients can know which phone caused a particular match. *)
16781678-16791679-type match_and_contact_index = {
16801680- contact_index : int; (** The index of this match in the import contact input. *)
16811681- match_ : Jsont.json; (** Profile of the matched user. *)
11131113+type output = {
11141114+ actors : Jsont.json list;
16821115}
1683111616841684-(** Jsont codec for {!type:match_and_contact_index}. *)
16851685-val match_and_contact_index_jsont : match_and_contact_index Jsont.t
11171117+(** Jsont codec for {!type:output}. *)
11181118+val output_jsont : output Jsont.t
1686111916871120 end
16881688- module RemoveData : sig
16891689-(** Removes all stored hashes used for contact matching, existing matches, and sync status. Requires authentication. *)
16901690-16911691-16921692-type input = unit
16931693-16941694-(** Jsont codec for {!type:input}. *)
16951695-val input_jsont : input Jsont.t
11211121+ end
11221122+ module Video : sig
11231123+ module GetUploadLimits : sig
11241124+(** Get video upload limits for the authenticated user. *)
169611251697112616981698-type output = unit
11271127+type output = {
11281128+ can_upload : bool;
11291129+ error : string option;
11301130+ message : string option;
11311131+ remaining_daily_bytes : int option;
11321132+ remaining_daily_videos : int option;
11331133+}
1699113417001135(** Jsont codec for {!type:output}. *)
17011136val output_jsont : output Jsont.t
1702113717031138 end
17041704- module DismissMatch : sig
17051705-(** Removes a match that was found via contact import. It shouldn't appear again if the same contact is re-imported. Requires authentication. *)
17061706-11391139+ module Defs : sig
1707114017081708-type input = {
17091709- subject : string; (** The subject's DID to dismiss the match with. *)
11411141+type job_status = {
11421142+ blob : Atp.Blob_ref.t option;
11431143+ did : string;
11441144+ error : string option;
11451145+ job_id : string;
11461146+ message : string option;
11471147+ progress : int option; (** Progress within the current processing state. *)
11481148+ state : string; (** The state of the video processing job. All values not listed as a known value indicate that the job is in process. *)
17101149}
1711115017121712-(** Jsont codec for {!type:input}. *)
17131713-val input_jsont : input Jsont.t
17141714-17151715-17161716-type output = unit
17171717-17181718-(** Jsont codec for {!type:output}. *)
17191719-val output_jsont : output Jsont.t
11511151+(** Jsont codec for {!type:job_status}. *)
11521152+val job_status_jsont : job_status Jsont.t
1720115317211154 end
17221722- module GetMatches : sig
17231723-(** Returns the matched contacts (contacts that were mutually imported). Excludes dismissed matches. Requires authentication. *)
11551155+ module GetJobStatus : sig
11561156+(** Get status details for a video processing job. *)
1724115717251158(** Query/procedure parameters. *)
17261159type params = {
17271727- cursor : string option;
17281728- limit : int option;
11601160+ job_id : string;
17291161}
1730116217311163(** Jsont codec for {!type:params}. *)
···173311651734116617351167type output = {
17361736- cursor : string option;
17371737- matches : Jsont.json list;
11681168+ job_status : Defs.job_status;
17381169}
1739117017401171(** Jsont codec for {!type:output}. *)
17411172val output_jsont : output Jsont.t
1742117317431174 end
17441744- module VerifyPhone : sig
17451745-(** Verifies control over a phone number with a code received via SMS and starts a contact import session. Requires authentication. *)
11751175+ module UploadVideo : sig
11761176+(** Upload a video to be processed then stored on the PDS. *)
174611771747117817481748-type input = {
17491749- code : string; (** The code received via SMS as a result of the call to `app.bsky.contact.startPhoneVerification`. *)
17501750- phone : string; (** The phone number to verify. Should be the same as the one passed to `app.bsky.contact.startPhoneVerification`. *)
17511751-}
17521752-17531753-(** Jsont codec for {!type:input}. *)
11791179+type input = unit
17541180val input_jsont : input Jsont.t
175511811756118217571183type output = {
17581758- token : string; (** JWT to be used in a call to `app.bsky.contact.importContacts`. It is only valid for a single call. *)
11841184+ job_status : Defs.job_status;
17591185}
1760118617611187(** Jsont codec for {!type:output}. *)
17621188val output_jsont : output Jsont.t
1763118917641190 end
17651765- module StartPhoneVerification : sig
17661766-(** Starts a phone verification flow. The phone passed will receive a code via SMS that should be passed to `app.bsky.contact.verifyPhone`. Requires authentication. *)
11911191+ end
11921192+ module Richtext : sig
11931193+ module Facet : sig
11941194+(** Facet feature for a hashtag. The text usually includes a '#' prefix, but the facet reference should not (except in the case of 'double hash tags'). *)
1767119511961196+type tag = {
11971197+ tag : string;
11981198+}
1768119917691769-type input = {
17701770- phone : string; (** The phone number to receive the code via SMS. *)
12001200+(** Jsont codec for {!type:tag}. *)
12011201+val tag_jsont : tag Jsont.t
12021202+12031203+(** Facet feature for mention of another account. The text is usually a handle, including a '\@' prefix, but the facet reference is a DID. *)
12041204+12051205+type mention = {
12061206+ did : string;
17711207}
1772120817731773-(** Jsont codec for {!type:input}. *)
17741774-val input_jsont : input Jsont.t
12091209+(** Jsont codec for {!type:mention}. *)
12101210+val mention_jsont : mention Jsont.t
1775121112121212+(** Facet feature for a URL. The text URL may have been simplified or truncated, but the facet reference should be a complete URL. *)
1776121317771777-type output = unit
12141214+type link = {
12151215+ uri : string;
12161216+}
1778121717791779-(** Jsont codec for {!type:output}. *)
17801780-val output_jsont : output Jsont.t
12181218+(** Jsont codec for {!type:link}. *)
12191219+val link_jsont : link Jsont.t
1781122017821782- end
17831783- module SendNotification : sig
17841784-(** System endpoint to send notifications related to contact imports. Requires role authentication. *)
12211221+(** Specifies the sub-string range a facet feature applies to. Start index is inclusive, end index is exclusive. Indices are zero-indexed, counting bytes of the UTF-8 encoded text. NOTE: some languages, like Javascript, use UTF-16 or Unicode codepoints for string slice indexing; in these languages, convert to byte arrays before working with facets. *)
1785122217861786-17871787-type input = {
17881788- from : string; (** The DID of who this notification comes from. *)
17891789- to_ : string; (** The DID of who this notification should go to. *)
12231223+type byte_slice = {
12241224+ byte_end : int;
12251225+ byte_start : int;
17901226}
1791122717921792-(** Jsont codec for {!type:input}. *)
17931793-val input_jsont : input Jsont.t
12281228+(** Jsont codec for {!type:byte_slice}. *)
12291229+val byte_slice_jsont : byte_slice Jsont.t
1794123012311231+(** Annotation of a sub-string within rich text. *)
1795123217961796-type output = unit
12331233+type main = {
12341234+ features : Jsont.json list;
12351235+ index : byte_slice;
12361236+}
1797123717981798-(** Jsont codec for {!type:output}. *)
17991799-val output_jsont : output Jsont.t
12381238+(** Jsont codec for {!type:main}. *)
12391239+val main_jsont : main Jsont.t
1800124018011241 end
18021802- module GetSyncStatus : sig
18031803-(** Gets the user's current contact import status. Requires authentication. *)
12421242+ end
12431243+ module AuthCreatePosts : sig
1804124418051805-(** Query/procedure parameters. *)
18061806-type params = unit
12451245+type main = unit
12461246+val main_jsont : main Jsont.t
1807124718081808-(** Jsont codec for {!type:params}. *)
18091809-val params_jsont : params Jsont.t
12481248+ end
12491249+ module Ageassurance : sig
12501250+ module Defs : sig
12511251+(** The status of the Age Assurance process. *)
1810125212531253+type status = string
12541254+val status_jsont : status Jsont.t
1811125518121812-type output = {
18131813- sync_status : Defs.sync_status option; (** If present, indicates the user has imported their contacts. If not present, indicates the user never used the feature or called `app.bsky.contact.removeData` and didn't import again since. *)
12561256+(** Additional metadata needed to compute Age Assurance state client-side. *)
12571257+12581258+type state_metadata = {
12591259+ account_created_at : string option; (** The account creation timestamp. *)
18141260}
1815126118161816-(** Jsont codec for {!type:output}. *)
18171817-val output_jsont : output Jsont.t
12621262+(** Jsont codec for {!type:state_metadata}. *)
12631263+val state_metadata_jsont : state_metadata Jsont.t
1818126418191819- end
18201820- module ImportContacts : sig
18211821-(** Import contacts for securely matching with other users. This follows the protocol explained in https://docs.bsky.app/blog/contact-import-rfc. Requires authentication. *)
12651265+(** Object used to store Age Assurance data in stash. *)
1822126618231823-18241824-type input = {
18251825- contacts : string list; (** List of phone numbers in global E.164 format (e.g., '+12125550123'). Phone numbers that cannot be normalized into a valid phone number will be discarded. Should not repeat the 'phone' input used in `app.bsky.contact.verifyPhone`. *)
18261826- token : string; (** JWT to authenticate the call. Use the JWT received as a response to the call to `app.bsky.contact.verifyPhone`. *)
12671267+type event = {
12681268+ access : string; (** The access level granted based on Age Assurance data we've processed. *)
12691269+ attempt_id : string; (** The unique identifier for this instance of the Age Assurance flow, in UUID format. *)
12701270+ complete_ip : string option; (** The IP address used when completing the Age Assurance flow. *)
12711271+ complete_ua : string option; (** The user agent used when completing the Age Assurance flow. *)
12721272+ country_code : string; (** The ISO 3166-1 alpha-2 country code provided when beginning the Age Assurance flow. *)
12731273+ created_at : string; (** The date and time of this write operation. *)
12741274+ email : string option; (** The email used for Age Assurance. *)
12751275+ init_ip : string option; (** The IP address used when initiating the Age Assurance flow. *)
12761276+ init_ua : string option; (** The user agent used when initiating the Age Assurance flow. *)
12771277+ region_code : string option; (** The ISO 3166-2 region code provided when beginning the Age Assurance flow. *)
12781278+ status : string; (** The status of the Age Assurance process. *)
18271279}
1828128018291829-(** Jsont codec for {!type:input}. *)
18301830-val input_jsont : input Jsont.t
12811281+(** Jsont codec for {!type:event}. *)
12821282+val event_jsont : event Jsont.t
1831128312841284+(** The Age Assurance configuration for a specific region. *)
1832128518331833-type output = {
18341834- matches_and_contact_indexes : Defs.match_and_contact_index list; (** The users that matched during import and their indexes on the input contacts, so the client can correlate with its local list. *)
12861286+type config_region = {
12871287+ country_code : string; (** The ISO 3166-1 alpha-2 country code this configuration applies to. *)
12881288+ min_access_age : int; (** The minimum age (as a whole integer) required to use Bluesky in this region. *)
12891289+ region_code : string option; (** The ISO 3166-2 region code this configuration applies to. If omitted, the configuration applies to the entire country. *)
12901290+ rules : Jsont.json list; (** The ordered list of Age Assurance rules that apply to this region. Rules should be applied in order, and the first matching rule determines the access level granted. The rules array should always include a default rule as the last item. *)
18351291}
1836129218371837-(** Jsont codec for {!type:output}. *)
18381838-val output_jsont : output Jsont.t
12931293+(** Jsont codec for {!type:config_region}. *)
12941294+val config_region_jsont : config_region Jsont.t
12951295+12961296+(** The access level granted based on Age Assurance data we've processed. *)
1839129718401840- end
18411841- end
18421842- module Graph : sig
18431843- module Starterpack : sig
12981298+type access = string
12991299+val access_jsont : access Jsont.t
13001300+13011301+(** The user's computed Age Assurance state. *)
1844130218451845-type feed_item = {
18461846- uri : string;
13031303+type state = {
13041304+ access : access;
13051305+ last_initiated_at : string option; (** The timestamp when this state was last updated. *)
13061306+ status : status;
18471307}
1848130818491849-(** Jsont codec for {!type:feed_item}. *)
18501850-val feed_item_jsont : feed_item Jsont.t
13091309+(** Jsont codec for {!type:state}. *)
13101310+val state_jsont : state Jsont.t
1851131118521852-(** Record defining a starter pack of actors and feeds for new users. *)
13121312+(** Age Assurance rule that applies if the user has declared themselves under a certain age. *)
1853131318541854-type main = {
18551855- created_at : string;
18561856- description : string option;
18571857- description_facets : Richtext.Facet.main list option;
18581858- feeds : Jsont.json list option;
18591859- list_ : string; (** Reference (AT-URI) to the list record. *)
18601860- name : string; (** Display name for starter pack; can not be empty. *)
13141314+type config_region_rule_if_declared_under_age = {
13151315+ access : access;
13161316+ age : int; (** The age threshold as a whole integer. *)
18611317}
1862131818631863-(** Jsont codec for {!type:main}. *)
18641864-val main_jsont : main Jsont.t
13191319+(** Jsont codec for {!type:config_region_rule_if_declared_under_age}. *)
13201320+val config_region_rule_if_declared_under_age_jsont : config_region_rule_if_declared_under_age Jsont.t
1865132118661866- end
18671867- module GetFollows : sig
18681868-(** Enumerates accounts which a specified account (actor) follows. *)
13221322+(** Age Assurance rule that applies if the user has declared themselves equal-to or over a certain age. *)
1869132318701870-(** Query/procedure parameters. *)
18711871-type params = {
18721872- actor : string;
18731873- cursor : string option;
18741874- limit : int option;
13241324+type config_region_rule_if_declared_over_age = {
13251325+ access : access;
13261326+ age : int; (** The age threshold as a whole integer. *)
18751327}
1876132818771877-(** Jsont codec for {!type:params}. *)
18781878-val params_jsont : params Jsont.t
13291329+(** Jsont codec for {!type:config_region_rule_if_declared_over_age}. *)
13301330+val config_region_rule_if_declared_over_age_jsont : config_region_rule_if_declared_over_age Jsont.t
1879133113321332+(** Age Assurance rule that applies if the user has been assured to be under a certain age. *)
1880133318811881-type output = {
18821882- cursor : string option;
18831883- follows : Jsont.json list;
18841884- subject : Jsont.json;
13341334+type config_region_rule_if_assured_under_age = {
13351335+ access : access;
13361336+ age : int; (** The age threshold as a whole integer. *)
18851337}
1886133818871887-(** Jsont codec for {!type:output}. *)
18881888-val output_jsont : output Jsont.t
13391339+(** Jsont codec for {!type:config_region_rule_if_assured_under_age}. *)
13401340+val config_region_rule_if_assured_under_age_jsont : config_region_rule_if_assured_under_age Jsont.t
1889134118901890- end
18911891- module GetSuggestedFollowsByActor : sig
18921892-(** Enumerates follows similar to a given account (actor). Expected use is to recommend additional accounts immediately after following one account. *)
13421342+(** Age Assurance rule that applies if the user has been assured to be equal-to or over a certain age. *)
1893134318941894-(** Query/procedure parameters. *)
18951895-type params = {
18961896- actor : string;
13441344+type config_region_rule_if_assured_over_age = {
13451345+ access : access;
13461346+ age : int; (** The age threshold as a whole integer. *)
18971347}
1898134818991899-(** Jsont codec for {!type:params}. *)
19001900-val params_jsont : params Jsont.t
13491349+(** Jsont codec for {!type:config_region_rule_if_assured_over_age}. *)
13501350+val config_region_rule_if_assured_over_age_jsont : config_region_rule_if_assured_over_age Jsont.t
1901135113521352+(** Age Assurance rule that applies if the account is older than a certain date. *)
1902135319031903-type output = {
19041904- is_fallback : bool option; (** If true, response has fallen-back to generic results, and is not scoped using relativeToDid *)
19051905- rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
19061906- suggestions : Jsont.json list;
13541354+type config_region_rule_if_account_older_than = {
13551355+ access : access;
13561356+ date : string; (** The date threshold as a datetime string. *)
19071357}
1908135819091909-(** Jsont codec for {!type:output}. *)
19101910-val output_jsont : output Jsont.t
13591359+(** Jsont codec for {!type:config_region_rule_if_account_older_than}. *)
13601360+val config_region_rule_if_account_older_than_jsont : config_region_rule_if_account_older_than Jsont.t
1911136119121912- end
19131913- module Block : sig
19141914-(** Record declaring a 'block' relationship against another account. NOTE: blocks are public in Bluesky; see blog posts for details. *)
13621362+(** Age Assurance rule that applies if the account is equal-to or newer than a certain date. *)
1915136319161916-type main = {
19171917- created_at : string;
19181918- subject : string; (** DID of the account to be blocked. *)
13641364+type config_region_rule_if_account_newer_than = {
13651365+ access : access;
13661366+ date : string; (** The date threshold as a datetime string. *)
19191367}
1920136819211921-(** Jsont codec for {!type:main}. *)
19221922-val main_jsont : main Jsont.t
13691369+(** Jsont codec for {!type:config_region_rule_if_account_newer_than}. *)
13701370+val config_region_rule_if_account_newer_than_jsont : config_region_rule_if_account_newer_than Jsont.t
1923137119241924- end
19251925- module Listblock : sig
19261926-(** Record representing a block relationship against an entire an entire list of accounts (actors). *)
13721372+(** Age Assurance rule that applies by default. *)
1927137319281928-type main = {
19291929- created_at : string;
19301930- subject : string; (** Reference (AT-URI) to the mod list record. *)
13741374+type config_region_rule_default = {
13751375+ access : access;
19311376}
1932137719331933-(** Jsont codec for {!type:main}. *)
19341934-val main_jsont : main Jsont.t
13781378+(** Jsont codec for {!type:config_region_rule_default}. *)
13791379+val config_region_rule_default_jsont : config_region_rule_default Jsont.t
1935138019361936- end
19371937- module MuteThread : sig
19381938-(** Mutes a thread preventing notifications from the thread and any of its children. Mutes are private in Bluesky. Requires auth. *)
1939138119401940-19411941-type input = {
19421942- root : string;
13821382+type config = {
13831383+ regions : config_region list; (** The per-region Age Assurance configuration. *)
19431384}
1944138519451945-(** Jsont codec for {!type:input}. *)
19461946-val input_jsont : input Jsont.t
13861386+(** Jsont codec for {!type:config}. *)
13871387+val config_jsont : config Jsont.t
1947138819481389 end
19491949- module GetFollowers : sig
19501950-(** Enumerates accounts which follow a specified account (actor). *)
13901390+ module GetState : sig
13911391+(** Returns server-computed Age Assurance state, if available, and any additional metadata needed to compute Age Assurance state client-side. *)
1951139219521393(** Query/procedure parameters. *)
19531394type params = {
19541954- actor : string;
19551955- cursor : string option;
19561956- limit : int option;
13951395+ country_code : string;
13961396+ region_code : string option;
19571397}
1958139819591399(** Jsont codec for {!type:params}. *)
···196114011962140219631403type output = {
19641964- cursor : string option;
19651965- followers : Jsont.json list;
19661966- subject : Jsont.json;
14041404+ metadata : Defs.state_metadata;
14051405+ state : Defs.state;
19671406}
1968140719691408(** Jsont codec for {!type:output}. *)
19701409val output_jsont : output Jsont.t
1971141019721411 end
19731973- module UnmuteThread : sig
19741974-(** Unmutes the specified thread. Requires auth. *)
14121412+ module Begin : sig
14131413+(** Initiate Age Assurance for an account. *)
197514141976141519771416type input = {
19781978- root : string;
14171417+ country_code : string; (** An ISO 3166-1 alpha-2 code of the user's location. *)
14181418+ email : string; (** The user's email address to receive Age Assurance instructions. *)
14191419+ language : string; (** The user's preferred language for communication during the Age Assurance process. *)
14201420+ region_code : string option; (** An optional ISO 3166-2 code of the user's region or state within the country. *)
19791421}
1980142219811423(** Jsont codec for {!type:input}. *)
19821424val input_jsont : input Jsont.t
1983142519841984- end
19851985- module Follow : sig
19861986-(** Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView. *)
1987142619881988-type main = {
19891989- created_at : string;
19901990- subject : string;
19911991- via : Com.Atproto.Repo.StrongRef.main option;
19921992-}
14271427+type output = Defs.state
1993142819941994-(** Jsont codec for {!type:main}. *)
19951995-val main_jsont : main Jsont.t
14291429+(** Jsont codec for {!type:output}. *)
14301430+val output_jsont : output Jsont.t
1996143119971432 end
19981998- module UnmuteActor : sig
19991999-(** Unmutes the specified account. Requires auth. *)
14331433+ module GetConfig : sig
14341434+(** Returns Age Assurance configuration for use on the client. *)
200014352001143620022002-type input = {
20032003- actor : string;
20042004-}
20052005-20062006-(** Jsont codec for {!type:input}. *)
20072007-val input_jsont : input Jsont.t
20082008-20092009- end
20102010- module MuteActorList : sig
20112011-(** Creates a mute relationship for the specified list of accounts. Mutes are private in Bluesky. Requires auth. *)
20122012-20132013-20142014-type input = {
20152015- list_ : string;
20162016-}
14371437+type output = Defs.config
2017143820182018-(** Jsont codec for {!type:input}. *)
20192019-val input_jsont : input Jsont.t
14391439+(** Jsont codec for {!type:output}. *)
14401440+val output_jsont : output Jsont.t
2020144120211442 end
20222022- module UnmuteActorList : sig
20232023-(** Unmutes the specified list of accounts. Requires auth. *)
20242024-14431443+ end
14441444+ module Embed : sig
14451445+ module Defs : sig
14461446+(** width:height represents an aspect ratio. It may be approximate, and may not correspond to absolute dimensions in any given unit. *)
2025144720262026-type input = {
20272027- list_ : string;
14481448+type aspect_ratio = {
14491449+ height : int;
14501450+ width : int;
20281451}
2029145220302030-(** Jsont codec for {!type:input}. *)
20312031-val input_jsont : input Jsont.t
14531453+(** Jsont codec for {!type:aspect_ratio}. *)
14541454+val aspect_ratio_jsont : aspect_ratio Jsont.t
2032145520331456 end
20342034- module GetKnownFollowers : sig
20352035-(** Enumerates accounts which follow a specified account (actor) and are followed by the viewer. *)
14571457+ module External : sig
2036145820372037-(** Query/procedure parameters. *)
20382038-type params = {
20392039- actor : string;
20402040- cursor : string option;
20412041- limit : int option;
14591459+type view_external = {
14601460+ description : string;
14611461+ thumb : string option;
14621462+ title : string;
14631463+ uri : string;
20421464}
2043146520442044-(** Jsont codec for {!type:params}. *)
20452045-val params_jsont : params Jsont.t
14661466+(** Jsont codec for {!type:view_external}. *)
14671467+val view_external_jsont : view_external Jsont.t
204614682047146920482048-type output = {
20492049- cursor : string option;
20502050- followers : Jsont.json list;
20512051- subject : Jsont.json;
14701470+type external_ = {
14711471+ description : string;
14721472+ thumb : Atp.Blob_ref.t option;
14731473+ title : string;
14741474+ uri : string;
20521475}
2053147620542054-(** Jsont codec for {!type:output}. *)
20552055-val output_jsont : output Jsont.t
20562056-20572057- end
20582058- module MuteActor : sig
20592059-(** Creates a mute relationship for the specified account. Mutes are private in Bluesky. Requires auth. *)
14771477+(** Jsont codec for {!type:external_}. *)
14781478+val external__jsont : external_ Jsont.t
206014792061148020622062-type input = {
20632063- actor : string;
14811481+type view = {
14821482+ external_ : Jsont.json;
20641483}
2065148420662066-(** Jsont codec for {!type:input}. *)
20672067-val input_jsont : input Jsont.t
14851485+(** Jsont codec for {!type:view}. *)
14861486+val view_jsont : view Jsont.t
2068148720692069- end
20702070- module Listitem : sig
20712071-(** Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records. *)
14881488+(** A representation of some externally linked content (eg, a URL and 'card'), embedded in a Bluesky record (eg, a post). *)
2072148920731490type main = {
20742074- created_at : string;
20752075- list_ : string; (** Reference (AT-URI) to the list record (app.bsky.graph.list). *)
20762076- subject : string; (** The account which is included on the list. *)
14911491+ external_ : Jsont.json;
20771492}
2078149320791494(** Jsont codec for {!type:main}. *)
20801495val main_jsont : main Jsont.t
2081149620821497 end
20832083- module Defs : sig
14981498+ module Images : sig
2084149920852085-type starter_pack_view_basic = {
20862086- cid : string;
20872087- creator : Jsont.json;
20882088- indexed_at : string;
20892089- joined_all_time_count : int option;
20902090- joined_week_count : int option;
20912091- labels : Com.Atproto.Label.Defs.label list option;
20922092- list_item_count : int option;
20932093- record : Jsont.json;
20942094- uri : string;
15001500+type view_image = {
15011501+ alt : string; (** Alt text description of the image, for accessibility. *)
15021502+ aspect_ratio : Jsont.json option;
15031503+ fullsize : string; (** Fully-qualified URL where a large version of the image can be fetched. May or may not be the exact original blob. For example, CDN location provided by the App View. *)
15041504+ thumb : string; (** Fully-qualified URL where a thumbnail of the image can be fetched. For example, CDN location provided by the App View. *)
20951505}
2096150620972097-(** Jsont codec for {!type:starter_pack_view_basic}. *)
20982098-val starter_pack_view_basic_jsont : starter_pack_view_basic Jsont.t
15071507+(** Jsont codec for {!type:view_image}. *)
15081508+val view_image_jsont : view_image Jsont.t
2099150921002100-(** lists the bi-directional graph relationships between one actor (not indicated in the object), and the target actors (the DID included in the object) *)
2101151021022102-type relationship = {
21032103- blocked_by : string option; (** if the actor is blocked by this DID, contains the AT-URI of the block record *)
21042104- blocked_by_list : string option; (** if the actor is blocked by this DID via a block list, contains the AT-URI of the listblock record *)
21052105- blocking : string option; (** if the actor blocks this DID, this is the AT-URI of the block record *)
21062106- blocking_by_list : string option; (** if the actor blocks this DID via a block list, this is the AT-URI of the listblock record *)
21072107- did : string;
21082108- followed_by : string option; (** if the actor is followed by this DID, contains the AT-URI of the follow record *)
21092109- following : string option; (** if the actor follows this DID, this is the AT-URI of the follow record *)
15111511+type image = {
15121512+ alt : string; (** Alt text description of the image, for accessibility. *)
15131513+ aspect_ratio : Jsont.json option;
15141514+ image : Atp.Blob_ref.t;
21101515}
2111151621122112-(** Jsont codec for {!type:relationship}. *)
21132113-val relationship_jsont : relationship Jsont.t
15171517+(** Jsont codec for {!type:image}. *)
15181518+val image_jsont : image Jsont.t
2114151921152115-(** A list of actors used for only for reference purposes such as within a starter pack. *)
2116152021172117-type referencelist = string
21182118-val referencelist_jsont : referencelist Jsont.t
15211521+type view = {
15221522+ images : Jsont.json list;
15231523+}
2119152421202120-(** indicates that a handle or DID could not be resolved *)
15251525+(** Jsont codec for {!type:view}. *)
15261526+val view_jsont : view Jsont.t
2121152721222122-type not_found_actor = {
21232123- actor : string;
21242124- not_found : bool;
15281528+15291529+type main = {
15301530+ images : Jsont.json list;
21251531}
2126153221272127-(** Jsont codec for {!type:not_found_actor}. *)
21282128-val not_found_actor_jsont : not_found_actor Jsont.t
15331533+(** Jsont codec for {!type:main}. *)
15341534+val main_jsont : main Jsont.t
15351535+15361536+ end
15371537+ module Video : sig
2129153821302130-(** A list of actors to apply an aggregate moderation action (mute/block) on. *)
15391539+type view = {
15401540+ alt : string option;
15411541+ aspect_ratio : Jsont.json option;
15421542+ cid : string;
15431543+ playlist : string;
15441544+ thumbnail : string option;
15451545+}
2131154621322132-type modlist = string
21332133-val modlist_jsont : modlist Jsont.t
15471547+(** Jsont codec for {!type:view}. *)
15481548+val view_jsont : view Jsont.t
213415492135155021362136-type list_viewer_state = {
21372137- blocked : string option;
21382138- muted : bool option;
15511551+type caption = {
15521552+ file : Atp.Blob_ref.t;
15531553+ lang : string;
21391554}
2140155521412141-(** Jsont codec for {!type:list_viewer_state}. *)
21422142-val list_viewer_state_jsont : list_viewer_state Jsont.t
15561556+(** Jsont codec for {!type:caption}. *)
15571557+val caption_jsont : caption Jsont.t
214315582144155921452145-type list_purpose = string
21462146-val list_purpose_jsont : list_purpose Jsont.t
15601560+type main = {
15611561+ alt : string option; (** Alt text description of the video, for accessibility. *)
15621562+ aspect_ratio : Jsont.json option;
15631563+ captions : Jsont.json list option;
15641564+ video : Atp.Blob_ref.t; (** The mp4 video file. May be up to 100mb, formerly limited to 50mb. *)
15651565+}
2147156615671567+(** Jsont codec for {!type:main}. *)
15681568+val main_jsont : main Jsont.t
2148156921492149-type list_item_view = {
21502150- subject : Jsont.json;
21512151- uri : string;
15701570+ end
15711571+ module RecordWithMedia : sig
15721572+15731573+type view = {
15741574+ media : Jsont.json;
15751575+ record : Jsont.json;
21521576}
2153157721542154-(** Jsont codec for {!type:list_item_view}. *)
21552155-val list_item_view_jsont : list_item_view Jsont.t
15781578+(** Jsont codec for {!type:view}. *)
15791579+val view_jsont : view Jsont.t
15801580+2156158121572157-(** A list of actors used for curation purposes such as list feeds or interaction gating. *)
15821582+type main = {
15831583+ media : Jsont.json;
15841584+ record : Jsont.json;
15851585+}
2158158621592159-type curatelist = string
21602160-val curatelist_jsont : curatelist Jsont.t
15871587+(** Jsont codec for {!type:main}. *)
15881588+val main_jsont : main Jsont.t
2161158915901590+ end
15911591+ module Record : sig
2162159221632163-type list_view_basic = {
21642164- avatar : string option;
15931593+type view_record = {
15941594+ author : Jsont.json;
21651595 cid : string;
21662166- indexed_at : string option;
15961596+ embeds : Jsont.json list option;
15971597+ indexed_at : string;
21671598 labels : Com.Atproto.Label.Defs.label list option;
21682168- list_item_count : int option;
21692169- name : string;
21702170- purpose : Jsont.json;
15991599+ like_count : int option;
16001600+ quote_count : int option;
16011601+ reply_count : int option;
16021602+ repost_count : int option;
21711603 uri : string;
21722172- viewer : Jsont.json option;
16041604+ value : Jsont.json; (** The record data itself. *)
21731605}
2174160621752175-(** Jsont codec for {!type:list_view_basic}. *)
21762176-val list_view_basic_jsont : list_view_basic Jsont.t
16071607+(** Jsont codec for {!type:view_record}. *)
16081608+val view_record_jsont : view_record Jsont.t
217716092178161021792179-type list_view = {
21802180- avatar : string option;
21812181- cid : string;
21822182- creator : Jsont.json;
21832183- description : string option;
21842184- description_facets : Richtext.Facet.main list option;
21852185- indexed_at : string;
21862186- labels : Com.Atproto.Label.Defs.label list option;
21872187- list_item_count : int option;
21882188- name : string;
21892189- purpose : Jsont.json;
16111611+type view_not_found = {
16121612+ not_found : bool;
21901613 uri : string;
21912191- viewer : Jsont.json option;
21921614}
2193161521942194-(** Jsont codec for {!type:list_view}. *)
21952195-val list_view_jsont : list_view Jsont.t
16161616+(** Jsont codec for {!type:view_not_found}. *)
16171617+val view_not_found_jsont : view_not_found Jsont.t
219616182197161921982198-type starter_pack_view = {
21992199- cid : string;
22002200- creator : Jsont.json;
22012201- feeds : Jsont.json list option;
22022202- indexed_at : string;
22032203- joined_all_time_count : int option;
22042204- joined_week_count : int option;
22052205- labels : Com.Atproto.Label.Defs.label list option;
22062206- list_ : Jsont.json option;
22072207- list_items_sample : Jsont.json list option;
22082208- record : Jsont.json;
16201620+type view_detached = {
16211621+ detached : bool;
22091622 uri : string;
22101623}
2211162422122212-(** Jsont codec for {!type:starter_pack_view}. *)
22132213-val starter_pack_view_jsont : starter_pack_view Jsont.t
16251625+(** Jsont codec for {!type:view_detached}. *)
16261626+val view_detached_jsont : view_detached Jsont.t
2214162722152215- end
22162216- module GetBlocks : sig
22172217-(** Enumerates which accounts the requesting account is currently blocking. Requires auth. *)
2218162822192219-(** Query/procedure parameters. *)
22202220-type params = {
22212221- cursor : string option;
22222222- limit : int option;
16291629+type view_blocked = {
16301630+ author : Jsont.json;
16311631+ blocked : bool;
16321632+ uri : string;
22231633}
2224163422252225-(** Jsont codec for {!type:params}. *)
22262226-val params_jsont : params Jsont.t
16351635+(** Jsont codec for {!type:view_blocked}. *)
16361636+val view_blocked_jsont : view_blocked Jsont.t
222716372228163822292229-type output = {
22302230- blocks : Jsont.json list;
22312231- cursor : string option;
16391639+type view = {
16401640+ record : Jsont.json;
22321641}
2233164222342234-(** Jsont codec for {!type:output}. *)
22352235-val output_jsont : output Jsont.t
16431643+(** Jsont codec for {!type:view}. *)
16441644+val view_jsont : view Jsont.t
2236164522372237- end
22382238- module Verification : sig
22392239-(** Record declaring a verification relationship between two accounts. Verifications are only considered valid by an app if issued by an account the app considers trusted. *)
2240164622411647type main = {
22422242- created_at : string; (** Date of when the verification was created. *)
22432243- display_name : string; (** Display name of the subject the verification applies to at the moment of verifying, which might not be the same at the time of viewing. The verification is only valid if the current displayName matches the one at the time of verifying. *)
22442244- handle : string; (** Handle of the subject the verification applies to at the moment of verifying, which might not be the same at the time of viewing. The verification is only valid if the current handle matches the one at the time of verifying. *)
22452245- subject : string; (** DID of the subject the verification applies to. *)
16481648+ record : Com.Atproto.Repo.StrongRef.main;
22461649}
2247165022481651(** Jsont codec for {!type:main}. *)
22491652val main_jsont : main Jsont.t
2250165322511654 end
22522252- module GetMutes : sig
22532253-(** Enumerates accounts that the requesting account (actor) currently has muted. Requires auth. *)
16551655+ end
16561656+ module Contact : sig
16571657+ module SendNotification : sig
16581658+(** System endpoint to send notifications related to contact imports. Requires role authentication. *)
2254165922552255-(** Query/procedure parameters. *)
22562256-type params = {
22572257- cursor : string option;
22582258- limit : int option;
16601660+16611661+type input = {
16621662+ from : string; (** The DID of who this notification comes from. *)
16631663+ to_ : string; (** The DID of who this notification should go to. *)
22591664}
2260166522612261-(** Jsont codec for {!type:params}. *)
22622262-val params_jsont : params Jsont.t
16661666+(** Jsont codec for {!type:input}. *)
16671667+val input_jsont : input Jsont.t
226316682264166922652265-type output = {
22662266- cursor : string option;
22672267- mutes : Jsont.json list;
22682268-}
16701670+type output = unit
2269167122701672(** Jsont codec for {!type:output}. *)
22711673val output_jsont : output Jsont.t
2272167422731675 end
22742274- module GetRelationships : sig
22752275-(** Enumerates public relationships between one account, and a list of other accounts. Does not require auth. *)
16761676+ module StartPhoneVerification : sig
16771677+(** Starts a phone verification flow. The phone passed will receive a code via SMS that should be passed to `app.bsky.contact.verifyPhone`. Requires authentication. *)
16781678+2276167922772277-(** Query/procedure parameters. *)
22782278-type params = {
22792279- actor : string; (** Primary account requesting relationships for. *)
22802280- others : string list option; (** List of 'other' accounts to be related back to the primary. *)
16801680+type input = {
16811681+ phone : string; (** The phone number to receive the code via SMS. *)
22811682}
2282168322832283-(** Jsont codec for {!type:params}. *)
22842284-val params_jsont : params Jsont.t
16841684+(** Jsont codec for {!type:input}. *)
16851685+val input_jsont : input Jsont.t
228516862286168722872287-type output = {
22882288- actor : string option;
22892289- relationships : Jsont.json list;
22902290-}
16881688+type output = unit
2291168922921690(** Jsont codec for {!type:output}. *)
22931691val output_jsont : output Jsont.t
2294169222951693 end
22962296- module GetStarterPacksWithMembership : sig
22972297-(** A starter pack and an optional list item indicating membership of a target user to that starter pack. *)
22982298-22992299-type starter_pack_with_membership = {
23002300- list_item : Jsont.json option;
23012301- starter_pack : Jsont.json;
23022302-}
23032303-23042304-(** Jsont codec for {!type:starter_pack_with_membership}. *)
23052305-val starter_pack_with_membership_jsont : starter_pack_with_membership Jsont.t
23062306-23072307-(** Enumerates the starter packs created by the session user, and includes membership information about `actor` in those starter packs. Requires auth. *)
16941694+ module GetMatches : sig
16951695+(** Returns the matched contacts (contacts that were mutually imported). Excludes dismissed matches. Requires authentication. *)
2308169623091697(** Query/procedure parameters. *)
23101698type params = {
23112311- actor : string; (** The account (actor) to check for membership. *)
23121699 cursor : string option;
23131700 limit : int option;
23141701}
···2319170623201707type output = {
23211708 cursor : string option;
23222322- starter_packs_with_membership : Jsont.json list;
17091709+ matches : Jsont.json list;
23231710}
2324171123251712(** Jsont codec for {!type:output}. *)
23261713val output_jsont : output Jsont.t
2327171423281715 end
23292329- module GetActorStarterPacks : sig
23302330-(** Get a list of starter packs created by the actor. *)
17161716+ module VerifyPhone : sig
17171717+(** Verifies control over a phone number with a code received via SMS and starts a contact import session. Requires authentication. *)
17181718+2331171923322332-(** Query/procedure parameters. *)
23332333-type params = {
23342334- actor : string;
23352335- cursor : string option;
23362336- limit : int option;
17201720+type input = {
17211721+ code : string; (** The code received via SMS as a result of the call to `app.bsky.contact.startPhoneVerification`. *)
17221722+ phone : string; (** The phone number to verify. Should be the same as the one passed to `app.bsky.contact.startPhoneVerification`. *)
23371723}
2338172423392339-(** Jsont codec for {!type:params}. *)
23402340-val params_jsont : params Jsont.t
17251725+(** Jsont codec for {!type:input}. *)
17261726+val input_jsont : input Jsont.t
234117272342172823431729type output = {
23442344- cursor : string option;
23452345- starter_packs : Jsont.json list;
17301730+ token : string; (** JWT to be used in a call to `app.bsky.contact.importContacts`. It is only valid for a single call. *)
23461731}
2347173223481733(** Jsont codec for {!type:output}. *)
23491734val output_jsont : output Jsont.t
2350173523511736 end
23522352- module List : sig
23532353-(** Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists. *)
23542354-23552355-type main = {
23562356- avatar : Atp.Blob_ref.t option;
23572357- created_at : string;
23582358- description : string option;
23592359- description_facets : Richtext.Facet.main list option;
23602360- labels : Com.Atproto.Label.Defs.self_labels option;
23612361- name : string; (** Display name for list; can not be empty. *)
23622362- purpose : Jsont.json; (** Defines the purpose of the list (aka, moderation-oriented or curration-oriented) *)
23632363-}
17371737+ module RemoveData : sig
17381738+(** Removes all stored hashes used for contact matching, existing matches, and sync status. Requires authentication. *)
2364173923652365-(** Jsont codec for {!type:main}. *)
23662366-val main_jsont : main Jsont.t
2367174023682368- end
23692369- module SearchStarterPacks : sig
23702370-(** Find starter packs matching search criteria. Does not require auth. *)
17411741+type input = unit
2371174223722372-(** Query/procedure parameters. *)
23732373-type params = {
23742374- cursor : string option;
23752375- limit : int option;
23762376- q : string; (** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. *)
23772377-}
23782378-23792379-(** Jsont codec for {!type:params}. *)
23802380-val params_jsont : params Jsont.t
17431743+(** Jsont codec for {!type:input}. *)
17441744+val input_jsont : input Jsont.t
238117452382174623832383-type output = {
23842384- cursor : string option;
23852385- starter_packs : Jsont.json list;
23862386-}
17471747+type output = unit
2387174823881749(** Jsont codec for {!type:output}. *)
23891750val output_jsont : output Jsont.t
2390175123911752 end
23922392- module GetList : sig
23932393-(** Gets a 'view' (with additional context) of a specified list. *)
17531753+ module Defs : sig
2394175423952395-(** Query/procedure parameters. *)
23962396-type params = {
23972397- cursor : string option;
23982398- limit : int option;
23992399- list_ : string; (** Reference (AT-URI) of the list record to hydrate. *)
17551755+type sync_status = {
17561756+ matches_count : int; (** Number of existing contact matches resulting of the user imports and of their imported contacts having imported the user. Matches stop being counted when the user either follows the matched contact or dismisses the match. *)
17571757+ synced_at : string; (** Last date when contacts where imported. *)
17581758+}
17591759+17601760+(** Jsont codec for {!type:sync_status}. *)
17611761+val sync_status_jsont : sync_status Jsont.t
17621762+17631763+(** A stash object to be sent via bsync representing a notification to be created. *)
17641764+17651765+type notification = {
17661766+ from : string; (** The DID of who this notification comes from. *)
17671767+ to_ : string; (** The DID of who this notification should go to. *)
24001768}
2401176924022402-(** Jsont codec for {!type:params}. *)
24032403-val params_jsont : params Jsont.t
17701770+(** Jsont codec for {!type:notification}. *)
17711771+val notification_jsont : notification Jsont.t
2404177217731773+(** Associates a profile with the positional index of the contact import input in the call to `app.bsky.contact.importContacts`, so clients can know which phone caused a particular match. *)
2405177424062406-type output = {
24072407- cursor : string option;
24082408- items : Jsont.json list;
24092409- list_ : Jsont.json;
17751775+type match_and_contact_index = {
17761776+ contact_index : int; (** The index of this match in the import contact input. *)
17771777+ match_ : Jsont.json; (** Profile of the matched user. *)
24101778}
2411177924122412-(** Jsont codec for {!type:output}. *)
24132413-val output_jsont : output Jsont.t
17801780+(** Jsont codec for {!type:match_and_contact_index}. *)
17811781+val match_and_contact_index_jsont : match_and_contact_index Jsont.t
2414178224151783 end
24162416- module GetListBlocks : sig
24172417-(** Get mod lists that the requesting account (actor) is blocking. Requires auth. *)
17841784+ module DismissMatch : sig
17851785+(** Removes a match that was found via contact import. It shouldn't appear again if the same contact is re-imported. Requires authentication. *)
17861786+2418178724192419-(** Query/procedure parameters. *)
24202420-type params = {
24212421- cursor : string option;
24222422- limit : int option;
17881788+type input = {
17891789+ subject : string; (** The subject's DID to dismiss the match with. *)
24231790}
2424179124252425-(** Jsont codec for {!type:params}. *)
24262426-val params_jsont : params Jsont.t
17921792+(** Jsont codec for {!type:input}. *)
17931793+val input_jsont : input Jsont.t
242717942428179524292429-type output = {
24302430- cursor : string option;
24312431- lists : Jsont.json list;
24322432-}
17961796+type output = unit
2433179724341798(** Jsont codec for {!type:output}. *)
24351799val output_jsont : output Jsont.t
2436180024371801 end
24382438- module GetStarterPack : sig
24392439-(** Gets a view of a starter pack. *)
18021802+ module GetSyncStatus : sig
18031803+(** Gets the user's current contact import status. Requires authentication. *)
2440180424411805(** Query/procedure parameters. *)
24422442-type params = {
24432443- starter_pack : string; (** Reference (AT-URI) of the starter pack record. *)
24442444-}
18061806+type params = unit
2445180724461808(** Jsont codec for {!type:params}. *)
24471809val params_jsont : params Jsont.t
244818102449181124501812type output = {
24512451- starter_pack : Jsont.json;
18131813+ sync_status : Defs.sync_status option; (** If present, indicates the user has imported their contacts. If not present, indicates the user never used the feature or called `app.bsky.contact.removeData` and didn't import again since. *)
24521814}
2453181524541816(** Jsont codec for {!type:output}. *)
24551817val output_jsont : output Jsont.t
2456181824571819 end
24582458- module GetListsWithMembership : sig
24592459-(** A list and an optional list item indicating membership of a target user to that list. *)
18201820+ module ImportContacts : sig
18211821+(** Import contacts for securely matching with other users. This follows the protocol explained in https://docs.bsky.app/blog/contact-import-rfc. Requires authentication. *)
2460182224612461-type list_with_membership = {
24622462- list_ : Jsont.json;
24632463- list_item : Jsont.json option;
24642464-}
2465182324662466-(** Jsont codec for {!type:list_with_membership}. *)
24672467-val list_with_membership_jsont : list_with_membership Jsont.t
24682468-24692469-(** Enumerates the lists created by the session user, and includes membership information about `actor` in those lists. Only supports curation and moderation lists (no reference lists, used in starter packs). Requires auth. *)
24702470-24712471-(** Query/procedure parameters. *)
24722472-type params = {
24732473- actor : string; (** The account (actor) to check for membership. *)
24742474- cursor : string option;
24752475- limit : int option;
24762476- purposes : string list option; (** Optional filter by list purpose. If not specified, all supported types are returned. *)
18241824+type input = {
18251825+ contacts : string list; (** List of phone numbers in global E.164 format (e.g., '+12125550123'). Phone numbers that cannot be normalized into a valid phone number will be discarded. Should not repeat the 'phone' input used in `app.bsky.contact.verifyPhone`. *)
18261826+ token : string; (** JWT to authenticate the call. Use the JWT received as a response to the call to `app.bsky.contact.verifyPhone`. *)
24771827}
2478182824792479-(** Jsont codec for {!type:params}. *)
24802480-val params_jsont : params Jsont.t
18291829+(** Jsont codec for {!type:input}. *)
18301830+val input_jsont : input Jsont.t
248118312482183224831833type output = {
24842484- cursor : string option;
24852485- lists_with_membership : Jsont.json list;
18341834+ matches_and_contact_indexes : Defs.match_and_contact_index list; (** The users that matched during import and their indexes on the input contacts, so the client can correlate with its local list. *)
24861835}
2487183624881837(** Jsont codec for {!type:output}. *)
24891838val output_jsont : output Jsont.t
2490183924911840 end
24922492- module GetListMutes : sig
24932493-(** Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth. *)
18411841+ end
18421842+ module Feed : sig
18431843+ module Repost : sig
18441844+(** Record representing a 'repost' of an existing Bluesky post. *)
2494184524952495-(** Query/procedure parameters. *)
24962496-type params = {
24972497- cursor : string option;
24982498- limit : int option;
18461846+type main = {
18471847+ created_at : string;
18481848+ subject : Com.Atproto.Repo.StrongRef.main;
18491849+ via : Com.Atproto.Repo.StrongRef.main option;
24991850}
2500185125012501-(** Jsont codec for {!type:params}. *)
25022502-val params_jsont : params Jsont.t
18521852+(** Jsont codec for {!type:main}. *)
18531853+val main_jsont : main Jsont.t
2503185418551855+ end
18561856+ module DescribeFeedGenerator : sig
2504185725052505-type output = {
25062506- cursor : string option;
25072507- lists : Jsont.json list;
18581858+type links = {
18591859+ privacy_policy : string option;
18601860+ terms_of_service : string option;
25081861}
2509186225102510-(** Jsont codec for {!type:output}. *)
25112511-val output_jsont : output Jsont.t
18631863+(** Jsont codec for {!type:links}. *)
18641864+val links_jsont : links Jsont.t
2512186525132513- end
25142514- module GetStarterPacks : sig
25152515-(** Get views for a list of starter packs. *)
2516186625172517-(** Query/procedure parameters. *)
25182518-type params = {
25192519- uris : string list;
18671867+type feed = {
18681868+ uri : string;
25201869}
2521187025222522-(** Jsont codec for {!type:params}. *)
25232523-val params_jsont : params Jsont.t
18711871+(** Jsont codec for {!type:feed}. *)
18721872+val feed_jsont : feed Jsont.t
18731873+18741874+(** Get information about a feed generator, including policies and offered feed URIs. Does not require auth; implemented by Feed Generator services (not App View). *)
252418752525187625261877type output = {
25272527- starter_packs : Jsont.json list;
18781878+ did : string;
18791879+ feeds : Jsont.json list;
18801880+ links : Jsont.json option;
25281881}
2529188225301883(** Jsont codec for {!type:output}. *)
25311884val output_jsont : output Jsont.t
2532188525331886 end
25342534- module GetLists : sig
25352535-(** Enumerates the lists created by a specified account (actor). *)
18871887+ module Threadgate : sig
18881888+(** Allow replies from actors mentioned in your post. *)
2536188925372537-(** Query/procedure parameters. *)
25382538-type params = {
25392539- actor : string; (** The account (actor) to enumerate lists from. *)
25402540- cursor : string option;
25412541- limit : int option;
25422542- purposes : string list option; (** Optional filter by list purpose. If not specified, all supported types are returned. *)
25432543-}
18901890+type mention_rule = unit
2544189125452545-(** Jsont codec for {!type:params}. *)
25462546-val params_jsont : params Jsont.t
18921892+(** Jsont codec for {!type:mention_rule}. *)
18931893+val mention_rule_jsont : mention_rule Jsont.t
2547189418951895+(** Allow replies from actors on a list. *)
2548189625492549-type output = {
25502550- cursor : string option;
25512551- lists : Jsont.json list;
18971897+type list_rule = {
18981898+ list_ : string;
25521899}
2553190025542554-(** Jsont codec for {!type:output}. *)
25552555-val output_jsont : output Jsont.t
19011901+(** Jsont codec for {!type:list_rule}. *)
19021902+val list_rule_jsont : list_rule Jsont.t
2556190325572557- end
25582558- end
25592559- module Feed : sig
25602560- module Post : sig
25612561-(** Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings. *)
19041904+(** Allow replies from actors you follow. *)
2562190525632563-type text_slice = {
25642564- end_ : int;
25652565- start : int;
25662566-}
19061906+type following_rule = unit
2567190725682568-(** Jsont codec for {!type:text_slice}. *)
25692569-val text_slice_jsont : text_slice Jsont.t
19081908+(** Jsont codec for {!type:following_rule}. *)
19091909+val following_rule_jsont : following_rule Jsont.t
2570191019111911+(** Allow replies from actors who follow you. *)
2571191225722572-type reply_ref = {
25732573- parent : Com.Atproto.Repo.StrongRef.main;
25742574- root : Com.Atproto.Repo.StrongRef.main;
25752575-}
19131913+type follower_rule = unit
2576191425772577-(** Jsont codec for {!type:reply_ref}. *)
25782578-val reply_ref_jsont : reply_ref Jsont.t
19151915+(** Jsont codec for {!type:follower_rule}. *)
19161916+val follower_rule_jsont : follower_rule Jsont.t
2579191725802580-(** Deprecated: use facets instead. *)
19181918+(** Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository. *)
2581191925822582-type entity = {
25832583- index : Jsont.json;
25842584- type_ : string; (** Expected values are 'mention' and 'link'. *)
25852585- value : string;
19201920+type main = {
19211921+ allow : Jsont.json list option; (** List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply. *)
19221922+ created_at : string;
19231923+ hidden_replies : string list option; (** List of hidden reply URIs. *)
19241924+ post : string; (** Reference (AT-URI) to the post record. *)
25861925}
2587192625882588-(** Jsont codec for {!type:entity}. *)
25892589-val entity_jsont : entity Jsont.t
19271927+(** Jsont codec for {!type:main}. *)
19281928+val main_jsont : main Jsont.t
2590192925912591-(** Record containing a Bluesky post. *)
19301930+ end
19311931+ module Like : sig
19321932+(** Record declaring a 'like' of a piece of subject content. *)
2592193325931934type main = {
25942594- created_at : string; (** Client-declared timestamp when this post was originally created. *)
25952595- embed : Jsont.json option;
25962596- entities : Jsont.json list option; (** DEPRECATED: replaced by app.bsky.richtext.facet. *)
25972597- facets : Richtext.Facet.main list option; (** Annotations of text (mentions, URLs, hashtags, etc) *)
25982598- labels : Com.Atproto.Label.Defs.self_labels option; (** Self-label values for this post. Effectively content warnings. *)
25992599- langs : string list option; (** Indicates human language of post primary text content. *)
26002600- reply : Jsont.json option;
26012601- tags : string list option; (** Additional hashtags, in addition to any included in post text and facets. *)
26022602- text : string; (** The primary post content. May be an empty string, if there are embeds. *)
19351935+ created_at : string;
19361936+ subject : Com.Atproto.Repo.StrongRef.main;
19371937+ via : Com.Atproto.Repo.StrongRef.main option;
26031938}
2604193926051940(** Jsont codec for {!type:main}. *)
···26421977val output_jsont : output Jsont.t
2643197826441979 end
26452645- module Postgate : sig
26462646-(** Disables embedding of this post. *)
26472647-26482648-type disable_rule = unit
26492649-26502650-(** Jsont codec for {!type:disable_rule}. *)
26512651-val disable_rule_jsont : disable_rule Jsont.t
26522652-26532653-(** Record defining interaction rules for a post. The record key (rkey) of the postgate record must match the record key of the post, and that record must be in the same repository. *)
26542654-26552655-type main = {
26562656- created_at : string;
26572657- detached_embedding_uris : string list option; (** List of AT-URIs embedding this post that the author has detached from. *)
26582658- embedding_rules : Jsont.json list option; (** List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed. *)
26592659- post : string; (** Reference (AT-URI) to the post record. *)
26602660-}
26612661-26622662-(** Jsont codec for {!type:main}. *)
26632663-val main_jsont : main Jsont.t
26642664-26652665- end
26661980 module GetRepostedBy : sig
26671981(** Get a list of reposts for a given post. *)
26681982···26892003val output_jsont : output Jsont.t
2690200426912005 end
26922692- module DescribeFeedGenerator : sig
20062006+ module Generator : sig
20072007+(** Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository. *)
2693200826942694-type links = {
26952695- privacy_policy : string option;
26962696- terms_of_service : string option;
26972697-}
26982698-26992699-(** Jsont codec for {!type:links}. *)
27002700-val links_jsont : links Jsont.t
27012701-27022702-27032703-type feed = {
27042704- uri : string;
27052705-}
27062706-27072707-(** Jsont codec for {!type:feed}. *)
27082708-val feed_jsont : feed Jsont.t
27092709-27102710-(** Get information about a feed generator, including policies and offered feed URIs. Does not require auth; implemented by Feed Generator services (not App View). *)
27112711-27122712-27132713-type output = {
20092009+type main = {
20102010+ accepts_interactions : bool option; (** Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions *)
20112011+ avatar : Atp.Blob_ref.t option;
20122012+ content_mode : string option;
20132013+ created_at : string;
20142014+ description : string option;
20152015+ description_facets : Richtext.Facet.main list option;
27142016 did : string;
27152715- feeds : Jsont.json list;
27162716- links : Jsont.json option;
20172017+ display_name : string;
20182018+ labels : Com.Atproto.Label.Defs.self_labels option; (** Self-label values *)
27172019}
2718202027192719-(** Jsont codec for {!type:output}. *)
27202720-val output_jsont : output Jsont.t
20212021+(** Jsont codec for {!type:main}. *)
20222022+val main_jsont : main Jsont.t
2721202327222024 end
27232723- module Threadgate : sig
27242724-(** Allow replies from actors mentioned in your post. *)
27252725-27262726-type mention_rule = unit
27272727-27282728-(** Jsont codec for {!type:mention_rule}. *)
27292729-val mention_rule_jsont : mention_rule Jsont.t
27302730-27312731-(** Allow replies from actors on a list. *)
27322732-27332733-type list_rule = {
27342734- list_ : string;
27352735-}
27362736-27372737-(** Jsont codec for {!type:list_rule}. *)
27382738-val list_rule_jsont : list_rule Jsont.t
27392739-27402740-(** Allow replies from actors you follow. *)
27412741-27422742-type following_rule = unit
27432743-27442744-(** Jsont codec for {!type:following_rule}. *)
27452745-val following_rule_jsont : following_rule Jsont.t
20252025+ module Postgate : sig
20262026+(** Disables embedding of this post. *)
2746202727472747-(** Allow replies from actors who follow you. *)
20282028+type disable_rule = unit
2748202927492749-type follower_rule = unit
20302030+(** Jsont codec for {!type:disable_rule}. *)
20312031+val disable_rule_jsont : disable_rule Jsont.t
2750203227512751-(** Jsont codec for {!type:follower_rule}. *)
27522752-val follower_rule_jsont : follower_rule Jsont.t
27532753-27542754-(** Record defining interaction gating rules for a thread (aka, reply controls). The record key (rkey) of the threadgate record must match the record key of the thread's root post, and that record must be in the same repository. *)
20332033+(** Record defining interaction rules for a post. The record key (rkey) of the postgate record must match the record key of the post, and that record must be in the same repository. *)
2755203427562035type main = {
27572757- allow : Jsont.json list option; (** List of rules defining who can reply to this post. If value is an empty array, no one can reply. If value is undefined, anyone can reply. *)
27582036 created_at : string;
27592759- hidden_replies : string list option; (** List of hidden reply URIs. *)
20372037+ detached_embedding_uris : string list option; (** List of AT-URIs embedding this post that the author has detached from. *)
20382038+ embedding_rules : Jsont.json list option; (** List of rules defining who can embed this post. If value is an empty array or is undefined, no particular rules apply and anyone can embed. *)
27602039 post : string; (** Reference (AT-URI) to the post record. *)
27612761-}
27622762-27632763-(** Jsont codec for {!type:main}. *)
27642764-val main_jsont : main Jsont.t
27652765-27662766- end
27672767- module Like : sig
27682768-(** Record declaring a 'like' of a piece of subject content. *)
27692769-27702770-type main = {
27712771- created_at : string;
27722772- subject : Com.Atproto.Repo.StrongRef.main;
27732773- via : Com.Atproto.Repo.StrongRef.main option;
27742040}
2775204127762042(** Jsont codec for {!type:main}. *)
···30482314val feed_view_post_jsont : feed_view_post Jsont.t
3049231530502316 end
30513051- module Repost : sig
30523052-(** Record representing a 'repost' of an existing Bluesky post. *)
23172317+ module Post : sig
23182318+(** Deprecated. Use app.bsky.richtext instead -- A text segment. Start is inclusive, end is exclusive. Indices are for utf16-encoded strings. *)
23192319+23202320+type text_slice = {
23212321+ end_ : int;
23222322+ start : int;
23232323+}
23242324+23252325+(** Jsont codec for {!type:text_slice}. *)
23262326+val text_slice_jsont : text_slice Jsont.t
23272327+23282328+23292329+type reply_ref = {
23302330+ parent : Com.Atproto.Repo.StrongRef.main;
23312331+ root : Com.Atproto.Repo.StrongRef.main;
23322332+}
23332333+23342334+(** Jsont codec for {!type:reply_ref}. *)
23352335+val reply_ref_jsont : reply_ref Jsont.t
23362336+23372337+(** Deprecated: use facets instead. *)
23382338+23392339+type entity = {
23402340+ index : Jsont.json;
23412341+ type_ : string; (** Expected values are 'mention' and 'link'. *)
23422342+ value : string;
23432343+}
23442344+23452345+(** Jsont codec for {!type:entity}. *)
23462346+val entity_jsont : entity Jsont.t
23472347+23482348+(** Record containing a Bluesky post. *)
3053234930542350type main = {
30553055- created_at : string;
30563056- subject : Com.Atproto.Repo.StrongRef.main;
30573057- via : Com.Atproto.Repo.StrongRef.main option;
23512351+ created_at : string; (** Client-declared timestamp when this post was originally created. *)
23522352+ embed : Jsont.json option;
23532353+ entities : Jsont.json list option; (** DEPRECATED: replaced by app.bsky.richtext.facet. *)
23542354+ facets : Richtext.Facet.main list option; (** Annotations of text (mentions, URLs, hashtags, etc) *)
23552355+ labels : Com.Atproto.Label.Defs.self_labels option; (** Self-label values for this post. Effectively content warnings. *)
23562356+ langs : string list option; (** Indicates human language of post primary text content. *)
23572357+ reply : Jsont.json option;
23582358+ tags : string list option; (** Additional hashtags, in addition to any included in post text and facets. *)
23592359+ text : string; (** The primary post content. May be an empty string, if there are embeds. *)
30582360}
3059236130602362(** Jsont codec for {!type:main}. *)
30612363val main_jsont : main Jsont.t
3062236430632365 end
30643064- module Generator : sig
30653065-(** Record declaring of the existence of a feed generator, and containing metadata about it. The record can exist in any repository. *)
23662366+ module GetPosts : sig
23672367+(** Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'. *)
3066236830673067-type main = {
30683068- accepts_interactions : bool option; (** Declaration that a feed accepts feedback interactions from a client through app.bsky.feed.sendInteractions *)
30693069- avatar : Atp.Blob_ref.t option;
30703070- content_mode : string option;
30713071- created_at : string;
30723072- description : string option;
30733073- description_facets : Richtext.Facet.main list option;
30743074- did : string;
30753075- display_name : string;
30763076- labels : Com.Atproto.Label.Defs.self_labels option; (** Self-label values *)
23692369+(** Query/procedure parameters. *)
23702370+type params = {
23712371+ uris : string list; (** List of post AT-URIs to return hydrated views for. *)
23722372+}
23732373+23742374+(** Jsont codec for {!type:params}. *)
23752375+val params_jsont : params Jsont.t
23762376+23772377+23782378+type output = {
23792379+ posts : Jsont.json list;
30772380}
3078238130793079-(** Jsont codec for {!type:main}. *)
30803080-val main_jsont : main Jsont.t
23822382+(** Jsont codec for {!type:output}. *)
23832383+val output_jsont : output Jsont.t
3081238430822385 end
30833083- module GetPostThread : sig
30843084-(** Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests. *)
23862386+ module GetQuotes : sig
23872387+(** Get a list of quotes for a given post. *)
3085238830862389(** Query/procedure parameters. *)
30872390type params = {
30883088- depth : int option; (** How many levels of reply depth should be included in response. *)
30893089- parent_height : int option; (** How many levels of parent (and grandparent, etc) post to include. *)
30903090- uri : string; (** Reference (AT-URI) to post record. *)
23912391+ cid : string option; (** If supplied, filters to quotes of specific version (by CID) of the post record. *)
23922392+ cursor : string option;
23932393+ limit : int option;
23942394+ uri : string; (** Reference (AT-URI) of post record *)
30912395}
3092239630932397(** Jsont codec for {!type:params}. *)
···309523993096240030972401type output = {
30983098- thread : Jsont.json;
30993099- threadgate : Jsont.json option;
24022402+ cid : string option;
24032403+ cursor : string option;
24042404+ posts : Jsont.json list;
24052405+ uri : string;
31002406}
3101240731022408(** Jsont codec for {!type:output}. *)
31032409val output_jsont : output Jsont.t
3104241031052411 end
31063106- module GetFeed : sig
31073107-(** Get a hydrated feed from an actor's selected feed generator. Implemented by App View. *)
24122412+ module GetAuthorFeed : sig
24132413+(** Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth. *)
3108241431092415(** Query/procedure parameters. *)
31102416type params = {
24172417+ actor : string;
31112418 cursor : string option;
31123112- feed : string;
24192419+ filter : string option; (** Combinations of post/repost types to include in response. *)
24202420+ include_pins : bool option;
31132421 limit : int option;
31142422}
31152423···31262434val output_jsont : output Jsont.t
3127243531282436 end
31293129- module GetQuotes : sig
31303130-(** Get a list of quotes for a given post. *)
24372437+ module GetTimeline : sig
24382438+(** Get a view of the requesting account's home timeline. This is expected to be some form of reverse-chronological feed. *)
3131243931322440(** Query/procedure parameters. *)
31332441type params = {
31343134- cid : string option; (** If supplied, filters to quotes of specific version (by CID) of the post record. *)
24422442+ algorithm : string option; (** Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism. *)
31352443 cursor : string option;
31362444 limit : int option;
31373137- uri : string; (** Reference (AT-URI) of post record *)
31382445}
3139244631402447(** Jsont codec for {!type:params}. *)
···314224493143245031442451type output = {
31453145- cid : string option;
31462452 cursor : string option;
31473147- posts : Jsont.json list;
31483148- uri : string;
24532453+ feed : Jsont.json list;
31492454}
3150245531512456(** Jsont codec for {!type:output}. *)
···31752480val output_jsont : output Jsont.t
3176248131772482 end
24832483+ module GetSuggestedFeeds : sig
24842484+(** Get a list of suggested feeds (feed generators) for the requesting account. *)
24852485+24862486+(** Query/procedure parameters. *)
24872487+type params = {
24882488+ cursor : string option;
24892489+ limit : int option;
24902490+}
24912491+24922492+(** Jsont codec for {!type:params}. *)
24932493+val params_jsont : params Jsont.t
24942494+24952495+24962496+type output = {
24972497+ cursor : string option;
24982498+ feeds : Jsont.json list;
24992499+}
25002500+25012501+(** Jsont codec for {!type:output}. *)
25022502+val output_jsont : output Jsont.t
25032503+25042504+ end
31782505 module GetActorLikes : sig
31792506(** Get a list of posts liked by an actor. Requires auth, actor must be the requesting account. *)
31802507···31982525val output_jsont : output Jsont.t
3199252632002527 end
32013201- module GetFeedSkeleton : sig
32023202-(** Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service. *)
25282528+ module GetActorFeeds : sig
25292529+(** Get a list of feeds (feed generator records) created by the actor (in the actor's repo). *)
3203253032042531(** Query/procedure parameters. *)
32052532type params = {
25332533+ actor : string;
32062534 cursor : string option;
32073207- feed : string; (** Reference to feed generator record describing the specific feed being requested. *)
32082535 limit : int option;
32092536}
32102537···3214254132152542type output = {
32162543 cursor : string option;
32173217- feed : Jsont.json list;
32183218- req_id : string option; (** Unique identifier per request that may be passed back alongside interactions. *)
25442544+ feeds : Jsont.json list;
32192545}
3220254632212547(** Jsont codec for {!type:output}. *)
···32552581val output_jsont : output Jsont.t
3256258232572583 end
25842584+ module GetPostThread : sig
25852585+(** Get posts in a thread. Does not require auth, but additional metadata and filtering will be applied for authed requests. *)
25862586+25872587+(** Query/procedure parameters. *)
25882588+type params = {
25892589+ depth : int option; (** How many levels of reply depth should be included in response. *)
25902590+ parent_height : int option; (** How many levels of parent (and grandparent, etc) post to include. *)
25912591+ uri : string; (** Reference (AT-URI) to post record. *)
25922592+}
25932593+25942594+(** Jsont codec for {!type:params}. *)
25952595+val params_jsont : params Jsont.t
25962596+25972597+25982598+type output = {
25992599+ thread : Jsont.json;
26002600+ threadgate : Jsont.json option;
26012601+}
26022602+26032603+(** Jsont codec for {!type:output}. *)
26042604+val output_jsont : output Jsont.t
26052605+26062606+ end
26072607+ module GetFeedSkeleton : sig
26082608+(** Get a skeleton of a feed provided by a feed generator. Auth is optional, depending on provider requirements, and provides the DID of the requester. Implemented by Feed Generator Service. *)
26092609+26102610+(** Query/procedure parameters. *)
26112611+type params = {
26122612+ cursor : string option;
26132613+ feed : string; (** Reference to feed generator record describing the specific feed being requested. *)
26142614+ limit : int option;
26152615+}
26162616+26172617+(** Jsont codec for {!type:params}. *)
26182618+val params_jsont : params Jsont.t
26192619+26202620+26212621+type output = {
26222622+ cursor : string option;
26232623+ feed : Jsont.json list;
26242624+ req_id : string option; (** Unique identifier per request that may be passed back alongside interactions. *)
26252625+}
26262626+26272627+(** Jsont codec for {!type:output}. *)
26282628+val output_jsont : output Jsont.t
26292629+26302630+ end
26312631+ module GetFeed : sig
26322632+(** Get a hydrated feed from an actor's selected feed generator. Implemented by App View. *)
26332633+26342634+(** Query/procedure parameters. *)
26352635+type params = {
26362636+ cursor : string option;
26372637+ feed : string;
26382638+ limit : int option;
26392639+}
26402640+26412641+(** Jsont codec for {!type:params}. *)
26422642+val params_jsont : params Jsont.t
26432643+26442644+26452645+type output = {
26462646+ cursor : string option;
26472647+ feed : Jsont.json list;
26482648+}
26492649+26502650+(** Jsont codec for {!type:output}. *)
26512651+val output_jsont : output Jsont.t
26522652+26532653+ end
32582654 module GetFeedGenerators : sig
32592655(** Get information about a list of feed generators. *)
32602656···32932689val output_jsont : output Jsont.t
3294269032952691 end
32963296- module GetAuthorFeed : sig
32973297-(** Get a view of an actor's 'author feed' (post and reposts by the author). Does not require auth. *)
26922692+ module GetFeedGenerator : sig
26932693+(** Get information about a feed generator. Implemented by AppView. *)
26942694+26952695+(** Query/procedure parameters. *)
26962696+type params = {
26972697+ feed : string; (** AT-URI of the feed generator record. *)
26982698+}
26992699+27002700+(** Jsont codec for {!type:params}. *)
27012701+val params_jsont : params Jsont.t
27022702+27032703+27042704+type output = {
27052705+ is_online : bool; (** Indicates whether the feed generator service has been online recently, or else seems to be inactive. *)
27062706+ is_valid : bool; (** Indicates whether the feed generator service is compatible with the record declaration. *)
27072707+ view : Jsont.json;
27082708+}
27092709+27102710+(** Jsont codec for {!type:output}. *)
27112711+val output_jsont : output Jsont.t
27122712+27132713+ end
27142714+ end
27152715+ module Graph : sig
27162716+ module Defs : sig
27172717+27182718+type starter_pack_view_basic = {
27192719+ cid : string;
27202720+ creator : Jsont.json;
27212721+ indexed_at : string;
27222722+ joined_all_time_count : int option;
27232723+ joined_week_count : int option;
27242724+ labels : Com.Atproto.Label.Defs.label list option;
27252725+ list_item_count : int option;
27262726+ record : Jsont.json;
27272727+ uri : string;
27282728+}
27292729+27302730+(** Jsont codec for {!type:starter_pack_view_basic}. *)
27312731+val starter_pack_view_basic_jsont : starter_pack_view_basic Jsont.t
27322732+27332733+(** lists the bi-directional graph relationships between one actor (not indicated in the object), and the target actors (the DID included in the object) *)
27342734+27352735+type relationship = {
27362736+ blocked_by : string option; (** if the actor is blocked by this DID, contains the AT-URI of the block record *)
27372737+ blocked_by_list : string option; (** if the actor is blocked by this DID via a block list, contains the AT-URI of the listblock record *)
27382738+ blocking : string option; (** if the actor blocks this DID, this is the AT-URI of the block record *)
27392739+ blocking_by_list : string option; (** if the actor blocks this DID via a block list, this is the AT-URI of the listblock record *)
27402740+ did : string;
27412741+ followed_by : string option; (** if the actor is followed by this DID, contains the AT-URI of the follow record *)
27422742+ following : string option; (** if the actor follows this DID, this is the AT-URI of the follow record *)
27432743+}
27442744+27452745+(** Jsont codec for {!type:relationship}. *)
27462746+val relationship_jsont : relationship Jsont.t
27472747+27482748+(** A list of actors used for only for reference purposes such as within a starter pack. *)
27492749+27502750+type referencelist = string
27512751+val referencelist_jsont : referencelist Jsont.t
27522752+27532753+(** indicates that a handle or DID could not be resolved *)
27542754+27552755+type not_found_actor = {
27562756+ actor : string;
27572757+ not_found : bool;
27582758+}
27592759+27602760+(** Jsont codec for {!type:not_found_actor}. *)
27612761+val not_found_actor_jsont : not_found_actor Jsont.t
27622762+27632763+(** A list of actors to apply an aggregate moderation action (mute/block) on. *)
27642764+27652765+type modlist = string
27662766+val modlist_jsont : modlist Jsont.t
27672767+27682768+27692769+type list_viewer_state = {
27702770+ blocked : string option;
27712771+ muted : bool option;
27722772+}
27732773+27742774+(** Jsont codec for {!type:list_viewer_state}. *)
27752775+val list_viewer_state_jsont : list_viewer_state Jsont.t
27762776+27772777+27782778+type list_purpose = string
27792779+val list_purpose_jsont : list_purpose Jsont.t
27802780+27812781+27822782+type list_item_view = {
27832783+ subject : Jsont.json;
27842784+ uri : string;
27852785+}
27862786+27872787+(** Jsont codec for {!type:list_item_view}. *)
27882788+val list_item_view_jsont : list_item_view Jsont.t
27892789+27902790+(** A list of actors used for curation purposes such as list feeds or interaction gating. *)
27912791+27922792+type curatelist = string
27932793+val curatelist_jsont : curatelist Jsont.t
27942794+27952795+27962796+type list_view_basic = {
27972797+ avatar : string option;
27982798+ cid : string;
27992799+ indexed_at : string option;
28002800+ labels : Com.Atproto.Label.Defs.label list option;
28012801+ list_item_count : int option;
28022802+ name : string;
28032803+ purpose : Jsont.json;
28042804+ uri : string;
28052805+ viewer : Jsont.json option;
28062806+}
28072807+28082808+(** Jsont codec for {!type:list_view_basic}. *)
28092809+val list_view_basic_jsont : list_view_basic Jsont.t
28102810+28112811+28122812+type list_view = {
28132813+ avatar : string option;
28142814+ cid : string;
28152815+ creator : Jsont.json;
28162816+ description : string option;
28172817+ description_facets : Richtext.Facet.main list option;
28182818+ indexed_at : string;
28192819+ labels : Com.Atproto.Label.Defs.label list option;
28202820+ list_item_count : int option;
28212821+ name : string;
28222822+ purpose : Jsont.json;
28232823+ uri : string;
28242824+ viewer : Jsont.json option;
28252825+}
28262826+28272827+(** Jsont codec for {!type:list_view}. *)
28282828+val list_view_jsont : list_view Jsont.t
28292829+28302830+28312831+type starter_pack_view = {
28322832+ cid : string;
28332833+ creator : Jsont.json;
28342834+ feeds : Jsont.json list option;
28352835+ indexed_at : string;
28362836+ joined_all_time_count : int option;
28372837+ joined_week_count : int option;
28382838+ labels : Com.Atproto.Label.Defs.label list option;
28392839+ list_ : Jsont.json option;
28402840+ list_items_sample : Jsont.json list option;
28412841+ record : Jsont.json;
28422842+ uri : string;
28432843+}
28442844+28452845+(** Jsont codec for {!type:starter_pack_view}. *)
28462846+val starter_pack_view_jsont : starter_pack_view Jsont.t
28472847+28482848+ end
28492849+ module UnmuteActor : sig
28502850+(** Unmutes the specified account. Requires auth. *)
28512851+28522852+28532853+type input = {
28542854+ actor : string;
28552855+}
28562856+28572857+(** Jsont codec for {!type:input}. *)
28582858+val input_jsont : input Jsont.t
28592859+28602860+ end
28612861+ module Listitem : sig
28622862+(** Record representing an account's inclusion on a specific list. The AppView will ignore duplicate listitem records. *)
28632863+28642864+type main = {
28652865+ created_at : string;
28662866+ list_ : string; (** Reference (AT-URI) to the list record (app.bsky.graph.list). *)
28672867+ subject : string; (** The account which is included on the list. *)
28682868+}
28692869+28702870+(** Jsont codec for {!type:main}. *)
28712871+val main_jsont : main Jsont.t
28722872+28732873+ end
28742874+ module GetSuggestedFollowsByActor : sig
28752875+(** Enumerates follows similar to a given account (actor). Expected use is to recommend additional accounts immediately after following one account. *)
28762876+28772877+(** Query/procedure parameters. *)
28782878+type params = {
28792879+ actor : string;
28802880+}
28812881+28822882+(** Jsont codec for {!type:params}. *)
28832883+val params_jsont : params Jsont.t
28842884+28852885+28862886+type output = {
28872887+ is_fallback : bool option; (** If true, response has fallen-back to generic results, and is not scoped using relativeToDid *)
28882888+ rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
28892889+ suggestions : Jsont.json list;
28902890+}
28912891+28922892+(** Jsont codec for {!type:output}. *)
28932893+val output_jsont : output Jsont.t
28942894+28952895+ end
28962896+ module MuteActorList : sig
28972897+(** Creates a mute relationship for the specified list of accounts. Mutes are private in Bluesky. Requires auth. *)
28982898+28992899+29002900+type input = {
29012901+ list_ : string;
29022902+}
29032903+29042904+(** Jsont codec for {!type:input}. *)
29052905+val input_jsont : input Jsont.t
29062906+29072907+ end
29082908+ module GetFollowers : sig
29092909+(** Enumerates accounts which follow a specified account (actor). *)
3298291032992911(** Query/procedure parameters. *)
33002912type params = {
33012913 actor : string;
33022914 cursor : string option;
33033303- filter : string option; (** Combinations of post/repost types to include in response. *)
33043304- include_pins : bool option;
33052915 limit : int option;
33062916}
33072917···3311292133122922type output = {
33132923 cursor : string option;
33143314- feed : Jsont.json list;
29242924+ followers : Jsont.json list;
29252925+ subject : Jsont.json;
33152926}
3316292733172928(** Jsont codec for {!type:output}. *)
33182929val output_jsont : output Jsont.t
3319293033202931 end
33213321- module GetFeedGenerator : sig
33223322-(** Get information about a feed generator. Implemented by AppView. *)
29322932+ module MuteActor : sig
29332933+(** Creates a mute relationship for the specified account. Mutes are private in Bluesky. Requires auth. *)
29342934+29352935+29362936+type input = {
29372937+ actor : string;
29382938+}
29392939+29402940+(** Jsont codec for {!type:input}. *)
29412941+val input_jsont : input Jsont.t
29422942+29432943+ end
29442944+ module UnmuteActorList : sig
29452945+(** Unmutes the specified list of accounts. Requires auth. *)
29462946+29472947+29482948+type input = {
29492949+ list_ : string;
29502950+}
29512951+29522952+(** Jsont codec for {!type:input}. *)
29532953+val input_jsont : input Jsont.t
29542954+29552955+ end
29562956+ module GetBlocks : sig
29572957+(** Enumerates which accounts the requesting account is currently blocking. Requires auth. *)
3323295833242959(** Query/procedure parameters. *)
33252960type params = {
33263326- feed : string; (** AT-URI of the feed generator record. *)
29612961+ cursor : string option;
29622962+ limit : int option;
33272963}
3328296433292965(** Jsont codec for {!type:params}. *)
···333129673332296833332969type output = {
33343334- is_online : bool; (** Indicates whether the feed generator service has been online recently, or else seems to be inactive. *)
33353335- is_valid : bool; (** Indicates whether the feed generator service is compatible with the record declaration. *)
33363336- view : Jsont.json;
29702970+ blocks : Jsont.json list;
29712971+ cursor : string option;
33372972}
3338297333392974(** Jsont codec for {!type:output}. *)
33402975val output_jsont : output Jsont.t
3341297633422977 end
33433343- module GetSuggestedFeeds : sig
33443344-(** Get a list of suggested feeds (feed generators) for the requesting account. *)
29782978+ module Listblock : sig
29792979+(** Record representing a block relationship against an entire an entire list of accounts (actors). *)
29802980+29812981+type main = {
29822982+ created_at : string;
29832983+ subject : string; (** Reference (AT-URI) to the mod list record. *)
29842984+}
29852985+29862986+(** Jsont codec for {!type:main}. *)
29872987+val main_jsont : main Jsont.t
29882988+29892989+ end
29902990+ module MuteThread : sig
29912991+(** Mutes a thread preventing notifications from the thread and any of its children. Mutes are private in Bluesky. Requires auth. *)
29922992+29932993+29942994+type input = {
29952995+ root : string;
29962996+}
29972997+29982998+(** Jsont codec for {!type:input}. *)
29992999+val input_jsont : input Jsont.t
30003000+30013001+ end
30023002+ module Follow : sig
30033003+(** Record declaring a social 'follow' relationship of another account. Duplicate follows will be ignored by the AppView. *)
30043004+30053005+type main = {
30063006+ created_at : string;
30073007+ subject : string;
30083008+ via : Com.Atproto.Repo.StrongRef.main option;
30093009+}
30103010+30113011+(** Jsont codec for {!type:main}. *)
30123012+val main_jsont : main Jsont.t
30133013+30143014+ end
30153015+ module Starterpack : sig
30163016+30173017+type feed_item = {
30183018+ uri : string;
30193019+}
30203020+30213021+(** Jsont codec for {!type:feed_item}. *)
30223022+val feed_item_jsont : feed_item Jsont.t
30233023+30243024+(** Record defining a starter pack of actors and feeds for new users. *)
30253025+30263026+type main = {
30273027+ created_at : string;
30283028+ description : string option;
30293029+ description_facets : Richtext.Facet.main list option;
30303030+ feeds : Jsont.json list option;
30313031+ list_ : string; (** Reference (AT-URI) to the list record. *)
30323032+ name : string; (** Display name for starter pack; can not be empty. *)
30333033+}
30343034+30353035+(** Jsont codec for {!type:main}. *)
30363036+val main_jsont : main Jsont.t
30373037+30383038+ end
30393039+ module GetFollows : sig
30403040+(** Enumerates accounts which a specified account (actor) follows. *)
3345304133463042(** Query/procedure parameters. *)
33473043type params = {
30443044+ actor : string;
33483045 cursor : string option;
33493046 limit : int option;
33503047}
···3355305233563053type output = {
33573054 cursor : string option;
33583358- feeds : Jsont.json list;
30553055+ follows : Jsont.json list;
30563056+ subject : Jsont.json;
33593057}
3360305833613059(** Jsont codec for {!type:output}. *)
33623060val output_jsont : output Jsont.t
3363306133643062 end
33653365- module GetActorFeeds : sig
33663366-(** Get a list of feeds (feed generator records) created by the actor (in the actor's repo). *)
30633063+ module Verification : sig
30643064+(** Record declaring a verification relationship between two accounts. Verifications are only considered valid by an app if issued by an account the app considers trusted. *)
30653065+30663066+type main = {
30673067+ created_at : string; (** Date of when the verification was created. *)
30683068+ display_name : string; (** Display name of the subject the verification applies to at the moment of verifying, which might not be the same at the time of viewing. The verification is only valid if the current displayName matches the one at the time of verifying. *)
30693069+ handle : string; (** Handle of the subject the verification applies to at the moment of verifying, which might not be the same at the time of viewing. The verification is only valid if the current handle matches the one at the time of verifying. *)
30703070+ subject : string; (** DID of the subject the verification applies to. *)
30713071+}
30723072+30733073+(** Jsont codec for {!type:main}. *)
30743074+val main_jsont : main Jsont.t
30753075+30763076+ end
30773077+ module UnmuteThread : sig
30783078+(** Unmutes the specified thread. Requires auth. *)
30793079+30803080+30813081+type input = {
30823082+ root : string;
30833083+}
30843084+30853085+(** Jsont codec for {!type:input}. *)
30863086+val input_jsont : input Jsont.t
30873087+30883088+ end
30893089+ module GetMutes : sig
30903090+(** Enumerates accounts that the requesting account (actor) currently has muted. Requires auth. *)
30913091+30923092+(** Query/procedure parameters. *)
30933093+type params = {
30943094+ cursor : string option;
30953095+ limit : int option;
30963096+}
30973097+30983098+(** Jsont codec for {!type:params}. *)
30993099+val params_jsont : params Jsont.t
31003100+31013101+31023102+type output = {
31033103+ cursor : string option;
31043104+ mutes : Jsont.json list;
31053105+}
31063106+31073107+(** Jsont codec for {!type:output}. *)
31083108+val output_jsont : output Jsont.t
31093109+31103110+ end
31113111+ module GetKnownFollowers : sig
31123112+(** Enumerates accounts which follow a specified account (actor) and are followed by the viewer. *)
3367311333683114(** Query/procedure parameters. *)
33693115type params = {
···3378312433793125type output = {
33803126 cursor : string option;
33813381- feeds : Jsont.json list;
31273127+ followers : Jsont.json list;
31283128+ subject : Jsont.json;
33823129}
3383313033843131(** Jsont codec for {!type:output}. *)
33853132val output_jsont : output Jsont.t
3386313333873134 end
33883388- module GetPosts : sig
33893389-(** Gets post views for a specified list of posts (by AT-URI). This is sometimes referred to as 'hydrating' a 'feed skeleton'. *)
31353135+ module Block : sig
31363136+(** Record declaring a 'block' relationship against another account. NOTE: blocks are public in Bluesky; see blog posts for details. *)
31373137+31383138+type main = {
31393139+ created_at : string;
31403140+ subject : string; (** DID of the account to be blocked. *)
31413141+}
31423142+31433143+(** Jsont codec for {!type:main}. *)
31443144+val main_jsont : main Jsont.t
31453145+31463146+ end
31473147+ module GetRelationships : sig
31483148+(** Enumerates public relationships between one account, and a list of other accounts. Does not require auth. *)
3390314933913150(** Query/procedure parameters. *)
33923151type params = {
33933393- uris : string list; (** List of post AT-URIs to return hydrated views for. *)
31523152+ actor : string; (** Primary account requesting relationships for. *)
31533153+ others : string list option; (** List of 'other' accounts to be related back to the primary. *)
33943154}
3395315533963156(** Jsont codec for {!type:params}. *)
···339831583399315934003160type output = {
34013401- posts : Jsont.json list;
31613161+ actor : string option;
31623162+ relationships : Jsont.json list;
34023163}
3403316434043165(** Jsont codec for {!type:output}. *)
34053166val output_jsont : output Jsont.t
3406316734073168 end
34083408- module GetTimeline : sig
34093409-(** Get a view of the requesting account's home timeline. This is expected to be some form of reverse-chronological feed. *)
31693169+ module GetListsWithMembership : sig
31703170+(** A list and an optional list item indicating membership of a target user to that list. *)
31713171+31723172+type list_with_membership = {
31733173+ list_ : Jsont.json;
31743174+ list_item : Jsont.json option;
31753175+}
31763176+31773177+(** Jsont codec for {!type:list_with_membership}. *)
31783178+val list_with_membership_jsont : list_with_membership Jsont.t
31793179+31803180+(** Enumerates the lists created by the session user, and includes membership information about `actor` in those lists. Only supports curation and moderation lists (no reference lists, used in starter packs). Requires auth. *)
3410318134113182(** Query/procedure parameters. *)
34123183type params = {
34133413- algorithm : string option; (** Variant 'algorithm' for timeline. Implementation-specific. NOTE: most feed flexibility has been moved to feed generator mechanism. *)
31843184+ actor : string; (** The account (actor) to check for membership. *)
34143185 cursor : string option;
34153186 limit : int option;
31873187+ purposes : string list option; (** Optional filter by list purpose. If not specified, all supported types are returned. *)
34163188}
3417318934183190(** Jsont codec for {!type:params}. *)
···3421319334223194type output = {
34233195 cursor : string option;
34243424- feed : Jsont.json list;
31963196+ lists_with_membership : Jsont.json list;
34253197}
3426319834273199(** Jsont codec for {!type:output}. *)
34283200val output_jsont : output Jsont.t
3429320134303202 end
34313431- end
34323432- module Bookmark : sig
34333433- module DeleteBookmark : sig
34343434-(** Deletes a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication. *)
32033203+ module GetListMutes : sig
32043204+(** Enumerates mod lists that the requesting account (actor) currently has muted. Requires auth. *)
32053205+32063206+(** Query/procedure parameters. *)
32073207+type params = {
32083208+ cursor : string option;
32093209+ limit : int option;
32103210+}
3435321132123212+(** Jsont codec for {!type:params}. *)
32133213+val params_jsont : params Jsont.t
3436321434373437-type input = {
34383438- uri : string;
32153215+32163216+type output = {
32173217+ cursor : string option;
32183218+ lists : Jsont.json list;
34393219}
3440322034413441-(** Jsont codec for {!type:input}. *)
34423442-val input_jsont : input Jsont.t
32213221+(** Jsont codec for {!type:output}. *)
32223222+val output_jsont : output Jsont.t
3443322334443224 end
34453445- module CreateBookmark : sig
34463446-(** Creates a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication. *)
32253225+ module GetActorStarterPacks : sig
32263226+(** Get a list of starter packs created by the actor. *)
3447322732283228+(** Query/procedure parameters. *)
32293229+type params = {
32303230+ actor : string;
32313231+ cursor : string option;
32323232+ limit : int option;
32333233+}
3448323434493449-type input = {
34503450- cid : string;
34513451- uri : string;
32353235+(** Jsont codec for {!type:params}. *)
32363236+val params_jsont : params Jsont.t
32373237+32383238+32393239+type output = {
32403240+ cursor : string option;
32413241+ starter_packs : Jsont.json list;
34523242}
3453324334543454-(** Jsont codec for {!type:input}. *)
34553455-val input_jsont : input Jsont.t
32443244+(** Jsont codec for {!type:output}. *)
32453245+val output_jsont : output Jsont.t
3456324634573247 end
34583458- module Defs : sig
32483248+ module SearchStarterPacks : sig
32493249+(** Find starter packs matching search criteria. Does not require auth. *)
3459325034603460-type bookmark_view = {
34613461- created_at : string option;
34623462- item : Jsont.json;
34633463- subject : Com.Atproto.Repo.StrongRef.main; (** A strong ref to the bookmarked record. *)
32513251+(** Query/procedure parameters. *)
32523252+type params = {
32533253+ cursor : string option;
32543254+ limit : int option;
32553255+ q : string; (** Search query string. Syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. *)
34643256}
3465325734663466-(** Jsont codec for {!type:bookmark_view}. *)
34673467-val bookmark_view_jsont : bookmark_view Jsont.t
32583258+(** Jsont codec for {!type:params}. *)
32593259+val params_jsont : params Jsont.t
3468326034693469-(** Object used to store bookmark data in stash. *)
3470326134713471-type bookmark = {
34723472- subject : Com.Atproto.Repo.StrongRef.main; (** A strong ref to the record to be bookmarked. Currently, only `app.bsky.feed.post` records are supported. *)
32623262+type output = {
32633263+ cursor : string option;
32643264+ starter_packs : Jsont.json list;
34733265}
3474326634753475-(** Jsont codec for {!type:bookmark}. *)
34763476-val bookmark_jsont : bookmark Jsont.t
32673267+(** Jsont codec for {!type:output}. *)
32683268+val output_jsont : output Jsont.t
3477326934783270 end
34793479- module GetBookmarks : sig
34803480-(** Gets views of records bookmarked by the authenticated user. Requires authentication. *)
32713271+ module GetStarterPacksWithMembership : sig
32723272+(** A starter pack and an optional list item indicating membership of a target user to that starter pack. *)
32733273+32743274+type starter_pack_with_membership = {
32753275+ list_item : Jsont.json option;
32763276+ starter_pack : Jsont.json;
32773277+}
32783278+32793279+(** Jsont codec for {!type:starter_pack_with_membership}. *)
32803280+val starter_pack_with_membership_jsont : starter_pack_with_membership Jsont.t
32813281+32823282+(** Enumerates the starter packs created by the session user, and includes membership information about `actor` in those starter packs. Requires auth. *)
3481328334823284(** Query/procedure parameters. *)
34833285type params = {
32863286+ actor : string; (** The account (actor) to check for membership. *)
34843287 cursor : string option;
34853288 limit : int option;
34863289}
···349032933491329434923295type output = {
34933493- bookmarks : Defs.bookmark_view list;
34943296 cursor : string option;
32973297+ starter_packs_with_membership : Jsont.json list;
34953298}
3496329934973300(** Jsont codec for {!type:output}. *)
34983301val output_jsont : output Jsont.t
3499330235003303 end
35013501- end
35023502- module Unspecced : sig
35033503- module GetSuggestedUsersSkeleton : sig
35043504-(** Get a skeleton of suggested users. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsers *)
33043304+ module GetLists : sig
33053305+(** Enumerates the lists created by a specified account (actor). *)
3505330635063307(** Query/procedure parameters. *)
35073308type params = {
35083508- category : string option; (** Category of users to get suggestions for. *)
33093309+ actor : string; (** The account (actor) to enumerate lists from. *)
33103310+ cursor : string option;
35093311 limit : int option;
35103510- viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). *)
33123312+ purposes : string list option; (** Optional filter by list purpose. If not specified, all supported types are returned. *)
35113313}
3512331435133315(** Jsont codec for {!type:params}. *)
···351533173516331835173319type output = {
35183518- dids : string list;
35193519- rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
33203320+ cursor : string option;
33213321+ lists : Jsont.json list;
35203322}
3521332335223324(** Jsont codec for {!type:output}. *)
35233325val output_jsont : output Jsont.t
3524332635253327 end
35263526- module GetOnboardingSuggestedStarterPacks : sig
35273527-(** Get a list of suggested starterpacks for onboarding *)
33283328+ module GetStarterPack : sig
33293329+(** Gets a view of a starter pack. *)
3528333035293331(** Query/procedure parameters. *)
35303332type params = {
33333333+ starter_pack : string; (** Reference (AT-URI) of the starter pack record. *)
33343334+}
33353335+33363336+(** Jsont codec for {!type:params}. *)
33373337+val params_jsont : params Jsont.t
33383338+33393339+33403340+type output = {
33413341+ starter_pack : Jsont.json;
33423342+}
33433343+33443344+(** Jsont codec for {!type:output}. *)
33453345+val output_jsont : output Jsont.t
33463346+33473347+ end
33483348+ module GetListBlocks : sig
33493349+(** Get mod lists that the requesting account (actor) is blocking. Requires auth. *)
33503350+33513351+(** Query/procedure parameters. *)
33523352+type params = {
33533353+ cursor : string option;
35313354 limit : int option;
35323355}
35333356···353633593537336035383361type output = {
33623362+ cursor : string option;
33633363+ lists : Jsont.json list;
33643364+}
33653365+33663366+(** Jsont codec for {!type:output}. *)
33673367+val output_jsont : output Jsont.t
33683368+33693369+ end
33703370+ module GetStarterPacks : sig
33713371+(** Get views for a list of starter packs. *)
33723372+33733373+(** Query/procedure parameters. *)
33743374+type params = {
33753375+ uris : string list;
33763376+}
33773377+33783378+(** Jsont codec for {!type:params}. *)
33793379+val params_jsont : params Jsont.t
33803380+33813381+33823382+type output = {
35393383 starter_packs : Jsont.json list;
35403384}
35413385···35433387val output_jsont : output Jsont.t
3544338835453389 end
35463546- module GetPopularFeedGenerators : sig
35473547-(** An unspecced view of globally popular feed generators. *)
33903390+ module GetList : sig
33913391+(** Gets a 'view' (with additional context) of a specified list. *)
3548339235493393(** Query/procedure parameters. *)
35503394type params = {
35513395 cursor : string option;
35523396 limit : int option;
35533553- query : string option;
33973397+ list_ : string; (** Reference (AT-URI) of the list record to hydrate. *)
35543398}
3555339935563400(** Jsont codec for {!type:params}. *)
···3559340335603404type output = {
35613405 cursor : string option;
35623562- feeds : Jsont.json list;
34063406+ items : Jsont.json list;
34073407+ list_ : Jsont.json;
35633408}
3564340935653410(** Jsont codec for {!type:output}. *)
35663411val output_jsont : output Jsont.t
3567341235683413 end
35693569- module GetSuggestedStarterPacksSkeleton : sig
35703570-(** Get a skeleton of suggested starterpacks. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedStarterpacks *)
34143414+ module List : sig
34153415+(** Record representing a list of accounts (actors). Scope includes both moderation-oriented lists and curration-oriented lists. *)
34163416+34173417+type main = {
34183418+ avatar : Atp.Blob_ref.t option;
34193419+ created_at : string;
34203420+ description : string option;
34213421+ description_facets : Richtext.Facet.main list option;
34223422+ labels : Com.Atproto.Label.Defs.self_labels option;
34233423+ name : string; (** Display name for list; can not be empty. *)
34243424+ purpose : Jsont.json; (** Defines the purpose of the list (aka, moderation-oriented or curration-oriented) *)
34253425+}
34263426+34273427+(** Jsont codec for {!type:main}. *)
34283428+val main_jsont : main Jsont.t
34293429+34303430+ end
34313431+ end
34323432+ module Bookmark : sig
34333433+ module Defs : sig
34343434+34353435+type bookmark_view = {
34363436+ created_at : string option;
34373437+ item : Jsont.json;
34383438+ subject : Com.Atproto.Repo.StrongRef.main; (** A strong ref to the bookmarked record. *)
34393439+}
34403440+34413441+(** Jsont codec for {!type:bookmark_view}. *)
34423442+val bookmark_view_jsont : bookmark_view Jsont.t
34433443+34443444+(** Object used to store bookmark data in stash. *)
34453445+34463446+type bookmark = {
34473447+ subject : Com.Atproto.Repo.StrongRef.main; (** A strong ref to the record to be bookmarked. Currently, only `app.bsky.feed.post` records are supported. *)
34483448+}
34493449+34503450+(** Jsont codec for {!type:bookmark}. *)
34513451+val bookmark_jsont : bookmark Jsont.t
34523452+34533453+ end
34543454+ module CreateBookmark : sig
34553455+(** Creates a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication. *)
34563456+34573457+34583458+type input = {
34593459+ cid : string;
34603460+ uri : string;
34613461+}
34623462+34633463+(** Jsont codec for {!type:input}. *)
34643464+val input_jsont : input Jsont.t
34653465+34663466+ end
34673467+ module DeleteBookmark : sig
34683468+(** Deletes a private bookmark for the specified record. Currently, only `app.bsky.feed.post` records are supported. Requires authentication. *)
34693469+34703470+34713471+type input = {
34723472+ uri : string;
34733473+}
34743474+34753475+(** Jsont codec for {!type:input}. *)
34763476+val input_jsont : input Jsont.t
34773477+34783478+ end
34793479+ module GetBookmarks : sig
34803480+(** Gets views of records bookmarked by the authenticated user. Requires authentication. *)
3571348135723482(** Query/procedure parameters. *)
35733483type params = {
34843484+ cursor : string option;
35743485 limit : int option;
35753575- viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). *)
35763486}
3577348735783488(** Jsont codec for {!type:params}. *)
···358034903581349135823492type output = {
35833583- starter_packs : string list;
34933493+ bookmarks : Defs.bookmark_view list;
34943494+ cursor : string option;
35843495}
3585349635863497(** Jsont codec for {!type:output}. *)
35873498val output_jsont : output Jsont.t
3588349935893500 end
35013501+ end
35023502+ module Unspecced : sig
35903503 module GetSuggestedFeeds : sig
35913504(** Get a list of suggested feeds *)
35923505···36073520val output_jsont : output Jsont.t
3608352136093522 end
36103610- module GetSuggestedFeedsSkeleton : sig
36113611-(** Get a skeleton of suggested feeds. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedFeeds *)
35233523+ module GetSuggestedUsers : sig
35243524+(** Get a list of suggested users *)
3612352536133526(** Query/procedure parameters. *)
36143527type params = {
35283528+ category : string option; (** Category of users to get suggestions for. *)
36153529 limit : int option;
36163616- viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). *)
36173530}
3618353136193532(** Jsont codec for {!type:params}. *)
···362135343622353536233536type output = {
36243624- feeds : string list;
35373537+ actors : Jsont.json list;
35383538+ rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
36253539}
3626354036273541(** Jsont codec for {!type:output}. *)
36283542val output_jsont : output Jsont.t
3629354336303544 end
36313631- module GetConfig : sig
35453545+ module GetSuggestedUsersSkeleton : sig
35463546+(** Get a skeleton of suggested users. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedUsers *)
3632354736333633-type live_now_config = {
36343634- did : string;
36353635- domains : string list;
35483548+(** Query/procedure parameters. *)
35493549+type params = {
35503550+ category : string option; (** Category of users to get suggestions for. *)
35513551+ limit : int option;
35523552+ viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). *)
36363553}
3637355436383638-(** Jsont codec for {!type:live_now_config}. *)
36393639-val live_now_config_jsont : live_now_config Jsont.t
36403640-36413641-(** Get miscellaneous runtime configuration. *)
35553555+(** Jsont codec for {!type:params}. *)
35563556+val params_jsont : params Jsont.t
364235573643355836443559type output = {
36453645- check_email_confirmed : bool option;
36463646- live_now : live_now_config list option;
35603560+ dids : string list;
35613561+ rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
36473562}
3648356336493564(** Jsont codec for {!type:output}. *)
···36703585val output_jsont : output Jsont.t
3671358636723587 end
36733673- module GetSuggestedUsers : sig
36743674-(** Get a list of suggested users *)
35883588+ module GetOnboardingSuggestedStarterPacksSkeleton : sig
35893589+(** Get a skeleton of suggested starterpacks for onboarding. Intended to be called and hydrated by app.bsky.unspecced.getOnboardingSuggestedStarterPacks *)
3675359036763591(** Query/procedure parameters. *)
36773592type params = {
36783678- category : string option; (** Category of users to get suggestions for. *)
36793593 limit : int option;
35943594+ viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). *)
36803595}
3681359636823597(** Jsont codec for {!type:params}. *)
···368435993685360036863601type output = {
36873687- actors : Jsont.json list;
36883688- rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
36023602+ starter_packs : string list;
36893603}
3690360436913605(** Jsont codec for {!type:output}. *)
36923606val output_jsont : output Jsont.t
3693360736943608 end
36953695- module GetOnboardingSuggestedStarterPacksSkeleton : sig
36963696-(** Get a skeleton of suggested starterpacks for onboarding. Intended to be called and hydrated by app.bsky.unspecced.getOnboardingSuggestedStarterPacks *)
36093609+ module GetSuggestedFeedsSkeleton : sig
36103610+(** Get a skeleton of suggested feeds. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedFeeds *)
3697361136983612(** Query/procedure parameters. *)
36993613type params = {
···370636203707362137083622type output = {
37093709- starter_packs : string list;
36233623+ feeds : string list;
36243624+}
36253625+36263626+(** Jsont codec for {!type:output}. *)
36273627+val output_jsont : output Jsont.t
36283628+36293629+ end
36303630+ module GetOnboardingSuggestedStarterPacks : sig
36313631+(** Get a list of suggested starterpacks for onboarding *)
36323632+36333633+(** Query/procedure parameters. *)
36343634+type params = {
36353635+ limit : int option;
36363636+}
36373637+36383638+(** Jsont codec for {!type:params}. *)
36393639+val params_jsont : params Jsont.t
36403640+36413641+36423642+type output = {
36433643+ starter_packs : Jsont.json list;
37103644}
3711364537123646(** Jsont codec for {!type:output}. *)
···38393773val age_assurance_event_jsont : age_assurance_event Jsont.t
3840377438413775 end
38423842- module GetTaggedSuggestions : sig
37763776+ module GetConfig : sig
3843377738443844-type suggestion = {
38453845- subject : string;
38463846- subject_type : string;
38473847- tag : string;
37783778+type live_now_config = {
37793779+ did : string;
37803780+ domains : string list;
38483781}
3849378238503850-(** Jsont codec for {!type:suggestion}. *)
38513851-val suggestion_jsont : suggestion Jsont.t
37833783+(** Jsont codec for {!type:live_now_config}. *)
37843784+val live_now_config_jsont : live_now_config Jsont.t
3852378538533853-(** Get a list of suggestions (feeds and users) tagged with categories *)
38543854-38553855-(** Query/procedure parameters. *)
38563856-type params = unit
38573857-38583858-(** Jsont codec for {!type:params}. *)
38593859-val params_jsont : params Jsont.t
37863786+(** Get miscellaneous runtime configuration. *)
386037873861378838623789type output = {
38633863- suggestions : suggestion list;
37903790+ check_email_confirmed : bool option;
37913791+ live_now : live_now_config list option;
38643792}
3865379338663794(** Jsont codec for {!type:output}. *)
38673795val output_jsont : output Jsont.t
3868379638693797 end
38703870- module SearchPostsSkeleton : sig
38713871-(** Backend Posts search, returns only skeleton *)
37983798+ module GetPopularFeedGenerators : sig
37993799+(** An unspecced view of globally popular feed generators. *)
3872380038733801(** Query/procedure parameters. *)
38743802type params = {
38753875- author : string option; (** Filter to posts by the given account. Handles are resolved to DID before query-time. *)
38763876- cursor : string option; (** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. *)
38773877- domain : string option; (** Filter to posts with URLs (facet links or embeds) linking to the given domain (hostname). Server may apply hostname normalization. *)
38783878- lang : string option; (** Filter to posts in the given language. Expected to be based on post language field, though server may override language detection. *)
38033803+ cursor : string option;
38793804 limit : int option;
38803880- mentions : string option; (** Filter to posts which mention the given account. Handles are resolved to DID before query-time. Only matches rich-text facet mentions. *)
38813881- q : string; (** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. *)
38823882- since : string option; (** Filter results for posts after the indicated datetime (inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYYY-MM-DD). *)
38833883- sort : string option; (** Specifies the ranking order of results. *)
38843884- tag : string list option; (** Filter to posts with the given tag (hashtag), based on rich-text facet or tag field. Do not include the hash (#) prefix. Multiple tags can be specified, with 'AND' matching. *)
38853885- until : string option; (** Filter results for posts before the indicated datetime (not inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYY-MM-DD). *)
38863886- url : string option; (** Filter to posts with links (facet links or embeds) pointing to this URL. Server may apply URL normalization or fuzzy matching. *)
38873887- viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). Used for 'from:me' queries. *)
38053805+ query : string option;
38883806}
3889380738903808(** Jsont codec for {!type:params}. *)
···3893381138943812type output = {
38953813 cursor : string option;
38963896- hits_total : int option; (** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. *)
38973897- posts : Defs.skeleton_search_post list;
38143814+ feeds : Jsont.json list;
38983815}
3899381639003817(** Jsont codec for {!type:output}. *)
39013818val output_jsont : output Jsont.t
3902381939033820 end
39043904- module GetPostThreadV2 : sig
38213821+ module GetTaggedSuggestions : sig
3905382239063906-type thread_item = {
39073907- depth : int; (** The nesting level of this item in the thread. Depth 0 means the anchor item. Items above have negative depths, items below have positive depths. *)
39083908- uri : string;
39093909- value : Jsont.json;
38233823+type suggestion = {
38243824+ subject : string;
38253825+ subject_type : string;
38263826+ tag : string;
39103827}
3911382839123912-(** Jsont codec for {!type:thread_item}. *)
39133913-val thread_item_jsont : thread_item Jsont.t
38293829+(** Jsont codec for {!type:suggestion}. *)
38303830+val suggestion_jsont : suggestion Jsont.t
3914383139153915-(** (NOTE: this endpoint is under development and WILL change without notice. Don't use it until it is moved out of `unspecced` or your application WILL break) Get posts in a thread. It is based in an anchor post at any depth of the tree, and returns posts above it (recursively resolving the parent, without further branching to their replies) and below it (recursive replies, with branching to their replies). Does not require auth, but additional metadata and filtering will be applied for authed requests. *)
38323832+(** Get a list of suggestions (feeds and users) tagged with categories *)
3916383339173834(** Query/procedure parameters. *)
39183918-type params = {
39193919- above : bool option; (** Whether to include parents above the anchor. *)
39203920- anchor : string; (** Reference (AT-URI) to post record. This is the anchor post, and the thread will be built around it. It can be any post in the tree, not necessarily a root post. *)
39213921- below : int option; (** How many levels of replies to include below the anchor. *)
39223922- branching_factor : int option; (** Maximum of replies to include at each level of the thread, except for the direct replies to the anchor, which are (NOTE: currently, during unspecced phase) all returned (NOTE: later they might be paginated). *)
39233923- sort : string option; (** Sorting for the thread replies. *)
39243924-}
38353835+type params = unit
3925383639263837(** Jsont codec for {!type:params}. *)
39273838val params_jsont : params Jsont.t
392838393929384039303841type output = {
39313931- has_other_replies : bool; (** Whether this thread has additional replies. If true, a call can be made to the `getPostThreadOtherV2` endpoint to retrieve them. *)
39323932- thread : thread_item list; (** A flat list of thread items. The depth of each item is indicated by the depth property inside the item. *)
39333933- threadgate : Jsont.json option;
38423842+ suggestions : suggestion list;
39343843}
3935384439363845(** Jsont codec for {!type:output}. *)
39373846val output_jsont : output Jsont.t
3938384739393848 end
39403940- module GetTrendingTopics : sig
39413941-(** Get a list of trending topics *)
38493849+ module GetSuggestedStarterPacksSkeleton : sig
38503850+(** Get a skeleton of suggested starterpacks. Intended to be called and hydrated by app.bsky.unspecced.getSuggestedStarterpacks *)
3942385139433852(** Query/procedure parameters. *)
39443853type params = {
39453854 limit : int option;
39463946- viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. *)
38553855+ viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). *)
39473856}
3948385739493858(** Jsont codec for {!type:params}. *)
···395138603952386139533862type output = {
39543954- suggested : Defs.trending_topic list;
39553955- topics : Defs.trending_topic list;
38633863+ starter_packs : string list;
39563864}
3957386539583866(** Jsont codec for {!type:output}. *)
39593867val output_jsont : output Jsont.t
3960386839613869 end
39623962- module GetTrendsSkeleton : sig
39633963-(** Get the skeleton of trends on the network. Intended to be called and then hydrated through app.bsky.unspecced.getTrends *)
38703870+ module InitAgeAssurance : sig
38713871+(** Initiate age assurance for an account. This is a one-time action that will start the process of verifying the user's age. *)
38723872+38733873+38743874+type input = {
38753875+ country_code : string; (** An ISO 3166-1 alpha-2 code of the user's location. *)
38763876+ email : string; (** The user's email address to receive assurance instructions. *)
38773877+ language : string; (** The user's preferred language for communication during the assurance process. *)
38783878+}
38793879+38803880+(** Jsont codec for {!type:input}. *)
38813881+val input_jsont : input Jsont.t
38823882+38833883+38843884+type output = Defs.age_assurance_state
38853885+38863886+(** Jsont codec for {!type:output}. *)
38873887+val output_jsont : output Jsont.t
38883888+38893889+ end
38903890+ module GetTrendingTopics : sig
38913891+(** Get a list of trending topics *)
3964389239653893(** Query/procedure parameters. *)
39663894type params = {
39673895 limit : int option;
39683968- viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). *)
38963896+ viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. *)
39693897}
3970389839713899(** Jsont codec for {!type:params}. *)
···397339013974390239753903type output = {
39763976- trends : Defs.skeleton_trend list;
39043904+ suggested : Defs.trending_topic list;
39053905+ topics : Defs.trending_topic list;
39773906}
3978390739793908(** Jsont codec for {!type:output}. *)
···40103939val output_jsont : output Jsont.t
4011394040123941 end
40134013- module InitAgeAssurance : sig
40144014-(** Initiate age assurance for an account. This is a one-time action that will start the process of verifying the user's age. *)
39423942+ module GetSuggestionsSkeleton : sig
39433943+(** Get a skeleton of suggested actors. Intended to be called and then hydrated through app.bsky.actor.getSuggestions *)
39443944+39453945+(** Query/procedure parameters. *)
39463946+type params = {
39473947+ cursor : string option;
39483948+ limit : int option;
39493949+ relative_to_did : string option; (** DID of the account to get suggestions relative to. If not provided, suggestions will be based on the viewer. *)
39503950+ viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. *)
39513951+}
4015395239533953+(** Jsont codec for {!type:params}. *)
39543954+val params_jsont : params Jsont.t
4016395540174017-type input = {
40184018- country_code : string; (** An ISO 3166-1 alpha-2 code of the user's location. *)
40194019- email : string; (** The user's email address to receive assurance instructions. *)
40204020- language : string; (** The user's preferred language for communication during the assurance process. *)
39563956+39573957+type output = {
39583958+ actors : Defs.skeleton_search_actor list;
39593959+ cursor : string option;
39603960+ rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
39613961+ relative_to_did : string option; (** DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer. *)
40213962}
4022396340234023-(** Jsont codec for {!type:input}. *)
40244024-val input_jsont : input Jsont.t
39643964+(** Jsont codec for {!type:output}. *)
39653965+val output_jsont : output Jsont.t
39663966+39673967+ end
39683968+ module GetAgeAssuranceState : sig
39693969+(** Returns the current state of the age assurance process for an account. This is used to check if the user has completed age assurance or if further action is required. *)
402539704026397140273972type output = Defs.age_assurance_state
···40554000val output_jsont : output Jsont.t
4056400140574002 end
40034003+ module SearchPostsSkeleton : sig
40044004+(** Backend Posts search, returns only skeleton *)
40054005+40064006+(** Query/procedure parameters. *)
40074007+type params = {
40084008+ author : string option; (** Filter to posts by the given account. Handles are resolved to DID before query-time. *)
40094009+ cursor : string option; (** Optional pagination mechanism; may not necessarily allow scrolling through entire result set. *)
40104010+ domain : string option; (** Filter to posts with URLs (facet links or embeds) linking to the given domain (hostname). Server may apply hostname normalization. *)
40114011+ lang : string option; (** Filter to posts in the given language. Expected to be based on post language field, though server may override language detection. *)
40124012+ limit : int option;
40134013+ mentions : string option; (** Filter to posts which mention the given account. Handles are resolved to DID before query-time. Only matches rich-text facet mentions. *)
40144014+ q : string; (** Search query string; syntax, phrase, boolean, and faceting is unspecified, but Lucene query syntax is recommended. *)
40154015+ since : string option; (** Filter results for posts after the indicated datetime (inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYYY-MM-DD). *)
40164016+ sort : string option; (** Specifies the ranking order of results. *)
40174017+ tag : string list option; (** Filter to posts with the given tag (hashtag), based on rich-text facet or tag field. Do not include the hash (#) prefix. Multiple tags can be specified, with 'AND' matching. *)
40184018+ until : string option; (** Filter results for posts before the indicated datetime (not inclusive). Expected to use 'sortAt' timestamp, which may not match 'createdAt'. Can be a datetime, or just an ISO date (YYY-MM-DD). *)
40194019+ url : string option; (** Filter to posts with links (facet links or embeds) pointing to this URL. Server may apply URL normalization or fuzzy matching. *)
40204020+ viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). Used for 'from:me' queries. *)
40214021+}
40224022+40234023+(** Jsont codec for {!type:params}. *)
40244024+val params_jsont : params Jsont.t
40254025+40264026+40274027+type output = {
40284028+ cursor : string option;
40294029+ hits_total : int option; (** Count of search hits. Optional, may be rounded/truncated, and may not be possible to paginate through all hits. *)
40304030+ posts : Defs.skeleton_search_post list;
40314031+}
40324032+40334033+(** Jsont codec for {!type:output}. *)
40344034+val output_jsont : output Jsont.t
40354035+40364036+ end
40584037 module SearchActorsSkeleton : sig
40594038(** Backend Actors (profile) search, returns only skeleton. *)
40604039···40814060val output_jsont : output Jsont.t
4082406140834062 end
40844084- module GetAgeAssuranceState : sig
40854085-(** Returns the current state of the age assurance process for an account. This is used to check if the user has completed age assurance or if further action is required. *)
40634063+ module GetTrends : sig
40644064+(** Get the current trends on the network *)
4086406540664066+(** Query/procedure parameters. *)
40674067+type params = {
40684068+ limit : int option;
40694069+}
4087407040884088-type output = Defs.age_assurance_state
40714071+(** Jsont codec for {!type:params}. *)
40724072+val params_jsont : params Jsont.t
40734073+40744074+40754075+type output = {
40764076+ trends : Defs.trend_view list;
40774077+}
4089407840904079(** Jsont codec for {!type:output}. *)
40914080val output_jsont : output Jsont.t
4092408140934082 end
40944094- module GetSuggestionsSkeleton : sig
40954095-(** Get a skeleton of suggested actors. Intended to be called and then hydrated through app.bsky.actor.getSuggestions *)
40834083+ module GetPostThreadV2 : sig
40844084+40854085+type thread_item = {
40864086+ depth : int; (** The nesting level of this item in the thread. Depth 0 means the anchor item. Items above have negative depths, items below have positive depths. *)
40874087+ uri : string;
40884088+ value : Jsont.json;
40894089+}
40904090+40914091+(** Jsont codec for {!type:thread_item}. *)
40924092+val thread_item_jsont : thread_item Jsont.t
40934093+40944094+(** (NOTE: this endpoint is under development and WILL change without notice. Don't use it until it is moved out of `unspecced` or your application WILL break) Get posts in a thread. It is based in an anchor post at any depth of the tree, and returns posts above it (recursively resolving the parent, without further branching to their replies) and below it (recursive replies, with branching to their replies). Does not require auth, but additional metadata and filtering will be applied for authed requests. *)
4096409540974096(** Query/procedure parameters. *)
40984097type params = {
40994099- cursor : string option;
41004100- limit : int option;
41014101- relative_to_did : string option; (** DID of the account to get suggestions relative to. If not provided, suggestions will be based on the viewer. *)
41024102- viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). Used to boost followed accounts in ranking. *)
40984098+ above : bool option; (** Whether to include parents above the anchor. *)
40994099+ anchor : string; (** Reference (AT-URI) to post record. This is the anchor post, and the thread will be built around it. It can be any post in the tree, not necessarily a root post. *)
41004100+ below : int option; (** How many levels of replies to include below the anchor. *)
41014101+ branching_factor : int option; (** Maximum of replies to include at each level of the thread, except for the direct replies to the anchor, which are (NOTE: currently, during unspecced phase) all returned (NOTE: later they might be paginated). *)
41024102+ sort : string option; (** Sorting for the thread replies. *)
41034103}
4104410441054105(** Jsont codec for {!type:params}. *)
···410741074108410841094109type output = {
41104110- actors : Defs.skeleton_search_actor list;
41114111- cursor : string option;
41124112- rec_id : int option; (** Snowflake for this recommendation, use when submitting recommendation events. *)
41134113- relative_to_did : string option; (** DID of the account these suggestions are relative to. If this is returned undefined, suggestions are based on the viewer. *)
41104110+ has_other_replies : bool; (** Whether this thread has additional replies. If true, a call can be made to the `getPostThreadOtherV2` endpoint to retrieve them. *)
41114111+ thread : thread_item list; (** A flat list of thread items. The depth of each item is indicated by the depth property inside the item. *)
41124112+ threadgate : Jsont.json option;
41144113}
4115411441164115(** Jsont codec for {!type:output}. *)
41174116val output_jsont : output Jsont.t
4118411741194118 end
41204120- module GetTrends : sig
41214121-(** Get the current trends on the network *)
41194119+ module GetTrendsSkeleton : sig
41204120+(** Get the skeleton of trends on the network. Intended to be called and then hydrated through app.bsky.unspecced.getTrends *)
4122412141234122(** Query/procedure parameters. *)
41244123type params = {
41254124 limit : int option;
41254125+ viewer : string option; (** DID of the account making the request (not included for public/unauthenticated queries). *)
41264126}
4127412741284128(** Jsont codec for {!type:params}. *)
···413041304131413141324132type output = {
41334133- trends : Defs.trend_view list;
41334133+ trends : Defs.skeleton_trend list;
41344134}
4135413541364136(** Jsont codec for {!type:output}. *)
···3030end
3131module Site : sig
3232 module Standard : sig
3333- module Graph : sig
3434- module Subscription : sig
3535-(** Record declaring a subscription to a publication. *)
3636-3737-type main = {
3838- publication : string; (** AT-URI reference to the publication record being subscribed to (ex: at://did:plc:abc123/site.standard.publication/xyz789). *)
3939-}
4040-4141-(** Jsont codec for {!type:main}. *)
4242-val main_jsont : main Jsont.t
4343-4444- end
4545- end
4633 module Document : sig
4734(** A document record representing a published article, blog post, or other content. Documents can belong to a publication or exist independently. *)
4835···9683 accent_foreground : Color.rgb; (** Color used for button text. *)
9784 background : Color.rgb; (** Color used for content background. *)
9885 foreground : Color.rgb; (** Color used for content text. *)
8686+}
8787+8888+(** Jsont codec for {!type:main}. *)
8989+val main_jsont : main Jsont.t
9090+9191+ end
9292+ end
9393+ module Graph : sig
9494+ module Subscription : sig
9595+(** Record declaring a subscription to a publication. *)
9696+9797+type main = {
9898+ publication : string; (** AT-URI reference to the publication record being subscribed to (ex: at://did:plc:abc123/site.standard.publication/xyz789). *)
9999}
100100101101(** Jsont codec for {!type:main}. *)
···12121313module Sh : sig
1414 module Tangled : sig
1515+ module Actor : sig
1616+ module Profile : sig
1717+(** A declaration of a Tangled account profile. *)
1818+1919+type main = {
2020+ bluesky : bool; (** Include link to this account on Bluesky. *)
2121+ description : string option; (** Free-form profile description text. *)
2222+ links : string list option;
2323+ location : string option; (** Free-form location text. *)
2424+ pinned_repositories : string list option; (** Any ATURI, it is up to appviews to validate these fields. *)
2525+ pronouns : string option; (** Preferred gender pronouns. *)
2626+ stats : string list option;
2727+}
2828+2929+(** Jsont codec for {!type:main}. *)
3030+val main_jsont : main Jsont.t
3131+3232+ end
3333+ end
3434+ module Owner : sig
3535+(** Get the owner of a service *)
3636+3737+3838+type output = {
3939+ owner : string;
4040+}
4141+4242+(** Jsont codec for {!type:output}. *)
4343+val output_jsont : output Jsont.t
4444+4545+ end
1546 module Spindle : sig
16471748type main = {
···34653566 end
3667 end
3737- module Git : sig
3838- module RefUpdate : sig
6868+ module Pipeline : sig
39694040-type individual_language_size = {
4141- lang : string;
4242- size : int;
7070+type trigger_repo = {
7171+ default_branch : string;
7272+ did : string;
7373+ knot : string;
7474+ repo : string;
4375}
44764545-(** Jsont codec for {!type:individual_language_size}. *)
4646-val individual_language_size_jsont : individual_language_size Jsont.t
7777+(** Jsont codec for {!type:trigger_repo}. *)
7878+val trigger_repo_jsont : trigger_repo Jsont.t
477948804949-type individual_email_commit_count = {
5050- count : int;
5151- email : string;
8181+type push_trigger_data = {
8282+ new_sha : string;
8383+ old_sha : string;
8484+ ref_ : string;
5285}
53865454-(** Jsont codec for {!type:individual_email_commit_count}. *)
5555-val individual_email_commit_count_jsont : individual_email_commit_count Jsont.t
8787+(** Jsont codec for {!type:push_trigger_data}. *)
8888+val push_trigger_data_jsont : push_trigger_data Jsont.t
568957905858-type lang_breakdown = {
5959- inputs : individual_language_size list option;
9191+type pull_request_trigger_data = {
9292+ action : string;
9393+ source_branch : string;
9494+ source_sha : string;
9595+ target_branch : string;
6096}
61976262-(** Jsont codec for {!type:lang_breakdown}. *)
6363-val lang_breakdown_jsont : lang_breakdown Jsont.t
9898+(** Jsont codec for {!type:pull_request_trigger_data}. *)
9999+val pull_request_trigger_data_jsont : pull_request_trigger_data Jsont.t
64100651016666-type commit_count_breakdown = {
6767- by_email : individual_email_commit_count list option;
102102+type pair = {
103103+ key : string;
104104+ value : string;
68105}
691067070-(** Jsont codec for {!type:commit_count_breakdown}. *)
7171-val commit_count_breakdown_jsont : commit_count_breakdown Jsont.t
107107+(** Jsont codec for {!type:pair}. *)
108108+val pair_jsont : pair Jsont.t
72109731107474-type meta = {
7575- commit_count : commit_count_breakdown;
7676- is_default_ref : bool;
7777- lang_breakdown : lang_breakdown option;
111111+type clone_opts = {
112112+ depth : int;
113113+ skip : bool;
114114+ submodules : bool;
78115}
791168080-(** Jsont codec for {!type:meta}. *)
8181-val meta_jsont : meta Jsont.t
117117+(** Jsont codec for {!type:clone_opts}. *)
118118+val clone_opts_jsont : clone_opts Jsont.t
821198383-(** An update to a git repository, emitted by knots. *)
120120+121121+type workflow = {
122122+ clone : clone_opts;
123123+ engine : string;
124124+ name : string;
125125+ raw : string;
126126+}
127127+128128+(** Jsont codec for {!type:workflow}. *)
129129+val workflow_jsont : workflow Jsont.t
130130+131131+132132+type manual_trigger_data = {
133133+ inputs : pair list option;
134134+}
135135+136136+(** Jsont codec for {!type:manual_trigger_data}. *)
137137+val manual_trigger_data_jsont : manual_trigger_data Jsont.t
138138+139139+140140+type trigger_metadata = {
141141+ kind : string;
142142+ manual : manual_trigger_data option;
143143+ pull_request : pull_request_trigger_data option;
144144+ push : push_trigger_data option;
145145+ repo : trigger_repo;
146146+}
147147+148148+(** Jsont codec for {!type:trigger_metadata}. *)
149149+val trigger_metadata_jsont : trigger_metadata Jsont.t
150150+8415185152type main = {
8686- committer_did : string; (** did of the user that pushed this ref *)
8787- meta : meta;
8888- new_sha : string; (** new SHA of this ref *)
8989- old_sha : string; (** old SHA of this ref *)
9090- ref_ : string; (** Ref being updated *)
9191- repo_did : string; (** did of the owner of the repo *)
9292- repo_name : string; (** name of the repo *)
153153+ trigger_metadata : trigger_metadata;
154154+ workflows : workflow list;
93155}
9415695157(** Jsont codec for {!type:main}. *)
96158val main_jsont : main Jsont.t
971599898- end
9999- end
100100- module Actor : sig
101101- module Profile : sig
102102-(** A declaration of a Tangled account profile. *)
160160+ module Status : sig
103161104162type main = {
105105- bluesky : bool; (** Include link to this account on Bluesky. *)
106106- description : string option; (** Free-form profile description text. *)
107107- links : string list option;
108108- location : string option; (** Free-form location text. *)
109109- pinned_repositories : string list option; (** Any ATURI, it is up to appviews to validate these fields. *)
110110- pronouns : string option; (** Preferred gender pronouns. *)
111111- stats : string list option;
163163+ created_at : string; (** time of creation of this status update *)
164164+ error : string option; (** error message if failed *)
165165+ exit_code : int option; (** exit code if failed *)
166166+ pipeline : string; (** ATURI of the pipeline *)
167167+ status : string; (** status of the workflow *)
168168+ workflow : string; (** name of the workflow within this pipeline *)
112169}
113170114171(** Jsont codec for {!type:main}. *)
···116173117174 end
118175 end
119119- module String : sig
176176+ module Knot : sig
120177121178type main = {
122122- contents : string;
123179 created_at : string;
124124- description : string;
125125- filename : string;
126180}
127181128182(** Jsont codec for {!type:main}. *)
129183val main_jsont : main Jsont.t
130184131131- end
132132- module Feed : sig
133133- module Star : sig
185185+ module Version : sig
186186+(** Get the version of a knot *)
187187+188188+189189+type output = {
190190+ version : string;
191191+}
192192+193193+(** Jsont codec for {!type:output}. *)
194194+val output_jsont : output Jsont.t
195195+196196+ end
197197+ module ListKeys : sig
198198+199199+type public_key = {
200200+ created_at : string; (** Key upload timestamp *)
201201+ did : string; (** DID associated with the public key *)
202202+ key : string; (** Public key contents *)
203203+}
204204+205205+(** Jsont codec for {!type:public_key}. *)
206206+val public_key_jsont : public_key Jsont.t
207207+208208+(** List all public keys stored in the knot server *)
209209+210210+(** Query/procedure parameters. *)
211211+type params = {
212212+ cursor : string option; (** Pagination cursor *)
213213+ limit : int option; (** Maximum number of keys to return *)
214214+}
215215+216216+(** Jsont codec for {!type:params}. *)
217217+val params_jsont : params Jsont.t
218218+219219+220220+type output = {
221221+ cursor : string option; (** Pagination cursor for next page *)
222222+ keys : public_key list;
223223+}
224224+225225+(** Jsont codec for {!type:output}. *)
226226+val output_jsont : output Jsont.t
227227+228228+ end
229229+ module Member : sig
134230135231type main = {
136232 created_at : string;
233233+ domain : string; (** domain that this member now belongs to *)
137234 subject : string;
138235}
139236···141238val main_jsont : main Jsont.t
142239143240 end
144144- module Reaction : sig
241241+ end
242242+ module Label : sig
243243+ module Op : sig
244244+245245+type operand = {
246246+ key : string; (** ATURI to the label definition *)
247247+ value : string; (** Stringified value of the label. This is first unstringed by appviews and then interpreted as a concrete value. *)
248248+}
249249+250250+(** Jsont codec for {!type:operand}. *)
251251+val operand_jsont : operand Jsont.t
252252+145253146254type main = {
147147- created_at : string;
148148- reaction : string;
149149- subject : string;
255255+ add : operand list;
256256+ delete : operand list;
257257+ performed_at : string;
258258+ subject : string; (** The subject (task, pull or discussion) of this label. Appviews may apply a `scope` check and refuse this op. *)
150259}
151260152261(** Jsont codec for {!type:main}. *)
153262val main_jsont : main Jsont.t
154263155264 end
156156- end
157157- module Repo : sig
265265+ module Definition : sig
266266+267267+type value_type = {
268268+ enum : string list option; (** Closed set of values that this label can take. *)
269269+ format : string; (** An optional constraint that can be applied on string concrete types. *)
270270+ type_ : string; (** The concrete type of this label's value. *)
271271+}
272272+273273+(** Jsont codec for {!type:value_type}. *)
274274+val value_type_jsont : value_type Jsont.t
275275+158276159277type main = {
278278+ color : string option; (** The hex value for the background color for the label. Appviews may choose to respect this. *)
160279 created_at : string;
161161- description : string option;
162162- knot : string; (** knot where the repo was created *)
163163- labels : string list option; (** List of labels that this repo subscribes to *)
164164- name : string; (** name of the repo *)
165165- source : string option; (** source of the repo *)
166166- spindle : string option; (** CI runner to send jobs to and receive results from *)
167167- topics : string list option; (** Topics related to the repo *)
168168- website : string option; (** Any URI related to the repo *)
280280+ multiple : bool option; (** Whether this label can be repeated for a given entity, eg.: \[reviewer:foo, reviewer:bar\] *)
281281+ name : string; (** The display name of this label. *)
282282+ scope : string list; (** The areas of the repo this label may apply to, eg.: sh.tangled.repo.issue. Appviews may choose to respect this. *)
283283+ value_type : value_type; (** The type definition of this label. Appviews may allow sorting for certain types. *)
169284}
170285171286(** Jsont codec for {!type:main}. *)
172287val main_jsont : main Jsont.t
173288174174- module Artifact : sig
289289+ end
290290+ end
291291+ module Feed : sig
292292+ module Reaction : sig
175293176294type main = {
177177- artifact : Atp.Blob_ref.t; (** the artifact *)
178178- created_at : string; (** time of creation of this artifact *)
179179- name : string; (** name of the artifact *)
180180- repo : string; (** repo that this artifact is being uploaded to *)
181181- tag : string; (** hash of the tag object that this artifact is attached to (only annotated tags are supported) *)
295295+ created_at : string;
296296+ reaction : string;
297297+ subject : string;
182298}
183299184300(** Jsont codec for {!type:main}. *)
185301val main_jsont : main Jsont.t
186302187303 end
188188- module MergeCheck : sig
304304+ module Star : sig
189305190190-type conflict_info = {
191191- filename : string; (** Name of the conflicted file *)
192192- reason : string; (** Reason for the conflict *)
306306+type main = {
307307+ created_at : string;
308308+ subject : string;
193309}
194310195195-(** Jsont codec for {!type:conflict_info}. *)
196196-val conflict_info_jsont : conflict_info Jsont.t
197197-198198-(** Check if a merge is possible between two branches *)
311311+(** Jsont codec for {!type:main}. *)
312312+val main_jsont : main Jsont.t
199313314314+ end
315315+ end
316316+ module PublicKey : sig
200317201201-type input = {
202202- branch : string; (** Target branch to merge into *)
203203- did : string; (** DID of the repository owner *)
204204- name : string; (** Name of the repository *)
205205- patch : string; (** Patch or pull request to check for merge conflicts *)
318318+type main = {
319319+ created_at : string; (** key upload timestamp *)
320320+ key : string; (** public key contents *)
321321+ name : string; (** human-readable name for this key *)
206322}
207323208208-(** Jsont codec for {!type:input}. *)
209209-val input_jsont : input Jsont.t
324324+(** Jsont codec for {!type:main}. *)
325325+val main_jsont : main Jsont.t
210326327327+ end
328328+ module Graph : sig
329329+ module Follow : sig
211330212212-type output = {
213213- conflicts : conflict_info list option; (** List of files with merge conflicts *)
214214- error : string option; (** Error message if check failed *)
215215- is_conflicted : bool; (** Whether the merge has conflicts *)
216216- message : string option; (** Additional message about the merge check *)
331331+type main = {
332332+ created_at : string;
333333+ subject : string;
217334}
218335219219-(** Jsont codec for {!type:output}. *)
220220-val output_jsont : output Jsont.t
336336+(** Jsont codec for {!type:main}. *)
337337+val main_jsont : main Jsont.t
221338222339 end
223223- module Tree : sig
340340+ end
341341+ module Git : sig
342342+ module RefUpdate : sig
224343225225-type readme = {
226226- contents : string; (** Contents of the readme file *)
227227- filename : string; (** Name of the readme file *)
344344+type individual_language_size = {
345345+ lang : string;
346346+ size : int;
228347}
229348230230-(** Jsont codec for {!type:readme}. *)
231231-val readme_jsont : readme Jsont.t
349349+(** Jsont codec for {!type:individual_language_size}. *)
350350+val individual_language_size_jsont : individual_language_size Jsont.t
232351233352234234-type last_commit = {
235235- hash : string; (** Commit hash *)
236236- message : string; (** Commit message *)
237237- when_ : string; (** Commit timestamp *)
353353+type individual_email_commit_count = {
354354+ count : int;
355355+ email : string;
238356}
239357240240-(** Jsont codec for {!type:last_commit}. *)
241241-val last_commit_jsont : last_commit Jsont.t
358358+(** Jsont codec for {!type:individual_email_commit_count}. *)
359359+val individual_email_commit_count_jsont : individual_email_commit_count Jsont.t
242360243361244244-type tree_entry = {
245245- last_commit : last_commit option;
246246- mode : string; (** File mode *)
247247- name : string; (** Relative file or directory name *)
248248- size : int; (** File size in bytes *)
362362+type lang_breakdown = {
363363+ inputs : individual_language_size list option;
249364}
250365251251-(** Jsont codec for {!type:tree_entry}. *)
252252-val tree_entry_jsont : tree_entry Jsont.t
366366+(** Jsont codec for {!type:lang_breakdown}. *)
367367+val lang_breakdown_jsont : lang_breakdown Jsont.t
253368254369255255-(** Query/procedure parameters. *)
256256-type params = {
257257- path : string option; (** Path within the repository tree *)
258258- ref_ : string; (** Git reference (branch, tag, or commit SHA) *)
259259- repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
370370+type commit_count_breakdown = {
371371+ by_email : individual_email_commit_count list option;
260372}
261373262262-(** Jsont codec for {!type:params}. *)
263263-val params_jsont : params Jsont.t
374374+(** Jsont codec for {!type:commit_count_breakdown}. *)
375375+val commit_count_breakdown_jsont : commit_count_breakdown Jsont.t
264376265377266266-type output = {
267267- dotdot : string option; (** Parent directory path *)
268268- files : tree_entry list;
269269- parent : string option; (** The parent path in the tree *)
270270- readme : readme option; (** Readme for this file tree *)
271271- ref_ : string; (** The git reference used *)
378378+type meta = {
379379+ commit_count : commit_count_breakdown;
380380+ is_default_ref : bool;
381381+ lang_breakdown : lang_breakdown option;
272382}
273383274274-(** Jsont codec for {!type:output}. *)
275275-val output_jsont : output Jsont.t
276276-277277- end
278278- module SetDefaultBranch : sig
279279-(** Set the default branch for a repository *)
384384+(** Jsont codec for {!type:meta}. *)
385385+val meta_jsont : meta Jsont.t
280386387387+(** An update to a git repository, emitted by knots. *)
281388282282-type input = {
283283- default_branch : string;
284284- repo : string;
389389+type main = {
390390+ committer_did : string; (** did of the user that pushed this ref *)
391391+ meta : meta;
392392+ new_sha : string; (** new SHA of this ref *)
393393+ old_sha : string; (** old SHA of this ref *)
394394+ ref_ : string; (** Ref being updated *)
395395+ repo_did : string; (** did of the owner of the repo *)
396396+ repo_name : string; (** name of the repo *)
285397}
286398287287-(** Jsont codec for {!type:input}. *)
288288-val input_jsont : input Jsont.t
399399+(** Jsont codec for {!type:main}. *)
400400+val main_jsont : main Jsont.t
289401290402 end
291291- module Compare : sig
403403+ end
404404+ module String : sig
292405293293-(** Query/procedure parameters. *)
294294-type params = {
295295- repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
296296- rev1 : string; (** First revision (commit, branch, or tag) *)
297297- rev2 : string; (** Second revision (commit, branch, or tag) *)
406406+type main = {
407407+ contents : string;
408408+ created_at : string;
409409+ description : string;
410410+ filename : string;
298411}
299412300300-(** Jsont codec for {!type:params}. *)
301301-val params_jsont : params Jsont.t
413413+(** Jsont codec for {!type:main}. *)
414414+val main_jsont : main Jsont.t
302415303303-(** Compare output in application/json *)
416416+ end
417417+ module Repo : sig
304418305305-type output = unit
306306-val output_jsont : output Jsont.t
419419+type main = {
420420+ created_at : string;
421421+ description : string option;
422422+ knot : string; (** knot where the repo was created *)
423423+ labels : string list option; (** List of labels that this repo subscribes to *)
424424+ name : string; (** name of the repo *)
425425+ source : string option; (** source of the repo *)
426426+ spindle : string option; (** CI runner to send jobs to and receive results from *)
427427+ topics : string list option; (** Topics related to the repo *)
428428+ website : string option; (** Any URI related to the repo *)
429429+}
307430308308- end
309309- module Merge : sig
310310-(** Merge a patch into a repository branch *)
431431+(** Jsont codec for {!type:main}. *)
432432+val main_jsont : main Jsont.t
311433434434+ module Issue : sig
312435313313-type input = {
314314- author_email : string option; (** Author email for the merge commit *)
315315- author_name : string option; (** Author name for the merge commit *)
316316- branch : string; (** Target branch to merge into *)
317317- commit_body : string option; (** Additional commit message body *)
318318- commit_message : string option; (** Merge commit message *)
319319- did : string; (** DID of the repository owner *)
320320- name : string; (** Name of the repository *)
321321- patch : string; (** Patch content to merge *)
436436+type main = {
437437+ body : string option;
438438+ created_at : string;
439439+ mentions : string list option;
440440+ references : string list option;
441441+ repo : string;
442442+ title : string;
322443}
323444324324-(** Jsont codec for {!type:input}. *)
325325-val input_jsont : input Jsont.t
445445+(** Jsont codec for {!type:main}. *)
446446+val main_jsont : main Jsont.t
326447327327- end
328328- module Tags : sig
448448+ module Comment : sig
329449330330-(** Query/procedure parameters. *)
331331-type params = {
332332- cursor : string option; (** Pagination cursor *)
333333- limit : int option; (** Maximum number of tags to return *)
334334- repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
450450+type main = {
451451+ body : string;
452452+ created_at : string;
453453+ issue : string;
454454+ mentions : string list option;
455455+ references : string list option;
456456+ reply_to : string option;
335457}
336458337337-(** Jsont codec for {!type:params}. *)
338338-val params_jsont : params Jsont.t
339339-340340-341341-type output = unit
342342-val output_jsont : output Jsont.t
459459+(** Jsont codec for {!type:main}. *)
460460+val main_jsont : main Jsont.t
343461344344- end
345345- module Collaborator : sig
462462+ end
463463+ module State : sig
346464347465type main = {
348348- created_at : string;
349349- repo : string; (** repo to add this user to *)
350350- subject : string;
466466+ issue : string;
467467+ state : string; (** state of the issue *)
351468}
352469353470(** Jsont codec for {!type:main}. *)
354471val main_jsont : main Jsont.t
355472473473+ module Closed : sig
474474+(** closed issue *)
475475+476476+type main = string
477477+val main_jsont : main Jsont.t
478478+479479+ end
480480+ module Open : sig
481481+(** open issue *)
482482+483483+type main = string
484484+val main_jsont : main Jsont.t
485485+486486+ end
487487+ end
356488 end
357357- module Branches : sig
489489+ module Pull : sig
358490359359-(** Query/procedure parameters. *)
360360-type params = {
361361- cursor : string option; (** Pagination cursor *)
362362- limit : int option; (** Maximum number of branches to return *)
363363- repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
491491+type target = {
492492+ branch : string;
493493+ repo : string;
364494}
365495366366-(** Jsont codec for {!type:params}. *)
367367-val params_jsont : params Jsont.t
496496+(** Jsont codec for {!type:target}. *)
497497+val target_jsont : target Jsont.t
368498369499370370-type output = unit
371371-val output_jsont : output Jsont.t
500500+type source = {
501501+ branch : string;
502502+ repo : string option;
503503+ sha : string;
504504+}
372505373373- end
374374- module GetDefaultBranch : sig
506506+(** Jsont codec for {!type:source}. *)
507507+val source_jsont : source Jsont.t
375508376376-type signature = {
377377- email : string; (** Author email *)
378378- name : string; (** Author name *)
379379- when_ : string; (** Author timestamp *)
509509+510510+type main = {
511511+ body : string option;
512512+ created_at : string;
513513+ mentions : string list option;
514514+ patch : string option; (** (deprecated) use patchBlob instead *)
515515+ patch_blob : Atp.Blob_ref.t; (** patch content *)
516516+ references : string list option;
517517+ source : source option;
518518+ target : target;
519519+ title : string;
380520}
381521382382-(** Jsont codec for {!type:signature}. *)
383383-val signature_jsont : signature Jsont.t
522522+(** Jsont codec for {!type:main}. *)
523523+val main_jsont : main Jsont.t
384524525525+ module Comment : sig
385526386386-(** Query/procedure parameters. *)
387387-type params = {
388388- repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
527527+type main = {
528528+ body : string;
529529+ created_at : string;
530530+ mentions : string list option;
531531+ pull : string;
532532+ references : string list option;
389533}
390534391391-(** Jsont codec for {!type:params}. *)
392392-val params_jsont : params Jsont.t
535535+(** Jsont codec for {!type:main}. *)
536536+val main_jsont : main Jsont.t
393537538538+ end
539539+ module Status : sig
394540395395-type output = {
396396- author : signature option;
397397- hash : string; (** Latest commit hash on default branch *)
398398- message : string option; (** Latest commit message *)
399399- name : string; (** Default branch name *)
400400- short_hash : string option; (** Short commit hash *)
401401- when_ : string; (** Timestamp of latest commit *)
541541+type main = {
542542+ pull : string;
543543+ status : string; (** status of the pull request *)
402544}
403545404404-(** Jsont codec for {!type:output}. *)
405405-val output_jsont : output Jsont.t
546546+(** Jsont codec for {!type:main}. *)
547547+val main_jsont : main Jsont.t
406548407407- end
408408- module Create : sig
409409-(** Create a new repository *)
549549+ module Merged : sig
550550+(** merged pull request *)
410551552552+type main = string
553553+val main_jsont : main Jsont.t
411554412412-type input = {
413413- default_branch : string option; (** Default branch to push to *)
414414- rkey : string; (** Rkey of the repository record *)
415415- source : string option; (** A source URL to clone from, populate this when forking or importing a repository. *)
416416-}
555555+ end
556556+ module Closed : sig
557557+(** closed pull request *)
417558418418-(** Jsont codec for {!type:input}. *)
419419-val input_jsont : input Jsont.t
559559+type main = string
560560+val main_jsont : main Jsont.t
420561562562+ end
563563+ module Open : sig
564564+(** open pull request *)
565565+566566+type main = string
567567+val main_jsont : main Jsont.t
568568+569569+ end
570570+ end
421571 end
422422- module ForkStatus : sig
423423-(** Check fork status relative to upstream source *)
572572+ module SetDefaultBranch : sig
573573+(** Set the default branch for a repository *)
424574425575426576type input = {
427427- branch : string; (** Branch to check status for *)
428428- did : string; (** DID of the fork owner *)
429429- hidden_ref : string; (** Hidden ref to use for comparison *)
430430- name : string; (** Name of the forked repository *)
431431- source : string; (** Source repository URL *)
577577+ default_branch : string;
578578+ repo : string;
432579}
433580434581(** Jsont codec for {!type:input}. *)
435582val input_jsont : input Jsont.t
436583437437-438438-type output = {
439439- status : int; (** Fork status: 0=UpToDate, 1=FastForwardable, 2=Conflict, 3=MissingBranch *)
440440-}
441441-442442-(** Jsont codec for {!type:output}. *)
443443-val output_jsont : output Jsont.t
444444-445584 end
446446- module Branch : sig
585585+ module GetDefaultBranch : sig
447586448587type signature = {
449588 email : string; (** Author email *)
···457596458597(** Query/procedure parameters. *)
459598type params = {
460460- name : string; (** Branch name to get information for *)
461599 repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
462600}
463601···467605468606type output = {
469607 author : signature option;
470470- hash : string; (** Latest commit hash on this branch *)
471471- is_default : bool option; (** Whether this is the default branch *)
608608+ hash : string; (** Latest commit hash on default branch *)
472609 message : string option; (** Latest commit message *)
473473- name : string; (** Branch name *)
610610+ name : string; (** Default branch name *)
474611 short_hash : string option; (** Short commit hash *)
475612 when_ : string; (** Timestamp of latest commit *)
476613}
···492629val input_jsont : input Jsont.t
493630494631 end
495495- module Log : sig
496496-497497-(** Query/procedure parameters. *)
498498-type params = {
499499- cursor : string option; (** Pagination cursor (commit SHA) *)
500500- limit : int option; (** Maximum number of commits to return *)
501501- path : string option; (** Path to filter commits by *)
502502- ref_ : string; (** Git reference (branch, tag, or commit SHA) *)
503503- repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
504504-}
505505-506506-(** Jsont codec for {!type:params}. *)
507507-val params_jsont : params Jsont.t
508508-509509-510510-type output = unit
511511-val output_jsont : output Jsont.t
632632+ module Delete : sig
633633+(** Delete a repository *)
512634513513- end
514514- module Blob : sig
515635516516-type submodule = {
517517- branch : string option; (** Branch to track in the submodule *)
518518- name : string; (** Submodule name *)
519519- url : string; (** Submodule repository URL *)
636636+type input = {
637637+ did : string; (** DID of the repository owner *)
638638+ name : string; (** Name of the repository to delete *)
639639+ rkey : string; (** Rkey of the repository record *)
520640}
521641522522-(** Jsont codec for {!type:submodule}. *)
523523-val submodule_jsont : submodule Jsont.t
642642+(** Jsont codec for {!type:input}. *)
643643+val input_jsont : input Jsont.t
524644645645+ end
646646+ module Tree : sig
525647526526-type signature = {
527527- email : string; (** Author email *)
528528- name : string; (** Author name *)
529529- when_ : string; (** Author timestamp *)
648648+type readme = {
649649+ contents : string; (** Contents of the readme file *)
650650+ filename : string; (** Name of the readme file *)
530651}
531652532532-(** Jsont codec for {!type:signature}. *)
533533-val signature_jsont : signature Jsont.t
653653+(** Jsont codec for {!type:readme}. *)
654654+val readme_jsont : readme Jsont.t
534655535656536657type last_commit = {
537537- author : signature option;
538658 hash : string; (** Commit hash *)
539659 message : string; (** Commit message *)
540540- short_hash : string option; (** Short commit hash *)
541660 when_ : string; (** Commit timestamp *)
542661}
543662···545664val last_commit_jsont : last_commit Jsont.t
546665547666667667+type tree_entry = {
668668+ last_commit : last_commit option;
669669+ mode : string; (** File mode *)
670670+ name : string; (** Relative file or directory name *)
671671+ size : int; (** File size in bytes *)
672672+}
673673+674674+(** Jsont codec for {!type:tree_entry}. *)
675675+val tree_entry_jsont : tree_entry Jsont.t
676676+677677+548678(** Query/procedure parameters. *)
549679type params = {
550550- path : string; (** Path to the file within the repository *)
551551- raw : bool option; (** Return raw file content instead of JSON response *)
680680+ path : string option; (** Path within the repository tree *)
552681 ref_ : string; (** Git reference (branch, tag, or commit SHA) *)
553682 repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
554683}
···558687559688560689type output = {
561561- content : string option; (** File content (base64 encoded for binary files) *)
562562- encoding : string option; (** Content encoding *)
563563- is_binary : bool option; (** Whether the file is binary *)
564564- last_commit : last_commit option;
565565- mime_type : string option; (** MIME type of the file *)
566566- path : string; (** The file path *)
690690+ dotdot : string option; (** Parent directory path *)
691691+ files : tree_entry list;
692692+ parent : string option; (** The parent path in the tree *)
693693+ readme : readme option; (** Readme for this file tree *)
567694 ref_ : string; (** The git reference used *)
568568- size : int option; (** File size in bytes *)
569569- submodule : submodule option; (** Submodule information if path is a submodule *)
570695}
571696572697(** Jsont codec for {!type:output}. *)
573698val output_jsont : output Jsont.t
574699575700 end
576576- module RemoveSecret : sig
577577-(** Remove a CI secret *)
701701+ module AddSecret : sig
702702+(** Add a CI secret *)
578703579704580705type input = {
581706 key : string;
582707 repo : string;
708708+ value : string;
583709}
584710585711(** Jsont codec for {!type:input}. *)
586712val input_jsont : input Jsont.t
587713588714 end
589589- module Languages : sig
715715+ module Branch : sig
590716591591-type language = {
592592- color : string option; (** Hex color code for this language *)
593593- extensions : string list option; (** File extensions associated with this language *)
594594- file_count : int option; (** Number of files in this language *)
595595- name : string; (** Programming language name *)
596596- percentage : int; (** Percentage of total codebase (0-100) *)
597597- size : int; (** Total size of files in this language (bytes) *)
717717+type signature = {
718718+ email : string; (** Author email *)
719719+ name : string; (** Author name *)
720720+ when_ : string; (** Author timestamp *)
598721}
599722600600-(** Jsont codec for {!type:language}. *)
601601-val language_jsont : language Jsont.t
723723+(** Jsont codec for {!type:signature}. *)
724724+val signature_jsont : signature Jsont.t
602725603726604727(** Query/procedure parameters. *)
605728type params = {
606606- ref_ : string option; (** Git reference (branch, tag, or commit SHA) *)
729729+ name : string; (** Branch name to get information for *)
607730 repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
608731}
609732···612735613736614737type output = {
615615- languages : language list;
616616- ref_ : string; (** The git reference used *)
617617- total_files : int option; (** Total number of files analyzed *)
618618- total_size : int option; (** Total size of all analyzed files in bytes *)
738738+ author : signature option;
739739+ hash : string; (** Latest commit hash on this branch *)
740740+ is_default : bool option; (** Whether this is the default branch *)
741741+ message : string option; (** Latest commit message *)
742742+ name : string; (** Branch name *)
743743+ short_hash : string option; (** Short commit hash *)
744744+ when_ : string; (** Timestamp of latest commit *)
619745}
620746621747(** Jsont codec for {!type:output}. *)
622748val output_jsont : output Jsont.t
623749624750 end
625625- module Diff : sig
751751+ module Log : sig
626752627753(** Query/procedure parameters. *)
628754type params = {
755755+ cursor : string option; (** Pagination cursor (commit SHA) *)
756756+ limit : int option; (** Maximum number of commits to return *)
757757+ path : string option; (** Path to filter commits by *)
629758 ref_ : string; (** Git reference (branch, tag, or commit SHA) *)
630759 repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
631760}
···638767val output_jsont : output Jsont.t
639768640769 end
641641- module ForkSync : sig
642642-(** Sync a forked repository with its upstream source *)
643643-644644-645645-type input = {
646646- branch : string; (** Branch to sync *)
647647- did : string; (** DID of the fork owner *)
648648- name : string; (** Name of the forked repository *)
649649- source : string; (** AT-URI of the source repository *)
650650-}
651651-652652-(** Jsont codec for {!type:input}. *)
653653-val input_jsont : input Jsont.t
654654-655655- end
656656- module AddSecret : sig
657657-(** Add a CI secret *)
658658-659659-660660-type input = {
661661- key : string;
662662- repo : string;
663663- value : string;
664664-}
665665-666666-(** Jsont codec for {!type:input}. *)
667667-val input_jsont : input Jsont.t
668668-669669- end
670670- module Delete : sig
671671-(** Delete a repository *)
672672-673673-674674-type input = {
675675- did : string; (** DID of the repository owner *)
676676- name : string; (** Name of the repository to delete *)
677677- rkey : string; (** Rkey of the repository record *)
678678-}
679679-680680-(** Jsont codec for {!type:input}. *)
681681-val input_jsont : input Jsont.t
682682-683683- end
684770 module ListSecrets : sig
685771686772type secret = {
···711797val output_jsont : output Jsont.t
712798713799 end
714714- module Archive : sig
800800+ module Tags : sig
715801716802(** Query/procedure parameters. *)
717803type params = {
718718- format : string option; (** Archive format *)
719719- prefix : string option; (** Prefix for files in the archive *)
720720- ref_ : string; (** Git reference (branch, tag, or commit SHA) *)
804804+ cursor : string option; (** Pagination cursor *)
805805+ limit : int option; (** Maximum number of tags to return *)
721806 repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
722807}
723808724809(** Jsont codec for {!type:params}. *)
725810val params_jsont : params Jsont.t
726811727727-(** Binary archive data *)
728812729813type output = unit
730814val output_jsont : output Jsont.t
731815732816 end
733733- module HiddenRef : sig
734734-(** Create a hidden ref in a repository *)
817817+ module Diff : sig
735818736736-737737-type input = {
738738- fork_ref : string; (** Fork reference name *)
739739- remote_ref : string; (** Remote reference name *)
740740- repo : string; (** AT-URI of the repository *)
819819+(** Query/procedure parameters. *)
820820+type params = {
821821+ ref_ : string; (** Git reference (branch, tag, or commit SHA) *)
822822+ repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
741823}
742824743743-(** Jsont codec for {!type:input}. *)
744744-val input_jsont : input Jsont.t
825825+(** Jsont codec for {!type:params}. *)
826826+val params_jsont : params Jsont.t
745827746828747747-type output = {
748748- error : string option; (** Error message if creation failed *)
749749- ref_ : string option; (** The created hidden ref name *)
750750- success : bool; (** Whether the hidden ref was created successfully *)
751751-}
752752-753753-(** Jsont codec for {!type:output}. *)
829829+type output = unit
754830val output_jsont : output Jsont.t
755831756832 end
757757- module Pull : sig
758758-759759-type target = {
760760- branch : string;
761761- repo : string;
762762-}
763763-764764-(** Jsont codec for {!type:target}. *)
765765-val target_jsont : target Jsont.t
766766-767767-768768-type source = {
769769- branch : string;
770770- repo : string option;
771771- sha : string;
772772-}
773773-774774-(** Jsont codec for {!type:source}. *)
775775-val source_jsont : source Jsont.t
776776-833833+ module Collaborator : sig
777834778835type main = {
779779- body : string option;
780836 created_at : string;
781781- mentions : string list option;
782782- patch : string option; (** (deprecated) use patchBlob instead *)
783783- patch_blob : Atp.Blob_ref.t; (** patch content *)
784784- references : string list option;
785785- source : source option;
786786- target : target;
787787- title : string;
837837+ repo : string; (** repo to add this user to *)
838838+ subject : string;
788839}
789840790841(** Jsont codec for {!type:main}. *)
791842val main_jsont : main Jsont.t
792843793793- module Comment : sig
844844+ end
845845+ module Create : sig
846846+(** Create a new repository *)
794847795795-type main = {
796796- body : string;
797797- created_at : string;
798798- mentions : string list option;
799799- pull : string;
800800- references : string list option;
848848+849849+type input = {
850850+ default_branch : string option; (** Default branch to push to *)
851851+ rkey : string; (** Rkey of the repository record *)
852852+ source : string option; (** A source URL to clone from, populate this when forking or importing a repository. *)
801853}
802854803803-(** Jsont codec for {!type:main}. *)
804804-val main_jsont : main Jsont.t
855855+(** Jsont codec for {!type:input}. *)
856856+val input_jsont : input Jsont.t
805857806806- end
807807- module Status : sig
858858+ end
859859+ module Artifact : sig
808860809861type main = {
810810- pull : string;
811811- status : string; (** status of the pull request *)
862862+ artifact : Atp.Blob_ref.t; (** the artifact *)
863863+ created_at : string; (** time of creation of this artifact *)
864864+ name : string; (** name of the artifact *)
865865+ repo : string; (** repo that this artifact is being uploaded to *)
866866+ tag : string; (** hash of the tag object that this artifact is attached to (only annotated tags are supported) *)
812867}
813868814869(** Jsont codec for {!type:main}. *)
815870val main_jsont : main Jsont.t
816871817817- module Closed : sig
818818-(** closed pull request *)
819819-820820-type main = string
821821-val main_jsont : main Jsont.t
872872+ end
873873+ module Archive : sig
822874823823- end
824824- module Open : sig
825825-(** open pull request *)
875875+(** Query/procedure parameters. *)
876876+type params = {
877877+ format : string option; (** Archive format *)
878878+ prefix : string option; (** Prefix for files in the archive *)
879879+ ref_ : string; (** Git reference (branch, tag, or commit SHA) *)
880880+ repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
881881+}
826882827827-type main = string
828828-val main_jsont : main Jsont.t
883883+(** Jsont codec for {!type:params}. *)
884884+val params_jsont : params Jsont.t
829885830830- end
831831- module Merged : sig
832832-(** merged pull request *)
886886+(** Binary archive data *)
833887834834-type main = string
835835-val main_jsont : main Jsont.t
888888+type output = unit
889889+val output_jsont : output Jsont.t
836890837837- end
838838- end
839891 end
840840- module Issue : sig
892892+ module RemoveSecret : sig
893893+(** Remove a CI secret *)
841894842842-type main = {
843843- body : string option;
844844- created_at : string;
845845- mentions : string list option;
846846- references : string list option;
895895+896896+type input = {
897897+ key : string;
847898 repo : string;
848848- title : string;
849899}
850900851851-(** Jsont codec for {!type:main}. *)
852852-val main_jsont : main Jsont.t
853853-854854- module Comment : sig
855855-856856-type main = {
857857- body : string;
858858- created_at : string;
859859- issue : string;
860860- mentions : string list option;
861861- references : string list option;
862862- reply_to : string option;
863863-}
901901+(** Jsont codec for {!type:input}. *)
902902+val input_jsont : input Jsont.t
864903865865-(** Jsont codec for {!type:main}. *)
866866-val main_jsont : main Jsont.t
904904+ end
905905+ module Merge : sig
906906+(** Merge a patch into a repository branch *)
867907868868- end
869869- module State : sig
870908871871-type main = {
872872- issue : string;
873873- state : string; (** state of the issue *)
909909+type input = {
910910+ author_email : string option; (** Author email for the merge commit *)
911911+ author_name : string option; (** Author name for the merge commit *)
912912+ branch : string; (** Target branch to merge into *)
913913+ commit_body : string option; (** Additional commit message body *)
914914+ commit_message : string option; (** Merge commit message *)
915915+ did : string; (** DID of the repository owner *)
916916+ name : string; (** Name of the repository *)
917917+ patch : string; (** Patch content to merge *)
874918}
875919876876-(** Jsont codec for {!type:main}. *)
877877-val main_jsont : main Jsont.t
878878-879879- module Closed : sig
880880-(** closed issue *)
920920+(** Jsont codec for {!type:input}. *)
921921+val input_jsont : input Jsont.t
881922882882-type main = string
883883-val main_jsont : main Jsont.t
884884-885885- end
886886- module Open : sig
887887-(** open issue *)
888888-889889-type main = string
890890-val main_jsont : main Jsont.t
891891-892892- end
893893- end
894923 end
895895- end
896896- module Label : sig
897897- module Definition : sig
924924+ module Blob : sig
898925899899-type value_type = {
900900- enum : string list option; (** Closed set of values that this label can take. *)
901901- format : string; (** An optional constraint that can be applied on string concrete types. *)
902902- type_ : string; (** The concrete type of this label's value. *)
926926+type submodule = {
927927+ branch : string option; (** Branch to track in the submodule *)
928928+ name : string; (** Submodule name *)
929929+ url : string; (** Submodule repository URL *)
903930}
904931905905-(** Jsont codec for {!type:value_type}. *)
906906-val value_type_jsont : value_type Jsont.t
932932+(** Jsont codec for {!type:submodule}. *)
933933+val submodule_jsont : submodule Jsont.t
907934908935909909-type main = {
910910- color : string option; (** The hex value for the background color for the label. Appviews may choose to respect this. *)
911911- created_at : string;
912912- multiple : bool option; (** Whether this label can be repeated for a given entity, eg.: \[reviewer:foo, reviewer:bar\] *)
913913- name : string; (** The display name of this label. *)
914914- scope : string list; (** The areas of the repo this label may apply to, eg.: sh.tangled.repo.issue. Appviews may choose to respect this. *)
915915- value_type : value_type; (** The type definition of this label. Appviews may allow sorting for certain types. *)
936936+type signature = {
937937+ email : string; (** Author email *)
938938+ name : string; (** Author name *)
939939+ when_ : string; (** Author timestamp *)
916940}
917941918918-(** Jsont codec for {!type:main}. *)
919919-val main_jsont : main Jsont.t
942942+(** Jsont codec for {!type:signature}. *)
943943+val signature_jsont : signature Jsont.t
920944921921- end
922922- module Op : sig
923945924924-type operand = {
925925- key : string; (** ATURI to the label definition *)
926926- value : string; (** Stringified value of the label. This is first unstringed by appviews and then interpreted as a concrete value. *)
946946+type last_commit = {
947947+ author : signature option;
948948+ hash : string; (** Commit hash *)
949949+ message : string; (** Commit message *)
950950+ short_hash : string option; (** Short commit hash *)
951951+ when_ : string; (** Commit timestamp *)
927952}
928953929929-(** Jsont codec for {!type:operand}. *)
930930-val operand_jsont : operand Jsont.t
954954+(** Jsont codec for {!type:last_commit}. *)
955955+val last_commit_jsont : last_commit Jsont.t
931956932957933933-type main = {
934934- add : operand list;
935935- delete : operand list;
936936- performed_at : string;
937937- subject : string; (** The subject (task, pull or discussion) of this label. Appviews may apply a `scope` check and refuse this op. *)
958958+(** Query/procedure parameters. *)
959959+type params = {
960960+ path : string; (** Path to the file within the repository *)
961961+ raw : bool option; (** Return raw file content instead of JSON response *)
962962+ ref_ : string; (** Git reference (branch, tag, or commit SHA) *)
963963+ repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
938964}
939965940940-(** Jsont codec for {!type:main}. *)
941941-val main_jsont : main Jsont.t
966966+(** Jsont codec for {!type:params}. *)
967967+val params_jsont : params Jsont.t
942968943943- end
944944- end
945945- module Graph : sig
946946- module Follow : sig
947969948948-type main = {
949949- created_at : string;
950950- subject : string;
970970+type output = {
971971+ content : string option; (** File content (base64 encoded for binary files) *)
972972+ encoding : string option; (** Content encoding *)
973973+ is_binary : bool option; (** Whether the file is binary *)
974974+ last_commit : last_commit option;
975975+ mime_type : string option; (** MIME type of the file *)
976976+ path : string; (** The file path *)
977977+ ref_ : string; (** The git reference used *)
978978+ size : int option; (** File size in bytes *)
979979+ submodule : submodule option; (** Submodule information if path is a submodule *)
951980}
952981953953-(** Jsont codec for {!type:main}. *)
954954-val main_jsont : main Jsont.t
982982+(** Jsont codec for {!type:output}. *)
983983+val output_jsont : output Jsont.t
955984956985 end
957957- end
958958- module Knot : sig
959959-960960-type main = {
961961- created_at : string;
962962-}
963963-964964-(** Jsont codec for {!type:main}. *)
965965-val main_jsont : main Jsont.t
986986+ module HiddenRef : sig
987987+(** Create a hidden ref in a repository *)
966988967967- module Member : sig
968989969969-type main = {
970970- created_at : string;
971971- domain : string; (** domain that this member now belongs to *)
972972- subject : string;
990990+type input = {
991991+ fork_ref : string; (** Fork reference name *)
992992+ remote_ref : string; (** Remote reference name *)
993993+ repo : string; (** AT-URI of the repository *)
973994}
974995975975-(** Jsont codec for {!type:main}. *)
976976-val main_jsont : main Jsont.t
996996+(** Jsont codec for {!type:input}. *)
997997+val input_jsont : input Jsont.t
977998978978- end
979979- module ListKeys : sig
980999981981-type public_key = {
982982- created_at : string; (** Key upload timestamp *)
983983- did : string; (** DID associated with the public key *)
984984- key : string; (** Public key contents *)
10001000+type output = {
10011001+ error : string option; (** Error message if creation failed *)
10021002+ ref_ : string option; (** The created hidden ref name *)
10031003+ success : bool; (** Whether the hidden ref was created successfully *)
9851004}
9861005987987-(** Jsont codec for {!type:public_key}. *)
988988-val public_key_jsont : public_key Jsont.t
10061006+(** Jsont codec for {!type:output}. *)
10071007+val output_jsont : output Jsont.t
9891008990990-(** List all public keys stored in the knot server *)
10091009+ end
10101010+ module Compare : sig
99110119921012(** Query/procedure parameters. *)
9931013type params = {
994994- cursor : string option; (** Pagination cursor *)
995995- limit : int option; (** Maximum number of keys to return *)
10141014+ repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
10151015+ rev1 : string; (** First revision (commit, branch, or tag) *)
10161016+ rev2 : string; (** Second revision (commit, branch, or tag) *)
9961017}
99710189981019(** Jsont codec for {!type:params}. *)
9991020val params_jsont : params Jsont.t
1000102110011001-10021002-type output = {
10031003- cursor : string option; (** Pagination cursor for next page *)
10041004- keys : public_key list;
10051005-}
10221022+(** Compare output in application/json *)
1006102310071007-(** Jsont codec for {!type:output}. *)
10241024+type output = unit
10081025val output_jsont : output Jsont.t
1009102610101027 end
10111011- module Version : sig
10121012-(** Get the version of a knot *)
10281028+ module ForkStatus : sig
10291029+(** Check fork status relative to upstream source *)
101310301014103110151015-type output = {
10161016- version : string;
10321032+type input = {
10331033+ branch : string; (** Branch to check status for *)
10341034+ did : string; (** DID of the fork owner *)
10351035+ hidden_ref : string; (** Hidden ref to use for comparison *)
10361036+ name : string; (** Name of the forked repository *)
10371037+ source : string; (** Source repository URL *)
10171038}
1018103910191019-(** Jsont codec for {!type:output}. *)
10201020-val output_jsont : output Jsont.t
10211021-10221022- end
10231023- end
10241024- module Owner : sig
10251025-(** Get the owner of a service *)
10401040+(** Jsont codec for {!type:input}. *)
10411041+val input_jsont : input Jsont.t
102610421027104310281044type output = {
10291029- owner : string;
10451045+ status : int; (** Fork status: 0=UpToDate, 1=FastForwardable, 2=Conflict, 3=MissingBranch *)
10301046}
1031104710321048(** Jsont codec for {!type:output}. *)
10331049val output_jsont : output Jsont.t
1034105010351035- end
10361036- module PublicKey : sig
10511051+ end
10521052+ module MergeCheck : sig
1037105310381038-type main = {
10391039- created_at : string; (** key upload timestamp *)
10401040- key : string; (** public key contents *)
10411041- name : string; (** human-readable name for this key *)
10541054+type conflict_info = {
10551055+ filename : string; (** Name of the conflicted file *)
10561056+ reason : string; (** Reason for the conflict *)
10421057}
1043105810441044-(** Jsont codec for {!type:main}. *)
10451045-val main_jsont : main Jsont.t
10461046-10471047- end
10481048- module Pipeline : sig
10491049-10501050-type trigger_repo = {
10511051- default_branch : string;
10521052- did : string;
10531053- knot : string;
10541054- repo : string;
10551055-}
10591059+(** Jsont codec for {!type:conflict_info}. *)
10601060+val conflict_info_jsont : conflict_info Jsont.t
1056106110571057-(** Jsont codec for {!type:trigger_repo}. *)
10581058-val trigger_repo_jsont : trigger_repo Jsont.t
10621062+(** Check if a merge is possible between two branches *)
105910631060106410611061-type push_trigger_data = {
10621062- new_sha : string;
10631063- old_sha : string;
10641064- ref_ : string;
10651065+type input = {
10661066+ branch : string; (** Target branch to merge into *)
10671067+ did : string; (** DID of the repository owner *)
10681068+ name : string; (** Name of the repository *)
10691069+ patch : string; (** Patch or pull request to check for merge conflicts *)
10651070}
1066107110671067-(** Jsont codec for {!type:push_trigger_data}. *)
10681068-val push_trigger_data_jsont : push_trigger_data Jsont.t
10721072+(** Jsont codec for {!type:input}. *)
10731073+val input_jsont : input Jsont.t
106910741070107510711071-type pull_request_trigger_data = {
10721072- action : string;
10731073- source_branch : string;
10741074- source_sha : string;
10751075- target_branch : string;
10761076+type output = {
10771077+ conflicts : conflict_info list option; (** List of files with merge conflicts *)
10781078+ error : string option; (** Error message if check failed *)
10791079+ is_conflicted : bool; (** Whether the merge has conflicts *)
10801080+ message : string option; (** Additional message about the merge check *)
10761081}
1077108210781078-(** Jsont codec for {!type:pull_request_trigger_data}. *)
10791079-val pull_request_trigger_data_jsont : pull_request_trigger_data Jsont.t
10831083+(** Jsont codec for {!type:output}. *)
10841084+val output_jsont : output Jsont.t
1080108510861086+ end
10871087+ module Branches : sig
1081108810821082-type pair = {
10831083- key : string;
10841084- value : string;
10891089+(** Query/procedure parameters. *)
10901090+type params = {
10911091+ cursor : string option; (** Pagination cursor *)
10921092+ limit : int option; (** Maximum number of branches to return *)
10931093+ repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
10851094}
1086109510871087-(** Jsont codec for {!type:pair}. *)
10881088-val pair_jsont : pair Jsont.t
10961096+(** Jsont codec for {!type:params}. *)
10971097+val params_jsont : params Jsont.t
108910981090109910911091-type clone_opts = {
10921092- depth : int;
10931093- skip : bool;
10941094- submodules : bool;
10951095-}
11001100+type output = unit
11011101+val output_jsont : output Jsont.t
1096110210971097-(** Jsont codec for {!type:clone_opts}. *)
10981098-val clone_opts_jsont : clone_opts Jsont.t
11031103+ end
11041104+ module ForkSync : sig
11051105+(** Sync a forked repository with its upstream source *)
109911061100110711011101-type workflow = {
11021102- clone : clone_opts;
11031103- engine : string;
11041104- name : string;
11051105- raw : string;
11081108+type input = {
11091109+ branch : string; (** Branch to sync *)
11101110+ did : string; (** DID of the fork owner *)
11111111+ name : string; (** Name of the forked repository *)
11121112+ source : string; (** AT-URI of the source repository *)
11061113}
1107111411081108-(** Jsont codec for {!type:workflow}. *)
11091109-val workflow_jsont : workflow Jsont.t
11151115+(** Jsont codec for {!type:input}. *)
11161116+val input_jsont : input Jsont.t
1110111711181118+ end
11191119+ module Languages : sig
1111112011121112-type manual_trigger_data = {
11131113- inputs : pair list option;
11211121+type language = {
11221122+ color : string option; (** Hex color code for this language *)
11231123+ extensions : string list option; (** File extensions associated with this language *)
11241124+ file_count : int option; (** Number of files in this language *)
11251125+ name : string; (** Programming language name *)
11261126+ percentage : int; (** Percentage of total codebase (0-100) *)
11271127+ size : int; (** Total size of files in this language (bytes) *)
11141128}
1115112911161116-(** Jsont codec for {!type:manual_trigger_data}. *)
11171117-val manual_trigger_data_jsont : manual_trigger_data Jsont.t
11301130+(** Jsont codec for {!type:language}. *)
11311131+val language_jsont : language Jsont.t
111811321119113311201120-type trigger_metadata = {
11211121- kind : string;
11221122- manual : manual_trigger_data option;
11231123- pull_request : pull_request_trigger_data option;
11241124- push : push_trigger_data option;
11251125- repo : trigger_repo;
11341134+(** Query/procedure parameters. *)
11351135+type params = {
11361136+ ref_ : string option; (** Git reference (branch, tag, or commit SHA) *)
11371137+ repo : string; (** Repository identifier in format 'did:plc:.../repoName' *)
11261138}
1127113911281128-(** Jsont codec for {!type:trigger_metadata}. *)
11291129-val trigger_metadata_jsont : trigger_metadata Jsont.t
11401140+(** Jsont codec for {!type:params}. *)
11411141+val params_jsont : params Jsont.t
113011421131114311321132-type main = {
11331133- trigger_metadata : trigger_metadata;
11341134- workflows : workflow list;
11441144+type output = {
11451145+ languages : language list;
11461146+ ref_ : string; (** The git reference used *)
11471147+ total_files : int option; (** Total number of files analyzed *)
11481148+ total_size : int option; (** Total size of all analyzed files in bytes *)
11351149}
1136115011371137-(** Jsont codec for {!type:main}. *)
11381138-val main_jsont : main Jsont.t
11391139-11401140- module Status : sig
11411141-11421142-type main = {
11431143- created_at : string; (** time of creation of this status update *)
11441144- error : string option; (** error message if failed *)
11451145- exit_code : int option; (** exit code if failed *)
11461146- pipeline : string; (** ATURI of the pipeline *)
11471147- status : string; (** status of the workflow *)
11481148- workflow : string; (** name of the workflow within this pipeline *)
11491149-}
11501150-11511151-(** Jsont codec for {!type:main}. *)
11521152-val main_jsont : main Jsont.t
11511151+(** Jsont codec for {!type:output}. *)
11521152+val output_jsont : output Jsont.t
1153115311541154 end
11551155 end
+669
ocaml-imap/IMPLEMENTATION-PLAN.md
···11+# Comprehensive IMAP Implementation Plan
22+33+This document consolidates all RFC implementation plans from `spec/` and `lib/imap/PLAN.md` into a single, prioritized implementation roadmap. Per the design goals, we favor OCaml variants over strings and do not require backwards compatibility.
44+55+## Executive Summary
66+77+The ocaml-imap library implements IMAP4rev2 (RFC 9051) with several extensions. This plan covers:
88+- **P0**: Critical fixes and core infrastructure
99+- **P1**: Core protocol compliance
1010+- **P2**: Extension support (SORT/THREAD, QUOTA, etc.)
1111+- **P3**: Advanced features (UTF-8, CONDSTORE/QRESYNC)
1212+- **P4**: Polish (documentation, unified flag library)
1313+1414+---
1515+1616+## Phase 0: Critical Fixes (P0)
1717+1818+These are blocking issues that need immediate attention.
1919+2020+### 0.1 Fix SEARCH Response Parsing (Client Library)
2121+2222+**Source**: `lib/imap/PLAN.md` - P0 Broken Functionality
2323+2424+**Problem**: `search` function always returns empty list - response is never parsed.
2525+2626+**Files**:
2727+- `lib/imap/read.ml` - Add SEARCH response parsing
2828+- `lib/imap/client.ml:536-544` - Fix to read response
2929+3030+**Implementation**:
3131+```ocaml
3232+(* In read.ml - add case for SEARCH response *)
3333+| "SEARCH" ->
3434+ let rec parse_numbers acc =
3535+ match R.peek_char r with
3636+ | Some ' ' -> sp r; parse_numbers (number r :: acc)
3737+ | Some c when c >= '0' && c <= '9' -> parse_numbers (number r :: acc)
3838+ | _ -> List.rev acc
3939+ in
4040+ let nums = parse_numbers [] in
4141+ crlf r;
4242+ Response.Search nums
4343+```
4444+4545+**Tests** (`test/test_read.ml`):
4646+```ocaml
4747+let test_search_response () =
4848+ let resp = parse "* SEARCH 2 4 7 11\r\n" in
4949+ Alcotest.(check (list int)) "search" [2; 4; 7; 11]
5050+ (match resp with Response.Search nums -> nums | _ -> [])
5151+5252+let test_search_empty () =
5353+ let resp = parse "* SEARCH\r\n" in
5454+ Alcotest.(check (list int)) "empty search" []
5555+ (match resp with Response.Search nums -> nums | _ -> [-1])
5656+```
5757+5858+### 0.2 Parse BODY/BODYSTRUCTURE Responses
5959+6060+**Source**: `lib/imap/PLAN.md` - P1 Incomplete Core Features
6161+6262+**Problem**: FETCH responses with BODY/BODYSTRUCTURE fall back to empty flags.
6363+6464+**Files**:
6565+- `lib/imap/read.ml:284-302` - Add BODY/BODYSTRUCTURE parsing
6666+- `lib/imap/body.ml` - Body structure types (may need new file)
6767+6868+**Implementation**: Parse nested multipart MIME structures recursively.
6969+7070+**Tests**:
7171+```ocaml
7272+let test_body_structure () =
7373+ let resp = parse {|* 1 FETCH (BODYSTRUCTURE ("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "7BIT" 1234 56))|} in
7474+ (* verify body structure parsed correctly *)
7575+```
7676+7777+### 0.3 Parse BODY[section] Literal Responses
7878+7979+**Source**: `lib/imap/PLAN.md` - P1
8080+8181+**Problem**: Cannot read actual message content from FETCH.
8282+8383+**Implementation**: Parse section specifiers and literal data:
8484+```ocaml
8585+(* Patterns: BODY[HEADER], BODY[TEXT], BODY[1.2.MIME], BODY[section]<origin> *)
8686+```
8787+8888+---
8989+9090+## Phase 1: Core Protocol Compliance (P1)
9191+9292+### 1.1 Complete ESEARCH Support (RFC 4731)
9393+9494+**Source**: `spec/PLAN-rfc4731.md`
9595+9696+**Current State**: Response type exists, parsing not implemented.
9797+9898+**Tasks**:
9999+1. Add ESEARCH response parsing to `lib/imap/read.ml`
100100+2. Add search return options to Command type
101101+3. Add serialization for `RETURN (MIN MAX COUNT ALL)`
102102+4. Add client API functions
103103+104104+**Types** (use variants, no strings):
105105+```ocaml
106106+type search_return_opt =
107107+ | Return_min
108108+ | Return_max
109109+ | Return_all
110110+ | Return_count
111111+112112+type esearch_result =
113113+ | Esearch_min of int
114114+ | Esearch_max of int
115115+ | Esearch_count of int
116116+ | Esearch_all of Seq.t
117117+```
118118+119119+**Tests**:
120120+```ocaml
121121+let test_esearch_parsing () =
122122+ let resp = parse "* ESEARCH (TAG \"A282\") MIN 2 COUNT 3\r\n" in
123123+ assert (resp = Response.Esearch {
124124+ tag = Some "A282";
125125+ uid = false;
126126+ results = [Esearch_min 2; Esearch_count 3]
127127+ })
128128+```
129129+130130+### 1.2 Parse APPENDUID/COPYUID Response Codes
131131+132132+**Source**: `lib/imap/PLAN.md` - P1
133133+134134+**Files**: `lib/imap/read.ml:169-228`
135135+136136+**Implementation**:
137137+```ocaml
138138+(* Add to response_code parsing *)
139139+| "APPENDUID" ->
140140+ sp r;
141141+ let uidvalidity = number32 r in
142142+ sp r;
143143+ let uid = number32 r in
144144+ Code.Appenduid (uidvalidity, uid)
145145+| "COPYUID" ->
146146+ sp r;
147147+ let uidvalidity = number32 r in
148148+ sp r;
149149+ let source_uids = parse_uid_set r in
150150+ sp r;
151151+ let dest_uids = parse_uid_set r in
152152+ Code.Copyuid (uidvalidity, source_uids, dest_uids)
153153+```
154154+155155+### 1.3 UNSELECT Capability Advertisement
156156+157157+**Source**: `spec/PLAN-rfc3691.md`
158158+159159+**Status**: Fully implemented except capability not advertised.
160160+161161+**Fix** (`lib/imapd/server.ml`):
162162+```ocaml
163163+let base_capabilities_pre_tls = [
164164+ (* existing *)
165165+ "UNSELECT"; (* RFC 3691 - already implemented *)
166166+]
167167+```
168168+169169+### 1.4 SPECIAL-USE Support (RFC 6154)
170170+171171+**Source**: `spec/PLAN-rfc6154.md`
172172+173173+**Current**: Types exist, capability not advertised, flags not returned.
174174+175175+**Tasks**:
176176+1. Add `SPECIAL-USE` to capabilities
177177+2. Return special-use flags in LIST responses
178178+3. Map standard mailbox names to attributes
179179+180180+**Types** (already exist, ensure completeness):
181181+```ocaml
182182+type special_use =
183183+ | All | Archive | Drafts | Flagged | Important
184184+ | Junk | Sent | Trash
185185+ | Snoozed | Scheduled | Memos (* draft-ietf-mailmaint *)
186186+```
187187+188188+**Tests**:
189189+```ocaml
190190+let test_list_special_use () =
191191+ (* LIST "" "*" should return \Drafts on Drafts mailbox *)
192192+```
193193+194194+---
195195+196196+## Phase 2: Extension Support (P2)
197197+198198+### 2.1 SORT/THREAD Extension (RFC 5256)
199199+200200+**Source**: `spec/PLAN-rfc5256.md`
201201+202202+**Scope**: Large feature - server-side sorting and threading.
203203+204204+#### 2.1.1 Thread Module Types
205205+206206+**New file**: `lib/imap/thread.ml`
207207+208208+```ocaml
209209+type algorithm =
210210+ | Orderedsubject (** Group by subject, sort by date *)
211211+ | References (** Full JWZ threading algorithm *)
212212+ | Extension of string
213213+214214+type 'a node =
215215+ | Message of 'a * 'a node list
216216+ | Dummy of 'a node list
217217+218218+type 'a t = 'a node list
219219+```
220220+221221+#### 2.1.2 Base Subject Extraction
222222+223223+**New file**: `lib/imap/subject.ml`
224224+225225+Implements RFC 5256 Section 2.1 algorithm:
226226+1. Decode RFC 2047 encoded-words
227227+2. Remove `Re:`, `Fw:`, `Fwd:` prefixes
228228+3. Remove `[blob]` prefixes
229229+4. Remove `(fwd)` trailers
230230+5. Unwrap `[fwd: ...]` wrappers
231231+232232+```ocaml
233233+val base_subject : string -> string
234234+val is_reply_or_forward : string -> bool
235235+```
236236+237237+#### 2.1.3 Sent Date Handling
238238+239239+**New file**: `lib/imap/date.ml`
240240+241241+```ocaml
242242+type t
243243+244244+val of_header : string -> t option
245245+val of_internaldate : string -> t
246246+val sent_date : date_header:string option -> internaldate:string -> t
247247+val compare : t -> t -> int
248248+```
249249+250250+#### 2.1.4 Server-Side SORT Handler
251251+252252+**File**: `lib/imapd/server.ml`
253253+254254+1. Implement sort key extraction
255255+2. Implement comparison by criteria
256256+3. Return SORT response
257257+258258+#### 2.1.5 Threading Algorithms
259259+260260+**New file**: `lib/imapd/thread.ml`
261261+262262+1. `orderedsubject` - simple subject-based grouping
263263+2. `references` - full JWZ algorithm (6 steps)
264264+265265+**Tests**:
266266+```ocaml
267267+let test_base_subject () =
268268+ assert (Subject.base_subject "Re: test" = "test");
269269+ assert (Subject.base_subject "Re: Re: test" = "test");
270270+ assert (Subject.base_subject "[PATCH] Re: [ocaml] test" = "test");
271271+ assert (Subject.base_subject "[fwd: wrapped]" = "wrapped")
272272+273273+let test_orderedsubject () =
274274+ (* Test grouping by subject *)
275275+276276+let test_references_threading () =
277277+ (* Test parent/child relationships *)
278278+```
279279+280280+### 2.2 QUOTA Extension (RFC 9208)
281281+282282+**Source**: `spec/PLAN-rfc9208.md`
283283+284284+#### 2.2.1 Protocol Types
285285+286286+**File**: `lib/imapd/protocol.ml`
287287+288288+```ocaml
289289+type quota_resource =
290290+ | Quota_storage (** KB of storage *)
291291+ | Quota_message (** Number of messages *)
292292+ | Quota_mailbox (** Number of mailboxes *)
293293+ | Quota_annotation_storage
294294+295295+type quota_resource_info = {
296296+ resource : quota_resource;
297297+ usage : int64;
298298+ limit : int64;
299299+}
300300+301301+(* Commands *)
302302+| Getquota of string
303303+| Getquotaroot of mailbox_name
304304+| Setquota of { root : string; limits : (quota_resource * int64) list }
305305+306306+(* Responses *)
307307+| Quota_response of { root : string; resources : quota_resource_info list }
308308+| Quotaroot_response of { mailbox : mailbox_name; roots : string list }
309309+```
310310+311311+#### 2.2.2 Storage Backend Interface
312312+313313+**File**: `lib/imapd/storage.mli`
314314+315315+```ocaml
316316+val get_quota_roots : t -> username:string -> mailbox_name -> string list
317317+val get_quota : t -> username:string -> string -> (quota_resource_info list, error) result
318318+val set_quota : t -> username:string -> string -> (quota_resource * int64) list -> (quota_resource_info list, error) result
319319+val check_quota : t -> username:string -> mailbox_name -> additional_size:int64 -> bool
320320+```
321321+322322+#### 2.2.3 Server Handlers
323323+324324+Implement `handle_getquota`, `handle_getquotaroot`, `handle_setquota`.
325325+326326+Add quota checks to APPEND/COPY/MOVE:
327327+```ocaml
328328+if not (Storage.check_quota ...) then
329329+ send_response flow (No { code = Some Code_overquota; ... })
330330+```
331331+332332+**Tests**:
333333+```ocaml
334334+let test_getquotaroot () =
335335+ (* GETQUOTAROOT INBOX returns quota info *)
336336+337337+let test_quota_exceeded () =
338338+ (* APPEND fails with OVERQUOTA when over limit *)
339339+```
340340+341341+### 2.3 LIST-EXTENDED (RFC 5258)
342342+343343+**Source**: `spec/PLAN-rfc5258.md`
344344+345345+**Types**:
346346+```ocaml
347347+type list_select_option =
348348+ | List_select_subscribed
349349+ | List_select_remote
350350+ | List_select_recursivematch
351351+ | List_select_special_use (* RFC 6154 *)
352352+353353+type list_return_option =
354354+ | List_return_subscribed
355355+ | List_return_children
356356+ | List_return_special_use
357357+358358+type list_extended_item =
359359+ | Childinfo of string list
360360+361361+type list_command =
362362+ | List_basic of { reference : string; pattern : string }
363363+ | List_extended of {
364364+ selection : list_select_option list;
365365+ reference : string;
366366+ patterns : string list;
367367+ return_opts : list_return_option list;
368368+ }
369369+```
370370+371371+**Tasks**:
372372+1. Update grammar for extended LIST syntax
373373+2. Add `\NonExistent` and `\Remote` attributes
374374+3. Implement subscription tracking in storage
375375+4. Handle RECURSIVEMATCH with CHILDINFO
376376+5. Add `LIST-EXTENDED` capability
377377+378378+---
379379+380380+## Phase 3: Advanced Features (P3)
381381+382382+### 3.1 UTF-8 Support (RFC 6855)
383383+384384+**Source**: `spec/PLAN-rfc6855.md`
385385+386386+#### 3.1.1 Session State Tracking
387387+388388+```ocaml
389389+type session_state = {
390390+ utf8_enabled : bool;
391391+ (* ... *)
392392+}
393393+```
394394+395395+#### 3.1.2 UTF-8 Validation
396396+397397+**New file**: `lib/imapd/utf8.ml`
398398+399399+```ocaml
400400+val is_valid_utf8 : string -> bool
401401+val has_non_ascii : string -> bool
402402+val is_valid_utf8_mailbox_name : string -> bool
403403+```
404404+405405+#### 3.1.3 ENABLE Handler Update
406406+407407+Track UTF8=ACCEPT state, reject SEARCH with CHARSET after enable.
408408+409409+#### 3.1.4 UTF8 APPEND Extension
410410+411411+Parse `UTF8 (literal)` syntax for 8-bit headers.
412412+413413+**Tests**:
414414+```ocaml
415415+let test_utf8_validation () =
416416+ assert (Utf8.is_valid_utf8 "Hello");
417417+ assert (Utf8.is_valid_utf8 "\xe4\xb8\xad\xe6\x96\x87");
418418+ assert (not (Utf8.is_valid_utf8 "\xff\xfe"))
419419+```
420420+421421+### 3.2 CONDSTORE/QRESYNC (RFC 7162)
422422+423423+**Source**: `lib/imap/PLAN.md` - P2, `PLAN.md` - Phase 2.2
424424+425425+#### 3.2.1 CONDSTORE Types
426426+427427+```ocaml
428428+(* Fetch items *)
429429+| Modseq
430430+| Item_modseq of int64
431431+432432+(* Response codes *)
433433+| Highestmodseq of int64
434434+| Nomodseq
435435+| Modified of Seq.t
436436+437437+(* Command modifiers *)
438438+type fetch_modifier = { changedsince : int64 option }
439439+type store_modifier = { unchangedsince : int64 option }
440440+```
441441+442442+#### 3.2.2 Storage Backend
443443+444444+Add `modseq` to message type and mailbox state:
445445+```ocaml
446446+type message = {
447447+ (* existing *)
448448+ modseq : int64;
449449+}
450450+451451+type mailbox_state = {
452452+ (* existing *)
453453+ highestmodseq : int64;
454454+}
455455+```
456456+457457+#### 3.2.3 QRESYNC
458458+459459+```ocaml
460460+type qresync_params = {
461461+ uidvalidity : int32;
462462+ modseq : int64;
463463+ known_uids : Seq.t option;
464464+ seq_match : (Seq.t * Seq.t) option;
465465+}
466466+467467+(* Response *)
468468+| Vanished of { earlier : bool; uids : Seq.t }
469469+```
470470+471471+---
472472+473473+## Phase 4: Polish and Infrastructure (P4)
474474+475475+### 4.1 RFC 5530 Response Code Documentation
476476+477477+**Source**: `spec/PLAN-rfc5530.md`
478478+479479+All 16 response codes already implemented. Add OCamldoc citations.
480480+481481+### 4.2 Unified Mail Flag Library
482482+483483+**Source**: `spec/PLAN-unified-mail-flag.md`
484484+485485+Create shared `mail-flag` library for IMAP/JMAP:
486486+487487+```
488488+mail-flag/
489489+├── keyword.ml # Message keywords (typed variants)
490490+├── system_flag.ml # IMAP \Seen, \Deleted, etc.
491491+├── mailbox_attr.ml # Mailbox attributes/roles
492492+├── flag_color.ml # Apple Mail flag colors
493493+├── imap_wire.ml # IMAP serialization
494494+└── jmap_wire.ml # JMAP serialization
495495+```
496496+497497+### 4.3 Infrastructure Improvements
498498+499499+**Source**: `PLAN.md` - Phase 1
500500+501501+1. **Replace Menhir with Eio.Buf_read** - Pure functional parser
502502+2. **Integrate conpool** - Connection pooling for client
503503+3. **Add bytesrw streaming** - Large message handling
504504+4. **Fuzz testing** - Parser robustness with Crowbar
505505+5. **Eio mock testing** - Deterministic tests
506506+507507+---
508508+509509+## Testing Strategy
510510+511511+### Unit Tests
512512+513513+Each module should have corresponding tests in `test/`:
514514+515515+| Module | Test File | Coverage |
516516+|--------|-----------|----------|
517517+| `lib/imap/read.ml` | `test/test_read.ml` | Response parsing |
518518+| `lib/imap/write.ml` | `test/test_write.ml` | Command serialization |
519519+| `lib/imap/subject.ml` | `test/test_subject.ml` | Base subject extraction |
520520+| `lib/imap/thread.ml` | `test/test_thread.ml` | Threading algorithms |
521521+| `lib/imapd/server.ml` | `test/test_server.ml` | Command handlers |
522522+| `lib/imapd/storage.ml` | `test/test_storage.ml` | Storage backends |
523523+524524+### Integration Tests
525525+526526+**File**: `test/integration/`
527527+528528+- Protocol compliance testing against real servers
529529+- ImapTest compatibility suite
530530+- Dovecot interoperability
531531+532532+### Fuzz Tests
533533+534534+**File**: `test/fuzz_parser.ml`
535535+536536+```ocaml
537537+let fuzz_command_parser =
538538+ Crowbar.(map [bytes] (fun input ->
539539+ try
540540+ ignore (Imap_parser.parse_command input);
541541+ true
542542+ with _ -> true (* Parser should never crash *)
543543+ ))
544544+```
545545+546546+---
547547+548548+## Implementation Order
549549+550550+### Sprint 1: P0 Critical Fixes
551551+1. [ ] Fix SEARCH response parsing
552552+2. [ ] Parse BODY/BODYSTRUCTURE responses
553553+3. [ ] Parse BODY[section] literals
554554+555555+### Sprint 2: P1 Core Compliance
556556+4. [ ] Complete ESEARCH support
557557+5. [ ] Parse APPENDUID/COPYUID response codes
558558+6. [ ] Add UNSELECT to capabilities
559559+7. [ ] Complete SPECIAL-USE support
560560+561561+### Sprint 3: P2 SORT/THREAD
562562+8. [ ] Thread module types
563563+9. [ ] Base subject extraction
564564+10. [ ] Sent date handling
565565+11. [ ] ORDEREDSUBJECT algorithm
566566+12. [ ] REFERENCES algorithm
567567+13. [ ] Server SORT/THREAD handlers
568568+569569+### Sprint 4: P2 QUOTA
570570+14. [ ] Quota protocol types
571571+15. [ ] Storage backend interface
572572+16. [ ] Memory storage quota
573573+17. [ ] Maildir storage quota
574574+18. [ ] Server handlers
575575+576576+### Sprint 5: P2 LIST-EXTENDED
577577+19. [ ] Extended LIST grammar
578578+20. [ ] New attributes
579579+21. [ ] Subscription tracking
580580+22. [ ] RECURSIVEMATCH support
581581+582582+### Sprint 6: P3 UTF-8 & CONDSTORE
583583+23. [ ] UTF-8 session state
584584+24. [ ] UTF-8 validation
585585+25. [ ] UTF8 APPEND extension
586586+26. [ ] CONDSTORE types
587587+27. [ ] CONDSTORE handlers
588588+28. [ ] QRESYNC support
589589+590590+### Sprint 7: P4 Polish
591591+29. [ ] Response code documentation
592592+30. [ ] Unified mail flag library
593593+31. [ ] Infrastructure improvements
594594+32. [ ] Comprehensive test suite
595595+596596+---
597597+598598+## File Modification Summary
599599+600600+### New Files
601601+602602+| File | Purpose |
603603+|------|---------|
604604+| `lib/imap/thread.ml` | Thread types and parsing |
605605+| `lib/imap/subject.ml` | Base subject extraction |
606606+| `lib/imap/date.ml` | Sent date handling |
607607+| `lib/imap/collation.ml` | Unicode collation |
608608+| `lib/imap/mime.ml` | RFC 2047 decoding |
609609+| `lib/imapd/thread.ml` | Threading algorithms |
610610+| `lib/imapd/utf8.ml` | UTF-8 validation |
611611+| `test/test_subject.ml` | Subject tests |
612612+| `test/test_thread.ml` | Threading tests |
613613+| `test/test_quota.ml` | Quota tests |
614614+| `test/fuzz_parser.ml` | Fuzz tests |
615615+616616+### Modified Files
617617+618618+| File | Changes |
619619+|------|---------|
620620+| `lib/imap/read.ml` | SEARCH, ESEARCH, BODY parsing |
621621+| `lib/imap/write.ml` | ESEARCH, THREAD serialization |
622622+| `lib/imap/command.ml` | Return options, THREAD command |
623623+| `lib/imap/response.ml` | ESEARCH, THREAD responses |
624624+| `lib/imap/client.ml` | Fix search, add esearch/thread |
625625+| `lib/imap/code.ml` | OCamldoc citations |
626626+| `lib/imap/list_attr.ml` | Add NonExistent, Remote |
627627+| `lib/imapd/protocol.ml` | Quota types, LIST-EXTENDED |
628628+| `lib/imapd/server.ml` | Handlers, capabilities |
629629+| `lib/imapd/storage.ml` | Quota ops, subscription tracking |
630630+| `lib/imapd/grammar.mly` | Extended LIST, QUOTA, UTF8 |
631631+| `lib/imapd/lexer.mll` | New tokens |
632632+| `lib/imapd/parser.ml` | Response serialization |
633633+634634+---
635635+636636+## Design Principles
637637+638638+1. **Favor OCaml variants** - Use typed variants over strings where possible
639639+2. **No backwards compatibility** - Clean API without legacy shims
640640+3. **RFC citations** - OCamldoc links to RFC sections
641641+4. **Incremental** - Each task is independently useful
642642+5. **Test-driven** - Tests accompany each feature
643643+6. **Eio-native** - Use Eio patterns throughout
644644+645645+---
646646+647647+## References
648648+649649+### Implemented RFCs
650650+- RFC 9051 - IMAP4rev2 (core)
651651+- RFC 8314 - Implicit TLS
652652+- RFC 2177 - IDLE
653653+- RFC 2342 - NAMESPACE
654654+- RFC 2971 - ID
655655+- RFC 4315 - UIDPLUS
656656+- RFC 5161 - ENABLE
657657+- RFC 6851 - MOVE
658658+- RFC 7888 - LITERAL+
659659+660660+### RFCs in This Plan
661661+- RFC 3691 - UNSELECT (partially complete)
662662+- RFC 4731 - ESEARCH
663663+- RFC 5256 - SORT/THREAD
664664+- RFC 5258 - LIST-EXTENDED
665665+- RFC 5530 - Response Codes (types complete)
666666+- RFC 6154 - SPECIAL-USE (partially complete)
667667+- RFC 6855 - UTF-8 Support
668668+- RFC 7162 - CONDSTORE/QRESYNC
669669+- RFC 9208 - QUOTA
+48-2
ocaml-imap/lib/imap/client.ml
···570570571571let search t ?charset criteria =
572572 require_selected t;
573573- let tag = send_command t (Command.Search { charset; criteria }) in
573573+ let tag = send_command t (Command.Search { charset; criteria; return_opts = None }) in
574574 let untagged, final = receive_responses t tag in
575575 check_ok tag untagged final;
576576 (* Extract search results from untagged responses *)
···582582583583let uid_search t ?charset criteria =
584584 require_selected t;
585585- let tag = send_command t (Command.Uid (Uid_search { charset; criteria })) in
585585+ let tag = send_command t (Command.Uid (Uid_search { charset; criteria; return_opts = None })) in
586586 let untagged, final = receive_responses t tag in
587587 check_ok tag untagged final;
588588 (* Extract UID search results from untagged responses - UIDs are returned as int64 *)
···685685 (function Response.Enabled exts -> enabled := exts | _ -> ())
686686 untagged;
687687 !enabled
688688+689689+(** {1 ESEARCH Support (RFC 4731)} *)
690690+691691+type esearch_result = {
692692+ min : int option;
693693+ max : int option;
694694+ count : int option;
695695+ all : Seq.t option;
696696+}
697697+698698+let empty_esearch_result = {
699699+ min = None;
700700+ max = None;
701701+ count = None;
702702+ all = None;
703703+}
704704+705705+let parse_esearch_response responses =
706706+ List.fold_left (fun acc resp ->
707707+ match resp with
708708+ | Response.Esearch { results; _ } ->
709709+ List.fold_left (fun acc item ->
710710+ match item with
711711+ | Response.Esearch_min n -> { acc with min = Some n }
712712+ | Response.Esearch_max n -> { acc with max = Some n }
713713+ | Response.Esearch_count n -> { acc with count = Some n }
714714+ | Response.Esearch_all seq -> { acc with all = Some seq }
715715+ ) acc results
716716+ | _ -> acc
717717+ ) empty_esearch_result responses
718718+719719+let esearch t ?charset ?(return_opts = [Command.Return_all]) criteria =
720720+ require_selected t;
721721+ require_capability t "ESEARCH";
722722+ let tag = send_command t (Command.Search { charset; criteria; return_opts = Some return_opts }) in
723723+ let untagged, final = receive_responses t tag in
724724+ check_ok tag untagged final;
725725+ parse_esearch_response untagged
726726+727727+let uid_esearch t ?charset ?(return_opts = [Command.Return_all]) criteria =
728728+ require_selected t;
729729+ require_capability t "ESEARCH";
730730+ let tag = send_command t (Command.Uid (Uid_search { charset; criteria; return_opts = Some return_opts })) in
731731+ let untagged, final = receive_responses t tag in
732732+ check_ok tag untagged final;
733733+ parse_esearch_response untagged
+30
ocaml-imap/lib/imap/client.mli
···247247248248val enable : t -> string list -> string list
249249(** [enable client extensions] enables protocol extensions. *)
250250+251251+(** {1 ESEARCH Support (RFC 4731)} *)
252252+253253+type esearch_result = {
254254+ min : int option;
255255+ max : int option;
256256+ count : int option;
257257+ all : Seq.t option;
258258+}
259259+(** ESEARCH result containing optional min, max, count, and all values. *)
260260+261261+val esearch :
262262+ t ->
263263+ ?charset:string ->
264264+ ?return_opts:Command.search_return_opt list ->
265265+ Search.t ->
266266+ esearch_result
267267+(** [esearch client ?charset ?return_opts criteria] performs an extended search.
268268+ Returns an {!esearch_result} with the requested information.
269269+ Default [return_opts] is [[Return_all]].
270270+ Requires ESEARCH extension. *)
271271+272272+val uid_esearch :
273273+ t ->
274274+ ?charset:string ->
275275+ ?return_opts:Command.search_return_opt list ->
276276+ Search.t ->
277277+ esearch_result
278278+(** [uid_esearch client ?charset ?return_opts criteria] like {!esearch} but for UID searches.
279279+ Requires ESEARCH extension. *)
···2525 | Binary of string * (int * int) option
2626 | Binary_peek of string * (int * int) option
2727 | Binary_size of string
2828+ | Modseq (** Request MODSEQ value - RFC 7162 CONDSTORE *)
28292930val pp_request : Format.formatter -> request -> unit
3031
+52-1
ocaml-imap/lib/imap/flag.ml
···5566(** Message Flags
7788- IMAP message flags as specified in RFC 9051 Section 2.3.2. *)
88+ Re-exports from {!Mail_flag} for IMAP-specific use.
99+ See {{:https://datatracker.ietf.org/doc/html/rfc9051#section-2.3.2}RFC 9051 Section 2.3.2}. *)
1010+1111+(** {1 System Flags} *)
9121013type system =
1114 | Seen (** Message has been read *)
···2023 | Flagged -> Fmt.string ppf "\\Flagged"
2124 | Deleted -> Fmt.string ppf "\\Deleted"
2225 | Draft -> Fmt.string ppf "\\Draft"
2626+2727+(** {1 Flags} *)
23282429type t =
2530 | System of system
···4752 | "\\DRAFT" -> Some (System Draft)
4853 | _ ->
4954 if String.length s > 0 && s.[0] <> '\\' then Some (Keyword s) else None
5555+5656+(** {1 Conversion to/from mail-flag} *)
5757+5858+let system_to_keyword : system -> Mail_flag.Keyword.t = function
5959+ | Seen -> `Seen
6060+ | Answered -> `Answered
6161+ | Flagged -> `Flagged
6262+ | Deleted -> `Deleted
6363+ | Draft -> `Draft
6464+6565+let system_of_keyword : Mail_flag.Keyword.standard -> system option = function
6666+ | `Seen -> Some Seen
6767+ | `Answered -> Some Answered
6868+ | `Flagged -> Some Flagged
6969+ | `Deleted -> Some Deleted
7070+ | `Draft -> Some Draft
7171+ | `Forwarded -> None
7272+7373+let to_mail_flag : t -> Mail_flag.Imap_wire.flag = function
7474+ | System Seen -> Mail_flag.Imap_wire.System `Seen
7575+ | System Answered -> Mail_flag.Imap_wire.System `Answered
7676+ | System Flagged -> Mail_flag.Imap_wire.System `Flagged
7777+ | System Deleted -> Mail_flag.Imap_wire.System `Deleted
7878+ | System Draft -> Mail_flag.Imap_wire.System `Draft
7979+ | Keyword k -> Mail_flag.Imap_wire.Keyword (Mail_flag.Keyword.of_string k)
8080+8181+let of_mail_flag : Mail_flag.Imap_wire.flag -> t = function
8282+ | Mail_flag.Imap_wire.System `Seen -> System Seen
8383+ | Mail_flag.Imap_wire.System `Answered -> System Answered
8484+ | Mail_flag.Imap_wire.System `Flagged -> System Flagged
8585+ | Mail_flag.Imap_wire.System `Deleted -> System Deleted
8686+ | Mail_flag.Imap_wire.System `Draft -> System Draft
8787+ | Mail_flag.Imap_wire.Keyword k -> Keyword (Mail_flag.Keyword.to_string k)
8888+8989+let to_keyword : t -> Mail_flag.Keyword.t = function
9090+ | System s -> system_to_keyword s
9191+ | Keyword k -> Mail_flag.Keyword.of_string k
9292+9393+let of_keyword (k : Mail_flag.Keyword.t) : t =
9494+ match k with
9595+ | `Seen -> System Seen
9696+ | `Answered -> System Answered
9797+ | `Flagged -> System Flagged
9898+ | `Deleted -> System Deleted
9999+ | `Draft -> System Draft
100100+ | other -> Keyword (Mail_flag.Keyword.to_string other)
+26-1
ocaml-imap/lib/imap/flag.mli
···5566(** Message Flags
7788- IMAP message flags as specified in RFC 9051 Section 2.3.2. *)
88+ Re-exports from {!Mail_flag} for IMAP-specific use.
99+ See {{:https://datatracker.ietf.org/doc/html/rfc9051#section-2.3.2}RFC 9051 Section 2.3.2}. *)
9101011(** {1 System Flags} *)
1112···3637val pp : Format.formatter -> t -> unit
3738val to_string : t -> string
3839val of_string : string -> t option
4040+4141+(** {1 Conversion to/from mail-flag}
4242+4343+ These functions allow interoperability with the {!Mail_flag} library
4444+ for cross-protocol flag handling. *)
4545+4646+val system_to_keyword : system -> Mail_flag.Keyword.t
4747+(** [system_to_keyword sys] converts an IMAP system flag to a mail-flag keyword. *)
4848+4949+val system_of_keyword : Mail_flag.Keyword.standard -> system option
5050+(** [system_of_keyword kw] converts a standard mail-flag keyword to an IMAP system flag.
5151+ Returns [None] for keywords like [`Forwarded] that have no IMAP system flag equivalent. *)
5252+5353+val to_mail_flag : t -> Mail_flag.Imap_wire.flag
5454+(** [to_mail_flag flag] converts an IMAP flag to a mail-flag wire format flag. *)
5555+5656+val of_mail_flag : Mail_flag.Imap_wire.flag -> t
5757+(** [of_mail_flag flag] converts a mail-flag wire format flag to an IMAP flag. *)
5858+5959+val to_keyword : t -> Mail_flag.Keyword.t
6060+(** [to_keyword flag] converts an IMAP flag to a mail-flag keyword. *)
6161+6262+val of_keyword : Mail_flag.Keyword.t -> t
6363+(** [of_keyword kw] converts a mail-flag keyword to an IMAP flag. *)
+3
ocaml-imap/lib/imap/imap.ml
···5757 - {!module:Fetch} - FETCH request/response items
5858 - {!module:Search} - SEARCH criteria
5959 - {!module:Sort} - SORT criteria (RFC 5256)
6060+ - {!module:Subject} - Base subject extraction (RFC 5256)
6061 - {!module:Store} - STORE actions
6162 - {!module:Status} - STATUS items
6263 - {!module:List_attr} - LIST mailbox attributes
···99100module Store = Store
100101module Status = Status
101102module List_attr = List_attr
103103+module Subject = Subject
104104+module Thread = Thread
102105103106(** {1 Client} *)
104107
+3
ocaml-imap/lib/imap/imap.mli
···5757 - {!module:Fetch} - FETCH request/response items
5858 - {!module:Search} - SEARCH criteria
5959 - {!module:Sort} - SORT criteria (RFC 5256)
6060+ - {!module:Subject} - Base subject extraction (RFC 5256)
6061 - {!module:Store} - STORE actions
6162 - {!module:Status} - STATUS items
6263 - {!module:List_attr} - LIST mailbox attributes
···99100module Store = Store
100101module Status = Status
101102module List_attr = List_attr
103103+module Subject = Subject
104104+module Thread = Thread
102105103106(** {1 Client} *)
104107
+102-1
ocaml-imap/lib/imap/list_attr.ml
···5566(** LIST Command Attributes
7788- Mailbox attributes returned by LIST command.
88+ Re-exports from {!Mail_flag.Mailbox_attr}.
99 See RFC 9051 Section 7.2.2. *)
10101111type t =
···4343 | Extension s -> Fmt.string ppf s
44444545let to_string a = Fmt.str "%a" pp a
4646+4747+let of_string s =
4848+ let s' = String.lowercase_ascii s in
4949+ (* Remove leading backslash if present *)
5050+ let s' = if String.length s' > 0 && s'.[0] = '\\' then
5151+ String.sub s' 1 (String.length s' - 1)
5252+ else s'
5353+ in
5454+ match s' with
5555+ | "noinferiors" -> Noinferiors
5656+ | "noselect" -> Noselect
5757+ | "marked" -> Marked
5858+ | "unmarked" -> Unmarked
5959+ | "subscribed" -> Subscribed
6060+ | "haschildren" -> Haschildren
6161+ | "hasnochildren" -> Hasnochildren
6262+ | "all" -> All
6363+ | "archive" -> Archive
6464+ | "drafts" -> Drafts
6565+ | "flagged" -> Flagged
6666+ | "junk" | "spam" -> Junk
6767+ | "sent" -> Sent
6868+ | "trash" -> Trash
6969+ | _ -> Extension s
7070+7171+(** {1 Conversion to/from mail-flag} *)
7272+7373+let to_mailbox_attr : t -> Mail_flag.Mailbox_attr.t = function
7474+ | Noinferiors -> `Noinferiors
7575+ | Noselect -> `Noselect
7676+ | Marked -> `Marked
7777+ | Unmarked -> `Unmarked
7878+ | Subscribed -> `Subscribed
7979+ | Haschildren -> `HasChildren
8080+ | Hasnochildren -> `HasNoChildren
8181+ | All -> `All
8282+ | Archive -> `Archive
8383+ | Drafts -> `Drafts
8484+ | Flagged -> `Flagged
8585+ | Junk -> `Junk
8686+ | Sent -> `Sent
8787+ | Trash -> `Trash
8888+ | Extension s -> `Extension s
8989+9090+let of_mailbox_attr : Mail_flag.Mailbox_attr.t -> t = function
9191+ | `Noinferiors -> Noinferiors
9292+ | `Noselect -> Noselect
9393+ | `Marked -> Marked
9494+ | `Unmarked -> Unmarked
9595+ | `Subscribed -> Subscribed
9696+ | `HasChildren -> Haschildren
9797+ | `HasNoChildren -> Hasnochildren
9898+ | `NonExistent -> Noselect (* NonExistent implies Noselect *)
9999+ | `Remote -> Extension "\\Remote"
100100+ | `All -> All
101101+ | `Archive -> Archive
102102+ | `Drafts -> Drafts
103103+ | `Flagged -> Flagged
104104+ | `Important -> Extension "\\Important"
105105+ | `Inbox -> Extension "\\Inbox"
106106+ | `Junk -> Junk
107107+ | `Sent -> Sent
108108+ | `Trash -> Trash
109109+ | `Snoozed -> Extension "\\Snoozed"
110110+ | `Scheduled -> Extension "\\Scheduled"
111111+ | `Memos -> Extension "\\Memos"
112112+ | `Extension s -> Extension s
113113+114114+let to_jmap_role : t -> string option = function
115115+ | All -> Some "all"
116116+ | Archive -> Some "archive"
117117+ | Drafts -> Some "drafts"
118118+ | Flagged -> Some "flagged"
119119+ | Junk -> Some "junk"
120120+ | Sent -> Some "sent"
121121+ | Trash -> Some "trash"
122122+ | Subscribed -> Some "subscribed"
123123+ | Noinferiors | Noselect | Marked | Unmarked
124124+ | Haschildren | Hasnochildren | Extension _ -> None
125125+126126+let of_jmap_role s =
127127+ match String.lowercase_ascii s with
128128+ | "all" -> Some All
129129+ | "archive" -> Some Archive
130130+ | "drafts" -> Some Drafts
131131+ | "flagged" -> Some Flagged
132132+ | "junk" -> Some Junk
133133+ | "sent" -> Some Sent
134134+ | "trash" -> Some Trash
135135+ | "subscribed" -> Some Subscribed
136136+ | _ -> None
137137+138138+let is_special_use = function
139139+ | All | Archive | Drafts | Flagged | Junk | Sent | Trash -> true
140140+ | Subscribed -> true (* Also a JMAP role *)
141141+ | Noinferiors | Noselect | Marked | Unmarked
142142+ | Haschildren | Hasnochildren | Extension _ -> false
143143+144144+let is_selectable = function
145145+ | Noselect -> false
146146+ | _ -> true
+29-1
ocaml-imap/lib/imap/list_attr.mli
···5566(** LIST Command Attributes
7788- Mailbox attributes returned by LIST command.
88+ Re-exports from {!Mail_flag.Mailbox_attr}.
99 See RFC 9051 Section 7.2.2. *)
10101111type t =
···27272828val pp : Format.formatter -> t -> unit
2929val to_string : t -> string
3030+val of_string : string -> t
3131+3232+(** {1 Conversion to/from mail-flag}
3333+3434+ These functions allow interoperability with the {!Mail_flag} library
3535+ for cross-protocol attribute handling. *)
3636+3737+val to_mailbox_attr : t -> Mail_flag.Mailbox_attr.t
3838+(** [to_mailbox_attr attr] converts an IMAP list attribute to a mail-flag mailbox attribute. *)
3939+4040+val of_mailbox_attr : Mail_flag.Mailbox_attr.t -> t
4141+(** [of_mailbox_attr attr] converts a mail-flag mailbox attribute to an IMAP list attribute. *)
4242+4343+val to_jmap_role : t -> string option
4444+(** [to_jmap_role attr] converts a special-use attribute to its JMAP role string.
4545+ Returns [None] for LIST attributes that don't correspond to JMAP roles. *)
4646+4747+val of_jmap_role : string -> t option
4848+(** [of_jmap_role role] parses a JMAP role string into a special-use attribute.
4949+ Returns [None] if the role string is not recognized. *)
5050+5151+val is_special_use : t -> bool
5252+(** [is_special_use attr] returns [true] if the attribute is a special-use
5353+ role (as opposed to a LIST attribute or extension). *)
5454+5555+val is_selectable : t -> bool
5656+(** [is_selectable attr] returns [false] if the attribute indicates the
5757+ mailbox cannot be selected (i.e., [Noselect]). *)
+530-78
ocaml-imap/lib/imap/read.ml
···106106 in
107107 loop []
108108109109+(** {1 UID Set Parsing}
110110+111111+ Parses UID sets in the format used by APPENDUID/COPYUID response codes.
112112+ Examples: "304", "319:320", "304,319:320,325" *)
113113+114114+let uid_set_range r =
115115+ let first = number r in
116116+ match R.peek_char r with
117117+ | Some ':' ->
118118+ R.char ':' r;
119119+ (* Check for * (wildcard) *)
120120+ (match R.peek_char r with
121121+ | Some '*' ->
122122+ R.char '*' r;
123123+ Seq.From first
124124+ | _ ->
125125+ let last = number r in
126126+ Seq.Range (first, last))
127127+ | _ -> Seq.Single first
128128+129129+let uid_set r =
130130+ let rec loop acc =
131131+ let range = uid_set_range r in
132132+ match R.peek_char r with
133133+ | Some ',' ->
134134+ R.char ',' r;
135135+ loop (range :: acc)
136136+ | _ -> List.rev (range :: acc)
137137+ in
138138+ loop []
139139+109140(** {1 Flags} *)
110141111142let system_flag r =
···175206 in
176207 loop []
177208209209+(** {1 Body Structure Parsing} *)
210210+211211+(** Parse a parenthesized list of key-value pairs for body parameters.
212212+ Format: ("key1" "value1" "key2" "value2" ...) or NIL *)
213213+let body_params r =
214214+ if is_nil r then (skip_nil r; [])
215215+ else (
216216+ R.char '(' r;
217217+ let rec loop acc =
218218+ match R.peek_char r with
219219+ | Some ')' ->
220220+ R.char ')' r;
221221+ List.rev acc
222222+ | Some ' ' ->
223223+ sp r;
224224+ loop acc
225225+ | _ ->
226226+ let k = astring r in
227227+ sp r;
228228+ let v = astring r in
229229+ loop ((k, v) :: acc)
230230+ in
231231+ loop [])
232232+233233+(** Parse body fields common to all body types.
234234+ Format: params content-id content-desc encoding size *)
235235+let body_fields r =
236236+ let params = body_params r in
237237+ sp r;
238238+ let content_id = nstring r in
239239+ sp r;
240240+ let description = nstring r in
241241+ sp r;
242242+ let encoding = astring r in
243243+ sp r;
244244+ let size = number64 r in
245245+ Body.{ params; content_id; description; encoding; size }
246246+247247+(** Parse body disposition.
248248+ Format: ("INLINE" ("filename" "test.txt")) or NIL *)
249249+let body_disposition r =
250250+ if is_nil r then (skip_nil r; None)
251251+ else (
252252+ R.char '(' r;
253253+ let disposition_type = astring r in
254254+ sp r;
255255+ let params = body_params r in
256256+ R.char ')' r;
257257+ Some (disposition_type, params))
258258+259259+(** Parse body language - single string or list of strings.
260260+ Format: NIL or "en" or ("en" "de") *)
261261+let body_language r =
262262+ if is_nil r then (skip_nil r; None)
263263+ else
264264+ match R.peek_char r with
265265+ | Some '(' ->
266266+ (* List of languages *)
267267+ R.char '(' r;
268268+ let rec loop acc =
269269+ match R.peek_char r with
270270+ | Some ')' ->
271271+ R.char ')' r;
272272+ Some (List.rev acc)
273273+ | Some ' ' ->
274274+ sp r;
275275+ loop acc
276276+ | _ ->
277277+ let lang = astring r in
278278+ loop (lang :: acc)
279279+ in
280280+ loop []
281281+ | _ ->
282282+ (* Single language *)
283283+ Some [astring r]
284284+285285+(** Skip remaining body extensions after the known ones *)
286286+let rec skip_body_extension r =
287287+ match R.peek_char r with
288288+ | Some '(' ->
289289+ R.char '(' r;
290290+ let rec loop () =
291291+ match R.peek_char r with
292292+ | Some ')' -> R.char ')' r
293293+ | Some ' ' -> sp r; loop ()
294294+ | _ -> skip_body_extension r; loop ()
295295+ in
296296+ loop ()
297297+ | Some '"' -> ignore (quoted_string r)
298298+ | Some '{' -> ignore (literal r)
299299+ | _ when is_nil r -> skip_nil r
300300+ | _ ->
301301+ (* Could be a number or atom *)
302302+ ignore (R.take_while (fun c -> c <> ' ' && c <> ')' && c <> '\r') r)
303303+304304+let skip_remaining_extensions r =
305305+ while R.peek_char r = Some ' ' do
306306+ sp r;
307307+ match R.peek_char r with
308308+ | Some ')' -> () (* End of body, don't consume *)
309309+ | _ -> skip_body_extension r
310310+ done
311311+178312(** {1 Response Codes} *)
179313180314let response_code r =
···184318 match String.uppercase_ascii name with
185319 | "ALERT" -> Code.Alert
186320 | "ALREADYEXISTS" -> Code.Alreadyexists
321321+ | "APPENDUID" ->
322322+ sp r;
323323+ let uidvalidity = number64 r in
324324+ sp r;
325325+ let uid = number64 r in
326326+ Code.Appenduid (uidvalidity, uid)
187327 | "AUTHENTICATIONFAILED" -> Code.Authenticationfailed
188328 | "AUTHORIZATIONFAILED" -> Code.Authorizationfailed
189329 | "CANNOT" -> Code.Cannot
···191331 | "CLIENTBUG" -> Code.Clientbug
192332 | "CLOSED" -> Code.Closed
193333 | "CONTACTADMIN" -> Code.Contactadmin
334334+ | "COPYUID" ->
335335+ sp r;
336336+ let uidvalidity = number64 r in
337337+ sp r;
338338+ let source_uids = uid_set r in
339339+ sp r;
340340+ let dest_uids = uid_set r in
341341+ Code.Copyuid (uidvalidity, source_uids, dest_uids)
194342 | "CORRUPTION" -> Code.Corruption
195343 | "EXPIRED" -> Code.Expired
196344 | "EXPUNGEISSUED" -> Code.Expungeissued
197345 | "HASCHILDREN" -> Code.Haschildren
346346+ | "HIGHESTMODSEQ" ->
347347+ (* RFC 7162 Section 3.1.2.1: HIGHESTMODSEQ response code
348348+ Returned in SELECT/EXAMINE to indicate the highest mod-sequence
349349+ value of all messages in the mailbox. *)
350350+ sp r;
351351+ Code.Highestmodseq (number64 r)
198352 | "INUSE" -> Code.Inuse
199353 | "LIMIT" -> Code.Limit
354354+ | "MODIFIED" ->
355355+ (* RFC 7162 Section 3.1.3: MODIFIED response code
356356+ Returned in response to STORE with UNCHANGEDSINCE modifier
357357+ when messages have been modified since the specified mod-sequence. *)
358358+ sp r;
359359+ Code.Modified (uid_set r)
360360+ | "NOMODSEQ" ->
361361+ (* RFC 7162 Section 3.1.2.2: NOMODSEQ response code
362362+ Indicates that the mailbox does not support persistent storage
363363+ of mod-sequences (e.g., a virtual mailbox). *)
364364+ Code.Nomodseq
200365 | "NONEXISTENT" -> Code.Nonexistent
201366 | "NOPERM" -> Code.Noperm
202367 | "OVERQUOTA" -> Code.Overquota
···286451 R.char ')' r;
287452 Envelope.{ date; subject; from; sender; reply_to; to_; cc; bcc; in_reply_to; message_id }
288453454454+(** {1 Body Structure Parsing - Recursive Part}
455455+456456+ These functions parse body structures per RFC 9051 Section 7.4.2.
457457+ They must be defined after envelope since MESSAGE/RFC822 bodies contain envelopes. *)
458458+459459+(** Parse a single body part (non-multipart).
460460+ Returns the body type and optional extension data. *)
461461+let rec body_type_1part r =
462462+ let media_type = astring r in
463463+ sp r;
464464+ let subtype = astring r in
465465+ sp r;
466466+ let fields = body_fields r in
467467+ let media_type_upper = String.uppercase_ascii media_type in
468468+469469+ (* Parse type-specific fields and build body_type *)
470470+ let (body_type : Body.body_type) =
471471+ if media_type_upper = "TEXT" then (
472472+ sp r;
473473+ let lines = number64 r in
474474+ Text { subtype; fields; lines }
475475+ ) else if media_type_upper = "MESSAGE" && String.uppercase_ascii subtype = "RFC822" then (
476476+ sp r;
477477+ let env = envelope r in
478478+ sp r;
479479+ let nested_body = body r in
480480+ sp r;
481481+ let lines = number64 r in
482482+ Message_rfc822 { fields; envelope = env; body = nested_body; lines }
483483+ ) else
484484+ Basic { media_type; subtype; fields }
485485+ in
486486+487487+ (* Parse optional extension data for BODYSTRUCTURE *)
488488+ let disposition, language, location =
489489+ match R.peek_char r with
490490+ | Some ' ' -> (
491491+ sp r;
492492+ match R.peek_char r with
493493+ | Some ')' -> (None, None, None) (* End of body *)
494494+ | _ ->
495495+ (* md5 - skip it *)
496496+ ignore (nstring r);
497497+ match R.peek_char r with
498498+ | Some ' ' -> (
499499+ sp r;
500500+ match R.peek_char r with
501501+ | Some ')' -> (None, None, None)
502502+ | _ ->
503503+ let disposition = body_disposition r in
504504+ match R.peek_char r with
505505+ | Some ' ' -> (
506506+ sp r;
507507+ match R.peek_char r with
508508+ | Some ')' -> (disposition, None, None)
509509+ | _ ->
510510+ let language = body_language r in
511511+ match R.peek_char r with
512512+ | Some ' ' -> (
513513+ sp r;
514514+ match R.peek_char r with
515515+ | Some ')' -> (disposition, language, None)
516516+ | _ ->
517517+ let location = nstring r in
518518+ skip_remaining_extensions r;
519519+ (disposition, language, location))
520520+ | _ -> (disposition, language, None))
521521+ | _ -> (disposition, None, None))
522522+ | _ -> (None, None, None))
523523+ | _ -> (None, None, None)
524524+ in
525525+526526+ Body.{ body_type; disposition; language; location }
527527+528528+(** Parse multipart body structure.
529529+ Format: (body)(body)... "subtype" [extensions] *)
530530+and body_type_mpart r =
531531+ (* Collect all nested body parts *)
532532+ let rec collect_parts acc =
533533+ match R.peek_char r with
534534+ | Some '(' ->
535535+ let part = body r in
536536+ collect_parts (part :: acc)
537537+ | _ -> List.rev acc
538538+ in
539539+ let parts = collect_parts [] in
540540+541541+ (* Parse subtype *)
542542+ sp r;
543543+ let subtype = astring r in
544544+545545+ (* Parse optional extension data for BODYSTRUCTURE *)
546546+ let params, disposition, language, location =
547547+ match R.peek_char r with
548548+ | Some ' ' -> (
549549+ sp r;
550550+ match R.peek_char r with
551551+ | Some ')' -> ([], None, None, None)
552552+ | _ ->
553553+ let params = body_params r in
554554+ match R.peek_char r with
555555+ | Some ' ' -> (
556556+ sp r;
557557+ match R.peek_char r with
558558+ | Some ')' -> (params, None, None, None)
559559+ | _ ->
560560+ let disposition = body_disposition r in
561561+ match R.peek_char r with
562562+ | Some ' ' -> (
563563+ sp r;
564564+ match R.peek_char r with
565565+ | Some ')' -> (params, disposition, None, None)
566566+ | _ ->
567567+ let language = body_language r in
568568+ match R.peek_char r with
569569+ | Some ' ' -> (
570570+ sp r;
571571+ match R.peek_char r with
572572+ | Some ')' -> (params, disposition, language, None)
573573+ | _ ->
574574+ let location = nstring r in
575575+ skip_remaining_extensions r;
576576+ (params, disposition, language, location))
577577+ | _ -> (params, disposition, language, None))
578578+ | _ -> (params, disposition, None, None))
579579+ | _ -> (params, None, None, None))
580580+ | _ -> ([], None, None, None)
581581+ in
582582+583583+ Body.{
584584+ body_type = Multipart { subtype; parts; params };
585585+ disposition;
586586+ language;
587587+ location;
588588+ }
589589+590590+(** Parse a body structure - either multipart or single part.
591591+ Multipart starts with nested parentheses, single part starts with a string. *)
592592+and body r =
593593+ R.char '(' r;
594594+ let result =
595595+ match R.peek_char r with
596596+ | Some '(' ->
597597+ (* Multipart - starts with nested body *)
598598+ body_type_mpart r
599599+ | _ ->
600600+ (* Single part - starts with media type string *)
601601+ body_type_1part r
602602+ in
603603+ R.char ')' r;
604604+ result
605605+606606+(** {1 Section Specifier Parsing}
607607+608608+ Parse section specifiers like HEADER, TEXT, 1.2.MIME, HEADER.FIELDS (From Subject) *)
609609+610610+(** Parse a header field list like (From Subject To).
611611+ Note: Currently unused as HEADER.FIELDS parsing is simplified. *)
612612+let _parse_header_fields r =
613613+ sp r;
614614+ R.char '(' r;
615615+ let rec loop acc =
616616+ match R.peek_char r with
617617+ | Some ')' ->
618618+ R.char ')' r;
619619+ List.rev acc
620620+ | Some ' ' ->
621621+ sp r;
622622+ loop acc
623623+ | _ ->
624624+ let field = astring r in
625625+ loop (field :: acc)
626626+ in
627627+ loop []
628628+629629+(** Parse a section specifier string into a Body.section option.
630630+ Section format per RFC 9051:
631631+ - Empty string means whole message
632632+ - HEADER, TEXT, MIME
633633+ - HEADER.FIELDS (field list)
634634+ - HEADER.FIELDS.NOT (field list)
635635+ - Part number like 1, 1.2, 1.2.3
636636+ - Part number with subsection like 1.HEADER, 1.2.TEXT, 1.2.MIME *)
637637+let parse_section_spec section_str =
638638+ if section_str = "" then None
639639+ else
640640+ let upper = String.uppercase_ascii section_str in
641641+ if upper = "HEADER" then Some Body.Header
642642+ else if upper = "TEXT" then Some Body.Text
643643+ else if upper = "MIME" then Some Body.Mime
644644+ else if String.length upper > 14 && String.sub upper 0 14 = "HEADER.FIELDS " then
645645+ (* HEADER.FIELDS (field1 field2...) - simplified parsing *)
646646+ Some Body.Header
647647+ else if String.length upper > 18 && String.sub upper 0 18 = "HEADER.FIELDS.NOT " then
648648+ (* HEADER.FIELDS.NOT (field1 field2...) - simplified parsing *)
649649+ Some Body.Header
650650+ else
651651+ (* Try to parse as part numbers: 1, 1.2, 1.2.3, possibly with .HEADER/.TEXT/.MIME suffix *)
652652+ let parts = String.split_on_char '.' section_str in
653653+ let rec parse_parts nums = function
654654+ | [] -> Some (Body.Part (List.rev nums, None))
655655+ | [s] when String.uppercase_ascii s = "HEADER" ->
656656+ Some (Body.Part (List.rev nums, Some Body.Header))
657657+ | [s] when String.uppercase_ascii s = "TEXT" ->
658658+ Some (Body.Part (List.rev nums, Some Body.Text))
659659+ | [s] when String.uppercase_ascii s = "MIME" ->
660660+ Some (Body.Part (List.rev nums, Some Body.Mime))
661661+ | s :: rest ->
662662+ (try
663663+ let n = int_of_string s in
664664+ parse_parts (n :: nums) rest
665665+ with Failure _ ->
666666+ (* Not a number, and not a known section type at end - skip *)
667667+ Some (Body.Part (List.rev nums, None)))
668668+ in
669669+ parse_parts [] parts
670670+289671(** {1 FETCH Response Items} *)
290672291673let fetch_item r =
···333715 | Some '[' ->
334716 (* BODY[section]<origin> literal-or-nil *)
335717 R.char '[' r;
336336- let _section = R.take_while (fun c -> c <> ']') r in
718718+ let section_str = R.take_while (fun c -> c <> ']') r in
337719 R.char ']' r;
338338- (* Skip optional origin <n> *)
720720+ let section = parse_section_spec section_str in
721721+ (* Parse optional origin <n> *)
339722 let origin =
340723 if R.peek_char r = Some '<' then (
341724 R.char '<' r;
···346729 in
347730 sp r;
348731 let data = nstring r in
349349- Fetch.Item_body_section { section = None; origin; data }
732732+ Fetch.Item_body_section { section; origin; data }
350733 | _ ->
351351- (* BODY without [] means bodystructure - skip for now *)
734734+ (* BODY without [] means basic bodystructure (no extensions) *)
352735 sp r;
353353- (* Skip the parenthesized body structure *)
354354- let depth = ref 0 in
355355- (match R.peek_char r with
356356- | Some '(' ->
357357- R.char '(' r;
358358- depth := 1;
359359- while !depth > 0 do
360360- match R.any_char r with
361361- | '(' -> incr depth
362362- | ')' -> decr depth
363363- | '"' -> ignore (R.take_while (fun c -> c <> '"') r); ignore (R.any_char r)
364364- | '{' ->
365365- let len = number r in
366366- R.char '}' r;
367367- crlf r;
368368- ignore (R.take len r)
369369- | _ -> ()
370370- done
371371- | _ -> ());
372372- (* Return a minimal body structure stub *)
373373- let stub_body : Body.t = {
374374- body_type = Body.Basic {
375375- media_type = "application";
376376- subtype = "octet-stream";
377377- fields = {
378378- params = [];
379379- content_id = None;
380380- description = None;
381381- encoding = "7bit";
382382- size = 0L;
383383- }
384384- };
385385- disposition = None;
386386- language = None;
387387- location = None;
388388- } in
389389- Fetch.Item_body stub_body)
736736+ let parsed_body = body r in
737737+ Fetch.Item_body parsed_body)
390738 | "BODYSTRUCTURE" ->
739739+ (* BODYSTRUCTURE includes extension data *)
391740 sp r;
392392- (* Skip the parenthesized body structure - return minimal stub *)
393393- let depth = ref 0 in
394394- (match R.peek_char r with
395395- | Some '(' ->
396396- R.char '(' r;
397397- depth := 1;
398398- while !depth > 0 do
399399- match R.any_char r with
400400- | '(' -> incr depth
401401- | ')' -> decr depth
402402- | '"' -> ignore (R.take_while (fun c -> c <> '"') r); ignore (R.any_char r)
403403- | '{' ->
404404- let len = number r in
405405- R.char '}' r;
406406- crlf r;
407407- ignore (R.take len r)
408408- | _ -> ()
409409- done
410410- | _ -> ());
411411- (* Return a minimal body structure stub *)
412412- let stub_body : Body.t = {
413413- body_type = Body.Basic {
414414- media_type = "application";
415415- subtype = "octet-stream";
416416- fields = {
417417- params = [];
418418- content_id = None;
419419- description = None;
420420- encoding = "7bit";
421421- size = 0L;
422422- }
423423- };
424424- disposition = None;
425425- language = None;
426426- location = None;
427427- } in
428428- Fetch.Item_bodystructure stub_body
741741+ let parsed_body = body r in
742742+ Fetch.Item_bodystructure parsed_body
429743 | _ -> Fetch.Item_flags []
430744431745let fetch_items r = parse_paren_list ~parse_item:fetch_item r
···444758 | "UNSEEN" -> Status.Unseen
445759 | "DELETED" -> Status.Deleted
446760 | "SIZE" -> Status.Size
761761+ | "HIGHESTMODSEQ" -> Status.Highestmodseq (* RFC 7162 CONDSTORE *)
447762 | _ -> Status.Messages
448763 in
449764 (item, value)
···488803 let shared = namespace_list r in
489804 Response.{ personal; other; shared }
490805806806+(** {1 ESEARCH Response (RFC 4731)} *)
807807+808808+let esearch_correlator r =
809809+ (* Parse (TAG "xxx") *)
810810+ R.char '(' r;
811811+ let name = atom r in
812812+ sp r;
813813+ let value = quoted_string r in
814814+ R.char ')' r;
815815+ if String.uppercase_ascii name = "TAG" then Some value else None
816816+817817+let esearch_result_item r =
818818+ let name = atom r in
819819+ match String.uppercase_ascii name with
820820+ | "MIN" ->
821821+ sp r;
822822+ Some (Response.Esearch_min (number r))
823823+ | "MAX" ->
824824+ sp r;
825825+ Some (Response.Esearch_max (number r))
826826+ | "COUNT" ->
827827+ sp r;
828828+ Some (Response.Esearch_count (number r))
829829+ | "ALL" ->
830830+ sp r;
831831+ Some (Response.Esearch_all (uid_set r))
832832+ | _ -> None
833833+834834+let esearch_data r =
835835+ (* Parse optional (TAG "xxx") correlator *)
836836+ let tag =
837837+ match R.peek_char r with
838838+ | Some '(' -> esearch_correlator r
839839+ | _ -> None
840840+ in
841841+ (* Skip space if present after correlator *)
842842+ if Option.is_some tag && R.peek_char r = Some ' ' then sp r;
843843+ (* Check for UID indicator *)
844844+ let uid =
845845+ match R.peek_char r with
846846+ | Some 'U' | Some 'u' ->
847847+ (* Peek ahead to check if it's "UID" *)
848848+ R.ensure r 3;
849849+ let buf = R.peek r in
850850+ if Cstruct.length buf >= 3
851851+ && Char.uppercase_ascii (Cstruct.get_char buf 0) = 'U'
852852+ && Char.uppercase_ascii (Cstruct.get_char buf 1) = 'I'
853853+ && Char.uppercase_ascii (Cstruct.get_char buf 2) = 'D'
854854+ then (
855855+ ignore (R.take 3 r); (* consume "UID" *)
856856+ if R.peek_char r = Some ' ' then sp r;
857857+ true
858858+ ) else false
859859+ | _ -> false
860860+ in
861861+ (* Parse result data items *)
862862+ let rec loop acc =
863863+ match R.peek_char r with
864864+ | Some '\r' | Some '\n' | None -> List.rev acc
865865+ | Some ' ' ->
866866+ sp r;
867867+ loop acc
868868+ | _ ->
869869+ (match esearch_result_item r with
870870+ | Some item -> loop (item :: acc)
871871+ | None -> List.rev acc)
872872+ in
873873+ let results = loop [] in
874874+ Response.Esearch { tag; uid; results }
875875+491876(** {1 ID Response} *)
492877493878let id_params r =
···509894 loop ((k, v) :: acc)
510895 in
511896 loop [])
897897+898898+(** {1 THREAD Response (RFC 5256)}
899899+900900+ Parses the THREAD response format as specified in RFC 5256 Section 4.
901901+902902+ The thread response has a recursive structure where each thread node
903903+ can be either a message number or a nested list of children.
904904+905905+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-4> RFC 5256 Section 4 *)
906906+907907+(** Parse a single thread node.
908908+909909+ A thread node is either:
910910+ - A message number optionally followed by children: "(n ...children...)"
911911+ - A dummy node (missing parent) starting with nested parens: "((...)...)"
912912+913913+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-4> RFC 5256 Section 4 *)
914914+let rec thread_node r =
915915+ R.char '(' r;
916916+ match R.peek_char r with
917917+ | Some '(' ->
918918+ (* Dummy node - starts with ( instead of number.
919919+ This represents a missing parent message in the thread. *)
920920+ let children = thread_children r in
921921+ R.char ')' r;
922922+ Thread.Dummy children
923923+ | _ ->
924924+ let n = number r in
925925+ let children = thread_children r in
926926+ R.char ')' r;
927927+ Thread.Message (n, children)
928928+929929+(** Parse thread children (zero or more thread nodes).
930930+931931+ Children can be separated by spaces or appear consecutively.
932932+ This handles formats like "(3 6 (4 23))" where 6 is a child of 3,
933933+ and (4 23) is a sibling subtree. *)
934934+and thread_children r =
935935+ let rec loop acc =
936936+ match R.peek_char r with
937937+ | Some '(' -> loop (thread_node r :: acc)
938938+ | Some ' ' -> sp r; loop acc
939939+ | _ -> List.rev acc
940940+ in
941941+ loop []
512942513943(** {1 Main Response Parser} *)
514944···6311061 done;
6321062 crlf r;
6331063 Response.Sort (List.rev !seqs)
10641064+ | "THREAD" ->
10651065+ (* RFC 5256 Section 4 - THREAD response format:
10661066+ thread-data = "THREAD" [SP 1*thread-list]
10671067+ thread-list = "(" thread-members / thread-nested ")"
10681068+ thread-members = thread-node *(SP thread-node)
10691069+ thread-nested = thread-list
10701070+ thread-node = nz-number / thread-nested
10711071+10721072+ Example: * THREAD (2)(3 6 (4 23)(44 7 96))
10731073+ - Thread 1: Message 2 (no children)
10741074+ - Thread 2: Message 3 with child 6, which has children 4,23 and 44->7->96
10751075+10761076+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-4> RFC 5256 Section 4 *)
10771077+ let threads = thread_children r in
10781078+ crlf r;
10791079+ Response.Thread threads
10801080+ | "ESEARCH" ->
10811081+ (* RFC 4731 ESEARCH response *)
10821082+ if R.peek_char r = Some ' ' then sp r;
10831083+ let result = esearch_data r in
10841084+ crlf r;
10851085+ result
6341086 | _ ->
6351087 let _ = rest_of_line r in
6361088 Response.Ok { tag = None; code = None; text = "" })
+2
ocaml-imap/lib/imap/response.ml
···4545 | Esearch of { tag : string option; uid : bool; results : esearch_result list }
4646 | Search of int list
4747 | Sort of int64 list
4848+ | Thread of int Thread.t
4849 | Flags of Flag.t list
4950 | Exists of int
5051 | Recent of int
···8384 | Esearch _ -> Fmt.string ppf "* ESEARCH ..."
8485 | Search seqs -> Fmt.pf ppf "* SEARCH %a" Fmt.(list ~sep:sp int) seqs
8586 | Sort seqs -> Fmt.pf ppf "* SORT %a" Fmt.(list ~sep:sp int64) seqs
8787+ | Thread threads -> Fmt.pf ppf "* THREAD %a" (Thread.pp Fmt.int) threads
8688 | Flags flags -> Fmt.pf ppf "* FLAGS (%a)" Fmt.(list ~sep:sp Flag.pp) flags
8789 | Exists n -> Fmt.pf ppf "* %d EXISTS" n
8890 | Recent n -> Fmt.pf ppf "* %d RECENT" n
+1
ocaml-imap/lib/imap/response.mli
···4545 | Esearch of { tag : string option; uid : bool; results : esearch_result list }
4646 | Search of int list
4747 | Sort of int64 list
4848+ | Thread of int Thread.t
4849 | Flags of Flag.t list
4950 | Exists of int
5051 | Recent of int
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** {0 Base Subject Extraction}
77+88+ Implements {{:https://datatracker.ietf.org/doc/html/rfc5256#section-2.1}RFC 5256 Section 2.1}
99+ for extracting the "base subject" from email Subject headers.
1010+1111+ The base subject is used for SORT by SUBJECT and threading operations. *)
1212+1313+(** Normalize whitespace: convert tabs to spaces, collapse multiple spaces to one,
1414+ and trim leading/trailing whitespace. *)
1515+let normalize_whitespace s =
1616+ (* Replace tabs with spaces *)
1717+ let s = String.map (fun c -> if c = '\t' then ' ' else c) s in
1818+ (* Collapse multiple spaces and trim *)
1919+ let buf = Buffer.create (String.length s) in
2020+ let last_was_space = ref true in (* Start true to skip leading spaces *)
2121+ String.iter (fun c ->
2222+ if c = ' ' then begin
2323+ if not !last_was_space then Buffer.add_char buf ' ';
2424+ last_was_space := true
2525+ end else begin
2626+ Buffer.add_char buf c;
2727+ last_was_space := false
2828+ end
2929+ ) s;
3030+ (* Remove trailing space if present *)
3131+ let result = Buffer.contents buf in
3232+ let len = String.length result in
3333+ if len > 0 && result.[len - 1] = ' ' then
3434+ String.sub result 0 (len - 1)
3535+ else
3636+ result
3737+3838+(** Case-insensitive string prefix check *)
3939+let starts_with_ci ~prefix s =
4040+ let plen = String.length prefix in
4141+ let slen = String.length s in
4242+ slen >= plen &&
4343+ let rec check i =
4444+ if i >= plen then true
4545+ else if Char.lowercase_ascii s.[i] = Char.lowercase_ascii prefix.[i] then
4646+ check (i + 1)
4747+ else false
4848+ in
4949+ check 0
5050+5151+(** Case-insensitive string suffix check *)
5252+let ends_with_ci ~suffix s =
5353+ let suflen = String.length suffix in
5454+ let slen = String.length s in
5555+ slen >= suflen &&
5656+ let rec check i =
5757+ if i >= suflen then true
5858+ else if Char.lowercase_ascii s.[slen - suflen + i] = Char.lowercase_ascii suffix.[i] then
5959+ check (i + 1)
6060+ else false
6161+ in
6262+ check 0
6363+6464+(** Remove trailing (fwd) and whitespace - subj-trailer from RFC 5256 Section 5.
6565+ Returns the string and whether anything was removed. *)
6666+let remove_trailer s =
6767+ let s = String.trim s in
6868+ if ends_with_ci ~suffix:"(fwd)" s then
6969+ let len = String.length s in
7070+ (String.trim (String.sub s 0 (len - 5)), true)
7171+ else
7272+ (s, false)
7373+7474+(** Skip a [blob] pattern starting at position i.
7575+ Returns the position after the blob and trailing whitespace, or None if no blob. *)
7676+let skip_blob s i =
7777+ let len = String.length s in
7878+ if i >= len || s.[i] <> '[' then None
7979+ else begin
8080+ (* Find matching ] - blob chars are anything except [ ] *)
8181+ let rec find_close j =
8282+ if j >= len then None
8383+ else if s.[j] = ']' then Some j
8484+ else if s.[j] = '[' then None (* nested [ not allowed in blob *)
8585+ else find_close (j + 1)
8686+ in
8787+ match find_close (i + 1) with
8888+ | None -> None
8989+ | Some close_pos ->
9090+ (* Skip trailing whitespace after ] *)
9191+ let rec skip_ws k =
9292+ if k >= len then k
9393+ else if s.[k] = ' ' || s.[k] = '\t' then skip_ws (k + 1)
9494+ else k
9595+ in
9696+ Some (skip_ws (close_pos + 1))
9797+ end
9898+9999+(** Try to remove a subj-refwd pattern: ("re" / ("fw" ["d"])) *WSP [subj-blob] ":"
100100+ Returns (rest_of_string, removed) *)
101101+let remove_refwd s =
102102+ let len = String.length s in
103103+ let try_prefix prefix =
104104+ if starts_with_ci ~prefix s then
105105+ let after_prefix = String.length prefix in
106106+ (* Skip optional whitespace *)
107107+ let rec skip_ws i =
108108+ if i >= len then i
109109+ else if s.[i] = ' ' || s.[i] = '\t' then skip_ws (i + 1)
110110+ else i
111111+ in
112112+ let after_ws = skip_ws after_prefix in
113113+ (* Try to skip optional blob *)
114114+ let after_blob = match skip_blob s after_ws with
115115+ | Some pos -> pos
116116+ | None -> after_ws
117117+ in
118118+ (* Must have colon *)
119119+ if after_blob < len && s.[after_blob] = ':' then
120120+ let rest = String.sub s (after_blob + 1) (len - after_blob - 1) in
121121+ Some (String.trim rest)
122122+ else
123123+ None
124124+ else
125125+ None
126126+ in
127127+ (* Try fwd first (longer), then fw, then re *)
128128+ match try_prefix "fwd" with
129129+ | Some rest -> (rest, true)
130130+ | None ->
131131+ match try_prefix "fw" with
132132+ | Some rest -> (rest, true)
133133+ | None ->
134134+ match try_prefix "re" with
135135+ | Some rest -> (rest, true)
136136+ | None -> (s, false)
137137+138138+(** Try to remove a leading [blob] if doing so leaves a non-empty base subject.
139139+ Returns (rest_of_string, removed) *)
140140+let remove_leading_blob s =
141141+ match skip_blob s 0 with
142142+ | None -> (s, false)
143143+ | Some after_blob ->
144144+ let rest = String.sub s after_blob (String.length s - after_blob) in
145145+ let rest = String.trim rest in
146146+ (* Only remove if non-empty base remains *)
147147+ if String.length rest > 0 then (rest, true)
148148+ else (s, false)
149149+150150+(** Remove [fwd: ... ] wrapper pattern.
151151+ Returns (unwrapped_content, was_wrapped) *)
152152+let remove_fwd_wrapper s =
153153+ let len = String.length s in
154154+ if len >= 6 && starts_with_ci ~prefix:"[fwd:" s && s.[len - 1] = ']' then begin
155155+ let inner = String.sub s 5 (len - 6) in
156156+ (String.trim inner, true)
157157+ end else
158158+ (s, false)
159159+160160+(** Internal state for tracking whether modifications occurred *)
161161+type extract_state = {
162162+ subject : string;
163163+ is_reply_or_fwd : bool;
164164+}
165165+166166+(** Extract base subject with full algorithm from RFC 5256 Section 2.1 *)
167167+let rec extract_base_subject_full subject =
168168+ (* Step 1: Normalize whitespace (RFC 2047 decoding would go here too) *)
169169+ let s = normalize_whitespace subject in
170170+ let state = { subject = s; is_reply_or_fwd = false } in
171171+172172+ (* Step 2: Remove trailing (fwd) - subj-trailer *)
173173+ let rec remove_trailers state =
174174+ let (s, removed) = remove_trailer state.subject in
175175+ if removed then
176176+ remove_trailers { subject = s; is_reply_or_fwd = true }
177177+ else
178178+ state
179179+ in
180180+ let state = remove_trailers state in
181181+182182+ (* Steps 3-5: Remove leading subj-leader and subj-blob, repeat until stable *)
183183+ let rec remove_leaders state =
184184+ (* Step 3: Remove subj-refwd *)
185185+ let (s, refwd_removed) = remove_refwd state.subject in
186186+ let new_is_reply = state.is_reply_or_fwd || refwd_removed in
187187+ let state = { subject = s; is_reply_or_fwd = new_is_reply } in
188188+189189+ (* Step 4: Remove leading [blob] if non-empty base remains *)
190190+ let (s, blob_removed) = remove_leading_blob state.subject in
191191+ let state = { subject = s; is_reply_or_fwd = state.is_reply_or_fwd } in
192192+193193+ (* Step 5: Repeat if any changes *)
194194+ if refwd_removed || blob_removed then
195195+ remove_leaders state
196196+ else
197197+ state
198198+ in
199199+ let state = remove_leaders state in
200200+201201+ (* Step 6: Check for [fwd: ... ] wrapper *)
202202+ let (s, fwd_wrapped) = remove_fwd_wrapper state.subject in
203203+ let new_is_reply = state.is_reply_or_fwd || fwd_wrapped in
204204+ let state = { subject = s; is_reply_or_fwd = new_is_reply } in
205205+206206+ (* If we unwrapped [fwd:], need to re-run the whole algorithm on the inner content *)
207207+ if fwd_wrapped then begin
208208+ let inner_result = extract_base_subject_full s in
209209+ { subject = inner_result.subject;
210210+ is_reply_or_fwd = true (* The outer [fwd:] wrapper counts *) }
211211+ end else
212212+ state
213213+214214+let base_subject subject =
215215+ let result = extract_base_subject_full subject in
216216+ result.subject
217217+218218+let is_reply_or_forward subject =
219219+ let result = extract_base_subject_full subject in
220220+ result.is_reply_or_fwd
+60
ocaml-imap/lib/imap/subject.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** {0 Base Subject Extraction}
77+88+ Implements {{:https://datatracker.ietf.org/doc/html/rfc5256#section-2.1}RFC 5256 Section 2.1}
99+ for extracting the "base subject" from email Subject headers.
1010+1111+ The base subject is used for SORT by SUBJECT and threading operations.
1212+1313+ {1 Algorithm Overview}
1414+1515+ The algorithm:
1616+ {ol
1717+ {li Converts RFC 2047 encoded-words to UTF-8 and normalizes whitespace}
1818+ {li Removes trailing [(fwd)] and whitespace (subj-trailer)}
1919+ {li Removes leading [Re:], [Fw:], [Fwd:] with optional [\[blob\]] (subj-leader)}
2020+ {li Removes leading [\[blob\]] if non-empty base remains}
2121+ {li Repeats steps 3-4 until no more changes}
2222+ {li Unwraps [\[fwd: ... \]] wrapper pattern}
2323+ }
2424+2525+ {1 ABNF from RFC 5256 Section 5}
2626+2727+ {v
2828+subj-refwd = ("re" / ("fw" ["d"])) *WSP [subj-blob] ":"
2929+subj-blob = "[" *BLOBCHAR "]" *WSP
3030+subj-trailer = "(fwd)" / WSP
3131+subj-fwd-hdr = "[fwd:"
3232+subj-fwd-trl = "]"
3333+ v} *)
3434+3535+val base_subject : string -> string
3636+(** [base_subject subject] extracts the base subject from [subject].
3737+3838+ The base subject is the normalized subject string with reply/forward
3939+ indicators removed, suitable for sorting and threading.
4040+4141+ Examples:
4242+ - [base_subject "Re: test"] returns ["test"]
4343+ - [base_subject "Re: Re: test"] returns ["test"]
4444+ - [base_subject "Fwd: test"] returns ["test"]
4545+ - [base_subject "\[PATCH\] Re: \[ocaml\] test"] returns ["test"]
4646+ - [base_subject "\[fwd: wrapped\]"] returns ["wrapped"]
4747+ - [base_subject "test (fwd)"] returns ["test"]
4848+ - [base_subject " spaced "] returns ["spaced"] *)
4949+5050+val is_reply_or_forward : string -> bool
5151+(** [is_reply_or_forward subject] returns [true] if the subject indicates
5252+ a reply or forward.
5353+5454+ This is determined by whether base subject extraction removed any of:
5555+ - [Re:], [Fw:], or [Fwd:] prefixes
5656+ - [(fwd)] trailer
5757+ - [\[fwd: ...\]] wrapper
5858+5959+ This is useful for threading algorithms that need to distinguish
6060+ original messages from replies/forwards. *)
+95
ocaml-imap/lib/imap/thread.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** {0 RFC 5256 THREAD Extension}
77+88+ Message threading algorithms as specified in
99+ {{:https://datatracker.ietf.org/doc/html/rfc5256}RFC 5256 Section 3}.
1010+1111+ The THREAD command allows clients to retrieve messages organized into
1212+ conversation threads based on message relationships. *)
1313+1414+(** {1 Threading Algorithms}
1515+1616+ RFC 5256 Section 3 defines two threading algorithms. Servers MUST
1717+ implement at least ORDEREDSUBJECT and SHOULD implement REFERENCES. *)
1818+1919+(** Threading algorithm used to organize messages into threads.
2020+2121+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-3> RFC 5256 Section 3 *)
2222+type algorithm =
2323+ | Orderedsubject
2424+ (** ORDEREDSUBJECT algorithm (RFC 5256 Section 3.1).
2525+ Groups messages by base subject (stripping Re:/Fwd: prefixes),
2626+ then sorts each group by sent date. Simple but effective for
2727+ basic threading. *)
2828+ | References
2929+ (** REFERENCES algorithm (RFC 5256 Section 3.2).
3030+ Implements the JWZ threading algorithm using Message-ID,
3131+ In-Reply-To, and References headers to build a complete
3232+ parent/child thread tree. More accurate than ORDEREDSUBJECT
3333+ but computationally more expensive. *)
3434+ | Extension of string
3535+ (** Future algorithm extensions. Servers may advertise additional
3636+ threading algorithms via the THREAD capability. *)
3737+3838+(** {1 Thread Result Structure}
3939+4040+ Thread results form a forest of trees. Each tree represents a
4141+ conversation thread, with messages as nodes. *)
4242+4343+(** A thread node in the result tree.
4444+4545+ Thread responses use a nested parenthesized structure where each
4646+ message may have zero or more child messages (replies).
4747+4848+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-4> RFC 5256 Section 4 *)
4949+type 'a node =
5050+ | Message of 'a * 'a node list
5151+ (** A message with its sequence number or UID (depending on whether
5252+ UID THREAD was used) and a list of child messages (replies).
5353+ The children are ordered by the threading algorithm. *)
5454+ | Dummy of 'a node list
5555+ (** A placeholder for a missing parent message. This occurs when
5656+ replies reference a message that is not in the search results
5757+ (e.g., it was deleted or not matched by the search criteria).
5858+ The REFERENCES algorithm may produce dummy nodes to maintain
5959+ thread structure. *)
6060+6161+(** Thread result: a list of root-level thread trees.
6262+6363+ Each element is a top-level thread. The threads are ordered according
6464+ to the threading algorithm (typically by date of the first message
6565+ in each thread).
6666+6767+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-4> RFC 5256 Section 4 *)
6868+type 'a t = 'a node list
6969+7070+(** {1 Pretty Printers} *)
7171+7272+let pp_algorithm ppf = function
7373+ | Orderedsubject -> Fmt.string ppf "ORDEREDSUBJECT"
7474+ | References -> Fmt.string ppf "REFERENCES"
7575+ | Extension s -> Fmt.pf ppf "%s" (String.uppercase_ascii s)
7676+7777+let algorithm_to_string alg = Fmt.str "%a" pp_algorithm alg
7878+7979+let algorithm_of_string s =
8080+ match String.uppercase_ascii s with
8181+ | "ORDEREDSUBJECT" -> Orderedsubject
8282+ | "REFERENCES" -> References
8383+ | other -> Extension other
8484+8585+let rec pp_node pp_elt ppf = function
8686+ | Message (elt, []) ->
8787+ pp_elt ppf elt
8888+ | Message (elt, children) ->
8989+ Fmt.pf ppf "(%a %a)" pp_elt elt
9090+ Fmt.(list ~sep:sp (pp_node pp_elt)) children
9191+ | Dummy children ->
9292+ Fmt.pf ppf "(%a)" Fmt.(list ~sep:sp (pp_node pp_elt)) children
9393+9494+let pp pp_elt ppf threads =
9595+ Fmt.(list ~sep:sp (pp_node pp_elt)) ppf threads
+86
ocaml-imap/lib/imap/thread.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** {0 RFC 5256 THREAD Extension}
77+88+ Message threading algorithms as specified in
99+ {{:https://datatracker.ietf.org/doc/html/rfc5256}RFC 5256 Section 3}.
1010+1111+ The THREAD command allows clients to retrieve messages organized into
1212+ conversation threads based on message relationships. *)
1313+1414+(** {1 Threading Algorithms}
1515+1616+ RFC 5256 Section 3 defines two threading algorithms. Servers MUST
1717+ implement at least ORDEREDSUBJECT and SHOULD implement REFERENCES. *)
1818+1919+(** Threading algorithm used to organize messages into threads.
2020+2121+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-3> RFC 5256 Section 3 *)
2222+type algorithm =
2323+ | Orderedsubject
2424+ (** ORDEREDSUBJECT algorithm (RFC 5256 Section 3.1).
2525+ Groups messages by base subject (stripping Re:/Fwd: prefixes),
2626+ then sorts each group by sent date. Simple but effective for
2727+ basic threading. *)
2828+ | References
2929+ (** REFERENCES algorithm (RFC 5256 Section 3.2).
3030+ Implements the JWZ threading algorithm using Message-ID,
3131+ In-Reply-To, and References headers to build a complete
3232+ parent/child thread tree. More accurate than ORDEREDSUBJECT
3333+ but computationally more expensive. *)
3434+ | Extension of string
3535+ (** Future algorithm extensions. Servers may advertise additional
3636+ threading algorithms via the THREAD capability. *)
3737+3838+(** {1 Thread Result Structure}
3939+4040+ Thread results form a forest of trees. Each tree represents a
4141+ conversation thread, with messages as nodes. *)
4242+4343+(** A thread node in the result tree.
4444+4545+ Thread responses use a nested parenthesized structure where each
4646+ message may have zero or more child messages (replies).
4747+4848+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-4> RFC 5256 Section 4 *)
4949+type 'a node =
5050+ | Message of 'a * 'a node list
5151+ (** A message with its sequence number or UID (depending on whether
5252+ UID THREAD was used) and a list of child messages (replies).
5353+ The children are ordered by the threading algorithm. *)
5454+ | Dummy of 'a node list
5555+ (** A placeholder for a missing parent message. This occurs when
5656+ replies reference a message that is not in the search results
5757+ (e.g., it was deleted or not matched by the search criteria).
5858+ The REFERENCES algorithm may produce dummy nodes to maintain
5959+ thread structure. *)
6060+6161+(** Thread result: a list of root-level thread trees.
6262+6363+ Each element is a top-level thread. The threads are ordered according
6464+ to the threading algorithm (typically by date of the first message
6565+ in each thread).
6666+6767+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-4> RFC 5256 Section 4 *)
6868+type 'a t = 'a node list
6969+7070+(** {1 Pretty Printers} *)
7171+7272+val pp_algorithm : Format.formatter -> algorithm -> unit
7373+(** [pp_algorithm ppf alg] prints the algorithm name in IMAP wire format. *)
7474+7575+val algorithm_to_string : algorithm -> string
7676+(** [algorithm_to_string alg] returns the algorithm name as a string. *)
7777+7878+val algorithm_of_string : string -> algorithm
7979+(** [algorithm_of_string s] parses an algorithm name from a string.
8080+ Unrecognized names are returned as [Extension s]. *)
8181+8282+val pp_node : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a node -> unit
8383+(** [pp_node pp_elt ppf node] prints a thread node using [pp_elt] for elements. *)
8484+8585+val pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
8686+(** [pp pp_elt ppf threads] prints a thread result using [pp_elt] for elements. *)
+45-5
ocaml-imap/lib/imap/write.ml
···116116 flags;
117117 W.char w ')'
118118119119+(** {1 Search Return Options (RFC 4731 ESEARCH)} *)
120120+121121+let search_return_opt w = function
122122+ | Command.Return_min -> W.string w "MIN"
123123+ | Command.Return_max -> W.string w "MAX"
124124+ | Command.Return_all -> W.string w "ALL"
125125+ | Command.Return_count -> W.string w "COUNT"
126126+127127+let search_return_opts w opts =
128128+ W.string w "RETURN (";
129129+ List.iteri (fun i opt ->
130130+ if i > 0 then sp w;
131131+ search_return_opt w opt
132132+ ) opts;
133133+ W.char w ')'
134134+119135(** {1 Search Keys} *)
120136121137let rec search_key w = function
···245261 write_partial w partial
246262 | Fetch.Binary_size section ->
247263 W.string w "BINARY.SIZE["; W.string w section; W.char w ']'
264264+ | Fetch.Modseq ->
265265+ (* RFC 7162 Section 3.1.5: MODSEQ fetch data item *)
266266+ W.string w "MODSEQ"
248267249268let fetch_items w = function
250269 | [ item ] -> fetch_item w item
···266285 | Status.Unseen -> W.string w "UNSEEN"
267286 | Status.Deleted -> W.string w "DELETED"
268287 | Status.Size -> W.string w "SIZE"
288288+ | Status.Highestmodseq -> W.string w "HIGHESTMODSEQ" (* RFC 7162 CONDSTORE *)
269289270290let status_items w items =
271291 W.char w '(';
···307327 criteria;
308328 W.char w ')'
309329330330+(** {1 Thread Algorithm} *)
331331+332332+let thread_algorithm w = function
333333+ | Thread.Orderedsubject -> W.string w "ORDEREDSUBJECT"
334334+ | Thread.References -> W.string w "REFERENCES"
335335+ | Thread.Extension s -> W.string w (String.uppercase_ascii s)
336336+310337(** {1 ID Parameters} *)
311338312339let id_params w = function
···324351325352(** {1 Commands} *)
326353327327-let write_search w charset criteria =
354354+let write_search w charset criteria return_opts =
328355 W.string w "SEARCH";
356356+ Option.iter (fun opts -> sp w; search_return_opts w opts) return_opts;
329357 Option.iter (fun cs -> W.string w " CHARSET "; astring w cs) charset;
330358 sp w;
331359 search_key w criteria
···333361let write_sort w charset criteria search =
334362 W.string w "SORT ";
335363 sort_criteria w criteria;
364364+ sp w;
365365+ astring w charset;
366366+ sp w;
367367+ search_key w search
368368+369369+let write_thread w algorithm charset search =
370370+ W.string w "THREAD ";
371371+ thread_algorithm w algorithm;
336372 sp w;
337373 astring w charset;
338374 sp w;
···403439 | Command.Close -> W.string w "CLOSE"
404440 | Command.Unselect -> W.string w "UNSELECT"
405441 | Command.Expunge -> W.string w "EXPUNGE"
406406- | Command.Search { charset; criteria } ->
407407- write_search w charset criteria
442442+ | Command.Search { charset; criteria; return_opts } ->
443443+ write_search w charset criteria return_opts
408444 | Command.Sort { charset; criteria; search } ->
409445 write_sort w charset criteria search
446446+ | Command.Thread { algorithm; charset; search } ->
447447+ write_thread w algorithm charset search
410448 | Command.Fetch { sequence; items; changedsince } ->
411449 W.string w "FETCH ";
412450 sequence_set w sequence;
···476514 sequence_set w sequence;
477515 sp w;
478516 astring w mailbox
479479- | Command.Uid_search { charset; criteria } ->
480480- write_search w charset criteria
517517+ | Command.Uid_search { charset; criteria; return_opts } ->
518518+ write_search w charset criteria return_opts
481519 | Command.Uid_sort { charset; criteria; search } ->
482520 write_sort w charset criteria search
521521+ | Command.Uid_thread { algorithm; charset; search } ->
522522+ write_thread w algorithm charset search
483523 | Command.Uid_expunge set ->
484524 W.string w "EXPUNGE ";
485525 sequence_set w set)
+4
ocaml-imap/lib/imap/write.mli
···4343val fetch_item : t -> Fetch.request -> unit
4444val fetch_items : t -> Fetch.request list -> unit
45454646+(** {1 Thread} *)
4747+4848+val thread_algorithm : t -> Thread.algorithm -> unit
4949+4650(** {1 Commands} *)
47514852val command : t -> tag:string -> Command.t -> unit
+2-2
ocaml-imap/lib/imapd/client.ml
···132132 | Flags_response f -> flags := f
133133 | Capability_response c -> caps := c
134134 | Enabled e -> enabled := e
135135- | List_response { flags = f; delimiter; name } ->
135135+ | List_response { flags = f; delimiter; name; _ } ->
136136 list_entries := { flags = f; delimiter; name } :: !list_entries
137137 | Status_response { mailbox; items } ->
138138 let messages =
···554554555555let list t ~reference ~pattern =
556556 require_authenticated t;
557557- let responses = run_command t (List { reference; pattern }) in
557557+ let responses = run_command t (List (List_basic { reference; pattern })) in
558558 let _, _, _, _, _, _, _, _, entries, _, _, _, _, _, _, _ =
559559 process_untagged responses
560560 in
···1212open Protocol
13131414(* Re-export types from Types for backward compatibility *)
1515+type thread_algorithm = Protocol.thread_algorithm =
1616+ | Thread_orderedsubject
1717+ | Thread_references
1818+ | Thread_extension of string
1919+2020+type thread_node = Protocol.thread_node =
2121+ | Thread_message of int * thread_node list
2222+ | Thread_dummy of thread_node list
2323+2424+type thread_result = Protocol.thread_result
2525+1526type command = Protocol.command =
1627 | Capability
1728 | Noop
···2738 | Rename of { old_name : mailbox_name; new_name : mailbox_name }
2839 | Subscribe of mailbox_name
2940 | Unsubscribe of mailbox_name
3030- | List of { reference : string; pattern : string }
4141+ | List of list_command (** LIST command - RFC 9051, RFC 5258 LIST-EXTENDED *)
3142 | Namespace
3243 | Status of { mailbox : mailbox_name; items : status_item list }
3344 | Append of { mailbox : mailbox_name; flags : flag list; date : string option; message : string }
···4253 | Move of { sequence : sequence_set; mailbox : mailbox_name }
4354 | Uid of uid_command
4455 | Id of (string * string) list option
5656+ (* QUOTA extension - RFC 9208 *)
5757+ | Getquota of string
5858+ | Getquotaroot of mailbox_name
5959+ | Setquota of { root : string; limits : (quota_resource * int64) list }
6060+ (* THREAD extension - RFC 5256 *)
6161+ | Thread of { algorithm : thread_algorithm; charset : string; criteria : search_key }
45624663type uid_command = Protocol.uid_command =
4764 | Uid_fetch of { sequence : sequence_set; items : fetch_item list }
···5067 | Uid_move of { sequence : sequence_set; mailbox : mailbox_name }
5168 | Uid_search of { charset : string option; criteria : search_key }
5269 | Uid_expunge of sequence_set
7070+ | Uid_thread of { algorithm : thread_algorithm; charset : string; criteria : search_key }
53715472type tagged_command = Protocol.tagged_command = {
5573 tag : string;
···6482 | Bye of { code : response_code option; text : string }
6583 | Capability_response of string list
6684 | Enabled of string list
6767- | List_response of { flags : list_flag list; delimiter : char option; name : mailbox_name }
8585+ | List_response of list_response_data (** RFC 9051, RFC 5258 LIST-EXTENDED *)
6886 | Namespace_response of namespace_data
6987 | Status_response of { mailbox : mailbox_name; items : (status_item * int64) list }
7088 | Esearch of { tag : string option; uid : bool; results : esearch_result list }
···7492 | Fetch_response of { seq : int; items : fetch_response_item list }
7593 | Continuation of string option
7694 | Id_response of (string * string) list option
9595+ (* QUOTA extension responses - RFC 9208 *)
9696+ | Quota_response of { root : string; resources : quota_resource_info list }
9797+ | Quotaroot_response of { mailbox : mailbox_name; roots : string list }
9898+ (* THREAD extension response - RFC 5256 *)
9999+ | Thread_response of thread_result
7710078101(* ===== Menhir Parser Interface ===== *)
79102···113136 write_string f "}\r\n";
114137 write_string f s
115138139139+(** Convert quota resource to IMAP string.
140140+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5}RFC 9208 Section 5}. *)
141141+let quota_resource_to_string = function
142142+ | Quota_storage -> "STORAGE"
143143+ | Quota_message -> "MESSAGE"
144144+ | Quota_mailbox -> "MAILBOX"
145145+ | Quota_annotation_storage -> "ANNOTATION-STORAGE"
146146+116147let write_flag f flag =
117148 write_string f (flag_to_string flag)
118149···219250 List.iter (fun c -> write_sp f; write_string f c) caps;
220251 write_crlf f
221252222222- | List_response { flags; delimiter; name } ->
253253+ | List_response { flags; delimiter; name; extended } ->
254254+ (* LIST response per RFC 9051 Section 7.3.1, RFC 5258 Section 3.4 *)
223255 write_string f "* LIST (";
224256 List.iteri (fun i flag ->
225257 if i > 0 then write_sp f;
···231263 | List_subscribed -> write_string f "\\Subscribed"
232264 | List_haschildren -> write_string f "\\HasChildren"
233265 | List_hasnochildren -> write_string f "\\HasNoChildren"
266266+ | List_nonexistent -> write_string f "\\NonExistent" (* RFC 5258 Section 3.4 *)
267267+ | List_remote -> write_string f "\\Remote" (* RFC 5258 Section 3.4 *)
234268 | List_all -> write_string f "\\All"
235269 | List_archive -> write_string f "\\Archive"
236270 | List_drafts -> write_string f "\\Drafts"
···246280 | None -> write_string f "NIL");
247281 write_sp f;
248282 write_quoted_string f name;
283283+ (* Extended data per RFC 5258 Section 3.5 *)
284284+ List.iter (fun ext ->
285285+ match ext with
286286+ | Childinfo subscriptions ->
287287+ (* CHILDINFO extended data item: "CHILDINFO" SP "(" tag-list ")" *)
288288+ write_sp f;
289289+ write_string f "(\"CHILDINFO\" (";
290290+ List.iteri (fun i tag ->
291291+ if i > 0 then write_sp f;
292292+ write_quoted_string f tag
293293+ ) subscriptions;
294294+ write_string f "))"
295295+ ) extended;
249296 write_crlf f
250297251298 | Namespace_response { personal; other; shared } ->
···371418 write_quoted_string f value
372419 ) pairs;
373420 write_char f ')');
421421+ write_crlf f
422422+423423+ (* QUOTA extension responses - RFC 9208 *)
424424+ | Quota_response { root; resources } ->
425425+ (* QUOTA response format: * QUOTA root (resource usage limit ...) *)
426426+ write_string f "* QUOTA ";
427427+ write_quoted_string f root;
428428+ write_string f " (";
429429+ List.iteri (fun i { resource; usage; limit } ->
430430+ if i > 0 then write_sp f;
431431+ write_string f (quota_resource_to_string resource);
432432+ write_sp f;
433433+ write_string f (Int64.to_string usage);
434434+ write_sp f;
435435+ write_string f (Int64.to_string limit)
436436+ ) resources;
437437+ write_char f ')';
438438+ write_crlf f
439439+440440+ | Quotaroot_response { mailbox; roots } ->
441441+ (* QUOTAROOT response format: * QUOTAROOT mailbox root ... *)
442442+ write_string f "* QUOTAROOT ";
443443+ write_quoted_string f mailbox;
444444+ List.iter (fun root ->
445445+ write_sp f;
446446+ write_quoted_string f root
447447+ ) roots;
448448+ write_crlf f
449449+450450+ (* THREAD extension response - RFC 5256 Section 4 *)
451451+ | Thread_response threads ->
452452+ (* THREAD response format: * THREAD [SP 1*thread-list]
453453+ Each thread node is either:
454454+ - (n) for a single message
455455+ - (n children...) for a message with children
456456+ - ((children...)) for a dummy parent
457457+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-4> RFC 5256 Section 4 *)
458458+ let rec write_thread_node = function
459459+ | Thread_message (n, []) ->
460460+ (* Single message with no children: (n) *)
461461+ write_char f '(';
462462+ write_string f (string_of_int n);
463463+ write_char f ')'
464464+ | Thread_message (n, children) ->
465465+ (* Message with children: (n child1 child2 ...) *)
466466+ write_char f '(';
467467+ write_string f (string_of_int n);
468468+ List.iter (fun child ->
469469+ write_sp f;
470470+ write_thread_node child
471471+ ) children;
472472+ write_char f ')'
473473+ | Thread_dummy children ->
474474+ (* Dummy node (missing parent): ((child1)(child2)...) *)
475475+ write_char f '(';
476476+ List.iteri (fun i child ->
477477+ if i > 0 then write_sp f;
478478+ write_thread_node child
479479+ ) children;
480480+ write_char f ')'
481481+ in
482482+ write_string f "* THREAD";
483483+ List.iter (fun thread ->
484484+ write_sp f;
485485+ write_thread_node thread
486486+ ) threads;
374487 write_crlf f
375488376489let response_to_string resp =
+19-2
ocaml-imap/lib/imapd/parser.mli
···15151616 Types are defined in {!Protocol} and re-exported here for convenience. *)
17171818+type thread_algorithm = Protocol.thread_algorithm =
1919+ | Thread_orderedsubject
2020+ | Thread_references
2121+ | Thread_extension of string
2222+1823type command = Protocol.command =
1924 | Capability
2025 | Noop
···3035 | Rename of { old_name : mailbox_name; new_name : mailbox_name }
3136 | Subscribe of mailbox_name
3237 | Unsubscribe of mailbox_name
3333- | List of { reference : string; pattern : string }
3838+ | List of list_command
3439 | Namespace
3540 | Status of { mailbox : mailbox_name; items : status_item list }
3641 | Append of { mailbox : mailbox_name; flags : flag list; date : string option; message : string }
···4550 | Move of { sequence : sequence_set; mailbox : mailbox_name }
4651 | Uid of uid_command
4752 | Id of (string * string) list option
5353+ (* QUOTA extension - RFC 9208 *)
5454+ | Getquota of string
5555+ | Getquotaroot of mailbox_name
5656+ | Setquota of { root : string; limits : (quota_resource * int64) list }
5757+ (* THREAD extension - RFC 5256 *)
5858+ | Thread of { algorithm : thread_algorithm; charset : string; criteria : search_key }
48594960type uid_command = Protocol.uid_command =
5061 | Uid_fetch of { sequence : sequence_set; items : fetch_item list }
···5364 | Uid_move of { sequence : sequence_set; mailbox : mailbox_name }
5465 | Uid_search of { charset : string option; criteria : search_key }
5566 | Uid_expunge of sequence_set
6767+ | Uid_thread of { algorithm : thread_algorithm; charset : string; criteria : search_key }
56685769type tagged_command = Protocol.tagged_command = {
5870 tag : string;
···6779 | Bye of { code : response_code option; text : string }
6880 | Capability_response of string list
6981 | Enabled of string list
7070- | List_response of { flags : list_flag list; delimiter : char option; name : mailbox_name }
8282+ | List_response of list_response_data
7183 | Namespace_response of namespace_data
7284 | Status_response of { mailbox : mailbox_name; items : (status_item * int64) list }
7385 | Esearch of { tag : string option; uid : bool; results : esearch_result list }
···7789 | Fetch_response of { seq : int; items : fetch_response_item list }
7890 | Continuation of string option
7991 | Id_response of (string * string) list option
9292+ (* QUOTA extension responses - RFC 9208 *)
9393+ | Quota_response of { root : string; resources : quota_resource_info list }
9494+ | Quotaroot_response of { mailbox : mailbox_name; roots : string list }
9595+ (* THREAD extension response - RFC 5256 *)
9696+ | Thread_response of thread_result
80978198(** {1 Parsing} *)
8299
+127-3
ocaml-imap/lib/imapd/protocol.ml
···183183 | Status_deleted
184184 | Status_size
185185186186-(* LIST flags - RFC 9051 Section 7.3.1 *)
186186+(* LIST flags - RFC 9051 Section 7.3.1, RFC 5258 Section 3.4 *)
187187type list_flag =
188188 | List_noinferiors
189189 | List_noselect
···192192 | List_subscribed
193193 | List_haschildren
194194 | List_hasnochildren
195195+ | List_nonexistent (** RFC 5258 Section 3.4 - Mailbox name refers to non-existent mailbox *)
196196+ | List_remote (** RFC 5258 Section 3.4 - Mailbox is remote, not on this server *)
195197 | List_all
196198 | List_archive
197199 | List_drafts
···200202 | List_sent
201203 | List_trash
202204 | List_extension of string
205205+206206+(** LIST selection options per RFC 5258 Section 3.1
207207+208208+ Selection options control which mailboxes are returned by LIST:
209209+ - SUBSCRIBED: Return subscribed mailboxes (like LSUB)
210210+ - REMOTE: Include remote mailboxes (not on this server)
211211+ - RECURSIVEMATCH: Include ancestors of matched mailboxes
212212+ - SPECIAL-USE: Return only special-use mailboxes (RFC 6154) *)
213213+type list_select_option =
214214+ | List_select_subscribed (** RFC 5258 Section 3.1.1 *)
215215+ | List_select_remote (** RFC 5258 Section 3.1.2 *)
216216+ | List_select_recursivematch (** RFC 5258 Section 3.1.3 *)
217217+ | List_select_special_use (** RFC 6154 Section 3 *)
218218+219219+(** LIST return options per RFC 5258 Section 3.2
220220+221221+ Return options control what additional data is returned:
222222+ - SUBSCRIBED: Include \Subscribed flag
223223+ - CHILDREN: Include \HasChildren/\HasNoChildren flags
224224+ - SPECIAL-USE: Include special-use flags (RFC 6154) *)
225225+type list_return_option =
226226+ | List_return_subscribed (** RFC 5258 Section 3.2.1 *)
227227+ | List_return_children (** RFC 5258 Section 3.2.2 *)
228228+ | List_return_special_use (** RFC 6154 Section 3 *)
229229+230230+(** Extended data items in LIST response per RFC 5258 Section 3.5 *)
231231+type list_extended_item =
232232+ | Childinfo of string list (** RFC 5258 Section 3.5 - CHILDINFO extended data *)
233233+234234+(** LIST command variants per RFC 5258 *)
235235+type list_command =
236236+ | List_basic of {
237237+ reference : string; (** Reference name (context for pattern) *)
238238+ pattern : string; (** Mailbox pattern with wildcards *)
239239+ }
240240+ | List_extended of {
241241+ selection : list_select_option list; (** RFC 5258 Section 3.1 *)
242242+ reference : string;
243243+ patterns : string list; (** Multiple patterns allowed *)
244244+ return_opts : list_return_option list; (** RFC 5258 Section 3.2 *)
245245+ }
246246+247247+(** Extended LIST response per RFC 5258 Section 3.4 *)
248248+type list_response_data = {
249249+ flags : list_flag list;
250250+ delimiter : char option;
251251+ name : mailbox_name;
252252+ extended : list_extended_item list; (** RFC 5258 Section 3.5 *)
253253+}
203254204255(* Connection state - RFC 9051 Section 3 *)
205256type connection_state =
···334385 else
335386 None
336387388388+(* === THREAD Types - RFC 5256 === *)
389389+390390+(** Threading algorithm for the THREAD command.
391391+ See {{:https://datatracker.ietf.org/doc/html/rfc5256#section-3}RFC 5256 Section 3}. *)
392392+type thread_algorithm =
393393+ | Thread_orderedsubject
394394+ (** ORDEREDSUBJECT algorithm (RFC 5256 Section 3.1).
395395+ Groups messages by base subject, then sorts by sent date. *)
396396+ | Thread_references
397397+ (** REFERENCES algorithm (RFC 5256 Section 3.2).
398398+ Implements the JWZ threading algorithm using Message-ID,
399399+ In-Reply-To, and References headers. *)
400400+ | Thread_extension of string
401401+ (** Future algorithm extensions. *)
402402+403403+(** A thread node in the THREAD response.
404404+ See {{:https://datatracker.ietf.org/doc/html/rfc5256#section-4}RFC 5256 Section 4}. *)
405405+type thread_node =
406406+ | Thread_message of int * thread_node list
407407+ (** A message with its sequence number/UID and child threads. *)
408408+ | Thread_dummy of thread_node list
409409+ (** A placeholder for a missing parent message. *)
410410+411411+(** Thread result: a list of root-level thread trees.
412412+ See {{:https://datatracker.ietf.org/doc/html/rfc5256#section-4}RFC 5256 Section 4}. *)
413413+type thread_result = thread_node list
414414+415415+(* === Quota Types - RFC 9208 === *)
416416+417417+(** Quota resource types.
418418+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5}RFC 9208 Section 5}. *)
419419+type quota_resource =
420420+ | Quota_storage (** STORAGE - physical space in KB.
421421+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.1}RFC 9208 Section 5.1}. *)
422422+ | Quota_message (** MESSAGE - number of messages.
423423+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.2}RFC 9208 Section 5.2}. *)
424424+ | Quota_mailbox (** MAILBOX - number of mailboxes.
425425+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.3}RFC 9208 Section 5.3}. *)
426426+ | Quota_annotation_storage (** ANNOTATION-STORAGE - annotation size in KB.
427427+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.4}RFC 9208 Section 5.4}. *)
428428+429429+(** A single quota resource with usage and limit.
430430+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-4.2.2}RFC 9208 Section 4.2.2}. *)
431431+type quota_resource_info = {
432432+ resource : quota_resource;
433433+ usage : int64; (** Current usage *)
434434+ limit : int64; (** Maximum allowed *)
435435+}
436436+337437(* === Commands - RFC 9051 Section 6 === *)
338438339439type command =
···351451 | Rename of { old_name : mailbox_name; new_name : mailbox_name }
352452 | Subscribe of mailbox_name
353453 | Unsubscribe of mailbox_name
354354- | List of { reference : string; pattern : string }
454454+ | List of list_command (** LIST command - RFC 9051, RFC 5258 LIST-EXTENDED *)
355455 | Namespace
356456 | Status of { mailbox : mailbox_name; items : status_item list }
357457 | Append of { mailbox : mailbox_name; flags : flag list; date : string option; message : string }
···366466 | Move of { sequence : sequence_set; mailbox : mailbox_name }
367467 | Uid of uid_command
368468 | Id of (string * string) list option (** RFC 2971 - NIL or list of field/value pairs *)
469469+ (* QUOTA extension - RFC 9208 *)
470470+ | Getquota of string (** GETQUOTA quota-root.
471471+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-4.2}RFC 9208 Section 4.2}. *)
472472+ | Getquotaroot of mailbox_name (** GETQUOTAROOT mailbox.
473473+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-4.3}RFC 9208 Section 4.3}. *)
474474+ | Setquota of { root : string; limits : (quota_resource * int64) list } (** SETQUOTA.
475475+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-4.1}RFC 9208 Section 4.1}. *)
476476+ (* THREAD extension - RFC 5256 *)
477477+ | Thread of { algorithm : thread_algorithm; charset : string; criteria : search_key }
478478+ (** THREAD command.
479479+ See {{:https://datatracker.ietf.org/doc/html/rfc5256#section-3}RFC 5256 Section 3}. *)
369480370481and uid_command =
371482 | Uid_fetch of { sequence : sequence_set; items : fetch_item list }
···374485 | Uid_move of { sequence : sequence_set; mailbox : mailbox_name }
375486 | Uid_search of { charset : string option; criteria : search_key }
376487 | Uid_expunge of sequence_set
488488+ | Uid_thread of { algorithm : thread_algorithm; charset : string; criteria : search_key }
489489+ (** UID THREAD command - RFC 5256. Returns UIDs instead of sequence numbers. *)
377490378491type tagged_command = {
379492 tag : string;
···419532 | Bye of { code : response_code option; text : string }
420533 | Capability_response of string list
421534 | Enabled of string list
422422- | List_response of { flags : list_flag list; delimiter : char option; name : mailbox_name }
535535+ | List_response of list_response_data (** RFC 9051, RFC 5258 LIST-EXTENDED *)
423536 | Namespace_response of namespace_data
424537 | Status_response of { mailbox : mailbox_name; items : (status_item * int64) list }
425538 | Esearch of { tag : string option; uid : bool; results : esearch_result list }
···429542 | Fetch_response of { seq : int; items : fetch_response_item list }
430543 | Continuation of string option
431544 | Id_response of (string * string) list option (** RFC 2971 - NIL or list of field/value pairs *)
545545+ (* QUOTA extension responses - RFC 9208 *)
546546+ | Quota_response of { root : string; resources : quota_resource_info list }
547547+ (** QUOTA response.
548548+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.1}RFC 9208 Section 5.1}. *)
549549+ | Quotaroot_response of { mailbox : mailbox_name; roots : string list }
550550+ (** QUOTAROOT response.
551551+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.2}RFC 9208 Section 5.2}. *)
552552+ (* THREAD extension response - RFC 5256 *)
553553+ | Thread_response of thread_result
554554+ (** THREAD response - a list of thread trees.
555555+ See {{:https://datatracker.ietf.org/doc/html/rfc5256#section-4}RFC 5256 Section 4}. *)
+115-2
ocaml-imap/lib/imapd/protocol.mli
···245245 | List_subscribed (** \Subscribed *)
246246 | List_haschildren (** \HasChildren *)
247247 | List_hasnochildren (** \HasNoChildren *)
248248+ | List_nonexistent (** \NonExistent - RFC 5258 Section 3.4 *)
249249+ | List_remote (** \Remote - RFC 5258 Section 3.4 *)
248250 | List_all (** \All - special-use *)
249251 | List_archive (** \Archive *)
250252 | List_drafts (** \Drafts *)
···253255 | List_sent (** \Sent *)
254256 | List_trash (** \Trash *)
255257 | List_extension of string (** Other flags *)
258258+259259+(** LIST selection options per RFC 5258 Section 3.1 *)
260260+type list_select_option =
261261+ | List_select_subscribed (** RFC 5258 Section 3.1.1 *)
262262+ | List_select_remote (** RFC 5258 Section 3.1.2 *)
263263+ | List_select_recursivematch (** RFC 5258 Section 3.1.3 *)
264264+ | List_select_special_use (** RFC 6154 Section 3 *)
265265+266266+(** LIST return options per RFC 5258 Section 3.2 *)
267267+type list_return_option =
268268+ | List_return_subscribed (** RFC 5258 Section 3.2.1 *)
269269+ | List_return_children (** RFC 5258 Section 3.2.2 *)
270270+ | List_return_special_use (** RFC 6154 Section 3 *)
271271+272272+(** Extended data items in LIST response per RFC 5258 Section 3.5 *)
273273+type list_extended_item =
274274+ | Childinfo of string list (** RFC 5258 Section 3.5 - CHILDINFO extended data *)
275275+276276+(** LIST command variants per RFC 5258 *)
277277+type list_command =
278278+ | List_basic of {
279279+ reference : string; (** Reference name *)
280280+ pattern : string; (** Mailbox pattern *)
281281+ }
282282+ | List_extended of {
283283+ selection : list_select_option list;
284284+ reference : string;
285285+ patterns : string list;
286286+ return_opts : list_return_option list;
287287+ }
288288+289289+(** Extended LIST response per RFC 5258 Section 3.4 *)
290290+type list_response_data = {
291291+ flags : list_flag list;
292292+ delimiter : char option;
293293+ name : mailbox_name;
294294+ extended : list_extended_item list;
295295+}
256296257297(** {1 Connection State}
258298···332372 | Code_unknown_cte
333373 | Code_other of string * string option
334374375375+(** {1 Quota Types}
376376+377377+ See {{:https://datatracker.ietf.org/doc/html/rfc9208}RFC 9208 - IMAP QUOTA Extension}. *)
378378+379379+(** Quota resource types.
380380+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5}RFC 9208 Section 5}. *)
381381+type quota_resource =
382382+ | Quota_storage (** STORAGE - physical space in KB.
383383+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.1}RFC 9208 Section 5.1}. *)
384384+ | Quota_message (** MESSAGE - number of messages.
385385+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.2}RFC 9208 Section 5.2}. *)
386386+ | Quota_mailbox (** MAILBOX - number of mailboxes.
387387+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.3}RFC 9208 Section 5.3}. *)
388388+ | Quota_annotation_storage (** ANNOTATION-STORAGE - annotation size in KB.
389389+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.4}RFC 9208 Section 5.4}. *)
390390+391391+(** A single quota resource with usage and limit.
392392+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-4.2.2}RFC 9208 Section 4.2.2}. *)
393393+type quota_resource_info = {
394394+ resource : quota_resource;
395395+ usage : int64; (** Current usage *)
396396+ limit : int64; (** Maximum allowed *)
397397+}
398398+399399+(** {1 Thread Types}
400400+401401+ See {{:https://datatracker.ietf.org/doc/html/rfc5256}RFC 5256 - IMAP SORT and THREAD Extensions}. *)
402402+403403+(** Threading algorithm for the THREAD command.
404404+ See {{:https://datatracker.ietf.org/doc/html/rfc5256#section-3}RFC 5256 Section 3}. *)
405405+type thread_algorithm =
406406+ | Thread_orderedsubject
407407+ (** ORDEREDSUBJECT algorithm (RFC 5256 Section 3.1). *)
408408+ | Thread_references
409409+ (** REFERENCES algorithm (RFC 5256 Section 3.2). *)
410410+ | Thread_extension of string
411411+ (** Future algorithm extensions. *)
412412+413413+(** A thread node in the THREAD response.
414414+ See {{:https://datatracker.ietf.org/doc/html/rfc5256#section-4}RFC 5256 Section 4}. *)
415415+type thread_node =
416416+ | Thread_message of int * thread_node list
417417+ (** A message with its sequence number/UID and child threads. *)
418418+ | Thread_dummy of thread_node list
419419+ (** A placeholder for a missing parent message. *)
420420+421421+(** Thread result: a list of root-level thread trees. *)
422422+type thread_result = thread_node list
423423+335424(** {1 Commands}
336425337426 See {{:https://datatracker.ietf.org/doc/html/rfc9051#section-6}RFC 9051 Section 6}. *)
···351440 | Rename of { old_name : mailbox_name; new_name : mailbox_name }
352441 | Subscribe of mailbox_name
353442 | Unsubscribe of mailbox_name
354354- | List of { reference : string; pattern : string }
443443+ | List of list_command (** LIST command - RFC 9051, RFC 5258 LIST-EXTENDED *)
355444 | Namespace
356445 | Status of { mailbox : mailbox_name; items : status_item list }
357446 | Append of { mailbox : mailbox_name; flags : flag list; date : string option; message : string }
···366455 | Move of { sequence : sequence_set; mailbox : mailbox_name }
367456 | Uid of uid_command
368457 | Id of (string * string) list option (** RFC 2971 *)
458458+ (* QUOTA extension - RFC 9208 *)
459459+ | Getquota of string (** GETQUOTA quota-root.
460460+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-4.2}RFC 9208 Section 4.2}. *)
461461+ | Getquotaroot of mailbox_name (** GETQUOTAROOT mailbox.
462462+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-4.3}RFC 9208 Section 4.3}. *)
463463+ | Setquota of { root : string; limits : (quota_resource * int64) list } (** SETQUOTA.
464464+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-4.1}RFC 9208 Section 4.1}. *)
465465+ (* THREAD extension - RFC 5256 *)
466466+ | Thread of { algorithm : thread_algorithm; charset : string; criteria : search_key }
467467+ (** THREAD command.
468468+ See {{:https://datatracker.ietf.org/doc/html/rfc5256#section-3}RFC 5256 Section 3}. *)
369469370470and uid_command =
371471 | Uid_fetch of { sequence : sequence_set; items : fetch_item list }
···374474 | Uid_move of { sequence : sequence_set; mailbox : mailbox_name }
375475 | Uid_search of { charset : string option; criteria : search_key }
376476 | Uid_expunge of sequence_set
477477+ | Uid_thread of { algorithm : thread_algorithm; charset : string; criteria : search_key }
478478+ (** UID THREAD command - RFC 5256. Returns UIDs instead of sequence numbers. *)
377479378480type tagged_command = {
379481 tag : string;
···421523 | Bye of { code : response_code option; text : string }
422524 | Capability_response of string list
423525 | Enabled of string list
424424- | List_response of { flags : list_flag list; delimiter : char option; name : mailbox_name }
526526+ | List_response of list_response_data (** RFC 9051, RFC 5258 LIST-EXTENDED *)
425527 | Namespace_response of namespace_data
426528 | Status_response of { mailbox : mailbox_name; items : (status_item * int64) list }
427529 | Esearch of { tag : string option; uid : bool; results : esearch_result list }
···431533 | Fetch_response of { seq : int; items : fetch_response_item list }
432534 | Continuation of string option
433535 | Id_response of (string * string) list option (** RFC 2971 *)
536536+ (* QUOTA extension responses - RFC 9208 *)
537537+ | Quota_response of { root : string; resources : quota_resource_info list }
538538+ (** QUOTA response.
539539+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.1}RFC 9208 Section 5.1}. *)
540540+ | Quotaroot_response of { mailbox : mailbox_name; roots : string list }
541541+ (** QUOTAROOT response.
542542+ See {{:https://datatracker.ietf.org/doc/html/rfc9208#section-5.2}RFC 9208 Section 5.2}. *)
543543+ (* THREAD extension response - RFC 5256 *)
544544+ | Thread_response of thread_result
545545+ (** THREAD response containing thread tree.
546546+ See {{:https://datatracker.ietf.org/doc/html/rfc5256#section-4}RFC 5256 Section 4}. *)
434547435548(** {1 Utility Functions} *)
436549
+1-1
ocaml-imap/lib/imapd/read.ml
···870870 sp r;
871871 let name = astring r in
872872 crlf r;
873873- List_response { flags; delimiter; name }
873873+ List_response { flags; delimiter; name; extended = [] }
874874 | "STATUS" ->
875875 sp r;
876876 let mailbox = astring r in
+172-49
ocaml-imap/lib/imapd/server.ml
···1313(* Module alias to access Storage types without conflicting with functor parameter *)
1414module Storage_types = Storage
15151616-(* Base capabilities per RFC 9051 *)
1616+(** Base capabilities per RFC 9051.
1717+ @see <https://datatracker.ietf.org/doc/html/rfc9051> RFC 9051: IMAP4rev2
1818+ @see <https://datatracker.ietf.org/doc/html/rfc6855#section-3> RFC 6855 Section 3: UTF8=ACCEPT *)
1719let base_capabilities_pre_tls = [
1820 "IMAP4rev2";
1921 "IMAP4rev1"; (* For compatibility *)
···2628 "ENABLE";
2729 "LITERAL+";
2830 "ID";
3131+ "UNSELECT"; (* RFC 3691 *)
3232+ "SPECIAL-USE"; (* RFC 6154 *)
3333+ "LIST-EXTENDED"; (* RFC 5258 *)
3434+ "CONDSTORE"; (* RFC 7162 - modification sequences for flags *)
3535+ (* QUOTA extension - RFC 9208 *)
3636+ "QUOTA";
3737+ "QUOTA=RES-STORAGE"; (* RFC 9208 Section 5.1 *)
3838+ "QUOTA=RES-MESSAGE"; (* RFC 9208 Section 5.2 *)
3939+ (* UTF-8 support - RFC 6855 *)
4040+ "UTF8=ACCEPT"; (* RFC 6855 Section 3 *)
4141+ (* THREAD extension - RFC 5256 *)
4242+ "THREAD=ORDEREDSUBJECT"; (* RFC 5256 Section 3.1 *)
4343+ "THREAD=REFERENCES"; (* RFC 5256 Section 3.2 *)
2944]
30453146let base_capabilities_post_tls = [
···3954 "ENABLE";
4055 "LITERAL+";
4156 "ID";
5757+ "UNSELECT"; (* RFC 3691 *)
5858+ "SPECIAL-USE"; (* RFC 6154 *)
5959+ "LIST-EXTENDED"; (* RFC 5258 *)
6060+ "CONDSTORE"; (* RFC 7162 - modification sequences for flags *)
6161+ (* QUOTA extension - RFC 9208 *)
6262+ "QUOTA";
6363+ "QUOTA=RES-STORAGE"; (* RFC 9208 Section 5.1 *)
6464+ "QUOTA=RES-MESSAGE"; (* RFC 9208 Section 5.2 *)
6565+ (* UTF-8 support - RFC 6855 *)
6666+ "UTF8=ACCEPT"; (* RFC 6855 Section 3 *)
6767+ (* THREAD extension - RFC 5256 *)
6868+ "THREAD=ORDEREDSUBJECT"; (* RFC 5256 Section 3.1 *)
6969+ "THREAD=REFERENCES"; (* RFC 5256 Section 3.2 *)
4270]
43714472(* Server configuration *)
···6290 (Storage : Storage.STORAGE)
6391 (Auth : Auth.AUTH) = struct
64929393+ (** Connection state with UTF-8 mode tracking.
9494+ @see <https://datatracker.ietf.org/doc/html/rfc6855#section-3> RFC 6855 Section 3 *)
6595 type connection_state =
6696 | Not_authenticated
6767- | Authenticated of { username : string }
6868- | Selected of { username : string; mailbox : string; readonly : bool }
9797+ | Authenticated of { username : string; utf8_enabled : bool }
9898+ | Selected of { username : string; mailbox : string; readonly : bool; utf8_enabled : bool }
6999 | Logout
7010071101 (* Action returned by command handlers *)
···151181 code = Some (Code_capability caps);
152182 text = "LOGIN completed"
153183 });
154154- Authenticated { username }
184184+ Authenticated { username; utf8_enabled = false }
155185 end else begin
156186 send_response flow (No {
157187 tag = Some tag;
···170200171201 (* Process SELECT/EXAMINE command - only valid in Authenticated/Selected state *)
172202 let handle_select t flow tag mailbox ~readonly state =
173173- let username = match state with
174174- | Authenticated { username } -> Some username
175175- | Selected { username; _ } -> Some username
176176- | _ -> None
203203+ let username, utf8_enabled = match state with
204204+ | Authenticated { username; utf8_enabled } -> Some username, utf8_enabled
205205+ | Selected { username; utf8_enabled; _ } -> Some username, utf8_enabled
206206+ | _ -> None, false
177207 in
178208 match username with
179209 | None ->
···191221 code = None;
192222 text = "Invalid mailbox name"
193223 });
194194- Authenticated { username }
224224+ Authenticated { username; utf8_enabled }
195225 end else
196226 match Storage.select_mailbox t.storage ~username mailbox ~readonly with
197227 | Error _ ->
···200230 code = Some Code_nonexistent;
201231 text = "Mailbox does not exist"
202232 });
203203- Authenticated { username }
233233+ Authenticated { username; utf8_enabled }
204234 | Ok mb_state ->
205235 (* Send untagged responses *)
206236 send_response flow (Flags_response mb_state.flags);
···227257 code;
228258 text = if readonly then "EXAMINE completed" else "SELECT completed"
229259 });
230230- Selected { username; mailbox; readonly }
260260+ Selected { username; mailbox; readonly; utf8_enabled }
231261232232- (* Process LIST command *)
233233- let handle_list t flow tag ~reference ~pattern state =
262262+ (* Process LIST command - RFC 9051, RFC 5258 LIST-EXTENDED *)
263263+ let handle_list t flow tag list_cmd state =
234264 let username = match state with
235235- | Authenticated { username } -> Some username
265265+ | Authenticated { username; _ } -> Some username
236266 | Selected { username; _ } -> Some username
237267 | _ -> None
238268 in
···245275 });
246276 state
247277 | Some username ->
248248- let mailboxes = Storage.list_mailboxes t.storage ~username ~reference ~pattern in
278278+ (* Extract reference and patterns from list command *)
279279+ let reference, patterns = match list_cmd with
280280+ | List_basic { reference; pattern } -> (reference, [pattern])
281281+ | List_extended { reference; patterns; _ } -> (reference, patterns)
282282+ in
283283+ (* Process each pattern and collect mailboxes *)
284284+ let all_mailboxes = List.concat_map (fun pattern ->
285285+ Storage.list_mailboxes t.storage ~username ~reference ~pattern
286286+ ) patterns in
287287+ (* Send LIST responses with extended data if needed *)
249288 List.iter (fun (mb : Storage_types.mailbox_info) ->
250289 send_response flow (List_response {
251290 flags = mb.flags;
252291 delimiter = mb.delimiter;
253292 name = mb.name;
293293+ extended = []; (* Extended data can be populated based on return options *)
254294 })
255255- ) mailboxes;
295295+ ) all_mailboxes;
256296 send_response flow (Ok { tag = Some tag; code = None; text = "LIST completed" });
257297 state
258298···264304 state
265305 end else
266306 let username = match state with
267267- | Authenticated { username } -> Some username
307307+ | Authenticated { username; _ } -> Some username
268308 | Selected { username; _ } -> Some username
269309 | _ -> None
270310 in
···320360 (* Process STORE command *)
321361 let handle_store t flow tag ~sequence ~silent ~action ~flags state =
322362 match state with
323323- | Selected { username; mailbox; readonly } ->
363363+ | Selected { username; mailbox; readonly; _ } ->
324364 if readonly then begin
325365 send_response flow (No { tag = Some tag; code = None; text = "Mailbox is read-only" });
326366 state
···351391 (* Process EXPUNGE command *)
352392 let handle_expunge t flow tag state =
353393 match state with
354354- | Selected { username; mailbox; readonly } ->
394394+ | Selected { username; mailbox; readonly; _ } ->
355395 if readonly then begin
356396 send_response flow (No { tag = Some tag; code = None; text = "Mailbox is read-only" });
357397 state
···376416 (* Process CLOSE command *)
377417 let handle_close t flow tag state =
378418 match state with
379379- | Selected { username; mailbox; readonly } ->
419419+ | Selected { username; mailbox; readonly; utf8_enabled } ->
380420 (* Silently expunge if not readonly *)
381421 if not readonly then
382422 ignore (Storage.expunge t.storage ~username ~mailbox);
383423 send_response flow (Ok { tag = Some tag; code = None; text = "CLOSE completed" });
384384- Authenticated { username }
424424+ Authenticated { username; utf8_enabled }
385425 | _ ->
386426 send_response flow (Bad {
387427 tag = Some tag;
···393433 (* Process UNSELECT command *)
394434 let handle_unselect flow tag state =
395435 match state with
396396- | Selected { username; _ } ->
436436+ | Selected { username; utf8_enabled; _ } ->
397437 send_response flow (Ok { tag = Some tag; code = None; text = "UNSELECT completed" });
398398- Authenticated { username }
438438+ Authenticated { username; utf8_enabled }
399439 | _ ->
400440 send_response flow (Bad {
401441 tag = Some tag;
···412452 state
413453 end else
414454 let username = match state with
415415- | Authenticated { username } -> Some username
455455+ | Authenticated { username; _ } -> Some username
416456 | Selected { username; _ } -> Some username
417457 | _ -> None
418458 in
···448488 state
449489 end else
450490 let username = match state with
451451- | Authenticated { username } -> Some username
491491+ | Authenticated { username; _ } -> Some username
452492 | Selected { username; _ } -> Some username
453493 | _ -> None
454494 in
···485525 state
486526 end else
487527 let username = match state with
488488- | Authenticated { username } -> Some username
528528+ | Authenticated { username; _ } -> Some username
489529 | Selected { username; _ } -> Some username
490530 | _ -> None
491531 in
···567607 state
568608 end else
569609 match state with
570570- | Selected { username; mailbox = src_mailbox; readonly } ->
610610+ | Selected { username; mailbox = src_mailbox; readonly; _ } ->
571611 if readonly then begin
572612 send_response flow (No { tag = Some tag; code = None; text = "Mailbox is read-only" });
573613 state
···605645 });
606646 state
607647608608- (* Process SEARCH command *)
609609- let handle_search t flow tag ~charset:_ ~criteria state =
648648+ (** Process SEARCH command.
649649+ @see <https://datatracker.ietf.org/doc/html/rfc6855#section-3> RFC 6855 Section 3
650650+ After ENABLE UTF8=ACCEPT, SEARCH with CHARSET is rejected. *)
651651+ let handle_search t flow tag ~charset ~criteria state =
610652 match state with
611611- | Selected { username; mailbox; _ } ->
612612- (match Storage.search t.storage ~username ~mailbox ~criteria with
613613- | Result.Error _ ->
614614- send_response flow (No { tag = Some tag; code = None; text = "SEARCH failed" })
615615- | Result.Ok uids ->
616616- (* Send ESEARCH response per RFC 9051 *)
617617- let results = if List.length uids > 0 then
618618- [Esearch_count (List.length uids); Esearch_all (List.map (fun uid -> Single (Int32.to_int uid)) uids)]
619619- else
620620- [Esearch_count 0]
621621- in
622622- send_response flow (Esearch { tag = Some tag; uid = false; results });
623623- send_response flow (Ok { tag = Some tag; code = None; text = "SEARCH completed" }));
624624- state
653653+ | Selected { username; mailbox; utf8_enabled; _ } ->
654654+ (* RFC 6855 Section 3: After ENABLE UTF8=ACCEPT, reject SEARCH with CHARSET *)
655655+ if utf8_enabled && Option.is_some charset then begin
656656+ send_response flow (Bad {
657657+ tag = Some tag;
658658+ code = None;
659659+ text = "CHARSET not allowed after ENABLE UTF8=ACCEPT"
660660+ });
661661+ state
662662+ end else begin
663663+ match Storage.search t.storage ~username ~mailbox ~criteria with
664664+ | Result.Error _ ->
665665+ send_response flow (No { tag = Some tag; code = None; text = "SEARCH failed" });
666666+ state
667667+ | Result.Ok uids ->
668668+ (* Send ESEARCH response per RFC 9051 *)
669669+ let results = if List.length uids > 0 then
670670+ [Esearch_count (List.length uids); Esearch_all (List.map (fun uid -> Single (Int32.to_int uid)) uids)]
671671+ else
672672+ [Esearch_count 0]
673673+ in
674674+ send_response flow (Esearch { tag = Some tag; uid = false; results });
675675+ send_response flow (Ok { tag = Some tag; code = None; text = "SEARCH completed" });
676676+ state
677677+ end
625678 | _ ->
626679 send_response flow (Bad {
627680 tag = Some tag;
···630683 });
631684 state
632685686686+ (** Process THREAD command - RFC 5256.
687687+688688+ The THREAD command is used to retrieve message threads from a mailbox.
689689+ It takes an algorithm, charset, and search criteria, returning threads
690690+ of messages matching the criteria.
691691+692692+ Note: This is a basic stub implementation that returns empty threads.
693693+ A full implementation would require:
694694+ - ORDEREDSUBJECT: subject.ml for base subject extraction (RFC 5256 Section 2.1)
695695+ - REFERENCES: Message-ID/In-Reply-To/References header parsing
696696+697697+ @see <https://datatracker.ietf.org/doc/html/rfc5256#section-3> RFC 5256 Section 3 *)
698698+ let handle_thread _t flow tag ~algorithm ~charset:_ ~criteria:_ state =
699699+ match state with
700700+ | Selected { username = _; mailbox = _; _ } ->
701701+ (* TODO: Implement actual threading algorithms.
702702+ For now, return empty thread result.
703703+ Full implementation would:
704704+ 1. Search for messages matching criteria
705705+ 2. Apply ORDEREDSUBJECT or REFERENCES algorithm
706706+ 3. Build thread tree structure *)
707707+ let _ = algorithm in (* Acknowledge the algorithm parameter *)
708708+ send_response flow (Thread_response []);
709709+ send_response flow (Ok { tag = Some tag; code = None; text = "THREAD completed" });
710710+ state
711711+ | _ ->
712712+ send_response flow (Bad {
713713+ tag = Some tag;
714714+ code = None;
715715+ text = "THREAD requires selected state"
716716+ });
717717+ state
718718+633719 (* Process APPEND command *)
634720 let handle_append t flow tag ~mailbox ~flags ~date ~message state =
635721 (* Security: Validate mailbox name *)
···638724 state
639725 end else
640726 let username = match state with
641641- | Authenticated { username } -> Some username
727727+ | Authenticated { username; _ } -> Some username
642728 | Selected { username; _ } -> Some username
643729 | _ -> None
644730 in
···694780 });
695781 state
696782697697- (* Process ENABLE command - RFC 5161 *)
783783+ (** Process ENABLE command - RFC 5161, RFC 6855.
784784+ After ENABLE UTF8=ACCEPT, the session accepts UTF-8 in quoted-strings.
785785+ @see <https://datatracker.ietf.org/doc/html/rfc5161> RFC 5161: ENABLE Extension
786786+ @see <https://datatracker.ietf.org/doc/html/rfc6855#section-3> RFC 6855 Section 3 *)
698787 let handle_enable flow tag ~capabilities state =
699788 match state with
700700- | Authenticated _ ->
789789+ | Authenticated { username; utf8_enabled } ->
701790 (* Filter to capabilities we actually support *)
702791 let enabled = List.filter (fun cap ->
703792 let cap_upper = String.uppercase_ascii cap in
704793 cap_upper = "IMAP4REV2" || cap_upper = "UTF8=ACCEPT"
705794 ) capabilities in
795795+ (* Check if UTF8=ACCEPT was requested and enabled *)
796796+ let new_utf8_enabled = utf8_enabled || List.exists (fun cap ->
797797+ String.uppercase_ascii cap = "UTF8=ACCEPT"
798798+ ) enabled in
706799 if List.length enabled > 0 then
707800 send_response flow (Enabled enabled);
708801 send_response flow (Ok { tag = Some tag; code = None; text = "ENABLE completed" });
709709- state
802802+ Authenticated { username; utf8_enabled = new_utf8_enabled }
710803 | _ ->
711804 send_response flow (Bad {
712805 tag = Some tag;
···779872 | Login { username; password } -> (handle_login t flow tag ~username ~password ~tls_active state, Continue)
780873 | Select mailbox -> (handle_select t flow tag mailbox ~readonly:false state, Continue)
781874 | Examine mailbox -> (handle_select t flow tag mailbox ~readonly:true state, Continue)
782782- | List { reference; pattern } -> (handle_list t flow tag ~reference ~pattern state, Continue)
875875+ | List list_cmd -> (handle_list t flow tag list_cmd state, Continue)
783876 | Status { mailbox; items } -> (handle_status t flow tag mailbox ~items state, Continue)
784877 | Fetch { sequence; items } -> (handle_fetch t flow tag ~sequence ~items state, Continue)
785878 | Store { sequence; silent; action; flags } -> (handle_store t flow tag ~sequence ~silent ~action ~flags state, Continue)
···792885 | Copy { sequence; mailbox } -> (handle_copy t flow tag ~sequence ~mailbox state, Continue)
793886 | Move { sequence; mailbox } -> (handle_move t flow tag ~sequence ~mailbox state, Continue)
794887 | Search { charset; criteria } -> (handle_search t flow tag ~charset ~criteria state, Continue)
888888+ | Thread { algorithm; charset; criteria } -> (handle_thread t flow tag ~algorithm ~charset ~criteria state, Continue)
795889 | Append { mailbox; flags; date; message } -> (handle_append t flow tag ~mailbox ~flags ~date ~message state, Continue)
796890 | Namespace -> (handle_namespace flow tag state, Continue)
797891 | Enable caps -> (handle_enable flow tag ~capabilities:caps state, Continue)
···816910 | Authenticate _ ->
817911 send_response flow (No { tag = Some tag; code = None; text = "Use LOGIN instead" });
818912 (state, Continue)
913913+ (* QUOTA extension - RFC 9208 *)
914914+ | Getquota root ->
915915+ (* GETQUOTA returns quota information for a quota root *)
916916+ (* For now, return empty quota - storage backend would provide real data *)
917917+ send_response flow (Quota_response { root; resources = [] });
918918+ send_response flow (Ok { tag = Some tag; code = None; text = "GETQUOTA completed" });
919919+ (state, Continue)
920920+ | Getquotaroot mailbox ->
921921+ (* GETQUOTAROOT returns the quota roots for a mailbox *)
922922+ (* Typically the user's root is the quota root *)
923923+ let roots = [mailbox] in (* Simplified: use mailbox as its own quota root *)
924924+ send_response flow (Quotaroot_response { mailbox; roots });
925925+ (* Also send QUOTA responses for each root *)
926926+ List.iter (fun root ->
927927+ send_response flow (Quota_response { root; resources = [] })
928928+ ) roots;
929929+ send_response flow (Ok { tag = Some tag; code = None; text = "GETQUOTAROOT completed" });
930930+ (state, Continue)
931931+ | Setquota { root; limits = _ } ->
932932+ (* SETQUOTA is admin-only in most implementations *)
933933+ send_response flow (No {
934934+ tag = Some tag;
935935+ code = Some Code_noperm;
936936+ text = Printf.sprintf "Cannot set quota for %s" root
937937+ });
938938+ (state, Continue)
819939820940 (* Handle UID prefixed commands *)
821941 and handle_uid_command t flow tag ~read_line_fn:_ uid_cmd state =
···834954 | Uid_expunge _sequence ->
835955 (* UID EXPUNGE only expunges messages in the given UID set *)
836956 handle_expunge t flow tag state
957957+ | Uid_thread { algorithm; charset; criteria } ->
958958+ (* UID THREAD returns UIDs instead of sequence numbers *)
959959+ handle_thread t flow tag ~algorithm ~charset ~criteria state
837960838961 (* Maximum line length to prevent DoS attacks via memory exhaustion.
839962 RFC 9051 Section 4 recommends supporting lines up to 8192 octets. *)
···9901113 text = "LOGIN completed"
9911114 });
9921115 (* Continue session as authenticated user *)
993993- let state = Authenticated { username } in
11161116+ let state = Authenticated { username; utf8_enabled = false } in
9941117 ignore (command_loop t flow state tls_active)
9951118 end else begin
9961119 (* Failed to drop privileges *)
+14-2
ocaml-imap/lib/imapd/storage.ml
···6060 flags : list_flag list;
6161}
62626363+(** Get SPECIAL-USE flags for a mailbox based on its name (RFC 6154) *)
6464+let get_special_use_for_mailbox name =
6565+ match String.lowercase_ascii name with
6666+ | "drafts" -> [List_drafts]
6767+ | "sent" | "sent messages" | "sent items" -> [List_sent]
6868+ | "trash" | "deleted messages" | "deleted items" -> [List_trash]
6969+ | "junk" | "spam" -> [List_junk]
7070+ | "archive" -> [List_archive]
7171+ | _ -> []
7272+6373(* Storage backend signature *)
6474module type STORAGE = sig
6575 type t
···168178 ensure_inbox user;
169179 Hashtbl.fold (fun name _mb acc ->
170180 if matches_pattern ~pattern name then
171171- { name; delimiter = Some '/'; flags = [] } :: acc
181181+ let flags = get_special_use_for_mailbox name in
182182+ { name; delimiter = Some '/'; flags } :: acc
172183 else acc
173184 ) user.mailboxes []
174185···700711 let name = String.sub entry 1 (String.length entry - 1) in
701712 let name = String.map (fun c -> if c = '.' then '/' else c) name in
702713 if Memory_storage.matches_pattern ~pattern name then
703703- { name; delimiter = Some '/'; flags = [] } :: acc
714714+ let flags = get_special_use_for_mailbox name in
715715+ { name; delimiter = Some '/'; flags } :: acc
704716 else acc
705717 else acc
706718 else acc
+181
ocaml-imap/lib/imapd/utf8.ml
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** UTF-8 validation per RFC 3629 for RFC 6855 IMAP UTF-8 support.
77+ @see <https://datatracker.ietf.org/doc/html/rfc6855> RFC 6855: IMAP Support for UTF-8
88+ @see <https://datatracker.ietf.org/doc/html/rfc3629> RFC 3629: UTF-8 encoding *)
99+1010+(** Check if a string contains any non-ASCII characters (bytes >= 128). *)
1111+let has_non_ascii s =
1212+ let len = String.length s in
1313+ let rec loop i =
1414+ if i >= len then false
1515+ else if Char.code s.[i] >= 128 then true
1616+ else loop (i + 1)
1717+ in
1818+ loop 0
1919+2020+(** Validate UTF-8 encoding per RFC 3629 Section 4.
2121+2222+ UTF-8 encoding (RFC 3629):
2323+ - 1-byte: 0xxxxxxx (U+0000..U+007F)
2424+ - 2-byte: 110xxxxx 10xxxxxx (U+0080..U+07FF)
2525+ - 3-byte: 1110xxxx 10xxxxxx 10xxxxxx (U+0800..U+FFFF)
2626+ - 4-byte: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx (U+10000..U+10FFFF)
2727+2828+ Continuation bytes always have form 10xxxxxx.
2929+3030+ @see <https://datatracker.ietf.org/doc/html/rfc3629#section-4> RFC 3629 Section 4 *)
3131+let is_valid_utf8 s =
3232+ let len = String.length s in
3333+ let rec loop i =
3434+ if i >= len then true
3535+ else
3636+ let b0 = Char.code s.[i] in
3737+ if b0 <= 0x7F then
3838+ (* 1-byte sequence: ASCII *)
3939+ loop (i + 1)
4040+ else if b0 land 0xE0 = 0xC0 then begin
4141+ (* 2-byte sequence: 110xxxxx 10xxxxxx *)
4242+ if i + 1 >= len then false
4343+ else
4444+ let b1 = Char.code s.[i + 1] in
4545+ (* Check continuation byte *)
4646+ if b1 land 0xC0 <> 0x80 then false
4747+ else
4848+ (* Check for overlong encoding: must encode U+0080 or higher *)
4949+ let codepoint = ((b0 land 0x1F) lsl 6) lor (b1 land 0x3F) in
5050+ if codepoint < 0x80 then false
5151+ else loop (i + 2)
5252+ end
5353+ else if b0 land 0xF0 = 0xE0 then begin
5454+ (* 3-byte sequence: 1110xxxx 10xxxxxx 10xxxxxx *)
5555+ if i + 2 >= len then false
5656+ else
5757+ let b1 = Char.code s.[i + 1] in
5858+ let b2 = Char.code s.[i + 2] in
5959+ (* Check continuation bytes *)
6060+ if b1 land 0xC0 <> 0x80 || b2 land 0xC0 <> 0x80 then false
6161+ else
6262+ let codepoint =
6363+ ((b0 land 0x0F) lsl 12) lor
6464+ ((b1 land 0x3F) lsl 6) lor
6565+ (b2 land 0x3F)
6666+ in
6767+ (* Check for overlong encoding: must encode U+0800 or higher *)
6868+ if codepoint < 0x800 then false
6969+ (* Check for surrogate pairs (U+D800..U+DFFF are invalid) *)
7070+ else if codepoint >= 0xD800 && codepoint <= 0xDFFF then false
7171+ else loop (i + 3)
7272+ end
7373+ else if b0 land 0xF8 = 0xF0 then begin
7474+ (* 4-byte sequence: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx *)
7575+ if i + 3 >= len then false
7676+ else
7777+ let b1 = Char.code s.[i + 1] in
7878+ let b2 = Char.code s.[i + 2] in
7979+ let b3 = Char.code s.[i + 3] in
8080+ (* Check continuation bytes *)
8181+ if b1 land 0xC0 <> 0x80 ||
8282+ b2 land 0xC0 <> 0x80 ||
8383+ b3 land 0xC0 <> 0x80 then false
8484+ else
8585+ let codepoint =
8686+ ((b0 land 0x07) lsl 18) lor
8787+ ((b1 land 0x3F) lsl 12) lor
8888+ ((b2 land 0x3F) lsl 6) lor
8989+ (b3 land 0x3F)
9090+ in
9191+ (* Check for overlong encoding: must encode U+10000 or higher *)
9292+ if codepoint < 0x10000 then false
9393+ (* Check valid Unicode range: max is U+10FFFF *)
9494+ else if codepoint > 0x10FFFF then false
9595+ else loop (i + 4)
9696+ end
9797+ else
9898+ (* Invalid start byte *)
9999+ false
100100+ in
101101+ loop 0
102102+103103+(** Decode a single UTF-8 codepoint at position [i] in string [s].
104104+ Returns the codepoint and the number of bytes consumed, or None if invalid.
105105+ Assumes is_valid_utf8 has already passed. *)
106106+let decode_codepoint s i =
107107+ let len = String.length s in
108108+ if i >= len then None
109109+ else
110110+ let b0 = Char.code s.[i] in
111111+ if b0 <= 0x7F then
112112+ Some (b0, 1)
113113+ else if b0 land 0xE0 = 0xC0 && i + 1 < len then
114114+ let b1 = Char.code s.[i + 1] in
115115+ let cp = ((b0 land 0x1F) lsl 6) lor (b1 land 0x3F) in
116116+ Some (cp, 2)
117117+ else if b0 land 0xF0 = 0xE0 && i + 2 < len then
118118+ let b1 = Char.code s.[i + 1] in
119119+ let b2 = Char.code s.[i + 2] in
120120+ let cp =
121121+ ((b0 land 0x0F) lsl 12) lor
122122+ ((b1 land 0x3F) lsl 6) lor
123123+ (b2 land 0x3F)
124124+ in
125125+ Some (cp, 3)
126126+ else if b0 land 0xF8 = 0xF0 && i + 3 < len then
127127+ let b1 = Char.code s.[i + 1] in
128128+ let b2 = Char.code s.[i + 2] in
129129+ let b3 = Char.code s.[i + 3] in
130130+ let cp =
131131+ ((b0 land 0x07) lsl 18) lor
132132+ ((b1 land 0x3F) lsl 12) lor
133133+ ((b2 land 0x3F) lsl 6) lor
134134+ (b3 land 0x3F)
135135+ in
136136+ Some (cp, 4)
137137+ else
138138+ None
139139+140140+(** Check if a codepoint is disallowed in mailbox names per RFC 6855 Section 3.
141141+142142+ Disallowed characters:
143143+ - U+0000..U+001F: C0 control characters
144144+ - U+007F: DELETE
145145+ - U+0080..U+009F: C1 control characters
146146+ - U+2028: LINE SEPARATOR
147147+ - U+2029: PARAGRAPH SEPARATOR
148148+149149+ @see <https://datatracker.ietf.org/doc/html/rfc6855#section-3> RFC 6855 Section 3
150150+ @see <https://datatracker.ietf.org/doc/html/rfc5198#section-2> RFC 5198 Section 2 *)
151151+let is_disallowed_mailbox_codepoint cp =
152152+ (* C0 control characters U+0000..U+001F *)
153153+ (cp >= 0x0000 && cp <= 0x001F) ||
154154+ (* DELETE U+007F *)
155155+ cp = 0x007F ||
156156+ (* C1 control characters U+0080..U+009F *)
157157+ (cp >= 0x0080 && cp <= 0x009F) ||
158158+ (* LINE SEPARATOR U+2028 *)
159159+ cp = 0x2028 ||
160160+ (* PARAGRAPH SEPARATOR U+2029 *)
161161+ cp = 0x2029
162162+163163+(** Validate a mailbox name for UTF-8 compliance per RFC 6855 Section 3.
164164+165165+ @see <https://datatracker.ietf.org/doc/html/rfc6855#section-3> RFC 6855 Section 3 *)
166166+let is_valid_utf8_mailbox_name s =
167167+ (* First check basic UTF-8 validity *)
168168+ if not (is_valid_utf8 s) then false
169169+ else
170170+ (* Then check for disallowed codepoints *)
171171+ let len = String.length s in
172172+ let rec loop i =
173173+ if i >= len then true
174174+ else
175175+ match decode_codepoint s i with
176176+ | None -> false
177177+ | Some (cp, bytes) ->
178178+ if is_disallowed_mailbox_codepoint cp then false
179179+ else loop (i + bytes)
180180+ in
181181+ loop 0
+33
ocaml-imap/lib/imapd/utf8.mli
···11+(*---------------------------------------------------------------------------
22+ Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>. All rights reserved.
33+ SPDX-License-Identifier: ISC
44+ ---------------------------------------------------------------------------*)
55+66+(** UTF-8 validation per RFC 3629 for RFC 6855 IMAP UTF-8 support.
77+ @see <https://datatracker.ietf.org/doc/html/rfc6855> RFC 6855: IMAP Support for UTF-8
88+ @see <https://datatracker.ietf.org/doc/html/rfc3629> RFC 3629: UTF-8 encoding *)
99+1010+(** {1 UTF-8 Validation} *)
1111+1212+val is_valid_utf8 : string -> bool
1313+(** [is_valid_utf8 s] returns [true] if [s] contains only valid UTF-8 sequences
1414+ per RFC 3629. Returns [true] for empty strings and pure ASCII strings.
1515+ @see <https://datatracker.ietf.org/doc/html/rfc3629#section-4> RFC 3629 Section 4 *)
1616+1717+val has_non_ascii : string -> bool
1818+(** [has_non_ascii s] returns [true] if [s] contains any bytes with value >= 128.
1919+ This is useful for detecting when UTF-8 validation is needed. *)
2020+2121+(** {1 Mailbox Name Validation} *)
2222+2323+val is_valid_utf8_mailbox_name : string -> bool
2424+(** [is_valid_utf8_mailbox_name s] validates a mailbox name for UTF-8 compliance
2525+ per RFC 6855 Section 3. Mailbox names must:
2626+ - Contain only valid UTF-8 sequences
2727+ - Comply with Net-Unicode (RFC 5198 Section 2)
2828+ - Not contain control characters U+0000-U+001F, U+0080-U+009F
2929+ - Not contain delete U+007F
3030+ - Not contain line separator U+2028 or paragraph separator U+2029
3131+3232+ @see <https://datatracker.ietf.org/doc/html/rfc6855#section-3> RFC 6855 Section 3
3333+ @see <https://datatracker.ietf.org/doc/html/rfc5198#section-2> RFC 5198 Section 2 *)
+90-5
ocaml-imap/lib/imapd/write.ml
···350350 | Unsubscribe mailbox ->
351351 W.string w "UNSUBSCRIBE ";
352352 astring w mailbox
353353- | List { reference; pattern } ->
353353+ | List list_cmd ->
354354 W.string w "LIST ";
355355- astring w reference;
356356- sp w;
357357- astring w pattern
355355+ (match list_cmd with
356356+ | List_basic { reference; pattern } ->
357357+ astring w reference;
358358+ sp w;
359359+ astring w pattern
360360+ | List_extended { selection; reference; patterns; return_opts } ->
361361+ (* Selection options - RFC 5258 Section 3.1 *)
362362+ W.char w '(';
363363+ List.iteri (fun i opt ->
364364+ if i > 0 then sp w;
365365+ match opt with
366366+ | List_select_subscribed -> W.string w "SUBSCRIBED"
367367+ | List_select_remote -> W.string w "REMOTE"
368368+ | List_select_recursivematch -> W.string w "RECURSIVEMATCH"
369369+ | List_select_special_use -> W.string w "SPECIAL-USE"
370370+ ) selection;
371371+ W.char w ')';
372372+ sp w;
373373+ astring w reference;
374374+ sp w;
375375+ (* Patterns - multiple patterns in parentheses *)
376376+ (match patterns with
377377+ | [p] -> astring w p
378378+ | ps ->
379379+ W.char w '(';
380380+ List.iteri (fun i p ->
381381+ if i > 0 then sp w;
382382+ astring w p
383383+ ) ps;
384384+ W.char w ')');
385385+ (* Return options - RFC 5258 Section 3.2 *)
386386+ (match return_opts with
387387+ | [] -> ()
388388+ | opts ->
389389+ sp w;
390390+ W.string w "RETURN (";
391391+ List.iteri (fun i opt ->
392392+ if i > 0 then sp w;
393393+ match opt with
394394+ | List_return_subscribed -> W.string w "SUBSCRIBED"
395395+ | List_return_children -> W.string w "CHILDREN"
396396+ | List_return_special_use -> W.string w "SPECIAL-USE"
397397+ ) opts;
398398+ W.char w ')'))
358399 | Namespace -> W.string w "NAMESPACE"
359400 | Status { mailbox; items } ->
360401 W.string w "STATUS ";
···449490 search_key w criteria
450491 | Uid_expunge set ->
451492 W.string w "EXPUNGE ";
452452- sequence_set w set)
493493+ sequence_set w set
494494+ | Uid_thread { algorithm; charset; criteria } ->
495495+ W.string w "THREAD ";
496496+ (match algorithm with
497497+ | Thread_orderedsubject -> W.string w "ORDEREDSUBJECT"
498498+ | Thread_references -> W.string w "REFERENCES"
499499+ | Thread_extension ext -> astring w ext);
500500+ sp w;
501501+ astring w charset;
502502+ sp w;
503503+ search_key w criteria)
453504 | Id params ->
454505 W.string w "ID ";
455506 id_params w params
507507+ (* QUOTA extension - RFC 9208 *)
508508+ | Getquota root ->
509509+ W.string w "GETQUOTA ";
510510+ astring w root
511511+ | Getquotaroot mailbox ->
512512+ W.string w "GETQUOTAROOT ";
513513+ astring w mailbox
514514+ | Setquota { root; limits } ->
515515+ W.string w "SETQUOTA ";
516516+ astring w root;
517517+ sp w;
518518+ W.char w '(';
519519+ List.iteri (fun i (res, limit) ->
520520+ if i > 0 then sp w;
521521+ (match res with
522522+ | Quota_storage -> W.string w "STORAGE"
523523+ | Quota_message -> W.string w "MESSAGE"
524524+ | Quota_mailbox -> W.string w "MAILBOX"
525525+ | Quota_annotation_storage -> W.string w "ANNOTATION-STORAGE");
526526+ sp w;
527527+ W.string w (Int64.to_string limit)
528528+ ) limits;
529529+ W.char w ')'
530530+ (* THREAD extension - RFC 5256 *)
531531+ | Thread { algorithm; charset; criteria } ->
532532+ W.string w "THREAD ";
533533+ (match algorithm with
534534+ | Thread_orderedsubject -> W.string w "ORDEREDSUBJECT"
535535+ | Thread_references -> W.string w "REFERENCES"
536536+ | Thread_extension ext -> astring w ext);
537537+ sp w;
538538+ astring w charset;
539539+ sp w;
540540+ search_key w criteria
456541457542let command w ~tag cmd =
458543 atom w tag;