use chrono::{DateTime, Utc, TimeZone};
use crate::Attachment;
use crate::VCardError;
use hex;
use hmac::{KeyInit, Hmac, Mac};
use serde::{Deserialize, Deserializer, Serialize};
use sha2::Sha256;
fn ms_to_datetime(ms: i64) -> DateTime<Utc> {
Utc.timestamp_millis_opt(ms).unwrap()
}
fn format_datetime(dt: DateTime<Utc>) -> String {
dt.format("%d.%m.%Y %H:%M:%S").to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct User {
pub user_id: i64,
pub first_name: String,
pub last_name: Option<String>,
pub username: Option<String>,
pub is_bot: bool,
pub last_activity_time: Option<i64>,
}
impl User {
pub fn last_activity_datetime(&self) -> Option<DateTime<Utc>> {
self.last_activity_time.map(ms_to_datetime)
}
pub fn last_activity_time_formatted(&self) -> Option<String> {
self.last_activity_datetime().map(format_datetime)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BotInfo {
#[serde(flatten)]
pub user: User,
pub description: Option<String>,
pub avatar_url: Option<String>,
pub full_avatar_url: Option<String>,
#[serde(default)]
pub commands: Vec<BotCommand>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BotCommand {
pub name: String,
pub description: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChatTypeKind {
Chat,
Dialog,
Unknown(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChatStatusKind {
Active,
Removed,
Left,
Closed,
Unknown(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ChatAdminPermissionKind {
ReadAllMessages,
AddRemoveMembers,
AddAdmins,
ChangeChatInfo,
PinMessage,
Write,
CanCall,
EditLink,
PostEditDeleteMessage,
EditMessage,
DeleteMessage,
ViewStats,
Delete,
Edit,
Unknown(String),
}
impl ChatTypeKind {
pub fn from_str(s: &str) -> Self {
match s {
"chat" => ChatTypeKind::Chat,
"dialog" => ChatTypeKind::Dialog,
other => ChatTypeKind::Unknown(other.to_string()),
}
}
pub fn as_str(&self) -> &str {
match self {
ChatTypeKind::Chat => "chat",
ChatTypeKind::Dialog => "dialog",
ChatTypeKind::Unknown(s) => s.as_str(),
}
}
}
impl ChatStatusKind {
pub fn from_str(s: &str) -> Self {
match s {
"active" => ChatStatusKind::Active,
"removed" => ChatStatusKind::Removed,
"left" => ChatStatusKind::Left,
"closed" => ChatStatusKind::Closed,
other => ChatStatusKind::Unknown(other.to_string()),
}
}
pub fn as_str(&self) -> &str {
match self {
ChatStatusKind::Active => "active",
ChatStatusKind::Removed => "removed",
ChatStatusKind::Left => "left",
ChatStatusKind::Closed => "closed",
ChatStatusKind::Unknown(s) => s.as_str(),
}
}
}
impl ChatAdminPermissionKind {
pub fn from_str(s: &str) -> Self {
match s {
"read_all_messages" => ChatAdminPermissionKind::ReadAllMessages,
"add_remove_members" => ChatAdminPermissionKind::AddRemoveMembers,
"add_admins" => ChatAdminPermissionKind::AddAdmins,
"change_chat_info" => ChatAdminPermissionKind::ChangeChatInfo,
"pin_message" => ChatAdminPermissionKind::PinMessage,
"write" => ChatAdminPermissionKind::Write,
"can_call" => ChatAdminPermissionKind::CanCall,
"edit_link" => ChatAdminPermissionKind::EditLink,
"post_edit_delete_message" => ChatAdminPermissionKind::PostEditDeleteMessage,
"edit_message" => ChatAdminPermissionKind::EditMessage,
"delete_message" => ChatAdminPermissionKind::DeleteMessage,
"view_stats" => ChatAdminPermissionKind::ViewStats,
"delete" => ChatAdminPermissionKind::Delete,
"edit" => ChatAdminPermissionKind::Edit,
other => ChatAdminPermissionKind::Unknown(other.to_string()),
}
}
pub fn as_str(&self) -> &str {
match self {
ChatAdminPermissionKind::ReadAllMessages => "read_all_messages",
ChatAdminPermissionKind::AddRemoveMembers => "add_remove_members",
ChatAdminPermissionKind::AddAdmins => "add_admins",
ChatAdminPermissionKind::ChangeChatInfo => "change_chat_info",
ChatAdminPermissionKind::PinMessage => "pin_message",
ChatAdminPermissionKind::Write => "write",
ChatAdminPermissionKind::CanCall => "can_call",
ChatAdminPermissionKind::EditLink => "edit_link",
ChatAdminPermissionKind::PostEditDeleteMessage => "post_edit_delete_message",
ChatAdminPermissionKind::EditMessage => "edit_message",
ChatAdminPermissionKind::DeleteMessage => "delete_message",
ChatAdminPermissionKind::ViewStats => "view_stats",
ChatAdminPermissionKind::Delete => "delete",
ChatAdminPermissionKind::Edit => "edit",
ChatAdminPermissionKind::Unknown(s) => s.as_str(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Chat {
pub chat_id: i64,
pub r#type: String,
pub status: String,
pub title: Option<String>,
pub icon: Option<Image>,
pub last_event_time: i64,
pub participants_count: i32,
pub owner_id: Option<i64>,
pub is_public: bool,
pub link: Option<String>,
pub description: Option<String>,
pub dialog_with_user: Option<User>,
pub chat_message_id: Option<String>,
pub pinned_message: Option<serde_json::Value>,
}
impl Chat {
pub fn type_kind(&self) -> ChatTypeKind {
ChatTypeKind::from_str(&self.r#type)
}
pub fn status_kind(&self) -> ChatStatusKind {
ChatStatusKind::from_str(&self.status)
}
pub fn last_event_datetime(&self) -> DateTime<Utc> {
ms_to_datetime(self.last_event_time)
}
pub fn last_event_time_formatted(&self) -> String {
format_datetime(self.last_event_datetime())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Image {
pub url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChatMember {
#[serde(flatten)]
pub user: User,
pub description: Option<String>,
pub avatar_url: Option<String>,
pub full_avatar_url: Option<String>,
pub last_access_time: i64,
pub is_owner: bool,
pub is_admin: bool,
pub join_time: i64,
#[serde(default)]
pub permissions: Vec<String>,
pub alias: Option<String>,
}
impl ChatMember {
pub fn permission_kinds(&self) -> Vec<ChatAdminPermissionKind> {
self.permissions.iter().map(|s| ChatAdminPermissionKind::from_str(s)).collect()
}
pub fn last_access_datetime(&self) -> DateTime<Utc> {
ms_to_datetime(self.last_access_time)
}
pub fn last_access_time_formatted(&self) -> String {
format_datetime(self.last_access_datetime())
}
pub fn join_datetime(&self) -> DateTime<Utc> {
ms_to_datetime(self.join_time)
}
pub fn join_time_formatted(&self) -> String {
format_datetime(self.join_datetime())
}
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct Message {
pub sender: Option<User>,
pub recipient: Recipient,
pub timestamp: i64,
pub body: Option<MessageBody>,
pub url: Option<String>,
}
impl Message {
pub fn timestamp_datetime(&self) -> DateTime<Utc> {
ms_to_datetime(self.timestamp)
}
pub fn timestamp_formatted(&self) -> String {
format_datetime(self.timestamp_datetime())
}
pub fn message_id(&self) -> Option<&str> {
self.body.as_ref().map(|b| b.mid.as_str())
}
}
#[derive(Debug, Clone, Serialize, Default, Deserialize)]
pub struct Recipient {
pub chat_id: Option<i64>,
pub user_id: Option<i64>,
pub chat_type: String,
}
#[derive(Debug, Clone, Default, Deserialize)]
pub struct MessageBody {
pub mid: String,
pub seq: i64,
pub text: Option<String>,
#[serde(default, deserialize_with = "deserialize_attachments")]
pub attachments: Vec<AttachmentData>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AttachmentData {
Image { payload: MediaPayload },
Video { payload: MediaPayload },
Audio { payload: MediaPayload },
File { payload: FilePayload },
Sticker { payload: StickerPayload },
InlineKeyboard { payload: KeyboardPayload },
Location { payload: LocationPayload },
Contact { payload: ContactPayload },
#[serde(other)]
Unknown,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MediaPayload {
pub token: Option<String>,
pub url: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct FilePayload {
pub token: Option<String>,
pub url: Option<String>,
pub filename: Option<String>,
pub size: Option<i64>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct StickerPayload {
pub code: String,
pub url: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct KeyboardPayload {
pub buttons: Vec<Vec<serde_json::Value>>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct LocationPayload {
pub latitude: f64,
pub longitude: f64,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ContactPayload {
pub name: Option<String>,
pub contact_id: Option<i64>,
pub vcf_info: Option<String>,
pub vcf_phone: Option<String>,
#[serde(default)]
pub hash: Option<String>,
}
impl ContactPayload {
pub fn parse_vcard(&self) -> Result<crate::vcard::VCard, VCardError> {
let vcf = self.vcf_info.as_ref().ok_or_else(|| VCardError::InvalidFormat("No vcf_info".into()))?;
crate::parse_vcard(vcf)
}
pub fn verify_hash(&self, secret: &[u8]) -> bool {
let Some(hash_str) = &self.hash else { return false };
let hash_bytes = match hex::decode(hash_str) {
Ok(h) => h,
Err(_) => return false,
};
if let Some(vcf) = &self.vcf_info {
type HmacSha256 = Hmac<Sha256>;
let mut mac = HmacSha256::new_from_slice(secret)
.expect("HMAC key can be any length");
mac.update(vcf.as_bytes());
let mac_bytes = mac.finalize().into_bytes().to_vec();
if mac_bytes == hash_bytes {
return true;
}
}
false
}
pub fn is_verified_with_bot(&self, bot: &crate::client::MaxClient) -> bool {
self.verify_hash(bot.token().as_bytes())
}
}
fn deserialize_attachments<'de, D>(deserializer: D) -> Result<Vec<AttachmentData>, D::Error>
where
D: Deserializer<'de>,
{
let raw: Option<Vec<serde_json::Value>> = Deserialize::deserialize(deserializer)?;
match raw {
Some(vec) => {
let mut result = Vec::new();
for value in vec {
if let Ok(att) = serde_json::from_value(value) {
result.push(att);
}
}
Ok(result)
}
None => Ok(Vec::new()),
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Contact {
pub name: String,
pub phone: String,
pub vcf_info: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Location {
pub latitude: f64,
pub longitude: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CallbackQuery {
pub callback_id: String,
pub timestamp: i64,
pub user: User,
pub payload: String,
}
impl CallbackQuery {
pub fn timestamp_datetime(&self) -> DateTime<Utc> {
ms_to_datetime(self.timestamp)
}
pub fn timestamp_formatted(&self) -> String {
format_datetime(self.timestamp_datetime())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Subscription {
pub url: String,
pub time: i64,
pub update_types: Vec<String>,
}
impl Subscription {
pub fn time_datetime(&self) -> DateTime<Utc> {
ms_to_datetime(self.time)
}
pub fn time_formatted(&self) -> String {
format_datetime(self.time_datetime())
}
}
#[derive(Debug, Clone, Default)]
pub struct UpdateChatInfo {
pub title: Option<String>,
pub description: Option<String>,
pub icon_token: Option<String>,
pub icon_url: Option<String>,
pub link: Option<String>,
pub is_public: Option<bool>,
pub pin: Option<String>,
pub notify: Option<bool>,
}
#[derive(Debug, Clone, Serialize)]
pub struct NewMessageBody {
#[serde(skip_serializing_if = "Option::is_none")]
pub text: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub attachments: Option<Vec<Attachment>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub format: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub disable_link_preview: Option<bool>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "update_type", rename_all = "snake_case")]
pub enum Update {
MessageCreated {
timestamp: i64,
message: Message,
},
MessageEdited {
timestamp: i64,
message: Message,
},
MessageCallback {
timestamp: i64,
callback: CallbackQuery,
message: Option<Message>,
user_locale: Option<String>,
},
BotStarted {
timestamp: i64,
chat_id: i64,
user: User,
payload: Option<String>,
user_locale: Option<String>,
},
BotAdded {
timestamp: i64,
chat_id: i64,
user: User,
is_channel: Option<bool>,
},
BotRemoved {
timestamp: i64,
chat_id: i64,
user: User,
is_channel: Option<bool>,
},
UserAdded {
timestamp: i64,
chat_id: i64,
user: User,
inviter_id: Option<i64>,
is_channel: Option<bool>,
},
UserRemoved {
timestamp: i64,
chat_id: i64,
user: User,
admin_id: Option<i64>,
is_channel: Option<bool>,
},
ChatTitleChanged {
timestamp: i64,
chat_id: i64,
user: User,
title: String,
},
#[serde(other)]
Unknown,
}
impl Update {
pub fn timestamp(&self) -> i64 {
match self {
Update::MessageCreated { timestamp, .. } => *timestamp,
Update::MessageEdited { timestamp, .. } => *timestamp,
Update::MessageCallback { timestamp, .. } => *timestamp,
Update::BotStarted { timestamp, .. } => *timestamp,
Update::BotAdded { timestamp, .. } => *timestamp,
Update::BotRemoved { timestamp, .. } => *timestamp,
Update::UserAdded { timestamp, .. } => *timestamp,
Update::UserRemoved { timestamp, .. } => *timestamp,
Update::ChatTitleChanged { timestamp, .. } => *timestamp,
Update::Unknown => 0,
}
}
pub fn timestamp_datetime(&self) -> DateTime<Utc> {
ms_to_datetime(self.timestamp())
}
pub fn timestamp_formatted(&self) -> String {
format_datetime(self.timestamp_datetime())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum UpdateType {
MessageCreated,
MessageCallback,
#[serde(other)]
Unknown,
}