use maud::{html, Markup, DOCTYPE}; use rocket::{Rocket,Build,launch,get,routes,fs::{FileServer}}; use std::collections::HashMap; use chrono::prelude::*; use rayon::prelude::*; #[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] #[serde(rename_all = "snake_case")] enum MediaType { Sticker, Animation, VideoFile, AudioFile, VoiceMessage, VideoMessage } #[derive(Debug, Clone)] struct ScoredChatMessage { pub chat_message: ChatMessage, pub last_used: i64, pub times: u32 } impl From for ScoredChatMessage { fn from(item: ChatMessage) -> ScoredChatMessage { ScoredChatMessage { chat_message: item.clone(), last_used: item.date_unixtime.parse().expect("Could not parse date_unixtime"), times: 1 } } } impl From for Markup { fn from(item: ScoredChatMessage) -> Markup { if let Some(mime_type) = &item.chat_message.mime_type { let message_file = item.chat_message.file.expect("No sticker file"); let message_date = DateTime::from_timestamp(item.last_used, 0).expect("Could not parse items"); match mime_type.as_str() { "image/webp" => { return html! { .sticker { img loading="lazy" src=(message_file); p { "Times used: " (item.times) br; (message_date.format("%Y-%m-%d %H:%M:%S")) } } }; } "video/webm" => { return html! { .sticker.animated { video loading="lazy" autoplay loop controls src=(message_file) {} p { "Times used: " (item.times) br; (message_date.format("%Y-%m-%d %H:%M:%S")) } } } } "application/x-tgsticker" => { return html! { .sticker.animated { tgs-player loading="lazy" autoplay loop controls src=(message_file) {} p { "Times used: " (item.times) br; (message_date.format("%Y-%m-%d %H:%M:%S")) } } } } _ => { return html! { p { "Unknown type " (mime_type) } }; } } } else { return html! { p { "No type" } }; } } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] struct ChatMessage { pub media_type: Option, pub mime_type: Option, pub file: Option, pub date_unixtime: String } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] struct Chat { name: String, messages: Vec } #[launch] fn rocket() -> Rocket { rocket::build() .mount("/", routes![index]) .mount("/stickers", FileServer::from("./stickers")) .mount("/video_files", FileServer::from("./video_files")) } #[get("/")] fn index() -> Markup { println!("Parsing JSON... (this can take a moment)"); let tg_export_result: Chat = serde_json::from_reader( std::fs::OpenOptions::new().read(true).open("./result.json").expect("Could not open ./result.json") ).expect("Could not parse result.json"); println!("Done!"); let messages: Vec = tg_export_result.messages.into_par_iter().filter(|m| { return if let Some(media_type) = &m.media_type { *media_type == MediaType::Sticker } else { false } }).collect(); let mut messages: Vec = messages.into_iter().fold(HashMap::new(), |mut acc: HashMap, message| { let file = message.file.as_ref().expect("No file").to_owned(); if acc.contains_key(&file) { if let Some(scored_chat_message) = acc.get_mut(&file) { let message_date = message.date_unixtime.parse().expect("Could not parse date_unixtime"); if scored_chat_message.last_used < message_date { scored_chat_message.last_used = message_date; } scored_chat_message.times = scored_chat_message.times + 1; } } else { acc.insert(file, ScoredChatMessage::from(message)); } acc }) .into_values() .collect(); messages.sort_by(|a, b| b.times.cmp(&a.times)); return html! { (DOCTYPE) head { script src="https://unpkg.com/@lottiefiles/lottie-player@0.4.0/dist/tgs-player.js" {} } body { style { r#"* { box-sizing: border-box; } h1 {text-align: center;} body { width: 100vw; margin: 0; padding: 0; } .container { max-width: 1024px; margin: 0 auto; } .container, .stickers, .sticker { display: flex; position: relative; flex-flow: row wrap; } .sticker { flex: 0 0 25%; align-items: flex-end; padding: 1rem; border: 1px solid #00000055; } .sticker p { flex: 1; text-align: center; } img, video, tgs-player { width: 100%; height: auto; margin: 0 auto; aspect-ratio: 1 / 1; object-fit: contain; }"# } .container { h1 { "Stickers with " (tg_export_result.name) } .stickers { @for message in messages { (>::into(message)) } } } } }; }