1use windows::{
2 core::{w, GUID, VARIANT},
3 Win32::System::Com::{CLSIDFromProgID, CoCreateInstance, IDispatch, DISPATCH_METHOD, DISPATCH_PROPERTYGET, DISPPARAMS}
4};
5
6use crate::{
7 co_initialize,
8 common::{dispatch::{HasDispatch, Invocation},
9 variant::{EvilVariant, TypedVariant, VariantError}},
10 WinError, LOCALE_USER_DEFAULT, OBJECT_CONTEXT};
11
12
13pub struct Outlook {
14 pub app : IDispatch,
15 pub namespace : IDispatch,
16}
17
18impl Outlook {
19 pub fn new() -> Result<Self, WinError> {
20 co_initialize();
21
22 let app : IDispatch = unsafe {
23 let clsid = CLSIDFromProgID(w!("Outlook.Application")).expect("Couldn't get CLSID");
24 CoCreateInstance(&clsid, None, OBJECT_CONTEXT)
25 }.map_err(
26 |e| WinError::Internal(e)
27 )?;
28
29 let params = DISPPARAMS {
30 cArgs : 1,
31 rgvarg : &mut VARIANT::from("MAPI"),
32 ..Default::default()
33 };
34
35 let mut namespace_variant = VARIANT::default();
36
37 unsafe {
38 app.Invoke(
39 272,
40 &GUID::zeroed(),
41 LOCALE_USER_DEFAULT,
42 DISPATCH_METHOD,
43 ¶ms,
44 Some(&mut namespace_variant),
45 None,
46 None,
47 )
48 }.unwrap();
49
50 let namespace = IDispatch::try_from(&namespace_variant).expect("couldnt cast VARIANT to IDispatch");
51
52 Ok(Outlook {
53 app,
54 namespace
55 })
56 }
57
58 pub fn get_folder(&self, path_to_folder : Vec<&str>) -> Result<Option<Folder>, WinError> {
60 let mut folder_names = path_to_folder.into_iter();
61
62 let top_folder_name = folder_names.next().expect("Folder chain cannot be empty");
63 let mut subfolder = match Folder(self.namespace.clone()).get_subfolder(top_folder_name)? {
64 None => return Ok(None),
65 Some(folder) => folder,
66 };
67
68 for subfolder_name in folder_names {
69 subfolder = match subfolder.get_subfolder(subfolder_name)? {
70 None => return Ok(None),
71 Some(folder) => folder,
72 };
73 };
74 Ok(Some(subfolder))
75 }
76}
77
78impl HasDispatch for Outlook {
79 fn dispatch(&self) -> &IDispatch {
80 &self.app
81 }
82}
83
84#[derive(Clone)]
85pub struct Folder(pub IDispatch);
86
87impl Folder {
88 pub fn get_subfolder(&self, folder_name : &str) -> Result<Option<Folder>, WinError> {
89 let params = DISPPARAMS {
90 cArgs : 1,
91 rgvarg : &mut VARIANT::from(folder_name),
92 ..Default::default()
93 };
94
95 let dispid = self.get_dispid("Folders")?;
96 let mut folder = VARIANT::default();
97
98 unsafe {
99 self.0.Invoke(
100 dispid,
101 &GUID::zeroed(),
102 LOCALE_USER_DEFAULT,
103 DISPATCH_PROPERTYGET,
104 ¶ms,
105 Some(&mut folder),
106 None,
107 None,
108 ).map_err(|e| WinError::Internal(e))?
109 }
110
111 return Ok(Some(Folder(IDispatch::try_from(&folder).expect("couldnt cast VARIANT to folder dispatch"))));
112 }
113
114 pub(crate) fn subfolder_names(&self) -> Result<Vec<String>, WinError> {
115 let mut foldernames = vec![];
116 let subfolders = match self.prop("Folders") {
117 Ok(TypedVariant::Dispatch(d)) => d,
118 Ok(result) => return Err(WinError::VariantError(VariantError::Mismatch { method: "Folders".to_string(), result })),
119 Err(e) => return Err(e),
120 };
121
122 let Ok(TypedVariant::Dispatch(first_folder)) = subfolders.call("GetFirst", Invocation::Method, None) else {
123 return Ok(foldernames);
124 };
125
126 match first_folder.prop("Name")? {
127 TypedVariant::Bstr(name) => foldernames.push(name.to_string()),
128 result => return Err(WinError::VariantError(VariantError::Mismatch {method : "Name".to_string(), result})),
129 };
130
131 loop {
132 match subfolders.call("GetNext", Invocation::Method, None) {
133 Ok(TypedVariant::Dispatch(subfolder)) => {
134 match subfolder.prop("Name")? {
135 TypedVariant::Bstr(name) => foldernames.push(name.to_string()),
136 result => return Err(WinError::VariantError(VariantError::Mismatch {method : "Name".to_string(), result})),
137 }
138 },
139 Ok(result) => {
140 return Err(WinError::VariantError(VariantError::Mismatch {method : "Name".to_string(), result}))
141 }
142 Err(WinError::VariantError(VariantError::NullPointer)) => break, Err(e) => return Err(e),
144 }
145 }
146 Ok(foldernames)
147 }
148
149 pub fn count(&self) -> Option<usize> {
150 let TypedVariant::Dispatch(items) = self.prop("Items").expect("Folder should have Items property") else {
151 return None
152 };
153
154 match items.prop("Count") {
155 Ok(TypedVariant::Int32(count)) => Some(count as usize),
156 Err(WinError::VariantError(VariantError::NullPointer)) => return Some(0),
157 _ => None,
158 }
159 }
160
161 pub fn emails(&self) -> Result<Vec<MailItem>, WinError> {
162 let mut items = Vec::with_capacity(self.count().unwrap_or(0));
163
164 items.extend(self.iter()?);
165 Ok(items)
166 }
167
168 fn iter(&self) -> Result<MailItemIterator, WinError> {
169 match self.prop("Items")? {
170 TypedVariant::Dispatch(d) => Ok(MailItemIterator(d,true)),
171 result => Err(WinError::VariantError(VariantError::Mismatch { method: "Items".to_string(), result })),
172 }
173 }
174}
175
176impl HasDispatch for Folder {
177 fn dispatch(&self) -> &IDispatch {
178 &self.0
179 }
180}
181
182pub struct MailItemIterator(IDispatch,bool);
183
184impl HasDispatch for MailItemIterator {
185 fn dispatch(&self) -> &IDispatch {
186 &self.0
187 }
188}
189
190impl Iterator for MailItemIterator {
191 type Item = MailItem;
192
193 fn next(&mut self) -> Option<Self::Item> {
194 let method = if self.1 {
195 self.1 = false;
196 "GetFirst"
197 } else {
198 "GetNext"
199 };
200 match self.call_evil(method, Invocation::Method, None) {
201 Ok(EvilVariant { vt : 9, union: 0, .. }) => return None, Ok(evil) if evil.vt == 9 => return Some(MailItem(IDispatch::from(evil))),
203 Ok(result) => panic!("Expected MailItem Dispatch while iterating, found {:?}", result),
204 Err(e) => panic!("MailItem Iterator failed with: {:?}", e),
205 };
206 }
207}
208
209pub struct MailItem(IDispatch);
210
211impl MailItem {
212 pub fn move_to(&self, target : &Folder) -> Result<(), WinError> {
214 let params = DISPPARAMS {
215 rgvarg : &mut VARIANT::from(target.0.clone()),
216 cArgs : 1,
217 ..Default::default()
218 };
219
220 self.call("Move", Invocation::Method, Some(params))?;
221
222 Ok(())
223 }
224
225 fn string_property(&self, name : &str) -> Result<String, WinError> {
227 match self.prop(name)? {
228 TypedVariant::Bstr(string) => Ok(string.to_string()),
229 result => Err(WinError::VariantError(VariantError::Mismatch { method: name.to_string(), result })),
230 }
231 }
232
233 pub fn subject(&self) -> Result<String, WinError> {
235 self.string_property("Subject")
236 }
237
238 pub fn body(&self) -> Result<String, WinError> {
240 self.string_property("Body")
241 }
242
243 pub fn received_time(&self) -> Result<String, WinError> {
244 self.string_property("ReceivedTime")
245 }
246
247 pub fn sender_address(&self) -> Result<String, WinError> {
248 self.string_property("SenderEmailAddress")
249 }
250}
251
252impl HasDispatch for MailItem {
253 fn dispatch(&self) -> &IDispatch {
254 &self.0
255 }
256}