cull_gmail/
gmail_client.rs

1use std::collections::BTreeMap;
2
3use google_gmail1::{
4    Gmail,
5    hyper_rustls::{HttpsConnector, HttpsConnectorBuilder},
6    hyper_util::{
7        client::legacy::{Client, connect::HttpConnector},
8        rt::TokioExecutor,
9    },
10    yup_oauth2::{ApplicationSecret, InstalledFlowAuthenticator, InstalledFlowReturnMethod},
11};
12
13mod message_summary;
14
15pub(crate) use message_summary::MessageSummary;
16
17use crate::{Credential, Error, Result, config::EolRule};
18
19/// Default for the maximum number of results to return on a page
20pub const DEFAULT_MAX_RESULTS: &str = "200";
21
22/// Struct to capture configuration for List API call.
23#[derive(Clone)]
24pub struct GmailClient {
25    hub: Gmail<HttpsConnector<HttpConnector>>,
26    label_map: BTreeMap<String, String>,
27    pub(crate) max_results: u32,
28    pub(crate) label_ids: Vec<String>,
29    pub(crate) query: String,
30    pub(crate) messages: Vec<MessageSummary>,
31    pub(crate) rule: Option<EolRule>,
32    pub(crate) execute: bool,
33}
34
35impl std::fmt::Debug for GmailClient {
36    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37        f.debug_struct("Labels")
38            .field("label_map", &self.label_map)
39            .finish()
40    }
41}
42
43impl GmailClient {
44    /// Create a new List struct and add the Gmail api connection.
45    pub async fn new(credential: &str) -> Result<Self> {
46        let (config_dir, secret) = {
47            let config_dir = crate::utils::assure_config_dir_exists("~/.cull-gmail")?;
48
49            let secret: ApplicationSecret = Credential::load_json_file(credential).into();
50            (config_dir, secret)
51        };
52
53        let executor = TokioExecutor::new();
54        let connector = HttpsConnectorBuilder::new()
55            .with_native_roots()
56            .unwrap()
57            .https_or_http()
58            .enable_http1()
59            .build();
60
61        let client = Client::builder(executor.clone()).build(connector.clone());
62
63        let auth = InstalledFlowAuthenticator::with_client(
64            secret,
65            InstalledFlowReturnMethod::HTTPRedirect,
66            Client::builder(executor).build(connector),
67        )
68        .persist_tokens_to_disk(format!("{config_dir}/gmail1"))
69        .build()
70        .await
71        .unwrap();
72
73        let hub = Gmail::new(client, auth);
74        let label_map = GmailClient::get_label_map(&hub).await?;
75
76        Ok(GmailClient {
77            hub,
78            label_map,
79            max_results: DEFAULT_MAX_RESULTS.parse::<u32>().unwrap(),
80            label_ids: Vec::new(),
81            query: String::new(),
82            messages: Vec::new(),
83            rule: None,
84            execute: false,
85        })
86    }
87
88    /// Create a new List struct and add the Gmail api connection.
89    async fn get_label_map(
90        hub: &Gmail<HttpsConnector<HttpConnector>>,
91    ) -> Result<BTreeMap<String, String>> {
92        let call = hub.users().labels_list("me");
93        let (_response, list) = call
94            .add_scope("https://mail.google.com/")
95            .doit()
96            .await
97            .map_err(Box::new)?;
98
99        let Some(label_list) = list.labels else {
100            return Err(Error::NoLabelsFound);
101        };
102
103        let mut label_map = BTreeMap::new();
104        for label in &label_list {
105            if label.id.is_some() && label.name.is_some() {
106                let name = label.name.clone().unwrap();
107                let id = label.id.clone().unwrap();
108                label_map.insert(name, id);
109            }
110        }
111
112        Ok(label_map)
113    }
114
115    /// Return the id for the name from the labels map.
116    /// Returns `None` if the name is not found in the map.
117    pub fn get_label_id(&self, name: &str) -> Option<String> {
118        self.label_map.get(name).cloned()
119    }
120
121    /// Show the label names and related id.
122    pub fn show_label(&self) {
123        for (name, id) in self.label_map.iter() {
124            log::info!("{name}: {id}")
125        }
126    }
127
128    /// Get the hub from the client
129    pub(crate) fn hub(&self) -> Gmail<HttpsConnector<HttpConnector>> {
130        self.hub.clone()
131    }
132
133    // /// Get the message list
134    // pub async fn get_message_list(&self) -> Result<MessageList> {
135    //     MessageList::new(self).await
136    // }
137}