194 lines
6.1 KiB
Rust
194 lines
6.1 KiB
Rust
use maud::{html, Markup, DOCTYPE};
|
|
use rocket::{Rocket,Build,launch,get,routes,fs::{FileServer}};
|
|
use std::{fs::OpenOptions, io::BufReader, collections::HashMap};
|
|
use chrono::prelude::*;
|
|
|
|
const TELEGRAM_TIMESTAMP_FORMAT: &str = "%FT%X";
|
|
|
|
#[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: NaiveDateTime,
|
|
pub times: u32
|
|
}
|
|
|
|
impl TryFrom<ChatMessage> for ScoredChatMessage {
|
|
type Error = chrono::format::ParseError;
|
|
|
|
fn try_from(value: ChatMessage) -> Result<Self, Self::Error> {
|
|
Ok(ScoredChatMessage {
|
|
chat_message: value.clone(),
|
|
last_used: NaiveDateTime::parse_from_str(&value.date, TELEGRAM_TIMESTAMP_FORMAT)?,
|
|
times: 1
|
|
})
|
|
}
|
|
}
|
|
|
|
|
|
impl From<ScoredChatMessage> 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 = item.last_used;
|
|
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<MediaType>,
|
|
pub mime_type: Option<String>,
|
|
pub file: Option<String>,
|
|
pub date_unixtime: String,
|
|
pub date: String
|
|
}
|
|
|
|
|
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
|
struct Chat {
|
|
name: String,
|
|
messages: Vec<ChatMessage>
|
|
}
|
|
|
|
|
|
#[launch]
|
|
fn rocket() -> Rocket<Build> {
|
|
rocket::build()
|
|
.mount("/", routes![index])
|
|
.mount("/stickers", FileServer::from("./stickers"))
|
|
.mount("/video_files", FileServer::from("./video_files"))
|
|
}
|
|
|
|
#[get("/")]
|
|
fn index() -> Markup {
|
|
let tg_export_result: Chat = serde_json::from_reader(
|
|
BufReader::new(OpenOptions::new().read(true).open("./result.json").expect("Could not open ./result.json"))
|
|
).expect("Could not parse result.json");
|
|
|
|
let mut messages: Vec<ScoredChatMessage> = tg_export_result.messages.into_iter()
|
|
.filter(|m| matches!(m.media_type, Some(MediaType::Sticker)))
|
|
.fold(HashMap::new(), |mut acc: HashMap<String, ScoredChatMessage>, message| {
|
|
if let Some(ref file) = message.file {
|
|
let message_date = NaiveDateTime::parse_from_str(&message.date, TELEGRAM_TIMESTAMP_FORMAT)
|
|
.expect("Could not parse date");
|
|
let entry = acc.entry(file.to_owned()).or_insert_with(|| ScoredChatMessage::try_from(message)
|
|
.expect("Could not parse date"));
|
|
entry.last_used = entry.last_used.max(message_date);
|
|
entry.times += 1;
|
|
}
|
|
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 {
|
|
(<ScoredChatMessage as Into<Markup>>::into(message))
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|