+63
-15
bin/jmap_test.ml
+63
-15
bin/jmap_test.ml
···
5
5
6
6
(** JMAP test client - connects to a JMAP server and queries recent emails *)
7
7
8
+
let ptime_to_string t =
9
+
let (y, m, d), ((hh, mm, ss), _tz) = Ptime.to_date_time t in
10
+
Printf.sprintf "%04d-%02d-%02d %02d:%02d:%02d" y m d hh mm ss
11
+
12
+
let debug_mode = ref false
13
+
14
+
let debug fmt =
15
+
if !debug_mode then
16
+
Printf.kfprintf (fun oc -> Printf.fprintf oc "\n%!") stderr ("[DEBUG] " ^^ fmt)
17
+
else
18
+
Printf.ifprintf stderr fmt
19
+
8
20
let () =
9
21
(* Parse command line arguments *)
10
-
let usage = "Usage: jmap-test <session-url> <api-key>" in
22
+
let usage = "Usage: jmap-test [--debug] <session-url> <api-key>" in
11
23
let args = ref [] in
12
-
Arg.parse [] (fun arg -> args := arg :: !args) usage;
24
+
let spec = [
25
+
("--debug", Arg.Set debug_mode, "Enable debug output");
26
+
("-d", Arg.Set debug_mode, "Enable debug output");
27
+
] in
28
+
Arg.parse spec (fun arg -> args := arg :: !args) usage;
13
29
let session_url, api_key =
14
30
match List.rev !args with
15
31
| [url; key] -> (url, key)
···
18
34
exit 1
19
35
in
20
36
37
+
debug "Session URL: %s" session_url;
38
+
debug "API key length: %d chars" (String.length api_key);
39
+
21
40
(* Run with Eio *)
22
41
Eio_main.run @@ fun env ->
23
42
Eio.Switch.run @@ fun sw ->
···
26
45
let requests = Requests.create ~sw env in
27
46
let auth = Requests.Auth.bearer ~token:api_key in
28
47
48
+
debug "Created HTTP client with Bearer auth";
29
49
Printf.printf "Connecting to %s...\n%!" session_url;
30
50
31
51
(* Create JMAP client from session URL *)
···
35
55
exit 1
36
56
| Ok client ->
37
57
let session = Jmap_eio.Client.session client in
38
-
Printf.printf "Connected! Username: %s\n%!" (Jmap_proto.Session.username session);
58
+
debug "Session state: %s" session.state;
59
+
debug "API URL: %s" session.api_url;
60
+
debug "Upload URL: %s" session.upload_url;
61
+
debug "Download URL: %s" session.download_url;
62
+
debug "Capabilities: %s" (String.concat ", " (List.map fst session.capabilities));
63
+
debug "Accounts: %d" (List.length session.accounts);
64
+
List.iter (fun (id, acct) ->
65
+
debug " Account %s: %s (personal=%b, read_only=%b)"
66
+
(Jmap_proto.Id.to_string id)
67
+
acct.Jmap_proto.Session.Account.name
68
+
acct.is_personal
69
+
acct.is_read_only
70
+
) session.accounts;
71
+
Printf.printf "Connected! Username: %s\n%!" session.username;
39
72
40
73
(* Get primary mail account *)
41
74
let primary_account_id =
···
45
78
prerr_endline "No primary mail account found";
46
79
exit 1
47
80
in
81
+
debug "Primary accounts: %s"
82
+
(String.concat ", " (List.map (fun (cap, id) ->
83
+
cap ^ "=" ^ Jmap_proto.Id.to_string id) session.primary_accounts));
48
84
Printf.printf "Primary mail account: %s\n%!" (Jmap_proto.Id.to_string primary_account_id);
49
85
50
86
(* Query for recent emails - get the 10 most recent *)
···
63
99
[query_inv]
64
100
in
65
101
102
+
debug "Built Email/query request with sort by receivedAt desc, limit 10";
66
103
Printf.printf "Querying recent emails...\n%!";
67
104
68
105
match Jmap_eio.Client.request client req with
···
70
107
Printf.eprintf "Query failed: %s\n" (Jmap_eio.Client.error_to_string e);
71
108
exit 1
72
109
| Ok response ->
110
+
debug "Query response received, parsing...";
73
111
(* Parse the query response *)
74
112
match Jmap_eio.Client.Parse.parse_email_query ~call_id:"q1" response with
75
113
| Error e ->
···
77
115
exit 1
78
116
| Ok query_result ->
79
117
let email_ids = query_result.ids in
118
+
debug "Query state: %s" query_result.query_state;
119
+
debug "Can calculate updates: %b" query_result.can_calculate_changes;
120
+
debug "Total results: %Ld" (Option.value query_result.total ~default:(-1L));
121
+
debug "Position: %Ld" query_result.position;
122
+
debug "Email IDs: %s"
123
+
(String.concat ", " (List.map Jmap_proto.Id.to_string email_ids));
80
124
Printf.printf "Found %d emails\n%!" (List.length email_ids);
81
125
82
126
if List.length email_ids = 0 then (
83
127
Printf.printf "No emails found.\n%!";
84
128
) else (
85
-
(* Fetch the email details *)
129
+
(* Fetch the email details - must include required properties *)
86
130
let get_inv = Jmap_eio.Client.Build.email_get
87
131
~call_id:"g1"
88
132
~account_id:primary_account_id
89
133
~ids:email_ids
90
-
~properties:["id"; "subject"; "from"; "receivedAt"; "preview"]
134
+
~properties:["id"; "blobId"; "threadId"; "mailboxIds"; "size";
135
+
"receivedAt"; "subject"; "from"; "preview"]
91
136
()
92
137
in
93
138
···
96
141
[get_inv]
97
142
in
98
143
144
+
debug "Built Email/get request for %d emails" (List.length email_ids);
99
145
Printf.printf "Fetching email details...\n%!";
100
146
101
147
match Jmap_eio.Client.request client req2 with
···
103
149
Printf.eprintf "Get failed: %s\n" (Jmap_eio.Client.error_to_string e);
104
150
exit 1
105
151
| Ok response2 ->
152
+
debug "Get response received, parsing...";
106
153
match Jmap_eio.Client.Parse.parse_email_get ~call_id:"g1" response2 with
107
154
| Error e ->
108
155
Printf.eprintf "Failed to parse get response: %s\n" (Jsont.Error.to_string e);
109
156
exit 1
110
157
| Ok get_result ->
158
+
debug "Get state: %s" get_result.state;
159
+
debug "Emails returned: %d" (List.length get_result.list);
160
+
debug "Not found: %d" (List.length get_result.not_found);
111
161
Printf.printf "\n=== Recent Emails ===\n\n%!";
112
-
List.iter (fun email ->
113
-
let id = Jmap_proto.Id.to_string (Jmap_mail.Email.id email) in
114
-
let subject = Option.value (Jmap_mail.Email.subject email) ~default:"(no subject)" in
115
-
let from_addrs = Option.value (Jmap_mail.Email.from email) ~default:[] in
162
+
List.iter (fun (email : Jmap_mail.Email.t) ->
163
+
let id = Jmap_proto.Id.to_string email.id in
164
+
let subject = Option.value email.subject ~default:"(no subject)" in
165
+
let from_addrs = Option.value email.from ~default:[] in
116
166
let from_str = match from_addrs with
117
167
| [] -> "(unknown sender)"
118
168
| addr :: _ ->
119
-
let name = Option.value (Jmap_mail.Email_address.name addr) ~default:"" in
120
-
let email_addr = Jmap_mail.Email_address.email addr in
169
+
let name = Option.value addr.name ~default:"" in
170
+
let email_addr = addr.email in
121
171
if name = "" then email_addr
122
172
else Printf.sprintf "%s <%s>" name email_addr
123
173
in
124
-
let received =
125
-
Jmap_proto.Date.Utc.to_string (Jmap_mail.Email.received_at email)
126
-
in
127
-
let preview = Jmap_mail.Email.preview email in
174
+
let received = ptime_to_string email.received_at in
175
+
let preview = email.preview in
128
176
let preview_short =
129
177
if String.length preview > 80 then
130
178
String.sub preview 0 77 ^ "..."