My agentic slop goes here. Not intended for anyone else!
at main 8.2 kB view raw
1(** PeerTube API client implementation (Eio version) *) 2 3(** Type representing a PeerTube client *) 4type 'net t_internal = { 5 base_url: string; 6 requests_session: (float Eio.Time.clock_ty Eio.Resource.t, 'net Eio.Net.ty Eio.Resource.t) Requests.t; 7} 8 9type t = [`Generic | `Unix] t_internal 10 11(** Create a new PeerTube client *) 12let create ~requests_session ~base_url : t = 13 { base_url; requests_session } 14 15(** Type representing a PeerTube video *) 16type video = { 17 id: int; 18 uuid: string; 19 name: string; 20 description: string option; 21 url: string; 22 embed_path: string; 23 published_at: Ptime.t; 24 originally_published_at: Ptime.t option; 25 thumbnail_path: string option; 26 tags: string list option; 27 unknown: Jsont.json; 28} 29 30(** Type for PeerTube API response containing videos *) 31type video_response = { 32 total: int; 33 data: video list; 34 unknown: Jsont.json; 35} 36 37(** Accessor functions for video *) 38let video_id (v : video) = v.id 39let video_uuid (v : video) = v.uuid 40let video_name (v : video) = v.name 41let video_description (v : video) = v.description 42let video_url (v : video) = v.url 43let video_embed_path (v : video) = v.embed_path 44let video_published_at (v : video) = v.published_at 45let video_originally_published_at (v : video) = v.originally_published_at 46let video_thumbnail_path (v : video) = v.thumbnail_path 47let video_tags (v : video) = v.tags 48let video_unknown (v : video) = v.unknown 49 50(** Accessor functions for video_response *) 51let video_response_total (vr : video_response) = vr.total 52let video_response_data (vr : video_response) = vr.data 53let video_response_unknown (vr : video_response) = vr.unknown 54 55(** RFC3339 timestamp codec *) 56module Rfc3339 = struct 57 let parse s = 58 Ptime.of_rfc3339 s |> Result.to_option |> Option.map (fun (t, _, _) -> t) 59 60 let format t = Ptime.to_rfc3339 ~frac_s:6 ~tz_offset_s:0 t 61 let pp ppf t = Format.pp_print_string ppf (format t) 62 63 let jsont = 64 let kind = "RFC 3339 timestamp" in 65 let doc = "An RFC 3339 date-time string" in 66 let dec s = 67 match parse s with 68 | Some t -> t 69 | None -> 70 Jsont.Error.msgf Jsont.Meta.none "%s: invalid RFC 3339 timestamp: %S" 71 kind s 72 in 73 Jsont.map ~kind ~doc ~dec ~enc:format Jsont.string 74end 75 76(** Jsont codec for video *) 77let video_jsont : video Jsont.t = 78 let kind = "PeerTube Video" in 79 let doc = "A PeerTube video object" in 80 81 let make_video id uuid name description url embed_path published_at 82 originally_published_at thumbnail_path tags unknown : video = 83 { id; uuid; name; description; url; embed_path; published_at; 84 originally_published_at; thumbnail_path; tags; unknown } 85 in 86 87 Jsont.Object.map ~kind ~doc make_video 88 |> Jsont.Object.mem "id" Jsont.int ~enc:video_id 89 |> Jsont.Object.mem "uuid" Jsont.string ~enc:video_uuid 90 |> Jsont.Object.mem "name" Jsont.string ~enc:video_name 91 |> Jsont.Object.opt_mem "description" Jsont.string ~enc:video_description 92 |> Jsont.Object.mem "url" Jsont.string ~enc:video_url 93 |> Jsont.Object.mem "embedPath" Jsont.string ~enc:video_embed_path 94 |> Jsont.Object.mem "publishedAt" Rfc3339.jsont ~enc:video_published_at 95 |> Jsont.Object.opt_mem "originallyPublishedAt" Rfc3339.jsont ~enc:video_originally_published_at 96 |> Jsont.Object.opt_mem "thumbnailPath" Jsont.string ~enc:video_thumbnail_path 97 |> Jsont.Object.opt_mem "tags" (Jsont.list Jsont.string) ~enc:video_tags 98 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:video_unknown 99 |> Jsont.Object.finish 100 101(** Jsont codec for video_response *) 102let video_response_jsont = 103 let kind = "PeerTube Video Response" in 104 let doc = "A PeerTube API response containing videos" in 105 106 let make_response total data unknown = 107 { total; data; unknown } 108 in 109 110 Jsont.Object.map ~kind ~doc make_response 111 |> Jsont.Object.mem "total" Jsont.int ~enc:video_response_total 112 |> Jsont.Object.mem "data" (Jsont.list video_jsont) ~enc:video_response_data 113 |> Jsont.Object.keep_unknown Jsont.json_mems ~enc:video_response_unknown 114 |> Jsont.Object.finish 115 116(** Parse a single video from JSON string *) 117let parse_video_string s = 118 match Jsont_bytesrw.decode_string' video_jsont s with 119 | Ok video -> video 120 | Error err -> failwith (Jsont.Error.to_string err) 121 122(** Parse a video response from JSON string *) 123let parse_video_response_string s = 124 match Jsont_bytesrw.decode_string' video_response_jsont s with 125 | Ok response -> response 126 | Error err -> failwith (Jsont.Error.to_string err) 127 128(** Fetch videos from a PeerTube instance channel with pagination support 129 @param count Number of videos to fetch per page 130 @param start Starting index for pagination (0-based) 131 @param client PeerTube client 132 @param channel Channel name to fetch videos from 133 @return The video response *) 134let fetch_channel_videos client ?(count=20) ?(start=0) channel = 135 let open Requests_json_api in 136 let url = Printf.sprintf "%s/api/v1/video-channels/%s/videos?count=%d&start=%d" 137 client.base_url channel count start in 138 get_json_exn client.requests_session url video_response_jsont 139 140(** Fetch all videos from a PeerTube instance channel using pagination 141 @param page_size Number of videos to fetch per page 142 @param max_pages Maximum number of pages to fetch (None for all pages) 143 @param client PeerTube client 144 @param channel Channel name to fetch videos from 145 @return All videos combined *) 146let fetch_all_channel_videos client ?(page_size=20) ?max_pages channel = 147 let rec fetch_pages start acc _total_count = 148 let response = fetch_channel_videos client ~count:page_size ~start channel in 149 let all_videos = acc @ response.data in 150 151 (* Determine if we need to fetch more pages *) 152 let fetched_count = start + List.length response.data in 153 let more_available = fetched_count < response.total in 154 let under_max_pages = match max_pages with 155 | None -> true 156 | Some max -> (start / page_size) + 1 < max 157 in 158 159 if more_available && under_max_pages then 160 fetch_pages fetched_count all_videos response.total 161 else 162 all_videos 163 in 164 fetch_pages 0 [] 0 165 166(** Fetch detailed information for a single video by UUID 167 @param client PeerTube client 168 @param uuid UUID of the video to fetch 169 @return The complete video details *) 170let fetch_video_details client uuid = 171 let open Requests_json_api in 172 let url = client.base_url / "api/v1/videos" / uuid in 173 get_json_exn client.requests_session url video_jsont 174 175(** Convert a PeerTube video to Bushel.Video.t compatible structure *) 176let to_bushel_video video = 177 let description = Option.value ~default:"" video.description in 178 let published_date = video.originally_published_at |> Option.value ~default:video.published_at in 179 (description, published_date, video.name, video.url, video.uuid, string_of_int video.id) 180 181(** Get the thumbnail URL for a video *) 182let thumbnail_url client video = 183 match video.thumbnail_path with 184 | Some path -> Some (client.base_url ^ path) 185 | None -> None 186 187(** Download a thumbnail to a file 188 @param client PeerTube client 189 @param fs The Eio filesystem capability 190 @param video The video to download the thumbnail for 191 @param output_path Path where to save the thumbnail 192 @return Ok () on success or Error with message *) 193let download_thumbnail client ~fs video output_path = 194 match thumbnail_url client video with 195 | None -> 196 Error (`Msg (Printf.sprintf "No thumbnail available for video %s" video.uuid)) 197 | Some url -> 198 try 199 let open Requests_json_api in 200 match get_result client.requests_session url with 201 | Error (status_code, _body) -> 202 Error (`Msg (Printf.sprintf "HTTP error downloading thumbnail: %d" status_code)) 203 | Ok body_str -> 204 try 205 let output_eio_path = Eio.Path.(fs / output_path) in 206 Eio.Path.save ~create:(`Or_truncate 0o644) output_eio_path body_str; 207 Ok () 208 with exn -> 209 Error (`Msg (Printf.sprintf "Failed to write thumbnail: %s" 210 (Printexc.to_string exn))) 211 with exn -> 212 Error (`Msg (Printf.sprintf "Failed to download thumbnail: %s" 213 (Printexc.to_string exn)))