mod error; pub use error::OolatoocsError; mod config; pub use config::{parse_toml, Config}; mod state; pub use state::init_db; #[allow(unused_imports)] use state::{read_state, write_state, TweetToToot}; mod mastodon; use mastodon::get_mastodon_timeline_since; pub use mastodon::register; mod utils; use utils::strip_everything; mod twitter; #[allow(unused_imports)] use twitter::{post_tweet, upload_chunk_media, upload_simple_media}; use futures::{stream, StreamExt}; use log::{error, warn}; use megalodon::entities::attachment::AttachmentType; use rusqlite::Connection; use std::error::Error; #[tokio::main] pub async fn run(config: &Config) { let conn = Connection::open(&config.oolatoocs.db_path) .unwrap_or_else(|e| panic!("Cannot open DB: {}", e)); let last_toot_id = read_state(&conn, None) .unwrap_or_else(|e| panic!("Cannot get last toot id: {}", e)) .map(|r| r.toot_id); let timeline = get_mastodon_timeline_since(&config.mastodon, last_toot_id) .await .unwrap_or_else(|e| panic!("Cannot get instance: {}", e)); for toot in timeline { let Ok(tweet_content) = strip_everything(&toot.content, &toot.tags) else { continue; // skip in case we can’t strip something }; let mut medias: Vec = vec![]; // if we wanted to cut toot in half, now would be the right time to do so let media_attachments = toot.media_attachments.clone(); let mut stream = stream::iter(media_attachments) .map(|media| { let twitter_config = config.twitter.clone(); tokio::task::spawn(async move { match media.r#type { AttachmentType::Image => { upload_simple_media(&twitter_config, &media.url, &media.description) .await } AttachmentType::Gifv => { upload_chunk_media(&twitter_config, &media.url, "tweet_gif").await } AttachmentType::Video => { upload_chunk_media(&twitter_config, &media.url, "tweet_video").await } _ => Err::>( OolatoocsError::new(&format!( "Cannot treat this type of media: {}", &media.url )) .into(), ), } }) }) .buffered(4); while let Some(result) = stream.next().await { match result { Ok(Ok(v)) => medias.push(v), Ok(Err(e)) => warn!("Cannot treat media: {}", e), Err(e) => error!("Something went wrong when joining the main thread: {}", e), } } // threads if necessary let reply_to = toot.in_reply_to_id.and_then(|t| { read_state(&conn, Some(t.parse::().unwrap())) .ok() .flatten() .map(|s| s.tweet_id) }); // posts corresponding tweet let tweet_id = post_tweet(&config.twitter, &tweet_content, &medias, &reply_to) .await .unwrap_or_else(|e| panic!("Cannot Tweet {}: {}", toot.id, e)); // writes the current state of the tweet write_state( &conn, TweetToToot { tweet_id, toot_id: toot.id.parse::().unwrap(), }, ) .unwrap_or_else(|e| panic!("Cannot store Toot/Tweet ({}/{}): {}", &toot.id, tweet_id, e)); } }