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::{InstalledFlowAuthenticator, InstalledFlowReturnMethod},
11};
12
13mod message_summary;
14
15pub(crate) use message_summary::MessageSummary;
16
17use crate::{ClientConfig, Error, Result, rules::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 Gmail Api connection and fetch label map using credential file.
45    // pub async fn new_from_credential_file(credential_file: &str) -> Result<Self> {
46    //     let (config_dir, secret) = {
47    //         let config_dir = crate::utils::assure_config_dir_exists("~/.cull-gmail")?;
48
49    //         let home_dir = env::home_dir().unwrap();
50
51    //         let path = home_dir.join(".cull-gmail").join(credential_file);
52    //         let json_str = fs::read_to_string(path).expect("could not read path");
53
54    //         let console: ConsoleApplicationSecret =
55    //             serde_json::from_str(&json_str).expect("could not convert to struct");
56
57    //         let secret: ApplicationSecret = console.installed.unwrap();
58    //         (config_dir, secret)
59    //     };
60
61    //     GmailClient::new_from_secret(secret, &config_dir).await
62    // }
63
64    /// Create a new List struct and add the Gmail api connection.
65    pub async fn new_with_config(config: ClientConfig) -> Result<Self> {
66        let executor = TokioExecutor::new();
67        let connector = HttpsConnectorBuilder::new()
68            .with_native_roots()
69            .unwrap()
70            .https_or_http()
71            .enable_http1()
72            .build();
73
74        let client = Client::builder(executor.clone()).build(connector.clone());
75        log::trace!("file to persist tokens to `{}`", config.persist_path());
76
77        let auth = InstalledFlowAuthenticator::with_client(
78            config.secret().clone(),
79            InstalledFlowReturnMethod::HTTPRedirect,
80            Client::builder(executor).build(connector),
81        )
82        .persist_tokens_to_disk(config.persist_path())
83        .build()
84        .await
85        .unwrap();
86
87        let hub = Gmail::new(client, auth);
88        let label_map = GmailClient::get_label_map(&hub).await?;
89
90        Ok(GmailClient {
91            hub,
92            label_map,
93            max_results: DEFAULT_MAX_RESULTS.parse::<u32>().unwrap(),
94            label_ids: Vec::new(),
95            query: String::new(),
96            messages: Vec::new(),
97            rule: None,
98            execute: false,
99        })
100    }
101
102    /// Create a new List struct and add the Gmail api connection.
103    async fn get_label_map(
104        hub: &Gmail<HttpsConnector<HttpConnector>>,
105    ) -> Result<BTreeMap<String, String>> {
106        let call = hub.users().labels_list("me");
107        let (_response, list) = call
108            .add_scope("https://mail.google.com/")
109            .doit()
110            .await
111            .map_err(Box::new)?;
112
113        let Some(label_list) = list.labels else {
114            return Err(Error::NoLabelsFound);
115        };
116
117        let mut label_map = BTreeMap::new();
118        for label in &label_list {
119            if label.id.is_some() && label.name.is_some() {
120                let name = label.name.clone().unwrap();
121                let id = label.id.clone().unwrap();
122                label_map.insert(name, id);
123            }
124        }
125
126        Ok(label_map)
127    }
128
129    /// Return the id for the name from the labels map.
130    /// Returns `None` if the name is not found in the map.
131    pub fn get_label_id(&self, name: &str) -> Option<String> {
132        self.label_map.get(name).cloned()
133    }
134
135    /// Show the label names and related id.
136    pub fn show_label(&self) {
137        for (name, id) in self.label_map.iter() {
138            log::info!("{name}: {id}")
139        }
140    }
141
142    /// Get the hub from the client
143    pub(crate) fn hub(&self) -> Gmail<HttpsConnector<HttpConnector>> {
144        self.hub.clone()
145    }
146}