use anyhow::{anyhow, Context, Result}; #[derive(Debug)] struct TgLink { username: String, message_id: usize, } impl TgLink { fn to_url(&self) -> String { format!("https://t.me/{}/{}?embed=1", self.username, self.message_id) } } fn fetch_embed(link: &TgLink) -> Result { let response = crimp::Request::get(&link.to_url()) .send() .context("failed to fetch embed data")? .as_string() .context("failed to decode embed data")? .error_for_status(|resp| { anyhow!("telegram request failed: {} ({})", resp.body, resp.status) })?; Ok(response.body) } #[derive(Debug)] struct TgMessage { author: String, message: Option, photos: Vec, videos: Vec, } fn extract_photo_url(style: &str) -> Option<&str> { let url_start = style.find("url('")? + 5; let url_end = style.find("')")?; Some(&style[url_start..url_end]) } fn parse_tgmessage(embed: &str) -> Result { use scraper::{Html, Selector}; let doc = Html::parse_document(embed); let author_sel = Selector::parse("a.tgme_widget_message_owner_name").unwrap(); let author = doc .select(&author_sel) .next() .ok_or_else(|| anyhow!("failed to find message author"))? .text() .collect::>() .concat(); let msg_sel = Selector::parse("div.tgme_widget_message_text.js-message_text").unwrap(); let message = doc .select(&msg_sel) .next() .map(|m| m.text().collect::>().concat()); let photo_sel = Selector::parse("a.tgme_widget_message_photo_wrap").unwrap(); let mut photos = vec![]; for photo in doc.select(&photo_sel) { if let Some(style) = photo.value().attr("style") { if let Some(url) = extract_photo_url(style) { photos.push(url.to_string()) } } } let video_sel = Selector::parse("i.tgme_widget_message_video_thumb").unwrap(); let mut videos = vec![]; for video in doc.select(&video_sel) { if let Some(style) = video.value().attr("style") { if let Some(url) = extract_photo_url(style) { videos.push(url.to_string()) } } } Ok(TgMessage { author, message, photos, videos, }) } fn shorten_link(link: &str) -> Result { let mut url = url::Url::parse("https://tinyurl.com/api-create.php")?; url.query_pairs_mut().clear().append_pair("url", link); let request = url.as_str(); let response = crimp::Request::get(request) .send() .context("failed to shorten URL")? .as_string() .context("failed to decode shortened URL")? .error_for_status(|resp| { anyhow!("tinyurl request failed: {} ({})", resp.body, resp.status) })?; Ok(response.body.trim().into()) } fn to_bbcode(link: &TgLink, msg: &TgMessage) -> Result { let mut out = String::new(); out.push_str(&format!("[quote=\"{}\"]\n", msg.author)); for video in &msg.videos { out.push_str(&format!("[url=\"{}\"]", link.to_url())); out.push_str(&format!("[img]{}[/img]", shorten_link(video)?)); out.push_str("[/url]\n"); out.push_str("[sub](Click thumbnail to open video)[/sub]\n") } for photo in &msg.photos { out.push_str(&format!("[timg]{}[/timg]\n", shorten_link(photo)?)); } if let Some(message) = &msg.message { out.push_str(message); } out.push_str(&format!( "\n\n[sub](via [url=\"{}\"]Telegram[/url])[/sub]", link.to_url(), )); out.push_str("\n[/quote]\n"); Ok(out) } fn main() { crimp::init(); let link = TgLink { // username: "RWApodcast".into(), // message_id: 113, username: "intelslava".into(), message_id: 25483, }; let embed = fetch_embed(&link).unwrap(); let msg = parse_tgmessage(&embed).unwrap(); println!("{}", to_bbcode(&link, &msg).unwrap()); }