feat: threads

This commit is contained in:
VC
2023-11-11 10:25:41 +01:00
parent eba13ba095
commit b9179d8cce
2 changed files with 45 additions and 23 deletions

View File

@@ -44,6 +44,7 @@ pub async fn run(config: &Config) {
// if we wanted to cut toot in half, now would be the right time to do so // if we wanted to cut toot in half, now would be the right time to do so
// treats media
for media in toot.media_attachments { for media in toot.media_attachments {
let id = match media.r#type { let id = match media.r#type {
AttachmentType::Image => { AttachmentType::Image => {
@@ -77,12 +78,20 @@ pub async fn run(config: &Config) {
medias.push(id); medias.push(id);
} }
println!("{:?}", medias); // threads if necessary
let reply_to = toot.in_reply_to_id.and_then(|t| {
read_state(&conn, Some(t.parse::<u64>().unwrap()))
.ok()
.flatten()
.map(|s| s.tweet_id)
});
let tweet_id = post_tweet(&config.twitter, &tweet_content, &medias) // posts corresponding tweet
let tweet_id = post_tweet(&config.twitter, &tweet_content, &medias, &reply_to)
.await .await
.unwrap_or_else(|e| panic!("Cannot Tweet {}: {}", toot.id, e)); .unwrap_or_else(|e| panic!("Cannot Tweet {}: {}", toot.id, e));
// writes the current state of the tweet
write_state( write_state(
&conn, &conn,
TweetToToot { TweetToToot {

View File

@@ -7,7 +7,7 @@ use reqwest::{
Body, Client, Body, Client,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::error::Error; use std::{error::Error, ops::Not};
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
const TWITTER_API_TWEET_URL: &str = "https://api.twitter.com/2/tweets"; const TWITTER_API_TWEET_URL: &str = "https://api.twitter.com/2/tweets";
@@ -20,24 +20,32 @@ const TWITTER_METADATA_MEDIA_URL: &str =
struct EmptyRequest {} struct EmptyRequest {}
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub struct Tweet { struct Tweet {
pub text: String, text: String,
pub media: TweetMediasIds, #[serde(skip_serializing_if = "Option::is_none")]
media: Option<TweetMediasIds>,
#[serde(skip_serializing_if = "Option::is_none")]
reply: Option<TweetReply>,
} }
#[derive(Serialize, Debug)] #[derive(Serialize, Debug)]
pub struct TweetMediasIds { struct TweetMediasIds {
pub media_ids: Vec<String>, media_ids: Vec<String>,
}
#[derive(Serialize, Debug)]
struct TweetReply {
in_reply_to_tweet_id: String,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct TweetResponse { struct TweetResponse {
pub data: TweetResponseData, data: TweetResponseData,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
pub struct TweetResponseData { struct TweetResponseData {
pub id: String, id: String,
} }
#[derive(Deserialize, Debug)] #[derive(Deserialize, Debug)]
@@ -112,7 +120,7 @@ pub async fn upload_simple_media(
// upload the media // upload the media
let client = Client::new(); let client = Client::new();
let res: UploadMediaResponse = client let res = client
.post(TWITTER_UPLOAD_MEDIA_URL) .post(TWITTER_UPLOAD_MEDIA_URL)
.header( .header(
"Authorization", "Authorization",
@@ -129,7 +137,7 @@ pub async fn upload_simple_media(
)) ))
.send() .send()
.await? .await?
.json() .json::<UploadMediaResponse>()
.await?; .await?;
debug!("Media ID: {}", res.media_id); debug!("Media ID: {}", res.media_id);
@@ -201,7 +209,7 @@ pub async fn upload_chunk_media(
debug!("Init the slot for uploading media: {}", u); debug!("Init the slot for uploading media: {}", u);
// init the slot for uploading // init the slot for uploading
let client = Client::new(); let client = Client::new();
let orig_media_id: UploadMediaResponse = client let orig_media_id = client
.post(TWITTER_UPLOAD_MEDIA_URL) .post(TWITTER_UPLOAD_MEDIA_URL)
.header( .header(
"Authorization", "Authorization",
@@ -221,7 +229,7 @@ pub async fn upload_chunk_media(
) )
.send() .send()
.await? .await?
.json() .json::<UploadMediaResponse>()
.await?; .await?;
debug!("Slot initiated with ID: {}", orig_media_id.media_id); debug!("Slot initiated with ID: {}", orig_media_id.media_id);
@@ -267,7 +275,7 @@ pub async fn upload_chunk_media(
debug!("Finalize media ID: {}", orig_media_id.media_id); debug!("Finalize media ID: {}", orig_media_id.media_id);
// Finalizing task // Finalizing task
let fin: UploadMediaResponse = client let fin = client
.post(TWITTER_UPLOAD_MEDIA_URL) .post(TWITTER_UPLOAD_MEDIA_URL)
.header( .header(
"Authorization", "Authorization",
@@ -285,7 +293,7 @@ pub async fn upload_chunk_media(
) )
.send() .send()
.await? .await?
.json() .json::<UploadMediaResponse>()
.await?; .await?;
if let Some(p_info) = fin.processing_info { if let Some(p_info) = fin.processing_info {
@@ -310,7 +318,7 @@ pub async fn upload_chunk_media(
orig_media_id.media_id, wait_sec orig_media_id.media_id, wait_sec
); );
let status: UploadMediaResponse = client let status = client
.get(TWITTER_UPLOAD_MEDIA_URL) .get(TWITTER_UPLOAD_MEDIA_URL)
.header( .header(
"Authorization", "Authorization",
@@ -324,7 +332,7 @@ pub async fn upload_chunk_media(
.query(&command) .query(&command)
.send() .send()
.await? .await?
.json() .json::<UploadMediaResponse>()
.await?; .await?;
let p_status = status.processing_info.unwrap(); // shouldnt be None at this point let p_status = status.processing_info.unwrap(); // shouldnt be None at this point
@@ -347,7 +355,8 @@ pub async fn upload_chunk_media(
"Processing still pending, waiting {} secs more…", "Processing still pending, waiting {} secs more…",
p_status.check_after_secs.unwrap() // unwrap is safe here, p_status.check_after_secs.unwrap() // unwrap is safe here,
// check_after_secs is only present // check_after_secs is only present
// when status is pending // when status is pending or in
// progress
); );
sleep(Duration::from_secs(p_status.check_after_secs.unwrap())).await; sleep(Duration::from_secs(p_status.check_after_secs.unwrap())).await;
continue; continue;
@@ -365,15 +374,19 @@ pub async fn post_tweet(
config: &TwitterConfig, config: &TwitterConfig,
content: &str, content: &str,
medias: &[u64], medias: &[u64],
reply_to: &Option<u64>,
) -> Result<u64, Box<dyn Error>> { ) -> Result<u64, Box<dyn Error>> {
let empty_request = EmptyRequest {}; // Why? Because fuck you, thats why! let empty_request = EmptyRequest {}; // Why? Because fuck you, thats why!
let token = get_token(config); let token = get_token(config);
let tweet = Tweet { let tweet = Tweet {
text: content.to_string(), text: content.to_string(),
media: TweetMediasIds { media: medias.is_empty().not().then(|| TweetMediasIds {
media_ids: medias.iter().map(|m| m.to_string()).collect(), media_ids: medias.iter().map(|m| m.to_string()).collect(),
}, }),
reply: reply_to.map(|s| TweetReply {
in_reply_to_tweet_id: s.to_string(),
}),
}; };
let client = Client::new(); let client = Client::new();