mirror of
https://framagit.org/veretcle/scootaloo.git
synced 2025-07-20 17:11:19 +02:00
493 lines
16 KiB
Rust
493 lines
16 KiB
Rust
use crate::config::MastodonConfig;
|
|
|
|
use egg_mode::entities::{MentionEntity, UrlEntity};
|
|
use megalodon::{generator, mastodon::Mastodon, megalodon::AppInputOptions};
|
|
use regex::Regex;
|
|
use std::{collections::HashMap, io::stdin};
|
|
|
|
/// Decodes the Twitter mention to something that will make sense once Twitter has joined the
|
|
/// Fediverse. Users in the global user list of Scootaloo are rewritten, as they are Mastodon users
|
|
/// as well
|
|
pub fn twitter_mentions(
|
|
toot: &mut String,
|
|
ums: &[MentionEntity],
|
|
masto: &HashMap<String, MastodonConfig>,
|
|
) {
|
|
let tm: HashMap<String, String> = ums
|
|
.iter()
|
|
.map(|s| {
|
|
(
|
|
format!("@{}", s.screen_name),
|
|
format!("@{}@twitter.com", s.screen_name),
|
|
)
|
|
})
|
|
.chain(
|
|
masto
|
|
.values()
|
|
.filter(|s| s.mastodon_screen_name.is_some())
|
|
.map(|s| {
|
|
(
|
|
format!("@{}", s.twitter_screen_name),
|
|
format!(
|
|
"@{}@{}",
|
|
s.mastodon_screen_name.as_ref().unwrap(),
|
|
s.base.split('/').last().unwrap()
|
|
),
|
|
)
|
|
})
|
|
.collect::<HashMap<String, String>>(),
|
|
)
|
|
.collect();
|
|
|
|
for (k, v) in tm {
|
|
*toot = toot.replace(&k, &v);
|
|
}
|
|
}
|
|
|
|
/// Decodes urls in toot
|
|
pub fn decode_urls(toot: &mut String, urls: &HashMap<String, String>) {
|
|
for (k, v) in urls {
|
|
*toot = toot.replace(k, v);
|
|
}
|
|
}
|
|
|
|
/// Reassociates source url with destination url for rewritting
|
|
/// this takes a Teet UrlEntity and an optional Regex
|
|
pub fn associate_urls(urls: &[UrlEntity], re: &Option<Regex>) -> HashMap<String, String> {
|
|
urls.iter()
|
|
.filter(|s| s.expanded_url.is_some())
|
|
.map(|s| {
|
|
(s.url.to_owned(), {
|
|
let mut def = s.expanded_url.as_deref().unwrap().to_owned();
|
|
|
|
if let Some(r) = re {
|
|
if r.is_match(s.expanded_url.as_deref().unwrap()) {
|
|
def = s.display_url.to_owned();
|
|
}
|
|
}
|
|
|
|
def
|
|
})
|
|
})
|
|
.collect::<HashMap<String, String>>()
|
|
}
|
|
|
|
/// Replaces the commonly used services by mirrors, if asked to
|
|
pub fn replace_alt_services(urls: &mut HashMap<String, String>, alts: &HashMap<String, String>) {
|
|
for val in urls.values_mut() {
|
|
for (k, v) in alts {
|
|
*val = val.replace(&format!("/{}/", k), &format!("/{}/", v));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Finds a Mastodon screen_name/base_url from a MastodonConfig
|
|
pub fn find_mastodon_screen_name_by_twitter_screen_name(
|
|
twitter_screen_name: &str,
|
|
masto: &HashMap<String, MastodonConfig>,
|
|
) -> Option<(String, String)> {
|
|
masto.iter().find_map(|(_, v)| {
|
|
if twitter_screen_name == v.twitter_screen_name && v.mastodon_screen_name.is_some() {
|
|
Some((
|
|
v.mastodon_screen_name.as_ref().unwrap().to_owned(),
|
|
v.base.to_owned(),
|
|
))
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
}
|
|
|
|
/// Replaces the original quoted tweet by the corresponding toot
|
|
pub fn replace_tweet_by_toot(
|
|
urls: &mut HashMap<String, String>,
|
|
twitter_screen_name: &str,
|
|
tweet_id: u64,
|
|
mastodon_screen_name: &str,
|
|
base_url: &str,
|
|
toot_id: &str,
|
|
) {
|
|
for val in urls.values_mut() {
|
|
if val.to_lowercase().starts_with(&format!(
|
|
"https://twitter.com/{}/status/{}",
|
|
twitter_screen_name.to_lowercase(),
|
|
tweet_id
|
|
)) {
|
|
*val = format!("{}/@{}/{}", base_url, mastodon_screen_name, toot_id);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Gets Mastodon Data
|
|
pub fn get_mastodon_token(masto: &MastodonConfig) -> Mastodon {
|
|
Mastodon::new(masto.base.to_string(), Some(masto.token.to_string()), None)
|
|
}
|
|
|
|
/// 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
|
|
#[tokio::main]
|
|
pub async fn register(host: &str, screen_name: &str) {
|
|
let mastodon = generator(megalodon::SNS::Mastodon, host.to_string(), None, None);
|
|
|
|
let options = AppInputOptions {
|
|
redirect_uris: None,
|
|
scopes: Some(
|
|
[
|
|
"read:accounts".to_string(),
|
|
"write:accounts".to_string(),
|
|
"write:media".to_string(),
|
|
"write:statuses".to_string(),
|
|
]
|
|
.to_vec(),
|
|
),
|
|
website: Some("https://framagit.org/veretcle/scootaloo".to_string()),
|
|
};
|
|
|
|
let app_data = mastodon
|
|
.register_app(env!("CARGO_PKG_NAME").to_string(), &options)
|
|
.await
|
|
.expect("Cannot build registration object!");
|
|
|
|
let url = app_data.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 token_data = mastodon
|
|
.fetch_access_token(
|
|
app_data.client_id.to_owned(),
|
|
app_data.client_secret.to_owned(),
|
|
input.trim().to_string(),
|
|
megalodon::default::NO_REDIRECT.to_string(),
|
|
)
|
|
.await
|
|
.expect("Unable to create access token!");
|
|
|
|
let mastodon = generator(
|
|
megalodon::SNS::Mastodon,
|
|
host.to_string(),
|
|
Some(token_data.access_token.to_owned()),
|
|
None,
|
|
);
|
|
|
|
let current_account = mastodon
|
|
.verify_account_credentials()
|
|
.await
|
|
.expect("Unable to access account information!")
|
|
.json();
|
|
|
|
println!(
|
|
r#"Please insert the following block at the end of your configuration file:
|
|
[mastodon.{}]
|
|
twitter_screen_name = "{}"
|
|
mastodon_screen_name = "{}"
|
|
base = "{}"
|
|
client_id = "{}"
|
|
client_secret = "{}"
|
|
redirect = "{}"
|
|
token = "{}""#,
|
|
screen_name.to_lowercase(),
|
|
screen_name,
|
|
current_account.username,
|
|
host,
|
|
app_data.client_id,
|
|
app_data.client_secret,
|
|
app_data.redirect_uri,
|
|
token_data.access_token,
|
|
);
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_replace_tweet_by_toot() {
|
|
let mut associated_urls = HashMap::from([
|
|
(
|
|
"https://t.co/perdudeouf".to_string(),
|
|
"https://www.perdu.com".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/realquoteshere".to_string(),
|
|
"https://twitter.com/nintendojofr/status/1590047921633755136".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/almostthere".to_string(),
|
|
"https://twitter.com/NintendojoFR/status/nope".to_string(),
|
|
),
|
|
(
|
|
"http://t.co/yetanotherone".to_string(),
|
|
"https://twitter.com/NINTENDOJOFR/status/1590047921633755136".to_string(),
|
|
),
|
|
]);
|
|
|
|
let expected_urls = HashMap::from([
|
|
(
|
|
"https://t.co/perdudeouf".to_string(),
|
|
"https://www.perdu.com".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/realquoteshere".to_string(),
|
|
"https://m.nintendojo.fr/@nintendojofr/109309605486908797".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/almostthere".to_string(),
|
|
"https://twitter.com/NintendojoFR/status/nope".to_string(),
|
|
),
|
|
(
|
|
"http://t.co/yetanotherone".to_string(),
|
|
"https://m.nintendojo.fr/@nintendojofr/109309605486908797".to_string(),
|
|
),
|
|
]);
|
|
|
|
replace_tweet_by_toot(
|
|
&mut associated_urls,
|
|
"NintendojoFR",
|
|
1590047921633755136,
|
|
"nintendojofr",
|
|
"https://m.nintendojo.fr",
|
|
"109309605486908797",
|
|
);
|
|
|
|
assert_eq!(associated_urls, expected_urls);
|
|
}
|
|
|
|
#[test]
|
|
fn test_find_mastodon_screen_name_by_twitter_screen_name() {
|
|
let masto_config = HashMap::from([
|
|
(
|
|
"test".to_string(),
|
|
MastodonConfig {
|
|
twitter_screen_name: "tonpere".to_string(),
|
|
mastodon_screen_name: Some("lalali".to_string()),
|
|
twitter_page_size: None,
|
|
base: "https://mstdn.net".to_string(),
|
|
client_id: "".to_string(),
|
|
client_secret: "".to_string(),
|
|
redirect: "".to_string(),
|
|
token: "".to_string(),
|
|
},
|
|
),
|
|
(
|
|
"test2".to_string(),
|
|
MastodonConfig {
|
|
twitter_screen_name: "tamerelol".to_string(),
|
|
mastodon_screen_name: None,
|
|
twitter_page_size: None,
|
|
base: "https://mastoot.fr".to_string(),
|
|
client_id: "".to_string(),
|
|
client_secret: "".to_string(),
|
|
redirect: "".to_string(),
|
|
token: "".to_string(),
|
|
},
|
|
),
|
|
(
|
|
"test3".to_string(),
|
|
MastodonConfig {
|
|
twitter_screen_name: "NintendojoFR".to_string(),
|
|
mastodon_screen_name: Some("nintendojofr".to_string()),
|
|
twitter_page_size: None,
|
|
base: "https://m.nintendojo.fr".to_string(),
|
|
client_id: "".to_string(),
|
|
client_secret: "".to_string(),
|
|
redirect: "".to_string(),
|
|
token: "".to_string(),
|
|
},
|
|
),
|
|
]);
|
|
|
|
// case sensitiveness, to avoid any mistake
|
|
assert_eq!(
|
|
None,
|
|
find_mastodon_screen_name_by_twitter_screen_name("nintendojofr", &masto_config)
|
|
);
|
|
assert_eq!(
|
|
Some((
|
|
"nintendojofr".to_string(),
|
|
"https://m.nintendojo.fr".to_string()
|
|
)),
|
|
find_mastodon_screen_name_by_twitter_screen_name("NintendojoFR", &masto_config)
|
|
);
|
|
// should return None if twitter_screen_name is undefined
|
|
assert_eq!(
|
|
None,
|
|
find_mastodon_screen_name_by_twitter_screen_name("tamerelol", &masto_config)
|
|
);
|
|
assert_eq!(
|
|
Some(("lalali".to_string(), "https://mstdn.net".to_string())),
|
|
find_mastodon_screen_name_by_twitter_screen_name("tonpere", &masto_config)
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_twitter_mentions() {
|
|
let mention_entities = vec![
|
|
MentionEntity {
|
|
id: 12345,
|
|
range: (1, 3),
|
|
name: "Ta Mere l0l".to_string(),
|
|
screen_name: "tamerelol".to_string(),
|
|
},
|
|
MentionEntity {
|
|
id: 6789,
|
|
range: (1, 3),
|
|
name: "TONPERE".to_string(),
|
|
screen_name: "tonpere".to_string(),
|
|
},
|
|
];
|
|
|
|
let mut toot = ":kikoo: @tamerelol @tonpere !".to_string();
|
|
|
|
let masto_config = HashMap::from([(
|
|
"test".to_string(),
|
|
(MastodonConfig {
|
|
twitter_screen_name: "tonpere".to_string(),
|
|
mastodon_screen_name: Some("lalali".to_string()),
|
|
twitter_page_size: None,
|
|
base: "https://mstdn.net".to_string(),
|
|
client_id: "".to_string(),
|
|
client_secret: "".to_string(),
|
|
redirect: "".to_string(),
|
|
token: "".to_string(),
|
|
}),
|
|
)]);
|
|
|
|
twitter_mentions(&mut toot, &mention_entities, &masto_config);
|
|
|
|
assert_eq!(&toot, ":kikoo: @tamerelol@twitter.com @lalali@mstdn.net !");
|
|
}
|
|
|
|
#[test]
|
|
fn test_decode_urls() {
|
|
let urls = HashMap::from([
|
|
(
|
|
"https://t.co/thisisatest".to_string(),
|
|
"https://www.nintendojo.fr/dojobar".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/nopenotinclusive".to_string(),
|
|
"invité.es".to_string(),
|
|
),
|
|
]);
|
|
|
|
let mut toot =
|
|
"Rendez-vous sur https://t.co/thisisatest avec nos https://t.co/nopenotinclusive !"
|
|
.to_string();
|
|
|
|
decode_urls(&mut toot, &urls);
|
|
|
|
assert_eq!(
|
|
&toot,
|
|
"Rendez-vous sur https://www.nintendojo.fr/dojobar avec nos invité.es !"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_associate_urls() {
|
|
let urls = vec![
|
|
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(),
|
|
},
|
|
UrlEntity {
|
|
display_url: "sadcat".to_string(),
|
|
expanded_url: None,
|
|
range: (1, 3),
|
|
url: "https://t.me/sadcat".to_string(),
|
|
},
|
|
UrlEntity {
|
|
display_url: "invité.es".to_string(),
|
|
expanded_url: Some("http://xn--invit-fsa.es".to_string()),
|
|
range: (85, 108),
|
|
url: "https://t.co/WAUgnpHLmo".to_string(),
|
|
},
|
|
];
|
|
|
|
let expected_urls = HashMap::from([
|
|
(
|
|
"https://t.me/tamerelol".to_string(),
|
|
"https://www.nintendojo.fr/dojobar".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/WAUgnpHLmo".to_string(),
|
|
"invité.es".to_string(),
|
|
),
|
|
]);
|
|
|
|
let re = Regex::new("(.+)\\.es$").ok();
|
|
|
|
let associated_urls = associate_urls(&urls, &re);
|
|
|
|
assert_eq!(associated_urls, expected_urls);
|
|
}
|
|
|
|
#[test]
|
|
fn test_replace_alt_services() {
|
|
let mut associated_urls = HashMap::from([
|
|
(
|
|
"https://t.co/youplaboom".to_string(),
|
|
"https://www.youtube.com/watch?v=dQw4w9WgXcQ".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/thisisfine".to_string(),
|
|
"https://twitter.com/Nintendo/status/1594590628771688448".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/nopenope".to_string(),
|
|
"https://www.nintendojo.fr/dojobar".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/broken".to_string(),
|
|
"http://youtu.be".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/alsobroken".to_string(),
|
|
"https://youtube.com".to_string(),
|
|
),
|
|
]);
|
|
|
|
let alt_services = HashMap::from([
|
|
("twitter.com".to_string(), "nitter.net".to_string()),
|
|
("youtu.be".to_string(), "invidio.us".to_string()),
|
|
("www.youtube.com".to_string(), "invidio.us".to_string()),
|
|
("youtube.com".to_string(), "invidio.us".to_string()),
|
|
]);
|
|
|
|
let expected_urls = HashMap::from([
|
|
(
|
|
"https://t.co/youplaboom".to_string(),
|
|
"https://invidio.us/watch?v=dQw4w9WgXcQ".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/thisisfine".to_string(),
|
|
"https://nitter.net/Nintendo/status/1594590628771688448".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/nopenope".to_string(),
|
|
"https://www.nintendojo.fr/dojobar".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/broken".to_string(),
|
|
"http://youtu.be".to_string(),
|
|
),
|
|
(
|
|
"https://t.co/alsobroken".to_string(),
|
|
"https://youtube.com".to_string(),
|
|
),
|
|
]);
|
|
|
|
replace_alt_services(&mut associated_urls, &alt_services);
|
|
|
|
assert_eq!(associated_urls, expected_urls);
|
|
}
|
|
}
|