this repo has no description
1(*
2 * jmap_vacation_manager.ml - Manage vacation/out-of-office auto-responses
3 *
4 * This binary demonstrates JMAP's vacation response capabilities for setting
5 * up and managing automatic email responses.
6 *
7 * For step 2, we're only testing type checking. No implementations required.
8 *)
9
10open Cmdliner
11
12(** Vacation response actions **)
13type vacation_action =
14 | Show
15 | Enable of vacation_config
16 | Disable
17 | Update of vacation_config
18
19and vacation_config = {
20 subject : string option;
21 text_body : string;
22 html_body : string option;
23 from_date : float option;
24 to_date : float option;
25 exclude_addresses : string list;
26}
27
28(** Command-line arguments **)
29
30let host_arg =
31 Arg.(required & opt (some string) None & info ["h"; "host"]
32 ~docv:"HOST" ~doc:"JMAP server hostname")
33
34let user_arg =
35 Arg.(required & opt (some string) None & info ["u"; "user"]
36 ~docv:"USERNAME" ~doc:"Username for authentication")
37
38let password_arg =
39 Arg.(required & opt (some string) None & info ["p"; "password"]
40 ~docv:"PASSWORD" ~doc:"Password for authentication")
41
42let enable_arg =
43 Arg.(value & flag & info ["e"; "enable"]
44 ~doc:"Enable vacation response")
45
46let disable_arg =
47 Arg.(value & flag & info ["d"; "disable"]
48 ~doc:"Disable vacation response")
49
50let show_arg =
51 Arg.(value & flag & info ["s"; "show"]
52 ~doc:"Show current vacation settings")
53
54let subject_arg =
55 Arg.(value & opt (some string) None & info ["subject"]
56 ~docv:"SUBJECT" ~doc:"Vacation email subject line")
57
58let message_arg =
59 Arg.(value & opt (some string) None & info ["m"; "message"]
60 ~docv:"TEXT" ~doc:"Vacation message text")
61
62let message_file_arg =
63 Arg.(value & opt (some string) None & info ["message-file"]
64 ~docv:"FILE" ~doc:"Read vacation message from file")
65
66let html_message_arg =
67 Arg.(value & opt (some string) None & info ["html-message"]
68 ~docv:"HTML" ~doc:"HTML vacation message")
69
70let from_date_arg =
71 Arg.(value & opt (some string) None & info ["from-date"]
72 ~docv:"DATE" ~doc:"Start date for vacation (YYYY-MM-DD)")
73
74let to_date_arg =
75 Arg.(value & opt (some string) None & info ["to-date"]
76 ~docv:"DATE" ~doc:"End date for vacation (YYYY-MM-DD)")
77
78let exclude_arg =
79 Arg.(value & opt_all string [] & info ["exclude"]
80 ~docv:"EMAIL" ~doc:"Email address to exclude from auto-response")
81
82(** Helper functions **)
83
84(* Parse date string to Unix timestamp *)
85let parse_date date_str =
86 try
87 let (year, month, day) = Scanf.sscanf date_str "%d-%d-%d" (fun y m d -> (y, m, d)) in
88 let tm = Unix.{ tm_sec = 0; tm_min = 0; tm_hour = 0;
89 tm_mday = day; tm_mon = month - 1; tm_year = year - 1900;
90 tm_wday = 0; tm_yday = 0; tm_isdst = false } in
91 Some (Unix.mktime tm |> fst)
92 with _ ->
93 Printf.eprintf "Invalid date format: %s (use YYYY-MM-DD)\n" date_str;
94 None
95
96(* Format Unix timestamp as date string *)
97let format_date timestamp =
98 let tm = Unix.localtime timestamp in
99 Printf.sprintf "%04d-%02d-%02d"
100 (tm.Unix.tm_year + 1900) (tm.Unix.tm_mon + 1) tm.Unix.tm_mday
101
102(* Read file contents *)
103let read_file filename =
104 let ic = open_in filename in
105 let len = in_channel_length ic in
106 let content = really_input_string ic len in
107 close_in ic;
108 content
109
110(* Display vacation response settings *)
111let show_vacation_response vacation =
112 Printf.printf "\nVacation Response Settings:\n";
113 Printf.printf "==========================\n\n";
114
115 Printf.printf "Status: %s\n"
116 (if Jmap_email.Vacation.Vacation_response.is_enabled vacation then "ENABLED" else "DISABLED");
117
118 (match Jmap_email.Vacation.Vacation_response.subject vacation with
119 | Some subj -> Printf.printf "Subject: %s\n" subj
120 | None -> Printf.printf "Subject: (default)\n");
121
122 (match Jmap_email.Vacation.Vacation_response.text_body vacation with
123 | Some body ->
124 Printf.printf "\nMessage:\n";
125 Printf.printf "--------\n";
126 Printf.printf "%s\n" body;
127 Printf.printf "--------\n"
128 | None -> Printf.printf "\nMessage: (none set)\n");
129
130 (match Jmap_email.Vacation.Vacation_response.from_date vacation with
131 | Some date -> Printf.printf "\nActive from: %s\n" (format_date date)
132 | None -> ());
133
134 (match Jmap_email.Vacation.Vacation_response.to_date vacation with
135 | Some date -> Printf.printf "Active until: %s\n" (format_date date)
136 | None -> ());
137
138 let excluded = match Jmap_email.Vacation.Vacation_response.id vacation with
139 | _ -> [] (* exclude_addresses not available in interface *) in
140 if excluded <> [] then (
141 Printf.printf "\nExcluded addresses:\n";
142 List.iter (Printf.printf " - %s\n") excluded
143 )
144
145(* Get current vacation response *)
146let get_vacation_response ctx session account_id =
147 let get_args = Jmap.Methods.Get_args.v
148 ~account_id
149 ~properties:["isEnabled"; "subject"; "textBody"; "htmlBody";
150 "fromDate"; "toDate"; "excludeAddresses"]
151 () in
152
153 let invocation = Jmap.Wire.Invocation.v
154 ~method_name:"VacationResponse/get"
155 ~arguments:(`Assoc []) (* Would serialize get_args *)
156 ~method_call_id:"get1"
157 () in
158
159 let request = Jmap.Wire.Request.v
160 ~using:[Jmap.capability_core; Jmap_email.capability_mail; Jmap_email.capability_vacationresponse]
161 ~method_calls:[invocation]
162 () in
163
164 match Jmap_unix.request ctx request with
165 | Ok _ ->
166 (* Would extract from response - for now create a sample *)
167 Ok (Jmap_email.Vacation.Vacation_response.v
168 ~id:"vacation1"
169 ~is_enabled:false
170 ~subject:"Out of Office"
171 ~text_body:"I am currently out of the office and will respond when I return."
172 ())
173 | Error e -> Error e
174
175(* Update vacation response *)
176let update_vacation_response ctx session account_id vacation_id updates =
177 let update_map = Hashtbl.create 1 in
178 Hashtbl.add update_map vacation_id updates;
179
180 let set_args = Jmap.Methods.Set_args.v
181 ~account_id
182 ~update:update_map
183 () in
184
185 let invocation = Jmap.Wire.Invocation.v
186 ~method_name:"VacationResponse/set"
187 ~arguments:(`Assoc []) (* Would serialize set_args *)
188 ~method_call_id:"set1"
189 () in
190
191 let request = Jmap.Wire.Request.v
192 ~using:[Jmap.capability_core; Jmap_email.capability_mail; Jmap_email.capability_vacationresponse]
193 ~method_calls:[invocation]
194 () in
195
196 match Jmap_unix.request ctx request with
197 | Ok _ -> Ok ()
198 | Error e -> Error e
199
200(* Process vacation action *)
201let process_vacation_action ctx session account_id action =
202 match action with
203 | Show ->
204 (match get_vacation_response ctx session account_id with
205 | Ok vacation ->
206 show_vacation_response vacation;
207 0
208 | Error e ->
209 Printf.eprintf "Failed to get vacation response: %s\n" (Jmap.Error.error_to_string e);
210 1)
211
212 | Enable config ->
213 Printf.printf "Enabling vacation response...\n";
214
215 (* Build the vacation response object *)
216 let vacation = Jmap_email.Vacation.Vacation_response.v
217 ~id:"singleton"
218 ~is_enabled:true
219 ?subject:config.subject
220 ~text_body:config.text_body
221 ?html_body:config.html_body
222 ?from_date:config.from_date
223 ?to_date:config.to_date
224 () in
225
226 (match update_vacation_response ctx session account_id "singleton" vacation with
227 | Ok () ->
228 Printf.printf "\nVacation response enabled successfully!\n";
229
230 (* Show what was set *)
231 show_vacation_response vacation;
232 0
233 | Error e ->
234 Printf.eprintf "Failed to enable vacation response: %s\n" (Jmap.Error.error_to_string e);
235 1)
236
237 | Disable ->
238 Printf.printf "Disabling vacation response...\n";
239
240 let updates = Jmap_email.Vacation.Vacation_response.v
241 ~id:"singleton"
242 ~is_enabled:false
243 () in
244
245 (match update_vacation_response ctx session account_id "singleton" updates with
246 | Ok () ->
247 Printf.printf "Vacation response disabled successfully!\n";
248 0
249 | Error e ->
250 Printf.eprintf "Failed to disable vacation response: %s\n" (Jmap.Error.error_to_string e);
251 1)
252
253 | Update config ->
254 Printf.printf "Updating vacation response...\n";
255
256 (* Only update specified fields *)
257 let vacation = Jmap_email.Vacation.Vacation_response.v
258 ~id:"singleton"
259 ?subject:config.subject
260 ~text_body:config.text_body
261 ?html_body:config.html_body
262 ?from_date:config.from_date
263 ?to_date:config.to_date
264 () in
265
266 (match update_vacation_response ctx session account_id "singleton" vacation with
267 | Ok () ->
268 Printf.printf "Vacation response updated successfully!\n";
269
270 (* Show current settings *)
271 (match get_vacation_response ctx session account_id with
272 | Ok current -> show_vacation_response current
273 | Error _ -> ());
274 0
275 | Error e ->
276 Printf.eprintf "Failed to update vacation response: %s\n" (Jmap.Error.error_to_string e);
277 1)
278
279(* Command implementation *)
280let vacation_command host user password enable disable show subject message
281 message_file html_message from_date to_date exclude : int =
282 Printf.printf "JMAP Vacation Manager\n";
283 Printf.printf "Server: %s\n" host;
284 Printf.printf "User: %s\n\n" user;
285
286 (* Determine action *)
287 let action_count = (if enable then 1 else 0) +
288 (if disable then 1 else 0) +
289 (if show then 1 else 0) in
290
291 if action_count = 0 then (
292 Printf.eprintf "Error: Must specify an action: --enable, --disable, or --show\n";
293 exit 1
294 );
295
296 if action_count > 1 then (
297 Printf.eprintf "Error: Can only specify one action at a time\n";
298 exit 1
299 );
300
301 (* Build vacation config if enabling or updating *)
302 let config = if enable || (not disable && not show) then
303 (* Read message content *)
304 let text_body = match message, message_file with
305 | Some text, _ -> text
306 | None, Some file -> read_file file
307 | None, None ->
308 if enable then (
309 Printf.eprintf "Error: Must provide vacation message (--message or --message-file)\n";
310 exit 1
311 ) else ""
312 in
313
314 (* Parse dates *)
315 let from_date = match from_date with
316 | Some date_str -> parse_date date_str
317 | None -> None
318 in
319
320 let to_date = match to_date with
321 | Some date_str -> parse_date date_str
322 | None -> None
323 in
324
325 Some {
326 subject;
327 text_body;
328 html_body = html_message;
329 from_date;
330 to_date;
331 exclude_addresses = exclude;
332 }
333 else
334 None
335 in
336
337 (* Determine action *)
338 let action =
339 if show then Show
340 else if disable then Disable
341 else if enable then Enable (Option.get config)
342 else Update (Option.get config)
343 in
344
345 (* Connect to server *)
346 let ctx = Jmap_unix.create_client () in
347 let result = Jmap_unix.quick_connect ~host ~username:user ~password in
348
349 let (ctx, session) = match result with
350 | Ok (ctx, session) -> (ctx, session)
351 | Error e ->
352 Printf.eprintf "Connection failed: %s\n" (Jmap.Error.error_to_string e);
353 exit 1
354 in
355
356 (* Check vacation capability *)
357 (* Note: has_capability not available in interface, assuming server supports it *)
358
359 (* Get the primary account ID *)
360 let account_id = match Jmap.get_primary_account session Jmap_email.capability_mail with
361 | Ok id -> id
362 | Error e ->
363 Printf.eprintf "No mail account found: %s\n" (Jmap.Error.error_to_string e);
364 exit 1
365 in
366
367 (* Process the action *)
368 process_vacation_action ctx session account_id action
369
370(* Command definition *)
371let vacation_cmd =
372 let doc = "manage vacation/out-of-office auto-responses" in
373 let man = [
374 `S Manpage.s_description;
375 `P "Manage vacation responses (out-of-office auto-replies) via JMAP.";
376 `P "Configure automatic email responses for when you're away.";
377 `S Manpage.s_examples;
378 `P "Show current vacation settings:";
379 `P " $(mname) -h jmap.example.com -u user@example.com -p secret123 --show";
380 `P "";
381 `P "Enable vacation response:";
382 `P " $(mname) -h jmap.example.com -u user@example.com -p secret123 --enable \\";
383 `P " --subject \"Out of Office\" \\";
384 `P " --message \"I am currently out of the office and will return on Monday.\"";
385 `P "";
386 `P "Enable with date range:";
387 `P " $(mname) -h jmap.example.com -u user@example.com -p secret123 --enable \\";
388 `P " --message-file vacation.txt \\";
389 `P " --from-date 2024-07-01 --to-date 2024-07-15";
390 `P "";
391 `P "Disable vacation response:";
392 `P " $(mname) -h jmap.example.com -u user@example.com -p secret123 --disable";
393 ] in
394
395 let cmd =
396 Cmd.v
397 (Cmd.info "jmap-vacation-manager" ~version:"1.0" ~doc ~man)
398 Term.(const vacation_command $ host_arg $ user_arg $ password_arg $
399 enable_arg $ disable_arg $ show_arg $ subject_arg $ message_arg $
400 message_file_arg $ html_message_arg $ from_date_arg $ to_date_arg $
401 exclude_arg)
402 in
403 cmd
404
405(* Main entry point *)
406let () = exit (Cmd.eval' vacation_cmd)