Files
scootaloo/src/mastodon.rs
2022-11-08 08:54:36 +01:00

229 lines
7.1 KiB
Rust

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<String, String> {
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<String, String> {
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 &amp; 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");
}
}