use std::collections::HashSet; use std::process::Command; use mastodon_async::helpers::toml; use mastodon_async::scopes; use mastodon_async::{prelude::*}; use mastodon_async::{entities::visibility::Visibility}; use mastodon_async::{helpers::cli, Result}; use reqwest; use std::io::Cursor; use std::io::Write; #[tokio::main] // requires `features = ["mt"] async fn main() -> Result<()> { let mastodon = if let Ok(data) = toml::from_file("mastodon-data.toml") { Mastodon::from(data) } else { register().await? }; /* let data = Data::default(); let client = Mastodon::from(data); let statuses = client.statuses(&AccountId::new("user-id"), Default::default()).await.unwrap(); dbg!(statuses); */ match get_next_url() { Some(url) => { post_image(&mastodon, &url).await; update_bio(&mastodon).await; }, None => post(&mastodon, "@Sugui@awoo.fai.st @MeDueleLaTeta@awoo.fai.st me quedé sin chicas").await }; Ok(()) } fn get_next_url() -> Option { let binding = std::fs::read("./posted.csv").expect("File not found").iter().map(|c| *c as char).collect::(); let posted = binding.lines().collect::>(); let binding = std::fs::read("./urls.csv").expect("File not found").iter().map(|c| *c as char).collect::(); let urls = binding.lines().collect::>(); let urls = urls.difference(&posted).collect::>(); if urls.is_empty() { None } else { let mut file = std::fs::OpenOptions::new() .write(true) .append(true) // This is needed to append to file .open("./posted.csv") .unwrap(); write!(file, "{}\n", urls[0]).unwrap(); Some(urls[0].to_string().clone()) } } async fn post_image(account: &Mastodon, url: &String) { fetch_url(url.to_string(), ".tmp".to_string()).await.unwrap(); let attachment = account.media("./.tmp", Some(url.to_string())).await.expect("upload"); let attachment = account.wait_for_processing(attachment, Default::default()).await.expect("processing"); let status = StatusBuilder::new() .media_ids(&[attachment.id]) .visibility(Visibility::Unlisted) .sensitive(true) .build() .unwrap(); account.new_status(status).await.unwrap(); } async fn update_bio(account: &Mastodon) { let binding = std::fs::read("./posted.csv").expect("File not found").iter().map(|c| *c as char).collect::(); let posted = binding.lines().collect::>(); let binding = std::fs::read("./urls.csv").expect("File not found").iter().map(|c| *c as char).collect::(); let urls = binding.lines().collect::>(); let remaining = urls.difference(&posted).count(); Command::new("curl") .args([ "-X", "PATCH", "https://awoo.fai.st/api/v1/accounts/update_credentials", "-H", &format!("Authorization:Bearer {}", account.data.token), "-d", &format!("note=Bot who posts images of sleeping girls every 6 hours.\n\n{} new images remaining", remaining), ]).spawn().unwrap().wait().unwrap(); } async fn post(account: &Mastodon, msg: &str) { let status = StatusBuilder::new() .visibility(Visibility::Direct) .status(msg) .build() .unwrap(); account.new_status(status).await.unwrap(); } async fn fetch_url(url: String, file_name: String) -> Result<()> { let response = reqwest::get(url).await?; let mut file = std::fs::File::create(file_name)?; let mut content = Cursor::new(response.bytes().await?); std::io::copy(&mut content, &mut file)?; Ok(()) } async fn register() -> Result { let registration = Registration::new("https://awoo.fai.st") .client_name("sleeping-girls-bot") .scopes(Scopes::write_all()) .build() .await?; let mastodon = cli::authenticate(registration).await?; // Save app data for using on the next run. toml::to_file(&mastodon.data, "mastodon-data.toml")?; Ok(mastodon) }