cull_gmail/
gmail_client.rs1use 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
19pub const DEFAULT_MAX_RESULTS: &str = "200";
21
22#[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 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 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 pub fn get_label_id(&self, name: &str) -> Option<String> {
118 self.label_map.get(name).cloned()
119 }
120
121 pub fn show_label(&self) {
123 for (name, id) in self.label_map.iter() {
124 log::info!("{name}: {id}")
125 }
126 }
127
128 pub(crate) fn hub(&self) -> Gmail<HttpsConnector<HttpConnector>> {
130 self.hub.clone()
131 }
132
133 }