cull_gmail/
list.rs

1use google_gmail1::{
2    Gmail,
3    api::ListMessagesResponse,
4    hyper_rustls::{HttpsConnector, HttpsConnectorBuilder},
5    hyper_util::{
6        client::legacy::{Client, connect::HttpConnector},
7        rt::TokioExecutor,
8    },
9    yup_oauth2::{ApplicationSecret, InstalledFlowAuthenticator, InstalledFlowReturnMethod},
10};
11
12use crate::{Credential, Error};
13
14/// Default for the maximum number of results to return on a page
15pub const DEFAULT_MAX_RESULTS: &str = "10";
16
17/// Struct to capture configuration for List API call.
18pub struct List {
19    hub: Gmail<HttpsConnector<HttpConnector>>,
20    max_results: u32,
21    label_ids: Vec<String>,
22}
23
24impl List {
25    /// Create a new List struct and add the Gmail api connection.
26    pub async fn new(credential: &str) -> Result<Self, Error> {
27        let (config_dir, secret) = {
28            let config_dir = crate::utils::assure_config_dir_exists("~/.cull-gmail")?;
29
30            let secret: ApplicationSecret = Credential::load_json_file(credential).into();
31            (config_dir, secret)
32        };
33
34        let executor = TokioExecutor::new();
35        let connector = HttpsConnectorBuilder::new()
36            .with_native_roots()
37            .unwrap()
38            .https_or_http()
39            .enable_http1()
40            .build();
41
42        let client = Client::builder(executor.clone()).build(connector.clone());
43
44        let auth = InstalledFlowAuthenticator::with_client(
45            secret,
46            InstalledFlowReturnMethod::HTTPRedirect,
47            Client::builder(executor).build(connector),
48        )
49        .persist_tokens_to_disk(format!("{config_dir}/gmail1"))
50        .build()
51        .await
52        .unwrap();
53
54        Ok(List {
55            hub: Gmail::new(client, auth),
56            max_results: DEFAULT_MAX_RESULTS.parse::<u32>().unwrap(),
57            label_ids: Vec::new(),
58        })
59    }
60
61    /// Set the maximum results
62    pub fn set_max_results(&mut self, value: u32) {
63        self.max_results = value;
64    }
65
66    /// Report the maximum results value
67    pub fn max_results(&self) -> u32 {
68        self.max_results
69    }
70
71    /// Add label to the labels collection
72    pub fn add_label(&mut self, label_id: &str) {
73        self.label_ids.push(label_id.to_string())
74    }
75
76    /// Run the Gmail api as configured
77    pub async fn run(&self, pages: u32) -> Result<(), Error> {
78        let log_estimate = |est: &Option<u32>| {
79            if let Some(estimate) = est {
80                log::debug!("Estimated {estimate} messages in total.");
81            } else {
82                log::debug!("Unknown number of messages found");
83            }
84        };
85
86        let list = self.get_messages(None).await?;
87        log_estimate(&list.result_size_estimate);
88        self.log_message_subjects(&list).await?;
89        match pages {
90            1 => {}
91            0 => {
92                let mut list = list;
93                let mut page = 1;
94                loop {
95                    page += 1;
96                    log::debug!("Processing page #{page}");
97                    log_estimate(&list.result_size_estimate);
98                    if list.next_page_token.is_none() {
99                        break;
100                    }
101                    list = self.get_messages(list.next_page_token).await?;
102                    self.log_message_subjects(&list).await?;
103                }
104            }
105            _ => {
106                let mut list = list;
107                for page in 2..=pages {
108                    log::debug!("Processing page #{page}");
109                    log_estimate(&list.result_size_estimate);
110                    if list.next_page_token.is_none() {
111                        break;
112                    }
113                    list = self.get_messages(list.next_page_token).await?;
114                    self.log_message_subjects(&list).await?;
115                }
116            }
117        }
118
119        Ok(())
120    }
121
122    async fn get_messages(
123        &self,
124        next_page_token: Option<String>,
125    ) -> Result<ListMessagesResponse, Error> {
126        let mut call = self
127            .hub
128            .users()
129            .messages_list("me")
130            .max_results(self.max_results);
131        // Add any labels specified
132        if !self.label_ids.is_empty() {
133            log::debug!("Setting labels for list: {:#?}", self.label_ids);
134            for id in self.label_ids.as_slice() {
135                call = call.add_label_ids(id);
136            }
137        }
138        // Add a page token if it exists
139        if let Some(page_token) = next_page_token {
140            log::debug!("Setting token for next page.");
141            call = call.page_token(&page_token);
142        }
143
144        let (_response, list) = call.doit().await.map_err(Box::new)?;
145
146        Ok(list)
147    }
148
149    async fn log_message_subjects(&self, list: &ListMessagesResponse) -> Result<(), Error> {
150        if let Some(messages) = &list.messages {
151            for message in messages {
152                if let Some(id) = &message.id {
153                    log::trace!("{id}");
154                    let (_res, m) = self
155                        .hub
156                        .users()
157                        .messages_get("me", id)
158                        .add_scope("https://www.googleapis.com/auth/gmail.metadata")
159                        .format("metadata")
160                        .add_metadata_headers("subject")
161                        .doit()
162                        .await
163                        .map_err(Box::new)?;
164
165                    let mut subject = String::new();
166                    if let Some(payload) = m.payload {
167                        if let Some(headers) = payload.headers {
168                            for header in headers {
169                                if header.name.is_some()
170                                    && header.name.unwrap() == "Subject"
171                                    && header.value.is_some()
172                                {
173                                    subject = header.value.unwrap();
174                                    break;
175                                } else {
176                                    continue;
177                                }
178                            }
179                        }
180                    }
181
182                    log::info!("{subject:?}");
183                }
184            }
185        }
186
187        Ok(())
188    }
189}