diff --git a/Cargo.toml b/Cargo.toml index 3b90aab..f8aac6b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ clap = "^4" egg-mode = "^0.16" rusqlite = "^0.27" isolang = "^2" -tokio = { version = "^1", features = ["full"]} +tokio = { version = "^1", features = ["rt"]} futures = "^0.3" megalodon = "^0.2" html-escape = "^0.2" @@ -25,3 +25,4 @@ mime = "^0.3" [profile.release] strip = true +lto = true diff --git a/src/error.rs b/src/error.rs index b95cdc9..084dd41 100644 --- a/src/error.rs +++ b/src/error.rs @@ -5,7 +5,7 @@ use std::{ fmt::{Display, Formatter, Result}, }; -use elefren::Error as elefrenError; +use megalodon::error::Error as megalodonError; #[derive(Debug)] pub struct ScootalooError { @@ -34,8 +34,8 @@ impl From> for ScootalooError { } } -impl From for ScootalooError { - fn from(error: elefrenError) -> Self { - ScootalooError::new(&format!("Error in elefren crate: {}", error)) +impl From for ScootalooError { + fn from(error: megalodonError) -> Self { + ScootalooError::new(&format!("Error in megalodon crate: {}", error)) } } diff --git a/src/lib.rs b/src/lib.rs index 37badf8..75f6abe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -19,10 +19,11 @@ mod state; pub use state::{init_db, migrate_db}; use state::{read_state, write_state, TweetToToot}; -use elefren::{prelude::*, status_builder::StatusBuilder, Language}; use futures::StreamExt; use html_escape::decode_html_entities; +use isolang::Language; use log::info; +use megalodon::{megalodon::PostStatusInputOptions, Megalodon}; use regex::Regex; use rusqlite::Connection; use std::sync::Arc; @@ -170,9 +171,21 @@ pub async fn run(config: Config) { info!("Building corresponding Mastodon status"); - let mut status_builder = StatusBuilder::new(); + let mut post_status = PostStatusInputOptions { + media_ids: None, + poll: None, + in_reply_to_id: None, + sensitive: None, + spoiler_text: None, + visibility: None, + scheduled_at: None, + language: None, + quote_id: None, + }; - status_builder.status(status_text).media_ids(status_medias); + if !status_medias.is_empty() { + post_status.media_ids = Some(status_medias); + } // thread if necessary if tweet.in_reply_to_user_id.is_some() { @@ -182,7 +195,7 @@ pub async fn run(config: Config) { &mastodon_config.twitter_screen_name, tweet.in_reply_to_status_id, ) { - status_builder.in_reply_to(&r.toot_id); + post_status.in_reply_to_id = Some(r.toot_id.to_owned()); } drop(lconn); } @@ -190,16 +203,17 @@ pub async fn run(config: Config) { // language if any if let Some(l) = &tweet.lang { if let Some(r) = Language::from_639_1(l) { - status_builder.language(r); + post_status.language = Some(r.to_string()); } } // can be activated for test purposes - // status_builder.visibility(elefren::status_builder::Visibility::Private); + // post_status.visibility = Some(megalodon::entities::StatusVisibility::Direct); - let status = status_builder.build()?; - - let published_status = mastodon.new_status(status)?; + let published_status = mastodon + .post_status(status_text, Some(&post_status)) + .await? + .json(); // this will return if it cannot publish the status preventing the last_tweet from // being written into db diff --git a/src/mastodon.rs b/src/mastodon.rs index 5d59726..684e266 100644 --- a/src/mastodon.rs +++ b/src/mastodon.rs @@ -1,9 +1,9 @@ use crate::config::MastodonConfig; use egg_mode::entities::{MentionEntity, UrlEntity}; -use elefren::{apps::App, prelude::*, scopes::Read, scopes::Scopes, scopes::Write}; +use megalodon::{generator, mastodon::Mastodon, megalodon::AppInputOptions}; use regex::Regex; -use std::{borrow::Cow, collections::HashMap, io::stdin}; +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 @@ -120,43 +120,36 @@ pub fn replace_tweet_by_toot( /// 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) + 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 -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(Write::Accounts) - .and(Scopes::write(Write::Media)) - .and(Scopes::write(Write::Statuses)) - .and(Scopes::read(Read::Accounts)), - ) - .website(Cow::from( - "https://framagit.org/veretcle/scootaloo".to_string(), - )); +#[tokio::main] +pub async fn register(host: &str, screen_name: &str) { + let mastodon = generator(megalodon::SNS::Mastodon, host.to_string(), None, None); - let app = builder.build().expect("Cannot build the app"); + 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 registration = Registration::new(host) - .register(app) - .expect("Cannot build registration object"); - let url = registration - .authorize_url() - .expect("Cannot generate registration URI!"); + 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: "); @@ -166,27 +159,47 @@ pub fn register(host: &str, screen_name: &str) { .read_line(&mut input) .expect("Unable to read back registration code!"); - let code = input.trim(); - let mastodon = registration - .complete(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 toml = toml::to_string(&*mastodon).unwrap(); + let mastodon = generator( + megalodon::SNS::Mastodon, + host.to_string(), + Some(token_data.access_token.to_owned()), + None, + ); let current_account = mastodon - .verify_credentials() - .expect("Unable to access account information!"); + .verify_account_credentials() + .await + .expect("Unable to access account information!") + .json(); println!( - "Please insert the following block at the end of your configuration file: + r#"Please insert the following block at the end of your configuration file: [mastodon.{}] -twitter_screen_name = \"{}\" -mastodon_screen_name = \"{}\" -{}", +twitter_screen_name = "{}" +mastodon_screen_name = "{}" +base = "{}" +client_id = "{}" +client_secret = "{}" +redirect = "{}" +token = "{}""#, screen_name.to_lowercase(), screen_name, current_account.username, - toml + host, + app_data.client_id, + app_data.client_secret, + app_data.redirect_uri, + token_data.access_token, ); } diff --git a/src/util.rs b/src/util.rs index cc873bd..b005279 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,22 +1,16 @@ use crate::{twitter::get_tweet_media, ScootalooError}; -use std::{borrow::Cow, error::Error}; - use egg_mode::tweet::Tweet; - -use elefren::prelude::*; - +use futures::{stream, stream::StreamExt}; use log::{error, info, warn}; - +use megalodon::{mastodon::Mastodon, megalodon::Megalodon}; use reqwest::Url; - +use std::error::Error; use tokio::{ fs::{create_dir_all, remove_file, File}, io::copy, }; -use futures::{stream, stream::StreamExt}; - /// Generate associative table between media ids and tweet extended entities pub async fn generate_media_ids( tweet: &Tweet, @@ -46,8 +40,10 @@ pub async fn generate_media_ids( let local_tweet_media_path = get_tweet_media(&media, &cache_path).await?; // upload media to Mastodon - let mastodon_media = - mastodon.media(Cow::from(local_tweet_media_path.to_owned()))?; + let mastodon_media = mastodon + .upload_media(local_tweet_media_path.to_owned(), None) + .await? + .json(); // at this point, we can safely erase the original file // it doesn’t matter if we can’t remove, cache_media fn is idempotent remove_file(&local_tweet_media_path).await.ok();