···11+# ISC License
22+33+Copyright (c) 2025 Anil Madhavapeddy <anil@recoil.org>
44+55+Permission to use, copy, modify, and/or distribute this software for any
66+purpose with or without fee is hereby granted, provided that the above
77+copyright notice and this permission notice appear in all copies.
88+99+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
1010+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
1111+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
1212+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
1313+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
1414+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
1515+PERFORMANCE OF THIS SOFTWARE.
···11+(** PeerTube API client for OCaml.
22+33+ This library provides a typed client for the
44+ {{:https://docs.joinpeertube.org/api-rest-reference.html}PeerTube video
55+ platform API}, using {{:https://github.com/ocaml-multicore/eio}Eio} for
66+ effect-based I/O and {{:https://erratique.ch/software/jsont}jsont} for JSON
77+ serialization.
88+99+ {1 Quick Start}
1010+1111+ {[
1212+ open Peertube
1313+1414+ let () =
1515+ Eio_main.run @@ fun env ->
1616+ Eio.Switch.run @@ fun sw ->
1717+ let session = Requests.create ~sw env in
1818+ let client = Client.create ~session ~base_url:"https://framatube.org" in
1919+2020+ (* List recent videos *)
2121+ let videos = Client.list_videos client () in
2222+ List.iter
2323+ (fun v -> Printf.printf "%s (%s)\n" (Video.name v) (Video.uuid v))
2424+ (Paginated.data videos)
2525+ ]}
2626+2727+ {1 Module Organization}
2828+2929+ The library is organized into focused modules, each with an abstract type
3030+ [t], a JSON codec [jsont], accessors, and a pretty printer [pp]:
3131+3232+ {2 Common Types}
3333+ - {!Labeled} - ID-label pairs for categories, licences, languages
3434+ - {!Privacy} - Video privacy levels
3535+ - {!Video_sort} - Video sort options for queries
3636+ - {!Paginated} - Paginated API responses
3737+3838+ {2 Accounts and Channels}
3939+ - {!Account_summary} - Account summary (embedded in other responses)
4040+ - {!Account} - Full account details
4141+ - {!Channel_summary} - Channel summary (embedded in other responses)
4242+ - {!Channel} - Full channel details
4343+4444+ {2 Videos}
4545+ - {!Video} - Video records with metadata
4646+4747+ {2 Playlists}
4848+ - {!Playlist} - Video playlists with nested {!Playlist.Privacy},
4949+ {!Playlist.Type}, and {!Playlist.Element}
5050+5151+ {2 Server Information}
5252+ - {!Instance_info} - Instance name and description
5353+ - {!Server_config} - Server configuration and features
5454+ - {!Server_stats} - Server statistics
5555+5656+ {2 API Client}
5757+ - {!Client} - Functions for making API requests *)
5858+5959+(** {1 Common Types} *)
6060+6161+(** Labeled values with ID and display label.
6262+6363+ Used for categories, licences, languages, and other enumerated values
6464+ returned by the PeerTube API. Each labeled value has an identifier and a
6565+ human-readable label. *)
6666+module Labeled : sig
6767+ type 'a t
6868+ (** A labeled value with an identifier and human-readable label. *)
6969+7070+ val make : id:'a -> label:string -> 'a t
7171+ (** [make ~id ~label] creates a labeled value. *)
7272+7373+ val id : 'a t -> 'a
7474+ (** [id t] returns the identifier. *)
7575+7676+ val label : 'a t -> string
7777+ (** [label t] returns the human-readable label. *)
7878+7979+ val int_jsont : int t Jsont.t
8080+ (** JSON codec for labeled values with integer IDs. *)
8181+8282+ val string_jsont : string t Jsont.t
8383+ (** JSON codec for labeled values with string IDs. *)
8484+8585+ val pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
8686+ (** [pp pp_id] is a pretty printer for labeled values, using [pp_id] for the
8787+ identifier. *)
8888+end
8989+9090+(** Video privacy levels.
9191+9292+ PeerTube videos can have different privacy settings that control who can
9393+ view them. *)
9494+module Privacy : sig
9595+ (** Video privacy level.
9696+ - [Public] - Visible to everyone
9797+ - [Unlisted] - Only accessible via direct link
9898+ - [Private] - Only visible to the owner
9999+ - [Internal] - Only visible to instance users *)
100100+ type t = Public | Unlisted | Private | Internal
101101+102102+ val to_int : t -> int
103103+ (** [to_int t] converts privacy to its API integer representation. *)
104104+105105+ val of_int : int -> t
106106+ (** [of_int n] converts an API integer to privacy level. *)
107107+108108+ val jsont : t Jsont.t
109109+ (** JSON codec for privacy. *)
110110+111111+ val pp : Format.formatter -> t -> unit
112112+ (** Pretty printer for privacy. *)
113113+end
114114+115115+(** Video sort options for API queries.
116116+117117+ When listing or searching videos, you can specify how results should be
118118+ sorted. *)
119119+module Video_sort : sig
120120+ (** Video sort order.
121121+ - [Newest] - Most recently published first
122122+ - [Oldest] - Oldest first
123123+ - [Views] - Most viewed first
124124+ - [Likes] - Most liked first
125125+ - [Trending] - Currently trending
126126+ - [Hot] - Hot/popular videos
127127+ - [Random] - Random order
128128+ - [Best] - Best match (for search) *)
129129+ type t = Newest | Oldest | Views | Likes | Trending | Hot | Random | Best
130130+131131+ val to_string : t -> string
132132+ (** [to_string t] converts sort option to API query string. *)
133133+134134+ val pp : Format.formatter -> t -> unit
135135+ (** Pretty printer for sort options. *)
136136+end
137137+138138+(** Paginated API responses.
139139+140140+ Most list endpoints return paginated results with a total count and a page
141141+ of data. Use [count] and [start] parameters in API calls to navigate through
142142+ pages. *)
143143+module Paginated : sig
144144+ type 'a t
145145+ (** A paginated response containing total count and a page of data. *)
146146+147147+ val make : total:int -> data:'a list -> 'a t
148148+ (** [make ~total ~data] creates a paginated response. *)
149149+150150+ val total : 'a t -> int
151151+ (** [total t] returns the total number of items available across all pages. *)
152152+153153+ val data : 'a t -> 'a list
154154+ (** [data t] returns the items in this page. *)
155155+156156+ val jsont : kind:string -> 'a Jsont.t -> 'a t Jsont.t
157157+ (** [jsont ~kind item_jsont] creates a JSON codec for paginated responses of
158158+ items encoded with [item_jsont]. *)
159159+160160+ val pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
161161+ (** [pp pp_item] is a pretty printer for paginated responses. *)
162162+end
163163+164164+(** {1 Account Types} *)
165165+166166+(** Summary of a PeerTube account.
167167+168168+ Account summaries are embedded in other responses (videos, channels, etc.)
169169+ and contain basic identifying information. For full account details, use
170170+ {!Client.get_account}. *)
171171+module Account_summary : sig
172172+ type t
173173+ (** Account summary type. *)
174174+175175+ val make :
176176+ id:int ->
177177+ name:string ->
178178+ display_name:string ->
179179+ url:string ->
180180+ host:string ->
181181+ avatar_path:string option ->
182182+ t
183183+ (** Create an account summary. *)
184184+185185+ val id : t -> int
186186+ (** Numeric account ID. *)
187187+188188+ val name : t -> string
189189+ (** Account name (handle without host, e.g., "username"). *)
190190+191191+ val display_name : t -> string
192192+ (** Human-readable display name. *)
193193+194194+ val url : t -> string
195195+ (** Full account URL. *)
196196+197197+ val host : t -> string
198198+ (** Instance host (e.g., "framatube.org"). *)
199199+200200+ val avatar_path : t -> string option
201201+ (** Avatar image path (relative to instance URL). *)
202202+203203+ val jsont : t Jsont.t
204204+ (** JSON codec. *)
205205+206206+ val pp : Format.formatter -> t -> unit
207207+ (** Pretty printer. *)
208208+end
209209+210210+(** Full PeerTube account details.
211211+212212+ Contains complete account information including statistics and the account
213213+ summary. *)
214214+module Account : sig
215215+ type t
216216+ (** Account type. *)
217217+218218+ val make :
219219+ summary:Account_summary.t ->
220220+ description:string option ->
221221+ created_at:Ptime.t ->
222222+ followers_count:int ->
223223+ following_count:int ->
224224+ following_hosts_count:int option ->
225225+ t
226226+ (** Create an account. *)
227227+228228+ val summary : t -> Account_summary.t
229229+ (** Basic account information. *)
230230+231231+ val description : t -> string option
232232+ (** Account description/bio. *)
233233+234234+ val created_at : t -> Ptime.t
235235+ (** Account creation timestamp. *)
236236+237237+ val followers_count : t -> int
238238+ (** Number of followers. *)
239239+240240+ val following_count : t -> int
241241+ (** Number of accounts being followed. *)
242242+243243+ val following_hosts_count : t -> int option
244244+ (** Number of federated hosts being followed. *)
245245+246246+ val jsont : t Jsont.t
247247+ (** JSON codec. *)
248248+249249+ val pp : Format.formatter -> t -> unit
250250+ (** Pretty printer. *)
251251+end
252252+253253+(** {1 Channel Types} *)
254254+255255+(** Summary of a PeerTube channel.
256256+257257+ Channel summaries are embedded in other responses (videos, playlists, etc.)
258258+ and contain basic identifying information. For full channel details, use
259259+ {!Client.get_channel}. *)
260260+module Channel_summary : sig
261261+ type t
262262+ (** Channel summary type. *)
263263+264264+ val make :
265265+ id:int ->
266266+ name:string ->
267267+ display_name:string ->
268268+ url:string ->
269269+ host:string ->
270270+ avatar_path:string option ->
271271+ t
272272+ (** Create a channel summary. *)
273273+274274+ val id : t -> int
275275+ (** Numeric channel ID. *)
276276+277277+ val name : t -> string
278278+ (** Channel name (handle without host). *)
279279+280280+ val display_name : t -> string
281281+ (** Human-readable display name. *)
282282+283283+ val url : t -> string
284284+ (** Full channel URL. *)
285285+286286+ val host : t -> string
287287+ (** Instance host. *)
288288+289289+ val avatar_path : t -> string option
290290+ (** Avatar image path (relative to instance URL). *)
291291+292292+ val jsont : t Jsont.t
293293+ (** JSON codec. *)
294294+295295+ val pp : Format.formatter -> t -> unit
296296+ (** Pretty printer. *)
297297+end
298298+299299+(** Full PeerTube channel details.
300300+301301+ Contains complete channel information including statistics, the owner
302302+ account, and the channel summary. *)
303303+module Channel : sig
304304+ type t
305305+ (** Channel type. *)
306306+307307+ val make :
308308+ summary:Channel_summary.t ->
309309+ description:string option ->
310310+ support:string option ->
311311+ created_at:Ptime.t ->
312312+ followers_count:int ->
313313+ following_count:int ->
314314+ banner_path:string option ->
315315+ owner_account:Account_summary.t option ->
316316+ t
317317+ (** Create a channel. *)
318318+319319+ val summary : t -> Channel_summary.t
320320+ (** Basic channel information. *)
321321+322322+ val description : t -> string option
323323+ (** Channel description. *)
324324+325325+ val support : t -> string option
326326+ (** Support text or links (e.g., donation info). *)
327327+328328+ val created_at : t -> Ptime.t
329329+ (** Channel creation timestamp. *)
330330+331331+ val followers_count : t -> int
332332+ (** Number of followers. *)
333333+334334+ val following_count : t -> int
335335+ (** Number of accounts being followed. *)
336336+337337+ val banner_path : t -> string option
338338+ (** Banner image path (relative to instance URL). *)
339339+340340+ val owner_account : t -> Account_summary.t option
341341+ (** Account that owns this channel. *)
342342+343343+ val jsont : t Jsont.t
344344+ (** JSON codec. *)
345345+346346+ val pp : Format.formatter -> t -> unit
347347+ (** Pretty printer. *)
348348+end
349349+350350+(** {1 Video Types} *)
351351+352352+(** PeerTube video record.
353353+354354+ Contains all metadata for a video including identifiers, timestamps,
355355+ statistics, and references to the channel and account that published it. *)
356356+module Video : sig
357357+ type t
358358+ (** Video type. *)
359359+360360+ val make :
361361+ id:int ->
362362+ uuid:string ->
363363+ short_uuid:string option ->
364364+ name:string ->
365365+ description:string option ->
366366+ url:string ->
367367+ embed_path:string ->
368368+ published_at:Ptime.t ->
369369+ originally_published_at:Ptime.t option ->
370370+ updated_at:Ptime.t option ->
371371+ thumbnail_path:string option ->
372372+ preview_path:string option ->
373373+ tags:string list ->
374374+ duration:int ->
375375+ views:int ->
376376+ likes:int ->
377377+ dislikes:int ->
378378+ is_local:bool ->
379379+ is_live:bool ->
380380+ privacy:Privacy.t ->
381381+ category:int Labeled.t option ->
382382+ licence:int Labeled.t option ->
383383+ language:string Labeled.t option ->
384384+ channel:Channel_summary.t option ->
385385+ account:Account_summary.t option ->
386386+ t
387387+ (** Create a video. *)
388388+389389+ val id : t -> int
390390+ (** Numeric video ID. *)
391391+392392+ val uuid : t -> string
393393+ (** Video UUID (standard format). *)
394394+395395+ val short_uuid : t -> string option
396396+ (** Short UUID for compact URLs. *)
397397+398398+ val name : t -> string
399399+ (** Video title. *)
400400+401401+ val description : t -> string option
402402+ (** Video description (may contain markdown). *)
403403+404404+ val url : t -> string
405405+ (** Full video URL. *)
406406+407407+ val embed_path : t -> string
408408+ (** Embed iframe path. *)
409409+410410+ val published_at : t -> Ptime.t
411411+ (** Publication timestamp. *)
412412+413413+ val originally_published_at : t -> Ptime.t option
414414+ (** Original publication timestamp (for re-uploads). *)
415415+416416+ val updated_at : t -> Ptime.t option
417417+ (** Last update timestamp. *)
418418+419419+ val thumbnail_path : t -> string option
420420+ (** Thumbnail image path (relative to instance URL). *)
421421+422422+ val preview_path : t -> string option
423423+ (** Preview image path (relative to instance URL). *)
424424+425425+ val tags : t -> string list
426426+ (** Video tags. *)
427427+428428+ val duration : t -> int
429429+ (** Duration in seconds. *)
430430+431431+ val views : t -> int
432432+ (** View count. *)
433433+434434+ val likes : t -> int
435435+ (** Like count. *)
436436+437437+ val dislikes : t -> int
438438+ (** Dislike count. *)
439439+440440+ val is_local : t -> bool
441441+ (** Whether the video is local to this instance (vs federated). *)
442442+443443+ val is_live : t -> bool
444444+ (** Whether this is a live stream. *)
445445+446446+ val privacy : t -> Privacy.t
447447+ (** Privacy level. *)
448448+449449+ val category : t -> int Labeled.t option
450450+ (** Video category (e.g., Music, Education). *)
451451+452452+ val licence : t -> int Labeled.t option
453453+ (** Video licence (e.g., Creative Commons). *)
454454+455455+ val language : t -> string Labeled.t option
456456+ (** Video language. *)
457457+458458+ val channel : t -> Channel_summary.t option
459459+ (** Channel that published the video. *)
460460+461461+ val account : t -> Account_summary.t option
462462+ (** Account that published the video. *)
463463+464464+ val jsont : t Jsont.t
465465+ (** JSON codec. *)
466466+467467+ val pp : Format.formatter -> t -> unit
468468+ (** Pretty printer. *)
469469+end
470470+471471+(** {1 Playlist Types} *)
472472+473473+(** PeerTube video playlists.
474474+475475+ Playlists contain ordered lists of videos with optional start/stop
476476+ timestamps for each entry. This module includes submodules for
477477+ {!Playlist.Privacy}, {!Playlist.Type}, and {!Playlist.Element}. *)
478478+module Playlist : sig
479479+ (** {2 Playlist Privacy} *)
480480+481481+ (** Playlist privacy levels. *)
482482+ module Privacy : sig
483483+ (** Playlist privacy level.
484484+ - [Public] - Visible to everyone
485485+ - [Unlisted] - Only accessible via direct link
486486+ - [Private] - Only visible to the owner *)
487487+ type t = Public | Unlisted | Private
488488+489489+ val to_int : t -> int
490490+ (** [to_int t] converts privacy to its API integer representation. *)
491491+492492+ val of_int : int -> t
493493+ (** [of_int n] converts an API integer to privacy level. *)
494494+495495+ val jsont : t Jsont.t
496496+ (** JSON codec. *)
497497+498498+ val pp : Format.formatter -> t -> unit
499499+ (** Pretty printer. *)
500500+ end
501501+502502+ (** {2 Playlist Type} *)
503503+504504+ (** Playlist types. *)
505505+ module Type : sig
506506+ (** Playlist type.
507507+ - [Regular] - User-created playlist
508508+ - [WatchLater] - Special "Watch Later" playlist *)
509509+ type t = Regular | WatchLater
510510+511511+ val to_int : t -> int
512512+ (** [to_int t] converts type to its API integer representation. *)
513513+514514+ val of_int : int -> t
515515+ (** [of_int n] converts an API integer to playlist type. *)
516516+517517+ val jsont : t Jsont.t
518518+ (** JSON codec. *)
519519+520520+ val pp : Format.formatter -> t -> unit
521521+ (** Pretty printer. *)
522522+ end
523523+524524+ (** {2 Playlist Element} *)
525525+526526+ (** An element in a PeerTube playlist.
527527+528528+ Playlist elements wrap videos with positioning and optional timestamp
529529+ information. *)
530530+ module Element : sig
531531+ type t
532532+ (** Playlist element type. *)
533533+534534+ val make :
535535+ id:int ->
536536+ position:int ->
537537+ start_timestamp:int option ->
538538+ stop_timestamp:int option ->
539539+ video:Video.t option ->
540540+ t
541541+ (** Create a playlist element. *)
542542+543543+ val id : t -> int
544544+ (** Element ID. *)
545545+546546+ val position : t -> int
547547+ (** Position in playlist (1-indexed). *)
548548+549549+ val start_timestamp : t -> int option
550550+ (** Start timestamp in seconds (for partial playback). *)
551551+552552+ val stop_timestamp : t -> int option
553553+ (** Stop timestamp in seconds (for partial playback). *)
554554+555555+ val video : t -> Video.t option
556556+ (** The video (may be [None] if video was deleted). *)
557557+558558+ val jsont : t Jsont.t
559559+ (** JSON codec. *)
560560+561561+ val pp : Format.formatter -> t -> unit
562562+ (** Pretty printer. *)
563563+ end
564564+565565+ (** {2 Playlist} *)
566566+567567+ type t
568568+ (** Playlist type. *)
569569+570570+ val make :
571571+ id:int ->
572572+ uuid:string ->
573573+ short_uuid:string option ->
574574+ display_name:string ->
575575+ description:string option ->
576576+ privacy:Privacy.t ->
577577+ url:string ->
578578+ thumbnail_path:string option ->
579579+ videos_length:int ->
580580+ playlist_type:Type.t ->
581581+ created_at:Ptime.t ->
582582+ updated_at:Ptime.t ->
583583+ owner_account:Account_summary.t option ->
584584+ video_channel:Channel_summary.t option ->
585585+ t
586586+ (** Create a playlist. *)
587587+588588+ val id : t -> int
589589+ (** Numeric playlist ID. *)
590590+591591+ val uuid : t -> string
592592+ (** Playlist UUID. *)
593593+594594+ val short_uuid : t -> string option
595595+ (** Short UUID for compact URLs. *)
596596+597597+ val display_name : t -> string
598598+ (** Display name. *)
599599+600600+ val description : t -> string option
601601+ (** Playlist description. *)
602602+603603+ val privacy : t -> Privacy.t
604604+ (** Privacy level. *)
605605+606606+ val url : t -> string
607607+ (** Full playlist URL. *)
608608+609609+ val thumbnail_path : t -> string option
610610+ (** Thumbnail image path (relative to instance URL). *)
611611+612612+ val videos_length : t -> int
613613+ (** Number of videos in playlist. *)
614614+615615+ val playlist_type : t -> Type.t
616616+ (** Playlist type (regular or watch later). *)
617617+618618+ val created_at : t -> Ptime.t
619619+ (** Creation timestamp. *)
620620+621621+ val updated_at : t -> Ptime.t
622622+ (** Last update timestamp. *)
623623+624624+ val owner_account : t -> Account_summary.t option
625625+ (** Account that owns this playlist. *)
626626+627627+ val video_channel : t -> Channel_summary.t option
628628+ (** Associated video channel (if any). *)
629629+630630+ val jsont : t Jsont.t
631631+ (** JSON codec. *)
632632+633633+ val pp : Format.formatter -> t -> unit
634634+ (** Pretty printer. *)
635635+end
636636+637637+(** {1 Server Types} *)
638638+639639+(** Basic PeerTube instance information.
640640+641641+ Contains the instance name, description, and basic configuration. *)
642642+module Instance_info : sig
643643+ type t
644644+ (** Instance info type. *)
645645+646646+ val make :
647647+ name:string ->
648648+ short_description:string ->
649649+ description:string option ->
650650+ terms:string option ->
651651+ is_nsfw:bool ->
652652+ default_nsfw_policy:string ->
653653+ default_client_route:string ->
654654+ t
655655+ (** Create instance info. *)
656656+657657+ val name : t -> string
658658+ (** Instance name. *)
659659+660660+ val short_description : t -> string
661661+ (** Short description (one line). *)
662662+663663+ val description : t -> string option
664664+ (** Full description (may contain markdown). *)
665665+666666+ val terms : t -> string option
667667+ (** Terms of service. *)
668668+669669+ val is_nsfw : t -> bool
670670+ (** Whether the instance is NSFW-focused. *)
671671+672672+ val default_nsfw_policy : t -> string
673673+ (** Default NSFW display policy ("display", "blur", "do_not_list"). *)
674674+675675+ val default_client_route : t -> string
676676+ (** Default client route (e.g., "/videos/trending"). *)
677677+678678+ val jsont : t Jsont.t
679679+ (** JSON codec. *)
680680+681681+ val pp : Format.formatter -> t -> unit
682682+ (** Pretty printer. *)
683683+end
684684+685685+(** PeerTube server configuration.
686686+687687+ Contains server version, enabled features, and instance information. *)
688688+module Server_config : sig
689689+ type t
690690+ (** Server config type. *)
691691+692692+ val make :
693693+ instance:Instance_info.t ->
694694+ server_version:string ->
695695+ server_commit:string option ->
696696+ signup_allowed:bool ->
697697+ signup_allowed_for_current_ip:bool ->
698698+ signup_requires_email_verification:bool ->
699699+ transcoding_enabled:bool ->
700700+ contact_form_enabled:bool ->
701701+ t
702702+ (** Create server config. *)
703703+704704+ val instance : t -> Instance_info.t
705705+ (** Instance information. *)
706706+707707+ val server_version : t -> string
708708+ (** PeerTube server version (e.g., "5.2.0"). *)
709709+710710+ val server_commit : t -> string option
711711+ (** Git commit hash (if available). *)
712712+713713+ val signup_allowed : t -> bool
714714+ (** Whether new user signup is allowed. *)
715715+716716+ val signup_allowed_for_current_ip : t -> bool
717717+ (** Whether signup is allowed for the current IP address. *)
718718+719719+ val signup_requires_email_verification : t -> bool
720720+ (** Whether signup requires email verification. *)
721721+722722+ val transcoding_enabled : t -> bool
723723+ (** Whether video transcoding is enabled. *)
724724+725725+ val contact_form_enabled : t -> bool
726726+ (** Whether the contact form is enabled. *)
727727+728728+ val jsont : t Jsont.t
729729+ (** JSON codec. *)
730730+731731+ val pp : Format.formatter -> t -> unit
732732+ (** Pretty printer. *)
733733+end
734734+735735+(** PeerTube server statistics.
736736+737737+ Contains usage statistics for the instance including user counts, video
738738+ counts, and federation information. *)
739739+module Server_stats : sig
740740+ type t
741741+ (** Server stats type. *)
742742+743743+ val make :
744744+ total_users:int ->
745745+ total_daily_active_users:int ->
746746+ total_weekly_active_users:int ->
747747+ total_monthly_active_users:int ->
748748+ total_local_videos:int ->
749749+ total_local_video_views:int ->
750750+ total_local_video_comments:int ->
751751+ total_local_video_files_size:int64 ->
752752+ total_videos:int ->
753753+ total_video_comments:int ->
754754+ total_local_video_channels:int ->
755755+ total_local_playlists:int ->
756756+ total_instance_followers:int ->
757757+ total_instance_following:int ->
758758+ t
759759+ (** Create server stats. *)
760760+761761+ val total_users : t -> int
762762+ (** Total registered users. *)
763763+764764+ val total_daily_active_users : t -> int
765765+ (** Users active in the last day. *)
766766+767767+ val total_weekly_active_users : t -> int
768768+ (** Users active in the last week. *)
769769+770770+ val total_monthly_active_users : t -> int
771771+ (** Users active in the last month. *)
772772+773773+ val total_local_videos : t -> int
774774+ (** Videos hosted on this instance. *)
775775+776776+ val total_local_video_views : t -> int
777777+ (** Total views on local videos. *)
778778+779779+ val total_local_video_comments : t -> int
780780+ (** Total comments on local videos. *)
781781+782782+ val total_local_video_files_size : t -> int64
783783+ (** Total size of local video files in bytes. *)
784784+785785+ val total_videos : t -> int
786786+ (** Total videos including federated content. *)
787787+788788+ val total_video_comments : t -> int
789789+ (** Total comments including federated content. *)
790790+791791+ val total_local_video_channels : t -> int
792792+ (** Video channels on this instance. *)
793793+794794+ val total_local_playlists : t -> int
795795+ (** Playlists on this instance. *)
796796+797797+ val total_instance_followers : t -> int
798798+ (** Instances following this instance. *)
799799+800800+ val total_instance_following : t -> int
801801+ (** Instances this instance follows. *)
802802+803803+ val jsont : t Jsont.t
804804+ (** JSON codec. *)
805805+806806+ val pp : Format.formatter -> t -> unit
807807+ (** Pretty printer. *)
808808+end
809809+810810+(** {1 API Client} *)
811811+812812+(** PeerTube API client functions.
813813+814814+ Create a session with {!Client.create}, then use it for API operations.
815815+ Functions raise {!Client.Api_error} on HTTP errors.
816816+817817+ {[
818818+ let client = Client.create ~session ~base_url:"https://framatube.org" in
819819+ let videos = Client.list_videos client () in
820820+ ...
821821+ ]} *)
822822+module Client : sig
823823+ (** {2 Session} *)
824824+825825+ type t
826826+ (** A PeerTube API session.
827827+828828+ Encapsulates the HTTP client and base URL for making API requests. In
829829+ future, this may also contain authentication credentials. *)
830830+831831+ val create : session:Requests.t -> base_url:string -> t
832832+ (** [create ~session ~base_url] creates a new PeerTube API session.
833833+834834+ @param session HTTP client session from the Requests library
835835+ @param base_url
836836+ Base URL of the PeerTube instance (e.g., "https://framatube.org") *)
837837+838838+ val base_url : t -> string
839839+ (** [base_url t] returns the base URL of the PeerTube instance. *)
840840+841841+ val http_session : t -> Requests.t
842842+ (** [http_session t] returns the underlying HTTP session. *)
843843+844844+ (** {2 Errors} *)
845845+846846+ val log_src : Logs.src
847847+ (** Log source for debug/info messages. *)
848848+849849+ exception Api_error of int * string
850850+ (** API error with HTTP status code and message. Raised when the server
851851+ returns an error response. *)
852852+853853+ (** {2 Video Operations} *)
854854+855855+ val list_videos :
856856+ t ->
857857+ ?count:int ->
858858+ ?start:int ->
859859+ ?sort:Video_sort.t ->
860860+ ?nsfw:bool ->
861861+ ?is_local:bool ->
862862+ ?is_live:bool ->
863863+ ?category_id:int ->
864864+ ?tags:string ->
865865+ unit ->
866866+ Video.t Paginated.t
867867+ (** List videos with optional filtering and sorting.
868868+869869+ @param count Number of videos per page (default: 20)
870870+ @param start Starting offset for pagination (default: 0)
871871+ @param sort Sort order (default: {!Video_sort.Newest})
872872+ @param nsfw Include NSFW videos (default: false)
873873+ @param is_local Only local videos
874874+ @param is_live Only live streams
875875+ @param category_id Filter by category ID
876876+ @param tags Filter by tags (comma-separated) *)
877877+878878+ val search_videos :
879879+ t ->
880880+ query:string ->
881881+ ?count:int ->
882882+ ?start:int ->
883883+ ?sort:Video_sort.t ->
884884+ ?search_target:[ `Local | `Search_index ] ->
885885+ ?duration_min:int ->
886886+ ?duration_max:int ->
887887+ ?published_after:Ptime.t ->
888888+ ?published_before:Ptime.t ->
889889+ unit ->
890890+ Video.t Paginated.t
891891+ (** Search for videos.
892892+893893+ @param query Search query string
894894+ @param count Number of results per page (default: 20)
895895+ @param start Starting offset (default: 0)
896896+ @param sort Sort order (default: {!Video_sort.Best})
897897+ @param search_target
898898+ [`Local] for this instance, [`Search_index] for federated search
899899+ @param duration_min Minimum duration in seconds
900900+ @param duration_max Maximum duration in seconds
901901+ @param published_after Only videos published after this date
902902+ @param published_before Only videos published before this date *)
903903+904904+ val fetch_channel_videos :
905905+ t ->
906906+ ?count:int ->
907907+ ?start:int ->
908908+ channel:string ->
909909+ unit ->
910910+ Video.t Paginated.t
911911+ (** Fetch videos from a channel with pagination.
912912+913913+ @param channel Channel name (e.g., "my_channel") *)
914914+915915+ val fetch_all_channel_videos :
916916+ t ->
917917+ ?page_size:int ->
918918+ ?max_pages:int ->
919919+ channel:string ->
920920+ unit ->
921921+ Video.t list
922922+ (** Fetch all videos from a channel using automatic pagination.
923923+924924+ Repeatedly fetches pages until all videos are retrieved or [max_pages] is
925925+ reached.
926926+927927+ @param page_size Videos per page (default: 20)
928928+ @param max_pages Maximum pages to fetch (default: unlimited) *)
929929+930930+ val fetch_video_details : t -> uuid:string -> unit -> Video.t
931931+ (** Fetch detailed information for a single video.
932932+933933+ @param uuid Video UUID *)
934934+935935+ val get_categories : t -> unit -> (int * string) list
936936+ (** Get available video categories.
937937+938938+ Returns a list of [(id, label)] pairs. *)
939939+940940+ val get_licences : t -> unit -> (int * string) list
941941+ (** Get available video licences.
942942+943943+ Returns a list of [(id, label)] pairs. *)
944944+945945+ val get_languages : t -> unit -> (string * string) list
946946+ (** Get available video languages.
947947+948948+ Returns a list of [(code, label)] pairs. *)
949949+950950+ (** {2 Channel Operations} *)
951951+952952+ val list_channels :
953953+ t ->
954954+ ?count:int ->
955955+ ?start:int ->
956956+ ?sort:string ->
957957+ unit ->
958958+ Channel.t Paginated.t
959959+ (** List all channels on the instance.
960960+961961+ @param sort Sort order (e.g., "-createdAt" for newest first) *)
962962+963963+ val search_channels :
964964+ t ->
965965+ query:string ->
966966+ ?count:int ->
967967+ ?start:int ->
968968+ unit ->
969969+ Channel.t Paginated.t
970970+ (** Search for channels.
971971+972972+ @param query Search query string *)
973973+974974+ val get_channel : t -> handle:string -> unit -> Channel.t
975975+ (** Get details for a specific channel.
976976+977977+ @param handle
978978+ Channel handle (e.g., "my_channel" or "my_channel@example.com") *)
979979+980980+ (** {2 Account Operations} *)
981981+982982+ val list_accounts :
983983+ t ->
984984+ ?count:int ->
985985+ ?start:int ->
986986+ ?sort:string ->
987987+ unit ->
988988+ Account.t Paginated.t
989989+ (** List accounts on the instance.
990990+991991+ @param sort Sort order (e.g., "-createdAt" for newest first) *)
992992+993993+ val get_account : t -> handle:string -> unit -> Account.t
994994+ (** Get details for a specific account.
995995+996996+ @param handle Account handle (e.g., "username" or "username@example.com")
997997+ *)
998998+999999+ val get_account_videos :
10001000+ t ->
10011001+ ?count:int ->
10021002+ ?start:int ->
10031003+ handle:string ->
10041004+ unit ->
10051005+ Video.t Paginated.t
10061006+ (** Get videos from a specific account. *)
10071007+10081008+ val get_account_channels :
10091009+ t ->
10101010+ ?count:int ->
10111011+ ?start:int ->
10121012+ handle:string ->
10131013+ unit ->
10141014+ Channel.t Paginated.t
10151015+ (** Get channels owned by an account. *)
10161016+10171017+ (** {2 Playlist Operations} *)
10181018+10191019+ val list_playlists :
10201020+ t -> ?count:int -> ?start:int -> unit -> Playlist.t Paginated.t
10211021+ (** List playlists on the instance. *)
10221022+10231023+ val search_playlists :
10241024+ t ->
10251025+ query:string ->
10261026+ ?count:int ->
10271027+ ?start:int ->
10281028+ unit ->
10291029+ Playlist.t Paginated.t
10301030+ (** Search for playlists.
10311031+10321032+ @param query Search query string *)
10331033+10341034+ val get_playlist : t -> id:string -> unit -> Playlist.t
10351035+ (** Get details for a specific playlist.
10361036+10371037+ @param id Playlist ID or UUID *)
10381038+10391039+ val get_playlist_videos :
10401040+ t ->
10411041+ ?count:int ->
10421042+ ?start:int ->
10431043+ id:string ->
10441044+ unit ->
10451045+ Playlist.Element.t Paginated.t
10461046+ (** Get videos in a playlist.
10471047+10481048+ @param id Playlist ID or UUID *)
10491049+10501050+ val get_account_playlists :
10511051+ t ->
10521052+ ?count:int ->
10531053+ ?start:int ->
10541054+ handle:string ->
10551055+ unit ->
10561056+ Playlist.t Paginated.t
10571057+ (** Get playlists owned by an account. *)
10581058+10591059+ (** {2 Server Operations} *)
10601060+10611061+ val get_config : t -> unit -> Server_config.t
10621062+ (** Get server configuration. *)
10631063+10641064+ val get_stats : t -> unit -> Server_stats.t
10651065+ (** Get server statistics. *)
10661066+10671067+ (** {2 Utilities} *)
10681068+10691069+ val thumbnail_url : t -> Video.t -> string option
10701070+ (** Get the full thumbnail URL for a video.
10711071+10721072+ Combines the session's base URL with the video's thumbnail path. Returns
10731073+ [None] if the video has no thumbnail. *)
10741074+10751075+ val download_thumbnail :
10761076+ t ->
10771077+ video:Video.t ->
10781078+ output_path:string ->
10791079+ unit ->
10801080+ (unit, [ `Msg of string ]) result
10811081+ (** Download a video's thumbnail to a file.
10821082+10831083+ @param output_path Path to save the thumbnail image *)
10841084+10851085+ val to_tuple : Video.t -> string * Ptime.t * string * string * string * string
10861086+ (** Convert a video to a tuple for external use.
10871087+10881088+ Returns [(description, published_date, title, url, uuid, id_string)]. *)
10891089+end
···11+(** Full PeerTube account details. *)
22+33+type t
44+(** Account type. *)
55+66+val make :
77+ summary:Peertube_account_summary.t ->
88+ description:string option ->
99+ created_at:Ptime.t ->
1010+ followers_count:int ->
1111+ following_count:int ->
1212+ following_hosts_count:int option ->
1313+ t
1414+(** Create an account. *)
1515+1616+val summary : t -> Peertube_account_summary.t
1717+(** Account summary (basic info). *)
1818+1919+val description : t -> string option
2020+(** Account description/bio. *)
2121+2222+val created_at : t -> Ptime.t
2323+(** Account creation timestamp. *)
2424+2525+val followers_count : t -> int
2626+(** Number of followers. *)
2727+2828+val following_count : t -> int
2929+(** Number of accounts being followed. *)
3030+3131+val following_hosts_count : t -> int option
3232+(** Number of hosts being followed (federation). *)
3333+3434+val jsont : t Jsont.t
3535+(** JSON codec. *)
3636+3737+val pp : Format.formatter -> t -> unit
3838+(** Pretty printer. *)
+39
lib/peertube_account_summary.ml
···11+(** Summary of a PeerTube account. *)
22+33+type t = {
44+ id : int;
55+ name : string;
66+ display_name : string;
77+ url : string;
88+ host : string;
99+ avatar_path : string option;
1010+}
1111+1212+let make ~id ~name ~display_name ~url ~host ~avatar_path =
1313+ { id; name; display_name; url; host; avatar_path }
1414+1515+let id t = t.id
1616+let name t = t.name
1717+let display_name t = t.display_name
1818+let url t = t.url
1919+let host t = t.host
2020+let avatar_path t = t.avatar_path
2121+2222+let jsont : t Jsont.t =
2323+ let make id name display_name url host avatar_path =
2424+ { id; name; display_name; url; host; avatar_path }
2525+ in
2626+ Jsont.Object.map ~kind:"account_summary" make
2727+ |> Jsont.Object.mem "id" Jsont.int ~enc:(fun a -> a.id)
2828+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun a -> a.name)
2929+ |> Jsont.Object.mem "displayName" Jsont.string ~enc:(fun a -> a.display_name)
3030+ |> Jsont.Object.mem "url" Jsont.string ~enc:(fun a -> a.url)
3131+ |> Jsont.Object.mem "host" Jsont.string ~enc:(fun a -> a.host)
3232+ |> Jsont.Object.mem "avatars" Peertube_avatar.jsont ~dec_absent:None
3333+ ~enc_omit:Option.is_none ~enc:(fun a -> a.avatar_path)
3434+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
3535+3636+let pp ppf t =
3737+ Fmt.pf ppf
3838+ "@[<hov 2>{ id = %d;@ name = %S;@ display_name = %S;@ host = %S }@]" t.id
3939+ t.name t.display_name t.host
+38
lib/peertube_account_summary.mli
···11+(** Summary of a PeerTube account (embedded in other responses). *)
22+33+type t
44+(** Account summary type. *)
55+66+val make :
77+ id:int ->
88+ name:string ->
99+ display_name:string ->
1010+ url:string ->
1111+ host:string ->
1212+ avatar_path:string option ->
1313+ t
1414+(** Create an account summary. *)
1515+1616+val id : t -> int
1717+(** Account ID. *)
1818+1919+val name : t -> string
2020+(** Account name (handle without host). *)
2121+2222+val display_name : t -> string
2323+(** Display name. *)
2424+2525+val url : t -> string
2626+(** Account URL. *)
2727+2828+val host : t -> string
2929+(** Instance host. *)
3030+3131+val avatar_path : t -> string option
3232+(** Avatar image path (relative). *)
3333+3434+val jsont : t Jsont.t
3535+(** JSON codec. *)
3636+3737+val pp : Format.formatter -> t -> unit
3838+(** Pretty printer. *)
+14
lib/peertube_avatar.ml
···11+(** Avatar path extraction from PeerTube API responses. *)
22+33+let avatar_jsont =
44+ let make path _width = path in
55+ Jsont.Object.map ~kind:"avatar" make
66+ |> Jsont.Object.mem "path" Jsont.string ~enc:Fun.id
77+ |> Jsont.Object.mem "width" Jsont.int ~dec_absent:0 ~enc:(Fun.const 0)
88+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
99+1010+let jsont : string option Jsont.t =
1111+ Jsont.map ~kind:"avatar_path"
1212+ ~dec:(function [] -> None | x :: _ -> Some x)
1313+ ~enc:(function None -> [] | Some p -> [ p ])
1414+ (Jsont.list avatar_jsont)
+7
lib/peertube_avatar.mli
···11+(** Avatar path extraction from PeerTube API responses.
22+33+ PeerTube returns avatars as an array of objects with path and width. This
44+ module extracts the first avatar path. *)
55+66+val jsont : string option Jsont.t
77+(** JSON codec that extracts the first avatar path from an avatars array. *)
···11+(** Full PeerTube channel details. *)
22+33+type t
44+(** Channel type. *)
55+66+val make :
77+ summary:Peertube_channel_summary.t ->
88+ description:string option ->
99+ support:string option ->
1010+ created_at:Ptime.t ->
1111+ followers_count:int ->
1212+ following_count:int ->
1313+ banner_path:string option ->
1414+ owner_account:Peertube_account_summary.t option ->
1515+ t
1616+(** Create a channel. *)
1717+1818+val summary : t -> Peertube_channel_summary.t
1919+(** Channel summary (basic info). *)
2020+2121+val description : t -> string option
2222+(** Channel description. *)
2323+2424+val support : t -> string option
2525+(** Support text/links. *)
2626+2727+val created_at : t -> Ptime.t
2828+(** Channel creation timestamp. *)
2929+3030+val followers_count : t -> int
3131+(** Number of followers. *)
3232+3333+val following_count : t -> int
3434+(** Number of accounts being followed. *)
3535+3636+val banner_path : t -> string option
3737+(** Banner image path (relative). *)
3838+3939+val owner_account : t -> Peertube_account_summary.t option
4040+(** Owner account. *)
4141+4242+val jsont : t Jsont.t
4343+(** JSON codec. *)
4444+4545+val pp : Format.formatter -> t -> unit
4646+(** Pretty printer. *)
+39
lib/peertube_channel_summary.ml
···11+(** Summary of a PeerTube channel. *)
22+33+type t = {
44+ id : int;
55+ name : string;
66+ display_name : string;
77+ url : string;
88+ host : string;
99+ avatar_path : string option;
1010+}
1111+1212+let make ~id ~name ~display_name ~url ~host ~avatar_path =
1313+ { id; name; display_name; url; host; avatar_path }
1414+1515+let id t = t.id
1616+let name t = t.name
1717+let display_name t = t.display_name
1818+let url t = t.url
1919+let host t = t.host
2020+let avatar_path t = t.avatar_path
2121+2222+let jsont : t Jsont.t =
2323+ let make id name display_name url host avatar_path =
2424+ { id; name; display_name; url; host; avatar_path }
2525+ in
2626+ Jsont.Object.map ~kind:"channel_summary" make
2727+ |> Jsont.Object.mem "id" Jsont.int ~enc:(fun c -> c.id)
2828+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun c -> c.name)
2929+ |> Jsont.Object.mem "displayName" Jsont.string ~enc:(fun c -> c.display_name)
3030+ |> Jsont.Object.mem "url" Jsont.string ~enc:(fun c -> c.url)
3131+ |> Jsont.Object.mem "host" Jsont.string ~enc:(fun c -> c.host)
3232+ |> Jsont.Object.mem "avatars" Peertube_avatar.jsont ~dec_absent:None
3333+ ~enc_omit:Option.is_none ~enc:(fun c -> c.avatar_path)
3434+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
3535+3636+let pp ppf t =
3737+ Fmt.pf ppf
3838+ "@[<hov 2>{ id = %d;@ name = %S;@ display_name = %S;@ host = %S }@]" t.id
3939+ t.name t.display_name t.host
+38
lib/peertube_channel_summary.mli
···11+(** Summary of a PeerTube channel (embedded in other responses). *)
22+33+type t
44+(** Channel summary type. *)
55+66+val make :
77+ id:int ->
88+ name:string ->
99+ display_name:string ->
1010+ url:string ->
1111+ host:string ->
1212+ avatar_path:string option ->
1313+ t
1414+(** Create a channel summary. *)
1515+1616+val id : t -> int
1717+(** Channel ID. *)
1818+1919+val name : t -> string
2020+(** Channel name (handle without host). *)
2121+2222+val display_name : t -> string
2323+(** Display name. *)
2424+2525+val url : t -> string
2626+(** Channel URL. *)
2727+2828+val host : t -> string
2929+(** Instance host. *)
3030+3131+val avatar_path : t -> string option
3232+(** Avatar image path (relative). *)
3333+3434+val jsont : t Jsont.t
3535+(** JSON codec. *)
3636+3737+val pp : Format.formatter -> t -> unit
3838+(** Pretty printer. *)
+502
lib/peertube_client.ml
···11+(** PeerTube API client functions. *)
22+33+type t = { session : Requests.t; base_url : string }
44+(** Session type encapsulating HTTP client and base URL. *)
55+66+let create ~session ~base_url = { session; base_url }
77+let base_url t = t.base_url
88+let http_session t = t.session
99+let log_src = Logs.Src.create "peertube" ~doc:"PeerTube API client"
1010+1111+module Log = (val Logs.src_log log_src : Logs.LOG)
1212+1313+exception Api_error of int * string
1414+1515+(* Error handling *)
1616+1717+let error_response_jsont : string Jsont.t =
1818+ let make _type error _status detail =
1919+ match detail with
2020+ | Some d -> d
2121+ | None -> Option.value ~default:"Unknown error" error
2222+ in
2323+ Jsont.Object.map ~kind:"error_response" make
2424+ |> Jsont.Object.mem "type" Jsont.string ~dec_absent:"" ~enc:(Fun.const "")
2525+ |> Jsont.Object.mem "error"
2626+ (Jsont.option Jsont.string)
2727+ ~dec_absent:None ~enc_omit:Option.is_none ~enc:(Fun.const None)
2828+ |> Jsont.Object.mem "status" Jsont.int ~dec_absent:0 ~enc:(Fun.const 0)
2929+ |> Jsont.Object.mem "detail"
3030+ (Jsont.option Jsont.string)
3131+ ~dec_absent:None ~enc_omit:Option.is_none ~enc:Option.some
3232+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
3333+3434+let raise_api_error status body =
3535+ let msg =
3636+ match Jsont_bytesrw.decode_string error_response_jsont body with
3737+ | Ok msg -> msg
3838+ | Error _ -> Printf.sprintf "HTTP %d" status
3939+ in
4040+ Log.err (fun m -> m "API error %d: %s" status msg);
4141+ raise (Api_error (status, msg))
4242+4343+(* HTTP helpers *)
4444+4545+let get_json ~session ~url codec =
4646+ Log.debug (fun m -> m "GET %s" url);
4747+ let response = Requests.get session url in
4848+ let status = Requests.Response.status_code response in
4949+ if Requests.Response.ok response then begin
5050+ Log.debug (fun m -> m "Response: %d OK" status);
5151+ let body = Requests.Response.text response in
5252+ match Jsont_bytesrw.decode_string codec body with
5353+ | Ok result -> result
5454+ | Error e ->
5555+ Log.err (fun m -> m "JSON parse error: %s" e);
5656+ failwith (Fmt.str "JSON parse error: %s" e)
5757+ end
5858+ else begin
5959+ let body = Requests.Response.text response in
6060+ raise_api_error status body
6161+ end
6262+6363+let build_url base parts params =
6464+ let path = String.concat "/" parts in
6565+ let params = List.filter_map Fun.id params in
6666+ let query =
6767+ if params = [] then ""
6868+ else "?" ^ String.concat "&" (List.map (fun (k, v) -> k ^ "=" ^ v) params)
6969+ in
7070+ base ^ "/" ^ path ^ query
7171+7272+(* Video operations *)
7373+7474+let video_paginated_jsont =
7575+ Peertube_paginated.jsont ~kind:"video_paginated" Peertube_video.jsont
7676+7777+let list_videos t ?(count = 20) ?(start = 0)
7878+ ?(sort = Peertube_video_sort.Newest) ?(nsfw = false) ?is_local ?is_live
7979+ ?category_id ?tags () =
8080+ let url =
8181+ build_url t.base_url [ "api"; "v1"; "videos" ]
8282+ [
8383+ Some ("count", string_of_int count);
8484+ Some ("start", string_of_int start);
8585+ Some ("sort", Peertube_video_sort.to_string sort);
8686+ Some ("nsfw", if nsfw then "true" else "false");
8787+ Option.map
8888+ (fun b -> ("isLocal", if b then "true" else "false"))
8989+ is_local;
9090+ Option.map (fun b -> ("isLive", if b then "true" else "false")) is_live;
9191+ Option.map (fun i -> ("categoryOneOf", string_of_int i)) category_id;
9292+ Option.map (fun t -> ("tagsOneOf", t)) tags;
9393+ ]
9494+ in
9595+ let result = get_json ~session:t.session ~url video_paginated_jsont in
9696+ Log.info (fun m ->
9797+ m "Listed %d videos (total: %d)"
9898+ (List.length (Peertube_paginated.data result))
9999+ (Peertube_paginated.total result));
100100+ result
101101+102102+let search_videos t ~query ?(count = 20) ?(start = 0)
103103+ ?(sort = Peertube_video_sort.Best) ?(search_target = `Local) ?duration_min
104104+ ?duration_max ?published_after ?published_before () =
105105+ let url =
106106+ build_url t.base_url
107107+ [ "api"; "v1"; "search"; "videos" ]
108108+ [
109109+ Some ("search", query);
110110+ Some ("count", string_of_int count);
111111+ Some ("start", string_of_int start);
112112+ Some ("sort", Peertube_video_sort.to_string sort);
113113+ Some
114114+ ( "searchTarget",
115115+ match search_target with
116116+ | `Local -> "local"
117117+ | `Search_index -> "search-index" );
118118+ Option.map (fun d -> ("durationMin", string_of_int d)) duration_min;
119119+ Option.map (fun d -> ("durationMax", string_of_int d)) duration_max;
120120+ Option.map (fun t -> ("startDate", Ptime.to_rfc3339 t)) published_after;
121121+ Option.map (fun t -> ("endDate", Ptime.to_rfc3339 t)) published_before;
122122+ ]
123123+ in
124124+ let result = get_json ~session:t.session ~url video_paginated_jsont in
125125+ Log.info (fun m ->
126126+ m "Search '%s' found %d videos (total: %d)" query
127127+ (List.length (Peertube_paginated.data result))
128128+ (Peertube_paginated.total result));
129129+ result
130130+131131+let fetch_channel_videos t ?(count = 20) ?(start = 0) ~channel () =
132132+ let url =
133133+ build_url t.base_url
134134+ [ "api"; "v1"; "video-channels"; channel; "videos" ]
135135+ [
136136+ Some ("count", string_of_int count); Some ("start", string_of_int start);
137137+ ]
138138+ in
139139+ let result = get_json ~session:t.session ~url video_paginated_jsont in
140140+ Log.info (fun m ->
141141+ m "Fetched %d videos from channel %s (total: %d)"
142142+ (List.length (Peertube_paginated.data result))
143143+ channel
144144+ (Peertube_paginated.total result));
145145+ result
146146+147147+let fetch_all_channel_videos t ?(page_size = 20) ?max_pages ~channel () =
148148+ Log.info (fun m ->
149149+ m "Fetching all videos from channel %s (page_size=%d, max_pages=%s)"
150150+ channel page_size
151151+ (match max_pages with None -> "unlimited" | Some n -> string_of_int n));
152152+ let rec fetch_pages page start acc =
153153+ let response = fetch_channel_videos t ~count:page_size ~start ~channel () in
154154+ let all_videos = acc @ Peertube_paginated.data response in
155155+ let fetched_count =
156156+ start + List.length (Peertube_paginated.data response)
157157+ in
158158+ let more_available = fetched_count < Peertube_paginated.total response in
159159+ let under_max_pages =
160160+ match max_pages with None -> true | Some max -> page < max
161161+ in
162162+ Log.debug (fun m ->
163163+ m "Page %d: fetched %d, total so far %d/%d" page
164164+ (List.length (Peertube_paginated.data response))
165165+ fetched_count
166166+ (Peertube_paginated.total response));
167167+ if more_available && under_max_pages then
168168+ fetch_pages (page + 1) fetched_count all_videos
169169+ else begin
170170+ Log.info (fun m ->
171171+ m "Finished fetching %d videos in %d pages" (List.length all_videos)
172172+ page);
173173+ all_videos
174174+ end
175175+ in
176176+ fetch_pages 1 0 []
177177+178178+let fetch_video_details t ~uuid () =
179179+ let url = build_url t.base_url [ "api"; "v1"; "videos"; uuid ] [] in
180180+ let video = get_json ~session:t.session ~url Peertube_video.jsont in
181181+ Log.info (fun m ->
182182+ m "Fetched video details: %s (%s)" (Peertube_video.name video) uuid);
183183+ video
184184+185185+(* Categories/languages/licences lookup *)
186186+187187+module StringMap = Map.Make (String)
188188+189189+let dict_jsont (key_codec : 'a Jsont.t) : ('a * string) list Jsont.t =
190190+ let map_jsont = Jsont.(Object.as_string_map string) in
191191+ Jsont.map ~kind:"dict"
192192+ ~dec:(fun smap ->
193193+ let bindings = StringMap.bindings smap in
194194+ List.filter_map
195195+ (fun (k, v) ->
196196+ match Jsont_bytesrw.decode_string key_codec ("\"" ^ k ^ "\"") with
197197+ | Ok key -> Some (key, v)
198198+ | Error _ -> None)
199199+ bindings)
200200+ ~enc:(fun pairs ->
201201+ List.fold_left
202202+ (fun m (k, v) ->
203203+ match Jsont_bytesrw.encode_string key_codec k with
204204+ | Ok s ->
205205+ let key = String.sub s 1 (String.length s - 2) in
206206+ StringMap.add key v m
207207+ | Error _ -> m)
208208+ StringMap.empty pairs)
209209+ map_jsont
210210+211211+let get_categories t () =
212212+ let url = build_url t.base_url [ "api"; "v1"; "videos"; "categories" ] [] in
213213+ let result = get_json ~session:t.session ~url (dict_jsont Jsont.int) in
214214+ Log.info (fun m -> m "Fetched %d categories" (List.length result));
215215+ result
216216+217217+let get_licences t () =
218218+ let url = build_url t.base_url [ "api"; "v1"; "videos"; "licences" ] [] in
219219+ let result = get_json ~session:t.session ~url (dict_jsont Jsont.int) in
220220+ Log.info (fun m -> m "Fetched %d licences" (List.length result));
221221+ result
222222+223223+let get_languages t () =
224224+ let url = build_url t.base_url [ "api"; "v1"; "videos"; "languages" ] [] in
225225+ let result = get_json ~session:t.session ~url (dict_jsont Jsont.string) in
226226+ Log.info (fun m -> m "Fetched %d languages" (List.length result));
227227+ result
228228+229229+(* Channel operations *)
230230+231231+let channel_paginated_jsont =
232232+ Peertube_paginated.jsont ~kind:"channel_paginated" Peertube_channel.jsont
233233+234234+let list_channels t ?(count = 20) ?(start = 0) ?(sort = "-createdAt") () =
235235+ let url =
236236+ build_url t.base_url
237237+ [ "api"; "v1"; "video-channels" ]
238238+ [
239239+ Some ("count", string_of_int count);
240240+ Some ("start", string_of_int start);
241241+ Some ("sort", sort);
242242+ ]
243243+ in
244244+ let result = get_json ~session:t.session ~url channel_paginated_jsont in
245245+ Log.info (fun m ->
246246+ m "Listed %d channels (total: %d)"
247247+ (List.length (Peertube_paginated.data result))
248248+ (Peertube_paginated.total result));
249249+ result
250250+251251+let search_channels t ~query ?(count = 20) ?(start = 0) () =
252252+ let url =
253253+ build_url t.base_url
254254+ [ "api"; "v1"; "search"; "video-channels" ]
255255+ [
256256+ Some ("search", query);
257257+ Some ("count", string_of_int count);
258258+ Some ("start", string_of_int start);
259259+ ]
260260+ in
261261+ let result = get_json ~session:t.session ~url channel_paginated_jsont in
262262+ Log.info (fun m ->
263263+ m "Search '%s' found %d channels (total: %d)" query
264264+ (List.length (Peertube_paginated.data result))
265265+ (Peertube_paginated.total result));
266266+ result
267267+268268+let get_channel t ~handle () =
269269+ let url = build_url t.base_url [ "api"; "v1"; "video-channels"; handle ] [] in
270270+ let channel = get_json ~session:t.session ~url Peertube_channel.jsont in
271271+ Log.info (fun m ->
272272+ m "Fetched channel: %s"
273273+ (Peertube_channel_summary.display_name
274274+ (Peertube_channel.summary channel)));
275275+ channel
276276+277277+(* Account operations *)
278278+279279+let account_paginated_jsont =
280280+ Peertube_paginated.jsont ~kind:"account_paginated" Peertube_account.jsont
281281+282282+let list_accounts t ?(count = 20) ?(start = 0) ?(sort = "-createdAt") () =
283283+ let url =
284284+ build_url t.base_url
285285+ [ "api"; "v1"; "accounts" ]
286286+ [
287287+ Some ("count", string_of_int count);
288288+ Some ("start", string_of_int start);
289289+ Some ("sort", sort);
290290+ ]
291291+ in
292292+ let result = get_json ~session:t.session ~url account_paginated_jsont in
293293+ Log.info (fun m ->
294294+ m "Listed %d accounts (total: %d)"
295295+ (List.length (Peertube_paginated.data result))
296296+ (Peertube_paginated.total result));
297297+ result
298298+299299+let get_account t ~handle () =
300300+ let url = build_url t.base_url [ "api"; "v1"; "accounts"; handle ] [] in
301301+ let account = get_json ~session:t.session ~url Peertube_account.jsont in
302302+ Log.info (fun m ->
303303+ m "Fetched account: %s"
304304+ (Peertube_account_summary.display_name
305305+ (Peertube_account.summary account)));
306306+ account
307307+308308+let get_account_videos t ?(count = 20) ?(start = 0) ~handle () =
309309+ let url =
310310+ build_url t.base_url
311311+ [ "api"; "v1"; "accounts"; handle; "videos" ]
312312+ [
313313+ Some ("count", string_of_int count); Some ("start", string_of_int start);
314314+ ]
315315+ in
316316+ let result = get_json ~session:t.session ~url video_paginated_jsont in
317317+ Log.info (fun m ->
318318+ m "Fetched %d videos from account %s (total: %d)"
319319+ (List.length (Peertube_paginated.data result))
320320+ handle
321321+ (Peertube_paginated.total result));
322322+ result
323323+324324+let get_account_channels t ?(count = 20) ?(start = 0) ~handle () =
325325+ let url =
326326+ build_url t.base_url
327327+ [ "api"; "v1"; "accounts"; handle; "video-channels" ]
328328+ [
329329+ Some ("count", string_of_int count); Some ("start", string_of_int start);
330330+ ]
331331+ in
332332+ let result = get_json ~session:t.session ~url channel_paginated_jsont in
333333+ Log.info (fun m ->
334334+ m "Fetched %d channels from account %s (total: %d)"
335335+ (List.length (Peertube_paginated.data result))
336336+ handle
337337+ (Peertube_paginated.total result));
338338+ result
339339+340340+(* Playlist operations *)
341341+342342+let playlist_paginated_jsont =
343343+ Peertube_paginated.jsont ~kind:"playlist_paginated" Peertube_playlist.jsont
344344+345345+let playlist_element_paginated_jsont =
346346+ Peertube_paginated.jsont ~kind:"playlist_element_paginated"
347347+ Peertube_playlist_element.jsont
348348+349349+let list_playlists t ?(count = 20) ?(start = 0) () =
350350+ let url =
351351+ build_url t.base_url
352352+ [ "api"; "v1"; "video-playlists" ]
353353+ [
354354+ Some ("count", string_of_int count); Some ("start", string_of_int start);
355355+ ]
356356+ in
357357+ let result = get_json ~session:t.session ~url playlist_paginated_jsont in
358358+ Log.info (fun m ->
359359+ m "Listed %d playlists (total: %d)"
360360+ (List.length (Peertube_paginated.data result))
361361+ (Peertube_paginated.total result));
362362+ result
363363+364364+let search_playlists t ~query ?(count = 20) ?(start = 0) () =
365365+ let url =
366366+ build_url t.base_url
367367+ [ "api"; "v1"; "search"; "video-playlists" ]
368368+ [
369369+ Some ("search", query);
370370+ Some ("count", string_of_int count);
371371+ Some ("start", string_of_int start);
372372+ ]
373373+ in
374374+ let result = get_json ~session:t.session ~url playlist_paginated_jsont in
375375+ Log.info (fun m ->
376376+ m "Search '%s' found %d playlists (total: %d)" query
377377+ (List.length (Peertube_paginated.data result))
378378+ (Peertube_paginated.total result));
379379+ result
380380+381381+let get_playlist t ~id () =
382382+ let url = build_url t.base_url [ "api"; "v1"; "video-playlists"; id ] [] in
383383+ let playlist = get_json ~session:t.session ~url Peertube_playlist.jsont in
384384+ Log.info (fun m ->
385385+ m "Fetched playlist: %s" (Peertube_playlist.display_name playlist));
386386+ playlist
387387+388388+let get_playlist_videos t ?(count = 20) ?(start = 0) ~id () =
389389+ let url =
390390+ build_url t.base_url
391391+ [ "api"; "v1"; "video-playlists"; id; "videos" ]
392392+ [
393393+ Some ("count", string_of_int count); Some ("start", string_of_int start);
394394+ ]
395395+ in
396396+ let result =
397397+ get_json ~session:t.session ~url playlist_element_paginated_jsont
398398+ in
399399+ Log.info (fun m ->
400400+ m "Fetched %d videos from playlist (total: %d)"
401401+ (List.length (Peertube_paginated.data result))
402402+ (Peertube_paginated.total result));
403403+ result
404404+405405+let get_account_playlists t ?(count = 20) ?(start = 0) ~handle () =
406406+ let url =
407407+ build_url t.base_url
408408+ [ "api"; "v1"; "accounts"; handle; "video-playlists" ]
409409+ [
410410+ Some ("count", string_of_int count); Some ("start", string_of_int start);
411411+ ]
412412+ in
413413+ let result = get_json ~session:t.session ~url playlist_paginated_jsont in
414414+ Log.info (fun m ->
415415+ m "Fetched %d playlists from account %s (total: %d)"
416416+ (List.length (Peertube_paginated.data result))
417417+ handle
418418+ (Peertube_paginated.total result));
419419+ result
420420+421421+(* Server operations *)
422422+423423+let get_config t () =
424424+ let url = build_url t.base_url [ "api"; "v1"; "config" ] [] in
425425+ let config = get_json ~session:t.session ~url Peertube_server_config.jsont in
426426+ Log.info (fun m ->
427427+ m "Fetched config for: %s (v%s)"
428428+ (Peertube_instance_info.name (Peertube_server_config.instance config))
429429+ (Peertube_server_config.server_version config));
430430+ config
431431+432432+let get_stats t () =
433433+ let url = build_url t.base_url [ "api"; "v1"; "server"; "stats" ] [] in
434434+ let stats = get_json ~session:t.session ~url Peertube_server_stats.jsont in
435435+ Log.info (fun m ->
436436+ m "Fetched stats: %d users, %d videos"
437437+ (Peertube_server_stats.total_users stats)
438438+ (Peertube_server_stats.total_videos stats));
439439+ stats
440440+441441+(* Utilities *)
442442+443443+let thumbnail_url t video =
444444+ Option.map
445445+ (fun path -> t.base_url ^ path)
446446+ (Peertube_video.thumbnail_path video)
447447+448448+let download_thumbnail t ~video ~output_path () =
449449+ match thumbnail_url t video with
450450+ | None ->
451451+ Log.warn (fun m ->
452452+ m "No thumbnail available for video %s" (Peertube_video.uuid video));
453453+ Error
454454+ (`Msg
455455+ (Printf.sprintf "No thumbnail available for video %s"
456456+ (Peertube_video.uuid video)))
457457+ | Some url ->
458458+ Log.debug (fun m -> m "Downloading thumbnail from %s" url);
459459+ let response = Requests.get t.session url in
460460+ let status = Requests.Response.status_code response in
461461+ if Requests.Response.ok response then begin
462462+ let body = Requests.Response.text response in
463463+ try
464464+ let oc = open_out_bin output_path in
465465+ output_string oc body;
466466+ close_out oc;
467467+ Log.info (fun m ->
468468+ m "Downloaded thumbnail for %s to %s"
469469+ (Peertube_video.uuid video)
470470+ output_path);
471471+ Ok ()
472472+ with exn ->
473473+ Log.err (fun m ->
474474+ m "Failed to write thumbnail to %s: %s" output_path
475475+ (Printexc.to_string exn));
476476+ Error
477477+ (`Msg
478478+ (Printf.sprintf "Failed to write thumbnail: %s"
479479+ (Printexc.to_string exn)))
480480+ end
481481+ else begin
482482+ Log.err (fun m ->
483483+ m "HTTP error %d downloading thumbnail from %s" status url);
484484+ Error
485485+ (`Msg (Printf.sprintf "HTTP error downloading thumbnail: %d" status))
486486+ end
487487+488488+let to_tuple video =
489489+ let description =
490490+ Option.value ~default:"" (Peertube_video.description video)
491491+ in
492492+ let published_date =
493493+ Option.value
494494+ ~default:(Peertube_video.published_at video)
495495+ (Peertube_video.originally_published_at video)
496496+ in
497497+ ( description,
498498+ published_date,
499499+ Peertube_video.name video,
500500+ Peertube_video.url video,
501501+ Peertube_video.uuid video,
502502+ string_of_int (Peertube_video.id video) )
+240
lib/peertube_client.mli
···11+(** PeerTube API client functions.
22+33+ This module provides functions for interacting with the PeerTube REST API.
44+*)
55+66+(** {1 Session} *)
77+88+type t
99+(** A PeerTube API session.
1010+1111+ Encapsulates the HTTP client and base URL for making API requests. In
1212+ future, this may also contain authentication credentials. *)
1313+1414+val create : session:Requests.t -> base_url:string -> t
1515+(** [create ~session ~base_url] creates a new PeerTube API session.
1616+1717+ @param session HTTP client session from the Requests library
1818+ @param base_url
1919+ Base URL of the PeerTube instance (e.g., "https://framatube.org") *)
2020+2121+val base_url : t -> string
2222+(** [base_url t] returns the base URL of the PeerTube instance. *)
2323+2424+val http_session : t -> Requests.t
2525+(** [http_session t] returns the underlying HTTP session. *)
2626+2727+(** {1 Logging and Errors} *)
2828+2929+val log_src : Logs.src
3030+(** Log source for the PeerTube client. *)
3131+3232+exception Api_error of int * string
3333+(** API error with HTTP status code and message. *)
3434+3535+(** {1 Video Operations} *)
3636+3737+val list_videos :
3838+ t ->
3939+ ?count:int ->
4040+ ?start:int ->
4141+ ?sort:Peertube_video_sort.t ->
4242+ ?nsfw:bool ->
4343+ ?is_local:bool ->
4444+ ?is_live:bool ->
4545+ ?category_id:int ->
4646+ ?tags:string ->
4747+ unit ->
4848+ Peertube_video.t Peertube_paginated.t
4949+(** List videos with optional filtering and sorting.
5050+5151+ @param count Number of videos per page (default: 20)
5252+ @param start Starting offset for pagination (default: 0)
5353+ @param sort Sort order (default: Newest)
5454+ @param nsfw Include NSFW videos (default: false)
5555+ @param is_local Only local videos (default: None = both)
5656+ @param is_live Only live videos (default: None = both)
5757+ @param category_id Filter by category ID
5858+ @param tags Filter by tags (comma-separated) *)
5959+6060+val search_videos :
6161+ t ->
6262+ query:string ->
6363+ ?count:int ->
6464+ ?start:int ->
6565+ ?sort:Peertube_video_sort.t ->
6666+ ?search_target:[ `Local | `Search_index ] ->
6767+ ?duration_min:int ->
6868+ ?duration_max:int ->
6969+ ?published_after:Ptime.t ->
7070+ ?published_before:Ptime.t ->
7171+ unit ->
7272+ Peertube_video.t Peertube_paginated.t
7373+(** Search for videos.
7474+7575+ @param query Search query string
7676+ @param count Number of results per page (default: 20)
7777+ @param start Starting offset (default: 0)
7878+ @param sort Sort order (default: Best for search)
7979+ @param search_target Search locally or everywhere (default: local)
8080+ @param duration_min Minimum duration in seconds
8181+ @param duration_max Maximum duration in seconds
8282+ @param published_after Only videos published after this date
8383+ @param published_before Only videos published before this date *)
8484+8585+val fetch_channel_videos :
8686+ t ->
8787+ ?count:int ->
8888+ ?start:int ->
8989+ channel:string ->
9090+ unit ->
9191+ Peertube_video.t Peertube_paginated.t
9292+(** Fetch videos from a channel with pagination. *)
9393+9494+val fetch_all_channel_videos :
9595+ t ->
9696+ ?page_size:int ->
9797+ ?max_pages:int ->
9898+ channel:string ->
9999+ unit ->
100100+ Peertube_video.t list
101101+(** Fetch all videos from a channel using automatic pagination. *)
102102+103103+val fetch_video_details : t -> uuid:string -> unit -> Peertube_video.t
104104+(** Fetch detailed information for a single video by UUID. *)
105105+106106+val get_categories : t -> unit -> (int * string) list
107107+(** Get available video categories. *)
108108+109109+val get_licences : t -> unit -> (int * string) list
110110+(** Get available video licences. *)
111111+112112+val get_languages : t -> unit -> (string * string) list
113113+(** Get available video languages. *)
114114+115115+(** {1 Channel Operations} *)
116116+117117+val list_channels :
118118+ t ->
119119+ ?count:int ->
120120+ ?start:int ->
121121+ ?sort:string ->
122122+ unit ->
123123+ Peertube_channel.t Peertube_paginated.t
124124+(** List all channels on the instance.
125125+126126+ @param count Number per page (default: 20)
127127+ @param start Starting offset (default: 0)
128128+ @param sort Sort order: createdAt, -createdAt, etc. *)
129129+130130+val search_channels :
131131+ t ->
132132+ query:string ->
133133+ ?count:int ->
134134+ ?start:int ->
135135+ unit ->
136136+ Peertube_channel.t Peertube_paginated.t
137137+(** Search for channels.
138138+139139+ @param query Search query string *)
140140+141141+val get_channel : t -> handle:string -> unit -> Peertube_channel.t
142142+(** Get details for a specific channel. *)
143143+144144+(** {1 Account Operations} *)
145145+146146+val list_accounts :
147147+ t ->
148148+ ?count:int ->
149149+ ?start:int ->
150150+ ?sort:string ->
151151+ unit ->
152152+ Peertube_account.t Peertube_paginated.t
153153+(** List accounts on the instance. *)
154154+155155+val get_account : t -> handle:string -> unit -> Peertube_account.t
156156+(** Get details for a specific account. *)
157157+158158+val get_account_videos :
159159+ t ->
160160+ ?count:int ->
161161+ ?start:int ->
162162+ handle:string ->
163163+ unit ->
164164+ Peertube_video.t Peertube_paginated.t
165165+(** Get videos from a specific account. *)
166166+167167+val get_account_channels :
168168+ t ->
169169+ ?count:int ->
170170+ ?start:int ->
171171+ handle:string ->
172172+ unit ->
173173+ Peertube_channel.t Peertube_paginated.t
174174+(** Get channels owned by an account. *)
175175+176176+(** {1 Playlist Operations} *)
177177+178178+val list_playlists :
179179+ t ->
180180+ ?count:int ->
181181+ ?start:int ->
182182+ unit ->
183183+ Peertube_playlist.t Peertube_paginated.t
184184+(** List playlists on the instance. *)
185185+186186+val search_playlists :
187187+ t ->
188188+ query:string ->
189189+ ?count:int ->
190190+ ?start:int ->
191191+ unit ->
192192+ Peertube_playlist.t Peertube_paginated.t
193193+(** Search for playlists. *)
194194+195195+val get_playlist : t -> id:string -> unit -> Peertube_playlist.t
196196+(** Get details for a specific playlist. *)
197197+198198+val get_playlist_videos :
199199+ t ->
200200+ ?count:int ->
201201+ ?start:int ->
202202+ id:string ->
203203+ unit ->
204204+ Peertube_playlist_element.t Peertube_paginated.t
205205+(** Get videos in a playlist. *)
206206+207207+val get_account_playlists :
208208+ t ->
209209+ ?count:int ->
210210+ ?start:int ->
211211+ handle:string ->
212212+ unit ->
213213+ Peertube_playlist.t Peertube_paginated.t
214214+(** Get playlists owned by an account. *)
215215+216216+(** {1 Server Operations} *)
217217+218218+val get_config : t -> unit -> Peertube_server_config.t
219219+(** Get server configuration. *)
220220+221221+val get_stats : t -> unit -> Peertube_server_stats.t
222222+(** Get server statistics. *)
223223+224224+(** {1 Utilities} *)
225225+226226+val thumbnail_url : t -> Peertube_video.t -> string option
227227+(** Get the full thumbnail URL for a video. *)
228228+229229+val download_thumbnail :
230230+ t ->
231231+ video:Peertube_video.t ->
232232+ output_path:string ->
233233+ unit ->
234234+ (unit, [ `Msg of string ]) result
235235+(** Download a video's thumbnail to a file. *)
236236+237237+val to_tuple :
238238+ Peertube_video.t -> string * Ptime.t * string * string * string * string
239239+(** Convert a video to a tuple for external use. Returns
240240+ [(description, published_date, title, url, uuid, id_string)]. *)
+64
lib/peertube_instance_info.ml
···11+(** Basic PeerTube server/instance information. *)
22+33+type t = {
44+ name : string;
55+ short_description : string;
66+ description : string option;
77+ terms : string option;
88+ is_nsfw : bool;
99+ default_nsfw_policy : string;
1010+ default_client_route : string;
1111+}
1212+1313+let make ~name ~short_description ~description ~terms ~is_nsfw
1414+ ~default_nsfw_policy ~default_client_route =
1515+ {
1616+ name;
1717+ short_description;
1818+ description;
1919+ terms;
2020+ is_nsfw;
2121+ default_nsfw_policy;
2222+ default_client_route;
2323+ }
2424+2525+let name t = t.name
2626+let short_description t = t.short_description
2727+let description t = t.description
2828+let terms t = t.terms
2929+let is_nsfw t = t.is_nsfw
3030+let default_nsfw_policy t = t.default_nsfw_policy
3131+let default_client_route t = t.default_client_route
3232+3333+let jsont : t Jsont.t =
3434+ let make name short_description description terms is_nsfw default_nsfw_policy
3535+ default_client_route =
3636+ {
3737+ name;
3838+ short_description;
3939+ description;
4040+ terms;
4141+ is_nsfw;
4242+ default_nsfw_policy;
4343+ default_client_route;
4444+ }
4545+ in
4646+ Jsont.Object.map ~kind:"instance_info" make
4747+ |> Jsont.Object.mem "name" Jsont.string ~enc:(fun i -> i.name)
4848+ |> Jsont.Object.mem "shortDescription" Jsont.string ~dec_absent:""
4949+ ~enc:(fun i -> i.short_description)
5050+ |> Jsont.Object.mem "description" (Jsont.option Jsont.string)
5151+ ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun i -> i.description)
5252+ |> Jsont.Object.mem "terms" (Jsont.option Jsont.string)
5353+ ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun i -> i.terms)
5454+ |> Jsont.Object.mem "isNSFW" Jsont.bool ~dec_absent:false ~enc:(fun i ->
5555+ i.is_nsfw)
5656+ |> Jsont.Object.mem "defaultNSFWPolicy" Jsont.string ~dec_absent:"display"
5757+ ~enc:(fun i -> i.default_nsfw_policy)
5858+ |> Jsont.Object.mem "defaultClientRoute" Jsont.string
5959+ ~dec_absent:"/videos/trending" ~enc:(fun i -> i.default_client_route)
6060+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
6161+6262+let pp ppf t =
6363+ Fmt.pf ppf "@[<hov 2>{ name = %S;@ short_description = %S;@ is_nsfw = %b }@]"
6464+ t.name t.short_description t.is_nsfw
+42
lib/peertube_instance_info.mli
···11+(** Basic PeerTube server/instance information. *)
22+33+type t
44+(** Instance info type. *)
55+66+val make :
77+ name:string ->
88+ short_description:string ->
99+ description:string option ->
1010+ terms:string option ->
1111+ is_nsfw:bool ->
1212+ default_nsfw_policy:string ->
1313+ default_client_route:string ->
1414+ t
1515+(** Create instance info. *)
1616+1717+val name : t -> string
1818+(** Instance name. *)
1919+2020+val short_description : t -> string
2121+(** Short description. *)
2222+2323+val description : t -> string option
2424+(** Full description. *)
2525+2626+val terms : t -> string option
2727+(** Terms of service. *)
2828+2929+val is_nsfw : t -> bool
3030+(** Whether the instance is NSFW-focused. *)
3131+3232+val default_nsfw_policy : t -> string
3333+(** Default NSFW display policy. *)
3434+3535+val default_client_route : t -> string
3636+(** Default client route. *)
3737+3838+val jsont : t Jsont.t
3939+(** JSON codec. *)
4040+4141+val pp : Format.formatter -> t -> unit
4242+(** Pretty printer. *)
+24
lib/peertube_labeled.ml
···11+(** Labeled values with an ID and display label. *)
22+33+type 'a t = { id : 'a; label : string }
44+55+let make ~id ~label = { id; label }
66+let id t = t.id
77+let label t = t.label
88+99+let int_jsont : int t Jsont.t =
1010+ let make id label = { id; label } in
1111+ Jsont.Object.map ~kind:"int_labeled" make
1212+ |> Jsont.Object.mem "id" Jsont.int ~enc:(fun l -> l.id)
1313+ |> Jsont.Object.mem "label" Jsont.string ~enc:(fun l -> l.label)
1414+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
1515+1616+let string_jsont : string t Jsont.t =
1717+ let make id label = { id; label } in
1818+ Jsont.Object.map ~kind:"string_labeled" make
1919+ |> Jsont.Object.mem "id" Jsont.string ~enc:(fun l -> l.id)
2020+ |> Jsont.Object.mem "label" Jsont.string ~enc:(fun l -> l.label)
2121+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
2222+2323+let pp pp_id ppf t =
2424+ Fmt.pf ppf "@[<hov 2>{ id = %a;@ label = %S }@]" pp_id t.id t.label
+25
lib/peertube_labeled.mli
···11+(** Labeled values with an ID and display label.
22+33+ Used for categories, licences, languages, and other enumerated values in the
44+ PeerTube API. *)
55+66+type 'a t
77+(** A labeled value with an ID and human-readable label. *)
88+99+val make : id:'a -> label:string -> 'a t
1010+(** Create a labeled value. *)
1111+1212+val id : 'a t -> 'a
1313+(** Get the ID. *)
1414+1515+val label : 'a t -> string
1616+(** Get the label. *)
1717+1818+val int_jsont : int t Jsont.t
1919+(** JSON codec for labeled values with int IDs. *)
2020+2121+val string_jsont : string t Jsont.t
2222+(** JSON codec for labeled values with string IDs. *)
2323+2424+val pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
2525+(** Pretty printer for labeled values. *)
+19
lib/peertube_paginated.ml
···11+(** Paginated API responses. *)
22+33+type 'a t = { total : int; data : 'a list }
44+55+let make ~total ~data = { total; data }
66+let total t = t.total
77+let data t = t.data
88+99+let jsont ~kind item_jsont =
1010+ let make total data = { total; data } in
1111+ Jsont.Object.map ~kind make
1212+ |> Jsont.Object.mem "total" Jsont.int ~enc:(fun r -> r.total)
1313+ |> Jsont.Object.mem "data" (Jsont.list item_jsont) ~enc:(fun r -> r.data)
1414+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
1515+1616+let pp pp_item ppf t =
1717+ Fmt.pf ppf "@[<hov 2>{ total = %d;@ data = %a }@]" t.total
1818+ Fmt.(brackets (list ~sep:semi pp_item))
1919+ t.data
+19
lib/peertube_paginated.mli
···11+(** Paginated API responses. *)
22+33+type 'a t
44+(** A paginated response containing total count and data list. *)
55+66+val make : total:int -> data:'a list -> 'a t
77+(** Create a paginated response. *)
88+99+val total : 'a t -> int
1010+(** Total number of items available. *)
1111+1212+val data : 'a t -> 'a list
1313+(** Items in this page. *)
1414+1515+val jsont : kind:string -> 'a Jsont.t -> 'a t Jsont.t
1616+(** Create a JSON codec for paginated responses. *)
1717+1818+val pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit
1919+(** Pretty printer for paginated responses. *)
···11+(** PeerTube video playlist. *)
22+33+type t
44+(** Playlist type. *)
55+66+val make :
77+ id:int ->
88+ uuid:string ->
99+ short_uuid:string option ->
1010+ display_name:string ->
1111+ description:string option ->
1212+ privacy:Peertube_playlist_privacy.t ->
1313+ url:string ->
1414+ thumbnail_path:string option ->
1515+ videos_length:int ->
1616+ playlist_type:Peertube_playlist_type.t ->
1717+ created_at:Ptime.t ->
1818+ updated_at:Ptime.t ->
1919+ owner_account:Peertube_account_summary.t option ->
2020+ video_channel:Peertube_channel_summary.t option ->
2121+ t
2222+(** Create a playlist. *)
2323+2424+val id : t -> int
2525+(** Playlist ID. *)
2626+2727+val uuid : t -> string
2828+(** Playlist UUID. *)
2929+3030+val short_uuid : t -> string option
3131+(** Short UUID. *)
3232+3333+val display_name : t -> string
3434+(** Display name. *)
3535+3636+val description : t -> string option
3737+(** Description. *)
3838+3939+val privacy : t -> Peertube_playlist_privacy.t
4040+(** Privacy level. *)
4141+4242+val url : t -> string
4343+(** Playlist URL. *)
4444+4545+val thumbnail_path : t -> string option
4646+(** Thumbnail path (relative). *)
4747+4848+val videos_length : t -> int
4949+(** Number of videos in playlist. *)
5050+5151+val playlist_type : t -> Peertube_playlist_type.t
5252+(** Playlist type. *)
5353+5454+val created_at : t -> Ptime.t
5555+(** Creation timestamp. *)
5656+5757+val updated_at : t -> Ptime.t
5858+(** Last update timestamp. *)
5959+6060+val owner_account : t -> Peertube_account_summary.t option
6161+(** Owner account. *)
6262+6363+val video_channel : t -> Peertube_channel_summary.t option
6464+(** Associated video channel. *)
6565+6666+val jsont : t Jsont.t
6767+(** JSON codec. *)
6868+6969+val pp : Format.formatter -> t -> unit
7070+(** Pretty printer. *)
+39
lib/peertube_playlist_element.ml
···11+(** An element in a PeerTube playlist. *)
22+33+type t = {
44+ id : int;
55+ position : int;
66+ start_timestamp : int option;
77+ stop_timestamp : int option;
88+ video : Peertube_video.t option;
99+}
1010+1111+let make ~id ~position ~start_timestamp ~stop_timestamp ~video =
1212+ { id; position; start_timestamp; stop_timestamp; video }
1313+1414+let id t = t.id
1515+let position t = t.position
1616+let start_timestamp t = t.start_timestamp
1717+let stop_timestamp t = t.stop_timestamp
1818+let video t = t.video
1919+2020+let jsont : t Jsont.t =
2121+ let make id position start_timestamp stop_timestamp video =
2222+ { id; position; start_timestamp; stop_timestamp; video }
2323+ in
2424+ Jsont.Object.map ~kind:"playlist_element" make
2525+ |> Jsont.Object.mem "id" Jsont.int ~enc:(fun e -> e.id)
2626+ |> Jsont.Object.mem "position" Jsont.int ~enc:(fun e -> e.position)
2727+ |> Jsont.Object.mem "startTimestamp" (Jsont.option Jsont.int) ~dec_absent:None
2828+ ~enc_omit:Option.is_none ~enc:(fun e -> e.start_timestamp)
2929+ |> Jsont.Object.mem "stopTimestamp" (Jsont.option Jsont.int) ~dec_absent:None
3030+ ~enc_omit:Option.is_none ~enc:(fun e -> e.stop_timestamp)
3131+ |> Jsont.Object.mem "video" (Jsont.option Peertube_video.jsont)
3232+ ~dec_absent:None ~enc_omit:Option.is_none ~enc:(fun e -> e.video)
3333+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
3434+3535+let pp ppf t =
3636+ Fmt.pf ppf "@[<hov 2>{ id = %d;@ position = %d;@ video = %a }@]" t.id
3737+ t.position
3838+ Fmt.(option ~none:(any "None") Peertube_video.pp)
3939+ t.video
+34
lib/peertube_playlist_element.mli
···11+(** An element in a PeerTube playlist. *)
22+33+type t
44+(** Playlist element type. *)
55+66+val make :
77+ id:int ->
88+ position:int ->
99+ start_timestamp:int option ->
1010+ stop_timestamp:int option ->
1111+ video:Peertube_video.t option ->
1212+ t
1313+(** Create a playlist element. *)
1414+1515+val id : t -> int
1616+(** Element ID. *)
1717+1818+val position : t -> int
1919+(** Position in playlist (0-indexed). *)
2020+2121+val start_timestamp : t -> int option
2222+(** Start timestamp in seconds. *)
2323+2424+val stop_timestamp : t -> int option
2525+(** Stop timestamp in seconds. *)
2626+2727+val video : t -> Peertube_video.t option
2828+(** The video. *)
2929+3030+val jsont : t Jsont.t
3131+(** JSON codec. *)
3232+3333+val pp : Format.formatter -> t -> unit
3434+(** Pretty printer. *)
+18
lib/peertube_playlist_privacy.ml
···11+(** Playlist privacy levels in PeerTube. *)
22+33+type t = Public | Unlisted | Private
44+55+let to_int = function Public -> 1 | Unlisted -> 2 | Private -> 3
66+let of_int = function 1 -> Public | 2 -> Unlisted | 3 -> Private | _ -> Public
77+88+let jsont : t Jsont.t =
99+ let make id _label = of_int id in
1010+ Jsont.Object.map ~kind:"playlist_privacy" make
1111+ |> Jsont.Object.mem "id" Jsont.int ~enc:to_int
1212+ |> Jsont.Object.mem "label" Jsont.string ~dec_absent:"" ~enc:(Fun.const "")
1313+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
1414+1515+let pp ppf = function
1616+ | Public -> Fmt.string ppf "Public"
1717+ | Unlisted -> Fmt.string ppf "Unlisted"
1818+ | Private -> Fmt.string ppf "Private"
+16
lib/peertube_playlist_privacy.mli
···11+(** Playlist privacy levels in PeerTube. *)
22+33+(** Playlist privacy level. *)
44+type t = Public | Unlisted | Private
55+66+val to_int : t -> int
77+(** Convert privacy to its integer representation. *)
88+99+val of_int : int -> t
1010+(** Convert integer to privacy level. *)
1111+1212+val jsont : t Jsont.t
1313+(** JSON codec for playlist privacy. *)
1414+1515+val pp : Format.formatter -> t -> unit
1616+(** Pretty printer for playlist privacy. *)
+17
lib/peertube_playlist_type.ml
···11+(** Playlist types in PeerTube. *)
22+33+type t = Regular | WatchLater
44+55+let to_int = function Regular -> 1 | WatchLater -> 2
66+let of_int = function 1 -> Regular | 2 -> WatchLater | _ -> Regular
77+88+let jsont : t Jsont.t =
99+ let make id _label = of_int id in
1010+ Jsont.Object.map ~kind:"playlist_type" make
1111+ |> Jsont.Object.mem "id" Jsont.int ~enc:to_int
1212+ |> Jsont.Object.mem "label" Jsont.string ~dec_absent:"" ~enc:(Fun.const "")
1313+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
1414+1515+let pp ppf = function
1616+ | Regular -> Fmt.string ppf "Regular"
1717+ | WatchLater -> Fmt.string ppf "WatchLater"
+16
lib/peertube_playlist_type.mli
···11+(** Playlist types in PeerTube. *)
22+33+(** Playlist type. *)
44+type t = Regular | WatchLater
55+66+val to_int : t -> int
77+(** Convert type to its integer representation. *)
88+99+val of_int : int -> t
1010+(** Convert integer to playlist type. *)
1111+1212+val jsont : t Jsont.t
1313+(** JSON codec for playlist type. *)
1414+1515+val pp : Format.formatter -> t -> unit
1616+(** Pretty printer for playlist type. *)
+29
lib/peertube_privacy.ml
···11+(** Video privacy levels in PeerTube. *)
22+33+type t = Public | Unlisted | Private | Internal
44+55+let to_int = function
66+ | Public -> 1
77+ | Unlisted -> 2
88+ | Private -> 3
99+ | Internal -> 4
1010+1111+let of_int = function
1212+ | 1 -> Public
1313+ | 2 -> Unlisted
1414+ | 3 -> Private
1515+ | 4 -> Internal
1616+ | _ -> Public
1717+1818+let jsont : t Jsont.t =
1919+ let make id _label = of_int id in
2020+ Jsont.Object.map ~kind:"privacy" make
2121+ |> Jsont.Object.mem "id" Jsont.int ~enc:to_int
2222+ |> Jsont.Object.mem "label" Jsont.string ~dec_absent:"" ~enc:(Fun.const "")
2323+ |> Jsont.Object.skip_unknown |> Jsont.Object.finish
2424+2525+let pp ppf = function
2626+ | Public -> Fmt.string ppf "Public"
2727+ | Unlisted -> Fmt.string ppf "Unlisted"
2828+ | Private -> Fmt.string ppf "Private"
2929+ | Internal -> Fmt.string ppf "Internal"
+16
lib/peertube_privacy.mli
···11+(** Video privacy levels in PeerTube. *)
22+33+(** Video privacy level. *)
44+type t = Public | Unlisted | Private | Internal
55+66+val to_int : t -> int
77+(** Convert privacy to its integer representation. *)
88+99+val of_int : int -> t
1010+(** Convert integer to privacy level. *)
1111+1212+val jsont : t Jsont.t
1313+(** JSON codec for privacy. *)
1414+1515+val pp : Format.formatter -> t -> unit
1616+(** Pretty printer for privacy. *)
+11
lib/peertube_ptime.ml
···11+(** Ptime JSON codec for RFC3339 date strings. *)
22+33+let parse_date str =
44+ match Ptime.of_rfc3339 str with
55+ | Ok (date, _, _) -> Ok date
66+ | Error _ -> Error (Fmt.str "Invalid RFC3339 date: %s" str)
77+88+let jsont : Ptime.t Jsont.t =
99+ Jsont.of_of_string ~kind:"ptime" ~enc:Ptime.to_rfc3339 parse_date
1010+1111+let pp = Ptime.pp_rfc3339 ()
+7
lib/peertube_ptime.mli
···11+(** Ptime JSON codec for RFC3339 date strings. *)
22+33+val jsont : Ptime.t Jsont.t
44+(** JSON codec for Ptime.t values encoded as RFC3339 strings. *)
55+66+val pp : Format.formatter -> Ptime.t -> unit
77+(** Pretty printer for Ptime.t values. *)
···11+(** PeerTube server statistics. *)
22+33+type t
44+(** Server stats type. *)
55+66+val make :
77+ total_users:int ->
88+ total_daily_active_users:int ->
99+ total_weekly_active_users:int ->
1010+ total_monthly_active_users:int ->
1111+ total_local_videos:int ->
1212+ total_local_video_views:int ->
1313+ total_local_video_comments:int ->
1414+ total_local_video_files_size:int64 ->
1515+ total_videos:int ->
1616+ total_video_comments:int ->
1717+ total_local_video_channels:int ->
1818+ total_local_playlists:int ->
1919+ total_instance_followers:int ->
2020+ total_instance_following:int ->
2121+ t
2222+(** Create server stats. *)
2323+2424+val total_users : t -> int
2525+(** Total registered users. *)
2626+2727+val total_daily_active_users : t -> int
2828+(** Daily active users. *)
2929+3030+val total_weekly_active_users : t -> int
3131+(** Weekly active users. *)
3232+3333+val total_monthly_active_users : t -> int
3434+(** Monthly active users. *)
3535+3636+val total_local_videos : t -> int
3737+(** Total local videos. *)
3838+3939+val total_local_video_views : t -> int
4040+(** Total local video views. *)
4141+4242+val total_local_video_comments : t -> int
4343+(** Total local video comments. *)
4444+4545+val total_local_video_files_size : t -> int64
4646+(** Total local video files size in bytes. *)
4747+4848+val total_videos : t -> int
4949+(** Total videos (including federated). *)
5050+5151+val total_video_comments : t -> int
5252+(** Total video comments (including federated). *)
5353+5454+val total_local_video_channels : t -> int
5555+(** Total local video channels. *)
5656+5757+val total_local_playlists : t -> int
5858+(** Total local playlists. *)
5959+6060+val total_instance_followers : t -> int
6161+(** Total instance followers. *)
6262+6363+val total_instance_following : t -> int
6464+(** Total instances being followed. *)
6565+6666+val jsont : t Jsont.t
6767+(** JSON codec. *)
6868+6969+val pp : Format.formatter -> t -> unit
7070+(** Pretty printer. *)
···11+(** PeerTube video record. *)
22+33+type t
44+(** Video type. *)
55+66+val make :
77+ id:int ->
88+ uuid:string ->
99+ short_uuid:string option ->
1010+ name:string ->
1111+ description:string option ->
1212+ url:string ->
1313+ embed_path:string ->
1414+ published_at:Ptime.t ->
1515+ originally_published_at:Ptime.t option ->
1616+ updated_at:Ptime.t option ->
1717+ thumbnail_path:string option ->
1818+ preview_path:string option ->
1919+ tags:string list ->
2020+ duration:int ->
2121+ views:int ->
2222+ likes:int ->
2323+ dislikes:int ->
2424+ is_local:bool ->
2525+ is_live:bool ->
2626+ privacy:Peertube_privacy.t ->
2727+ category:int Peertube_labeled.t option ->
2828+ licence:int Peertube_labeled.t option ->
2929+ language:string Peertube_labeled.t option ->
3030+ channel:Peertube_channel_summary.t option ->
3131+ account:Peertube_account_summary.t option ->
3232+ t
3333+(** Create a video. *)
3434+3535+val id : t -> int
3636+(** Video ID. *)
3737+3838+val uuid : t -> string
3939+(** Video UUID. *)
4040+4141+val short_uuid : t -> string option
4242+(** Short UUID. *)
4343+4444+val name : t -> string
4545+(** Video title. *)
4646+4747+val description : t -> string option
4848+(** Video description. *)
4949+5050+val url : t -> string
5151+(** Video URL. *)
5252+5353+val embed_path : t -> string
5454+(** Embed path. *)
5555+5656+val published_at : t -> Ptime.t
5757+(** Publication timestamp. *)
5858+5959+val originally_published_at : t -> Ptime.t option
6060+(** Original publication timestamp. *)
6161+6262+val updated_at : t -> Ptime.t option
6363+(** Last update timestamp. *)
6464+6565+val thumbnail_path : t -> string option
6666+(** Thumbnail path (relative). *)
6767+6868+val preview_path : t -> string option
6969+(** Preview path (relative). *)
7070+7171+val tags : t -> string list
7272+(** Video tags. *)
7373+7474+val duration : t -> int
7575+(** Duration in seconds. *)
7676+7777+val views : t -> int
7878+(** View count. *)
7979+8080+val likes : t -> int
8181+(** Like count. *)
8282+8383+val dislikes : t -> int
8484+(** Dislike count. *)
8585+8686+val is_local : t -> bool
8787+(** Whether the video is local to this instance. *)
8888+8989+val is_live : t -> bool
9090+(** Whether this is a live stream. *)
9191+9292+val privacy : t -> Peertube_privacy.t
9393+(** Privacy level. *)
9494+9595+val category : t -> int Peertube_labeled.t option
9696+(** Video category. *)
9797+9898+val licence : t -> int Peertube_labeled.t option
9999+(** Video licence. *)
100100+101101+val language : t -> string Peertube_labeled.t option
102102+(** Video language. *)
103103+104104+val channel : t -> Peertube_channel_summary.t option
105105+(** Channel that published the video. *)
106106+107107+val account : t -> Peertube_account_summary.t option
108108+(** Account that published the video. *)
109109+110110+val jsont : t Jsont.t
111111+(** JSON codec. *)
112112+113113+val pp : Format.formatter -> t -> unit
114114+(** Pretty printer. *)
+23
lib/peertube_video_sort.ml
···11+(** Video sort options for PeerTube API queries. *)
22+33+type t = Newest | Oldest | Views | Likes | Trending | Hot | Random | Best
44+55+let to_string = function
66+ | Newest -> "-publishedAt"
77+ | Oldest -> "publishedAt"
88+ | Views -> "-views"
99+ | Likes -> "-likes"
1010+ | Trending -> "-trending"
1111+ | Hot -> "-hot"
1212+ | Random -> "random"
1313+ | Best -> "-best"
1414+1515+let pp ppf = function
1616+ | Newest -> Fmt.string ppf "Newest"
1717+ | Oldest -> Fmt.string ppf "Oldest"
1818+ | Views -> Fmt.string ppf "Views"
1919+ | Likes -> Fmt.string ppf "Likes"
2020+ | Trending -> Fmt.string ppf "Trending"
2121+ | Hot -> Fmt.string ppf "Hot"
2222+ | Random -> Fmt.string ppf "Random"
2323+ | Best -> Fmt.string ppf "Best"
+10
lib/peertube_video_sort.mli
···11+(** Video sort options for PeerTube API queries. *)
22+33+(** Video sort order. *)
44+type t = Newest | Oldest | Views | Likes | Trending | Hot | Random | Best
55+66+val to_string : t -> string
77+(** Convert sort option to API query string. *)
88+99+val pp : Format.formatter -> t -> unit
1010+(** Pretty printer for sort options. *)
+38
peertube.opam
···11+# This file is generated by dune, edit dune-project instead
22+opam-version: "2.0"
33+synopsis: "PeerTube API client for OCaml using Eio"
44+description:
55+ "An OCaml client library for the PeerTube video platform API, built on Eio for effect-based I/O. Includes a command-line client (opeertube) for browsing videos, channels, accounts, and playlists."
66+maintainer: ["anil@recoil.org"]
77+authors: ["Anil Madhavapeddy"]
88+license: "ISC"
99+homepage: "https://git.recoil.org/anil.recoil.org/ocaml-peertube"
1010+bug-reports: "https://git.recoil.org/anil.recoil.org/ocaml-peertube/issues"
1111+depends: [
1212+ "dune" {>= "3.16"}
1313+ "ocaml" {>= "5.1.0"}
1414+ "eio" {>= "1.0"}
1515+ "eio_main" {>= "1.0"}
1616+ "requests" {>= "0.1"}
1717+ "jsont" {>= "0.1"}
1818+ "ptime" {>= "1.0"}
1919+ "fmt" {>= "0.9"}
2020+ "logs" {>= "0.7"}
2121+ "cmdliner" {>= "1.2"}
2222+ "odoc" {with-doc}
2323+]
2424+build: [
2525+ ["dune" "subst"] {dev}
2626+ [
2727+ "dune"
2828+ "build"
2929+ "-p"
3030+ name
3131+ "-j"
3232+ jobs
3333+ "@install"
3434+ "@runtest" {with-test}
3535+ "@doc" {with-doc}
3636+ ]
3737+]
3838+dev-repo: "https://git.recoil.org/anil.recoil.org/ocaml-peertube"