From a35b255d67877531da18db718bd15c885fdfa6f4 Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Fri, 19 Sep 2025 17:12:27 +0300 Subject: [PATCH] feat: MVP --- Cargo.lock | 28 ++++++++++++++ Cargo.toml | 2 + src/main.rs | 97 +++++++++++++++++++++++++++++++++++++++++------- src/telemetry.rs | 17 ++------- 4 files changed, 118 insertions(+), 26 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 19f8683..a2b8145 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,12 @@ version = "1.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3995eaeebcdf32f91f980d360f78732ddc061097ab4e39991ae7a6ace9194677" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -129,6 +135,17 @@ dependencies = [ "shlex", ] +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + [[package]] name = "cfg-if" version = "1.0.3" @@ -814,6 +831,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + [[package]] name = "io-uring" version = "0.7.10" @@ -1665,6 +1691,8 @@ version = "0.1.0" dependencies = [ "color-eyre", "dotenv", + "infer", + "once_cell", "regex", "teloxide", "tempfile", diff --git a/Cargo.toml b/Cargo.toml index 5599f4e..2626e0a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,8 @@ edition = "2024" [dependencies] color-eyre = "0.6" dotenv = "0.15" +infer = "0.19" +once_cell = "1.21.3" regex = "1.11" teloxide = { version = "0.17", features = ["macros"] } tempfile = "3" diff --git a/src/main.rs b/src/main.rs index 29f10d9..60419c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,11 @@ use color_eyre::{ }; use dotenv::dotenv; use regex::Regex; -use std::{fs::read_dir, process::Stdio}; +use std::{ + fs::{File, read_dir}, + io::Read, + path::Path, +}; use teloxide::{ Bot, prelude::Requester, @@ -18,8 +22,18 @@ use tempfile::tempdir; use tokio::process::Command; use tracing::error; +static VIDEO_EXTS: &[&str] = &["mp4", "webm"]; +static IMAGE_EXTS: &[&str] = &["jpg", "jpeg", "png"]; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum MediaKind { + Video, + Image, + Unknown, +} + #[tokio::main] -async fn main() -> Result<()> { +async fn main() -> color_eyre::Result<()> { dotenv().ok(); color_eyre::install()?; setup_logger()?; @@ -62,15 +76,17 @@ async fn fetch_and_send(bot: &Bot, chat_id: ChatId, shortcode: &str) -> Result<( let dir = tempdir().context("create tempdir")?; let dir_path = dir.path().to_path_buf(); - let target = format!("--{}", shortcode); + dbg!(&dir_path); + let target = format!("-{}", shortcode); + dbg!(&target); let status = Command::new("instaloader") + .arg("--dirname-pattern") .arg(dir_path.to_string_lossy().as_ref()) - .arg("--no-metadate-json") + .arg("--no-metadata-json") .arg("--no-compress-json") - // .arg("--quiet") + .arg("--quiet") + .arg("--") .arg(&target) - .stdout(Stdio::null()) - .stderr(Stdio::piped()) .status() .await .context("runnning instaloader")?; @@ -95,14 +111,69 @@ async fn fetch_and_send(bot: &Bot, chat_id: ChatId, shortcode: &str) -> Result<( return Err(eyre!("no media found")); } - let first = &media_files[0]; - let input = InputFile::file(first.clone()); + dbg!(&media_files); - let ext = first.extension().and_then(|s| s.to_str()).unwrap_or(""); - if matches!(ext, "jpg" | "jpeg") { - bot.send_photo(chat_id, input).await?; - } else { + if let Some(video_path) = media_files.iter().find(|p| is_video(p)) { + let input = InputFile::file(video_path.clone()); bot.send_video(chat_id, input).await?; + return Ok(()); } + + if let Some(image_path) = media_files.iter().find(|p| is_image(p)) { + let input = InputFile::file(image_path.clone()); + bot.send_photo(chat_id, input).await?; + return Ok(()); + } + + bot.send_message(chat_id, "No supported media found") + .await?; + Ok(()) } + +fn ext_lower(path: &Path) -> Option { + path.extension() + .and_then(|s| s.to_str()) + .map(|s| s.to_ascii_lowercase()) +} + +fn kind_by_magic(path: &Path) -> Option { + let mut f = File::open(path).ok()?; + let mut buf = [0u8; 8192]; + + let n = f.read(&mut buf).ok()?; + if n == 0 { + return None; + } + + if let Some(kind) = infer::get(&buf[..n]) { + let mt = kind.mime_type(); + if mt.starts_with("video/") { + return Some(MediaKind::Video); + } + if mt.starts_with("image/") { + return Some(MediaKind::Image); + } + } + None +} + +fn detect_media_kind(path: &Path) -> MediaKind { + if let Some(ext) = ext_lower(path) { + if VIDEO_EXTS.iter().any(|e| e.eq_ignore_ascii_case(&ext)) { + return MediaKind::Video; + } + if IMAGE_EXTS.iter().any(|e| e.eq_ignore_ascii_case(&ext)) { + return MediaKind::Image; + } + } + kind_by_magic(path).unwrap_or(MediaKind::Unknown) +} + +fn is_video(path: &Path) -> bool { + detect_media_kind(path) == MediaKind::Video +} + +fn is_image(path: &Path) -> bool { + detect_media_kind(path) == MediaKind::Image +} diff --git a/src/telemetry.rs b/src/telemetry.rs index 1b68617..988151e 100644 --- a/src/telemetry.rs +++ b/src/telemetry.rs @@ -1,23 +1,14 @@ use color_eyre::Result; -use std::{fs::create_dir_all, path::PathBuf}; -use tracing_appender::rolling; use tracing_bunyan_formatter::{BunyanFormattingLayer, JsonStorageLayer}; -use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; +use tracing_subscriber::{EnvFilter, layer::SubscriberExt, util::SubscriberInitExt}; /// # Errors pub fn setup_logger() -> Result<()> { - let log_dir_path = PathBuf::from(".logs"); - create_dir_all(&log_dir_path)?; - - let logfile = if cfg!(debug_assertions) { - rolling::daily(log_dir_path, "traxor.log") - } else { - rolling::never(log_dir_path, "traxor.log") - }; - - let formatter = BunyanFormattingLayer::new("traxor".into(), logfile); + let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| "info".into()); + let formatter = BunyanFormattingLayer::new("tg-relay-rs".into(), std::io::stdout); tracing_subscriber::registry() + .with(env_filter) .with(JsonStorageLayer) .with(formatter) .init();