mirror of
https://framagit.org/veretcle/scootaloo.git
synced 2025-07-21 09:31:19 +02:00
229 lines
7.1 KiB
Rust
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 & 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");
|
|
}
|
|
}
|