diff --git a/bot/src/cli.rs b/bot/src/cli.rs index d092106..21f0055 100644 --- a/bot/src/cli.rs +++ b/bot/src/cli.rs @@ -1,6 +1,6 @@ use clap::{Args, Parser, Subcommand}; -/// Bluesky Command Arguments +/// Bluesky command arguments #[derive(Args, Debug)] pub struct BlueskyCommand { /// The Bluesky bot user's handle. @@ -12,6 +12,14 @@ pub struct BlueskyCommand { pub bluesky_password: String, } +/// Mastodon command arguments +#[derive(Args, Debug)] +pub struct MastodonCommand { + /// The Bluesky bot user's handle. + #[arg(short = 'a', long)] + pub access_token: String, +} + #[derive(Parser, Debug)] #[command(version, about = "Social media posting bot.", long_about = None)] pub struct CliArgs { @@ -42,5 +50,5 @@ pub enum Command { /// Post on bluesky platform. Bluesky(BlueskyCommand), /// Post on Mastodon, the FediVerse - Mastodon, + Mastodon(MastodonCommand), } diff --git a/bot/src/main.rs b/bot/src/main.rs index 9c19389..68ea78b 100644 --- a/bot/src/main.rs +++ b/bot/src/main.rs @@ -1,5 +1,8 @@ use crate::bluesky::BlueSkyClient; use crate::cli::{CliArgs, Command}; +use crate::mastodon::api::{PartialMediaResponse, PartialPostStatusResponse, PostStatusRequest}; +use crate::mastodon::MastodonClient; +use anyhow::{anyhow, Error}; use clap::Parser; use infrastructure::RedisService; use log::{error, info, warn}; @@ -115,8 +118,54 @@ async fn main() -> Result<(), anyhow::Error> { } } } - Command::Mastodon => { - unimplemented!("This command is not currently implemented.") + Command::Mastodon(mastodon) => { + let mut mastodon_client = MastodonClient::new(mastodon.access_token); + // Read from stream + while running.load(Ordering::SeqCst) { + match redis_service + .read_stream::( + &args.redis_stream_name, + &args.redis_consumer_group, + &args.redis_consumer_name, + 5000, + ) + .await + { + Ok(post) => { + // Step1: Upload image to Mastodon + let media_response = if post.image.is_some() { + Ok(mastodon_client + .upload_media_by_url(post.image.clone().unwrap().as_str()) + .await?) + } else { + Err(anyhow!("No image exists on post.")) + }; + + // Step2: Post to Mastodon. + let mut status: PostStatusRequest = post.into(); + match media_response { + Ok(response) => { + status.media_ids.push(response.id); + } + Err(err) => { + error!("Error uploading image: {err}") + } + } + let response = mastodon_client.post_status(status).await; + match response { + Ok(response) => { + info!("Posted tooth on Mastodon! {response:?}") + } + Err(err) => { + error!("Failed to post toot on Mastodon: {err}") + } + } + } + Err(err) => { + error!("error reading stream: {err}") + } + } + } } } diff --git a/bot/src/mastodon.rs b/bot/src/mastodon.rs index 90a9c7f..00891e5 100644 --- a/bot/src/mastodon.rs +++ b/bot/src/mastodon.rs @@ -1,6 +1,6 @@ use crate::mastodon::api::{PartialMediaResponse, PartialPostStatusResponse, PostStatusRequest}; -mod api; +pub mod api; /// The Mastodon client for interacting with the platform. pub struct MastodonClient { @@ -10,7 +10,7 @@ pub struct MastodonClient { impl MastodonClient { /// Creates a new mastodon client from the given access token. - fn new(access_token: String) -> Self { + pub fn new(access_token: String) -> Self { let client = reqwest::Client::new(); MastodonClient { access_token, @@ -18,14 +18,17 @@ impl MastodonClient { } } - async fn post_status(&mut self, data: T) -> Result + pub async fn post_status( + &mut self, + data: T, + ) -> Result where T: Into, { unimplemented!() } - async fn upload_media_by_url( + pub async fn upload_media_by_url( &mut self, image_url: &str, ) -> Result { diff --git a/bot/src/mastodon/api.rs b/bot/src/mastodon/api.rs index 490e90d..b3060ea 100644 --- a/bot/src/mastodon/api.rs +++ b/bot/src/mastodon/api.rs @@ -1,3 +1,4 @@ +use post::NewsPost; use serde::{Deserialize, Serialize}; /// Is a truncated response from Mastodon's /api/v2/media endpoint. @@ -17,7 +18,13 @@ pub struct PostStatusRequest { pub status: String, pub language: String, pub visibility: String, - pub media_ids: Vec, + pub media_ids: Vec, +} + +impl From for PostStatusRequest { + fn from(value: NewsPost) -> Self { + todo!() + } } /// Is a partial response from /api/v1/statuses route.