use crate::config::MastodonConfig; use egg_mode::{ entities::{MentionEntity, UrlEntity}, tweet::Tweet, }; use elefren::{apps::App, prelude::*, scopes::Scopes}; use html_escape::decode_html_entities; use std::{borrow::Cow, collections::HashMap, io::stdin}; /// Decodes the Twitter mention to something that will make sense once Twitter has joined the /// Fediverse fn twitter_mentions(ums: &[MentionEntity]) -> HashMap { ums.iter() .map(|s| { ( format!("@{}", s.screen_name), format!("@{}@twitter.com", s.screen_name), ) }) .collect() } /// Decodes urls from UrlEntities fn decode_urls(urls: &[UrlEntity]) -> HashMap { urls.iter() .filter(|s| s.expanded_url.is_some()) .map(|s| { ( s.url.to_owned(), s.expanded_url.as_deref().unwrap().to_owned(), ) }) .collect() } /// Gets Mastodon Data pub fn get_mastodon_token(masto: &MastodonConfig) -> Mastodon { let data = Data { base: Cow::from(masto.base.to_owned()), client_id: Cow::from(masto.client_id.to_owned()), client_secret: Cow::from(masto.client_secret.to_owned()), redirect: Cow::from(masto.redirect.to_owned()), token: Cow::from(masto.token.to_owned()), }; Mastodon::from(data) } /// Builds toot text from tweet pub fn build_basic_status(tweet: &Tweet) -> String { let mut toot = tweet.text.to_owned(); for decoded_url in decode_urls(&tweet.entities.urls) { toot = toot.replace(&decoded_url.0, &decoded_url.1); } for decoded_mention in twitter_mentions(&tweet.entities.user_mentions) { toot = toot.replace(&decoded_mention.0, &decoded_mention.1); } decode_html_entities(&toot).to_string() } /// Generic register function /// As this function is supposed to be run only once, it will panic for every error it encounters /// Most of this function is a direct copy/paste of the official `elefren` crate pub fn register(host: &str, screen_name: &str) { let mut builder = App::builder(); builder .client_name(Cow::from(env!("CARGO_PKG_NAME").to_string())) .redirect_uris(Cow::from("urn:ietf:wg:oauth:2.0:oob".to_string())) .scopes(Scopes::write_all()) .website(Cow::from( "https://framagit.org/veretcle/scootaloo".to_string(), )); let app = builder.build().expect("Cannot build the app"); let registration = Registration::new(host) .register(app) .expect("Cannot build registration object"); let url = registration .authorize_url() .expect("Cannot generate registration URI!"); println!("Click this link to authorize on Mastodon: {}", url); println!("Paste the returned authorization code: "); let mut input = String::new(); stdin() .read_line(&mut input) .expect("Unable to read back registration code!"); let code = input.trim(); let mastodon = registration .complete(code) .expect("Unable to create access token!"); let toml = toml::to_string(&*mastodon).unwrap(); println!( "Please insert the following block at the end of your configuration file: [mastodon.{}] twitter_screen_name = \"{}\" {}", screen_name.to_lowercase(), screen_name, toml ); } #[cfg(test)] mod tests { use super::*; use chrono::prelude::*; use egg_mode::tweet::TweetEntities; #[test] fn test_twitter_mentions() { let mention_entity = MentionEntity { id: 12345, range: (1, 3), name: "Ta Mere l0l".to_string(), screen_name: "tamerelol".to_string(), }; let twitter_ums = vec![mention_entity]; let mut expected_mentions = HashMap::new(); expected_mentions.insert( "@tamerelol".to_string(), "@tamerelol@twitter.com".to_string(), ); let decoded_mentions = twitter_mentions(&twitter_ums); assert_eq!(expected_mentions, decoded_mentions); } #[test] fn test_decode_urls() { let url_entity1 = UrlEntity { display_url: "tamerelol".to_string(), expanded_url: Some("https://www.nintendojo.fr/dojobar".to_string()), range: (1, 3), url: "https://t.me/tamerelol".to_string(), }; let url_entity2 = UrlEntity { display_url: "tamerelol".to_string(), expanded_url: None, range: (1, 3), url: "https://t.me/tamerelol".to_string(), }; let twitter_urls = vec![url_entity1, url_entity2]; let mut expected_urls = HashMap::new(); expected_urls.insert( "https://t.me/tamerelol".to_string(), "https://www.nintendojo.fr/dojobar".to_string(), ); let decoded_urls = decode_urls(&twitter_urls); assert_eq!(expected_urls, decoded_urls); } #[test] fn test_build_basic_status() { let t = Tweet { coordinates: None, created_at: Utc::now(), current_user_retweet: None, display_text_range: None, entities: TweetEntities { hashtags: vec![], symbols: vec![], urls: vec![ UrlEntity { display_url: "youtube.com/watch?v=w5TrSa…".to_string(), expanded_url: Some("https://www.youtube.com/watch?v=w5TrSaoYmZ8".to_string()), range: (93, 116), url: "https://t.co/zXw0FfX2Nt".to_string(), } ], user_mentions: vec![ MentionEntity { id: 491500016, range: (80, 95), name: "Nintendo France".to_string(), screen_name: "NintendoFrance".to_string(), } ], media: None, }, extended_entities: None, favorite_count: 0, favorited: None, filter_level: None, id: 1491541246984306693, in_reply_to_user_id: None, in_reply_to_screen_name: None, in_reply_to_status_id: None, lang: None, place: None, possibly_sensitive: None, quoted_status: None, quoted_status_id: None, retweet_count: 0, retweeted: None, retweeted_status: None, source: None, text: "Mother 1 & 2 sur le NES/SNES online !\nDispo maintenant. cc @NintendoFrance https://t.co/zXw0FfX2Nt".to_string(), truncated: false, user: None, withheld_copyright: false, withheld_in_countries: None, withheld_scope: None, }; let t_out = build_basic_status(&t); assert_eq!(&t_out, "Mother 1 & 2 sur le NES/SNES online !\nDispo maintenant. cc @NintendoFrance@twitter.com https://www.youtube.com/watch?v=w5TrSaoYmZ8"); } }