use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Message {
pub id: String,
pub thread_id: String,
#[serde(default)]
pub label_ids: Vec<String>,
pub snippet: Option<String>,
pub history_id: Option<String>,
pub internal_date: Option<String>,
pub payload: Option<MessagePayload>,
pub size_estimate: Option<u64>,
pub raw: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessagePayload {
pub part_id: Option<String>,
pub mime_type: String,
pub filename: Option<String>,
pub headers: Vec<Header>,
pub body: MessageBody,
pub parts: Option<Vec<MessagePayload>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageBody {
pub attachment_id: Option<String>,
pub size: u64,
pub data: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Header {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Thread {
pub id: String,
pub snippet: Option<String>,
pub history_id: Option<String>,
pub messages: Option<Vec<Message>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Label {
pub id: String,
pub name: String,
pub message_list_visibility: Option<String>,
pub label_list_visibility: Option<String>,
pub r#type: Option<String>,
pub messages_total: Option<u64>,
pub messages_unread: Option<u64>,
pub threads_total: Option<u64>,
pub threads_unread: Option<u64>,
pub color: Option<LabelColor>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LabelColor {
pub text_color: String,
pub background_color: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Draft {
pub id: String,
pub message: Message,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Attachment {
pub size: u64,
pub data: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendAs {
pub send_as_email: String,
pub display_name: Option<String>,
pub reply_to_address: Option<String>,
pub signature: Option<String>,
#[serde(default)]
pub is_primary: bool,
#[serde(default)]
pub is_default: bool,
#[serde(default)]
pub treat_as_alias: bool,
pub verification_status: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct History {
pub id: String,
pub messages: Option<Vec<Message>>,
pub messages_added: Option<Vec<HistoryMessageAdded>>,
pub messages_deleted: Option<Vec<HistoryMessageDeleted>>,
pub labels_added: Option<Vec<HistoryLabelAdded>>,
pub labels_removed: Option<Vec<HistoryLabelRemoved>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryMessageAdded {
pub message: Message,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryMessageDeleted {
pub message: Message,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryLabelAdded {
pub message: Message,
#[serde(default)]
pub label_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryLabelRemoved {
pub message: Message,
#[serde(default)]
pub label_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Profile {
pub email_address: String,
pub messages_total: u64,
pub threads_total: u64,
pub history_id: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WatchResponse {
pub history_id: String,
pub expiration: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SearchResponse {
#[serde(default)]
pub messages: Vec<MessageRef>,
pub next_page_token: Option<String>,
pub result_size_estimate: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct MessageRef {
pub id: String,
pub thread_id: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct Filter {
#[serde(skip_serializing_if = "String::is_empty")]
pub id: String,
#[serde(skip_serializing_if = "FilterCriteria::is_empty")]
pub criteria: FilterCriteria,
pub action: FilterAction,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct FilterCriteria {
#[serde(skip_serializing_if = "Option::is_none")]
pub from: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub to: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub subject: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub negated_query: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub has_attachment: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub exclude_chats: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub size_comparison: Option<String>,
}
impl FilterCriteria {
fn is_empty(&self) -> bool {
self.from.is_none()
&& self.to.is_none()
&& self.subject.is_none()
&& self.query.is_none()
&& self.negated_query.is_none()
&& self.has_attachment.is_none()
&& self.exclude_chats.is_none()
&& self.size.is_none()
&& self.size_comparison.is_none()
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct FilterAction {
#[serde(skip_serializing_if = "Option::is_none")]
pub add_label_ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub remove_label_ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub forward: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct FilterList {
#[serde(default)]
pub filter: Vec<Filter>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_page_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_size_estimate: Option<u64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ForwardingAddress {
#[serde(skip_serializing_if = "String::is_empty")]
pub forwarding_email: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub verification_status: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ForwardingAddressList {
#[serde(default)]
pub forwarding_addresses: Vec<ForwardingAddress>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct AutoForwarding {
pub enabled: bool,
#[serde(skip_serializing_if = "String::is_empty")]
pub email_address: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub disposition: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct PopSettings {
#[serde(skip_serializing_if = "Option::is_none")]
pub access_window: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disposition: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ImapSettings {
pub enabled: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub expunge_behavior: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_expunge: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_folder_size: Option<i64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct LabelList {
#[serde(default)]
pub labels: Vec<Label>,
pub next_page_token: Option<String>,
pub result_size_estimate: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DraftList {
#[serde(default)]
pub drafts: Vec<Draft>,
pub next_page_token: Option<String>,
pub result_size_estimate: Option<u64>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
pub struct ThreadList {
pub threads: Vec<Thread>,
#[serde(skip_serializing_if = "Option::is_none")]
pub next_page_token: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result_size_estimate: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct HistoryList {
#[serde(default)]
pub history: Vec<History>,
pub next_page_token: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendAsList {
#[serde(default)]
pub send_as: Vec<SendAs>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchRequest {
pub requests: Vec<BatchRequestEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchRequestEntry {
pub id: String,
pub method: String,
pub path: String,
pub body: Option<serde_json::Value>,
pub headers: Option<HashMap<String, String>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchResponse {
pub responses: Vec<BatchResponseEntry>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchResponseEntry {
pub id: String,
pub status: u16,
pub headers: HashMap<String, String>,
pub body: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ModifyLabelsRequest {
pub add_label_ids: Vec<String>,
pub remove_label_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct BatchModifyLabelsRequest {
pub ids: Vec<String>,
pub add_label_ids: Vec<String>,
pub remove_label_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateLabelRequest {
pub name: String,
pub label_list_visibility: Option<String>,
pub message_list_visibility: Option<String>,
pub color: Option<LabelColor>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateLabelRequest {
#[serde(skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label_list_visibility: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub message_list_visibility: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub color: Option<LabelColor>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WatchRequest {
pub topic_name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub label_ids: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label_filter_action: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub label_filter_behavior: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ImportMessageRequest {
pub raw: String,
pub internal_date_source: Option<String>,
pub deleted: Option<bool>,
pub never_spam: Option<bool>,
pub process_for_calendar: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateDraftRequest {
pub message: DraftMessage,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DraftMessage {
pub raw: String,
pub thread_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SendMessageRequest {
pub raw: String,
pub thread_id: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AttachmentData {
pub filename: String,
pub content: Vec<u8>,
pub mime_type: String,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateLabelOptions {
pub label_list_visibility: Option<String>,
pub message_list_visibility: Option<String>,
pub color: Option<LabelColor>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateLabelOptions {
pub name: Option<String>,
pub label_list_visibility: Option<String>,
pub message_list_visibility: Option<String>,
pub color: Option<LabelColor>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CreateSendAsOptions {
pub send_as_email: String,
pub display_name: Option<String>,
pub reply_to_address: Option<String>,
pub signature: Option<String>,
pub treat_as_alias: Option<bool>,
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UpdateSendAsOptions {
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub reply_to_address: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub signature: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub treat_as_alias: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorResponse {
pub error: ErrorDetail,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ErrorDetail {
pub code: u16,
pub message: String,
pub status: String,
pub details: Option<Vec<serde_json::Value>>,
}
impl MessagePayload {
pub fn header(&self, name: &str) -> Option<&str> {
self.headers
.iter()
.find(|h| h.name.eq_ignore_ascii_case(name))
.map(|h| h.value.as_str())
}
pub fn subject(&self) -> Option<&str> {
self.header("Subject")
}
pub fn from(&self) -> Option<&str> {
self.header("From")
}
pub fn to(&self) -> Option<&str> {
self.header("To")
}
pub fn date(&self) -> Option<&str> {
self.header("Date")
}
pub fn message_id(&self) -> Option<&str> {
self.header("Message-ID")
}
pub fn in_reply_to(&self) -> Option<&str> {
self.header("In-Reply-To")
}
pub fn references(&self) -> Option<&str> {
self.header("References")
}
}
pub fn extract_body(msg: &Message) -> Option<String> {
let payload = msg.payload.as_ref()?;
let plain = find_part_body(payload, "text/plain");
if let Some(text) = plain {
return Some(text);
}
find_part_body(payload, "text/html")
}
fn find_part_body(part: &MessagePayload, mime: &str) -> Option<String> {
if part.mime_type == mime
&& let Some(encoded) = part.body.data.as_deref().filter(|d| !d.is_empty())
{
if let Some(decoded) = decode_body_part(encoded) {
return Some(decoded);
}
}
let parts = part.parts.as_ref()?;
for child in parts {
if let Some(text) = find_part_body(child, mime) {
return Some(text);
}
}
None
}
fn decode_body_part(encoded: &str) -> Option<String> {
use base64::Engine;
let mut buf = encoded.replace('-', "+").replace('_', "/");
while !buf.len().is_multiple_of(4) {
buf.push('=');
}
let bytes = base64::engine::general_purpose::STANDARD.decode(buf).ok()?;
Some(String::from_utf8_lossy(&bytes).into_owned())
}