OCaml bindings to the Peertube ActivityPub video sharing API
1(*---------------------------------------------------------------------------
2 Copyright (c) 2025 Anil Madhavapeddy. All rights reserved.
3 SPDX-License-Identifier: ISC
4 ---------------------------------------------------------------------------*)
5
6open Cmdliner
7
8let app_name = "peertube"
9
10(** {1 Common Options} *)
11
12let profile_arg =
13 let doc = "Profile name to use for authentication." in
14 Arg.(value & opt (some string) None & info ["profile"; "p"] ~docv:"PROFILE" ~doc)
15
16let server_url_arg =
17 let doc = "PeerTube server URL (e.g., https://peertube.example.com)." in
18 Arg.(required & opt (some string) None & info ["server"; "s"] ~docv:"URL" ~doc)
19
20let username_arg =
21 let doc = "Username for authentication." in
22 Arg.(required & opt (some string) None & info ["username"; "u"] ~docv:"USER" ~doc)
23
24let password_arg =
25 let doc = "Password for authentication. For security, prefer using the environment variable PEERTUBE_PASSWORD." in
26 Arg.(value & opt (some string) None & info ["password"] ~docv:"PASS" ~doc)
27
28(* Requests config term *)
29let requests_config_term fs =
30 Requests.Cmd.config_term app_name fs
31
32(** {1 Login Command} *)
33
34let login_cmd env fs =
35 let login_action profile server_url username password requests_config =
36 Error.wrap @@ fun () ->
37 Eio.Switch.run @@ fun sw ->
38 let password = match password with
39 | Some p -> p
40 | None ->
41 match Sys.getenv_opt "PEERTUBE_PASSWORD" with
42 | Some p -> p
43 | None -> Error.fail "Password required. Use --password or set PEERTUBE_PASSWORD environment variable."
44 in
45 let requests_config = Some requests_config in
46 let client = Client.login_password ~sw ~env ?requests_config ?profile ~server_url ~username ~password () in
47 Fmt.pr "@[<v>%a Logged in successfully@,%a@]@."
48 Fmt.(styled (`Fg `Green) string) "[OK]"
49 Session.pp (Client.session client);
50 0
51 in
52 let term = Term.(const login_action $ profile_arg $ server_url_arg $ username_arg $ password_arg $ requests_config_term fs) in
53 let info = Cmd.info "login"
54 ~doc:"Login to a PeerTube server"
55 ~man:[
56 `S Manpage.s_description;
57 `P "Authenticate with a PeerTube server using username and password.";
58 `P "The OAuth client credentials are automatically retrieved from the server.";
59 `S Manpage.s_examples;
60 `Pre " peertube auth login -s https://peertube.example.com -u myuser";
61 `P "You can also set the password via environment variable:";
62 `Pre " PEERTUBE_PASSWORD=mypass peertube auth login -s https://peertube.example.com -u myuser";
63 ]
64 in
65 Cmd.v info term
66
67(** {1 Logout Command} *)
68
69let logout_cmd _env fs =
70 let logout_action profile =
71 Error.wrap @@ fun () ->
72 Session.clear fs ?profile ();
73 Fmt.pr "@[%a Logged out@]@." Fmt.(styled (`Fg `Green) string) "[OK]";
74 0
75 in
76 let term = Term.(const logout_action $ profile_arg) in
77 let info = Cmd.info "logout"
78 ~doc:"Logout from PeerTube"
79 ~man:[
80 `S Manpage.s_description;
81 `P "Remove saved authentication credentials.";
82 ]
83 in
84 Cmd.v info term
85
86(** {1 Status Command} *)
87
88let status_cmd _env fs =
89 let status_action profile =
90 Error.wrap @@ fun () ->
91 match Session.load fs ?profile () with
92 | None ->
93 Fmt.pr "@[Not logged in@]@.";
94 1
95 | Some session ->
96 Fmt.pr "@[<v>%a@]@." Session.pp session;
97 0
98 in
99 let term = Term.(const status_action $ profile_arg) in
100 let info = Cmd.info "status"
101 ~doc:"Show current authentication status"
102 ~man:[
103 `S Manpage.s_description;
104 `P "Display information about the current session.";
105 ]
106 in
107 Cmd.v info term
108
109(** {1 Profiles Command} *)
110
111let profiles_cmd _env fs =
112 let profiles_action () =
113 Error.wrap @@ fun () ->
114 let profiles = Session.list_profiles fs in
115 let current = Session.get_current_profile fs in
116 if profiles = [] then begin
117 Fmt.pr "@[No profiles found. Use 'peertube auth login' to create one.@]@.";
118 0
119 end else begin
120 Fmt.pr "@[<v>%a@]@."
121 Fmt.(list ~sep:(any "@,") (fun ppf p ->
122 if p = current then
123 pf ppf "* %a (current)" (styled (`Fg `Green) string) p
124 else
125 pf ppf " %s" p)) profiles;
126 0
127 end
128 in
129 let term = Term.(const profiles_action $ const ()) in
130 let info = Cmd.info "profiles"
131 ~doc:"List available profiles"
132 ~man:[
133 `S Manpage.s_description;
134 `P "List all saved authentication profiles.";
135 ]
136 in
137 Cmd.v info term
138
139(** {1 Auth Command Group} *)
140
141let auth_cmd env fs =
142 let info = Cmd.info "auth"
143 ~doc:"Authentication commands"
144 ~man:[
145 `S Manpage.s_description;
146 `P "Commands for managing PeerTube authentication.";
147 ]
148 in
149 Cmd.group info [
150 login_cmd env fs;
151 logout_cmd env fs;
152 status_cmd env fs;
153 profiles_cmd env fs;
154 ]
155
156(** {1 Client Helper} *)
157
158(** [with_client ~sw ~env ~fs ?profile f] loads a session and creates a client,
159 then calls [f] with the client. Exits with an error if not logged in. *)
160let with_client ~sw ~env ~fs ?requests_config ?profile f =
161 match Session.load fs ?profile () with
162 | None ->
163 Error.fail "Not logged in. Use 'peertube auth login' first."
164 | Some session ->
165 let client = Client.resume ~sw ~env ?requests_config ?profile ~session () in
166 f client