cull_gmail/
message_list.rs

1use crate::{GmailClient, MessageSummary, Result};
2
3use google_gmail1::{
4    Gmail, api::ListMessagesResponse, hyper_rustls::HttpsConnector,
5    hyper_util::client::legacy::connect::HttpConnector,
6};
7
8use crate::utils::Elide;
9
10/// Methods to select lists of messages from the mailbox
11pub trait MessageList {
12    /// Log the initial characters of the subjects of the message in the list
13    fn log_message_subjects(&mut self) -> impl std::future::Future<Output = Result<()>> + Send;
14    /// List of messages
15    fn messages_list(
16        &mut self,
17        next_page_token: Option<String>,
18    ) -> impl std::future::Future<Output = Result<ListMessagesResponse>> + Send;
19    /// Run something
20    fn get_messages(&mut self, pages: u32) -> impl std::future::Future<Output = Result<()>> + Send;
21    /// Return the gmail hub
22    fn hub(&self) -> Gmail<HttpsConnector<HttpConnector>>;
23    /// Return the list of label_ids
24    fn label_ids(&self) -> Vec<String>;
25    /// Return the list of message ids
26    fn message_ids(&self) -> Vec<String>;
27    /// Return a summary of the messages (id and summary)
28    fn messages(&self) -> &Vec<MessageSummary>;
29    /// Set the query for the message list
30    fn set_query(&mut self, query: &str);
31    /// Add label ids to the list of labels for the message list
32    fn add_labels_ids(&mut self, label_ids: &[String]);
33    /// Add labels to the list of labels for the message list
34    fn add_labels(&mut self, labels: &[String]) -> Result<()>;
35    /// Report the max results value set
36    fn max_results(&self) -> u32;
37    /// Set the max_results value
38    fn set_max_results(&mut self, value: u32);
39}
40
41impl MessageList for GmailClient {
42    // /// Create a new List struct and add the Gmail api connection.
43    // async fn new(client: &GmailClient) -> Result<Self> {
44    //     Ok(MessageList {
45    //         hub: client.hub(),
46    //         max_results: DEFAULT_MAX_RESULTS.parse::<u32>().unwrap(),
47    //         label_ids: Vec::new(),
48    //         messages: Vec::new(),
49    //         query: String::new(),
50    //     })
51    // }
52
53    /// Set the maximum results
54    fn set_max_results(&mut self, value: u32) {
55        self.max_results = value;
56    }
57
58    /// Report the maximum results value
59    fn max_results(&self) -> u32 {
60        self.max_results
61    }
62
63    /// Add label to the labels collection
64    fn add_labels(&mut self, labels: &[String]) -> Result<()> {
65        log::debug!("labels from command line: {labels:?}");
66        let mut label_ids = Vec::new();
67        for label in labels {
68            if let Some(id) = self.get_label_id(label) {
69                label_ids.push(id)
70            }
71        }
72        self.add_labels_ids(label_ids.as_slice());
73        Ok(())
74    }
75
76    /// Add label to the labels collection
77    fn add_labels_ids(&mut self, label_ids: &[String]) {
78        if !label_ids.is_empty() {
79            for id in label_ids {
80                self.label_ids.push(id.to_string())
81            }
82        }
83    }
84
85    /// Set the query string
86    fn set_query(&mut self, query: &str) {
87        self.query = query.to_string()
88    }
89
90    /// Get the summary of the messages
91    fn messages(&self) -> &Vec<MessageSummary> {
92        &self.messages
93    }
94
95    /// Get a reference to the message_ids
96    fn message_ids(&self) -> Vec<String> {
97        self.messages
98            .iter()
99            .map(|m| m.id().to_string())
100            .collect::<Vec<_>>()
101            .clone()
102    }
103
104    /// Get a reference to the message_ids
105    fn label_ids(&self) -> Vec<String> {
106        self.label_ids.clone()
107    }
108
109    /// Get the hub
110    fn hub(&self) -> Gmail<HttpsConnector<HttpConnector>> {
111        self.hub().clone()
112    }
113
114    /// Run the Gmail api as configured
115    async fn get_messages(&mut self, pages: u32) -> Result<()> {
116        let list = self.messages_list(None).await?;
117        match pages {
118            1 => {}
119            0 => {
120                let mut list = list;
121                let mut page = 1;
122                loop {
123                    page += 1;
124                    log::debug!("Processing page #{page}");
125                    if list.next_page_token.is_none() {
126                        break;
127                    }
128                    list = self.messages_list(list.next_page_token).await?;
129                    // self.log_message_subjects(&list).await?;
130                }
131            }
132            _ => {
133                let mut list = list;
134                for page in 2..=pages {
135                    log::debug!("Processing page #{page}");
136                    if list.next_page_token.is_none() {
137                        break;
138                    }
139                    list = self.messages_list(list.next_page_token).await?;
140                    // self.log_message_subjects(&list).await?;
141                }
142            }
143        }
144
145        if log::max_level() >= log::Level::Info {
146            self.log_message_subjects().await?;
147        }
148
149        Ok(())
150    }
151
152    async fn messages_list(
153        &mut self,
154        next_page_token: Option<String>,
155    ) -> Result<ListMessagesResponse> {
156        let hub = self.hub();
157        let mut call = hub
158            .users()
159            .messages_list("me")
160            .max_results(self.max_results);
161        // Add any labels specified
162        if !self.label_ids.is_empty() {
163            log::debug!("Setting labels for list: {:#?}", self.label_ids);
164            for id in self.label_ids.as_slice() {
165                call = call.add_label_ids(id);
166            }
167        }
168        // Add query
169        if !self.query.is_empty() {
170            log::debug!("Setting query string `{}`", self.query);
171            call = call.q(&self.query);
172        }
173        // Add a page token if it exists
174        if let Some(page_token) = next_page_token {
175            log::debug!("Setting token for next page.");
176            call = call.page_token(&page_token);
177        }
178
179        let (_response, list) = call.doit().await.map_err(Box::new)?;
180        log::trace!(
181            "Estimated {} messages.",
182            list.result_size_estimate.unwrap_or(0)
183        );
184
185        if list.result_size_estimate.unwrap_or(0) == 0 {
186            log::warn!("Search returned no messages.");
187            return Ok(list);
188        }
189
190        let mut list_ids = list
191            .clone()
192            .messages
193            .unwrap()
194            .iter()
195            .flat_map(|item| item.id.as_ref().map(|id| MessageSummary::new(id)))
196            .collect();
197        self.messages.append(&mut list_ids);
198
199        Ok(list)
200    }
201
202    async fn log_message_subjects(&mut self) -> Result<()> {
203        let hub = self.hub();
204        for message in &mut self.messages {
205            log::trace!("{}", message.id());
206            let (_res, m) = hub
207                .users()
208                .messages_get("me", message.id())
209                .add_scope("https://www.googleapis.com/auth/gmail.metadata")
210                .format("metadata")
211                .add_metadata_headers("subject")
212                .doit()
213                .await
214                .map_err(Box::new)?;
215
216            let mut subject = String::new();
217            let Some(payload) = m.payload else { continue };
218            let Some(headers) = payload.headers else {
219                continue;
220            };
221
222            for header in headers {
223                if header.name.is_some()
224                    && header.name.unwrap() == "Subject"
225                    && header.value.is_some()
226                {
227                    subject = header.value.unwrap();
228                    break;
229                } else {
230                    continue;
231                }
232            }
233
234            if subject.is_empty() {
235                log::info!("***Email with no subject***");
236            } else {
237                subject.elide(24);
238                message.set_subject(&subject);
239                log::info!("{subject:?}");
240            }
241        }
242
243        Ok(())
244    }
245}