mirror of
https://framagit.org/veretcle/oolatoocs.git
synced 2025-07-20 20:41:17 +02:00
Merge branch '5-feat-repeat-mastodon-poll-in-twitter' into 'main'
feat: add poll from Mastodon to Twitter + pass owned values in post_tweet Closes #5 See merge request veretcle/oolatoocs!7
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -972,8 +972,9 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oolatoocs"
|
name = "oolatoocs"
|
||||||
version = "1.4.0"
|
version = "1.5.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
|
@@ -1,11 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "oolatoocs"
|
name = "oolatoocs"
|
||||||
version = "1.4.0"
|
version = "1.5.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chrono = "0.4.31"
|
||||||
clap = "^4"
|
clap = "^4"
|
||||||
env_logger = "^0.10"
|
env_logger = "^0.10"
|
||||||
futures = "^0.3"
|
futures = "^0.3"
|
||||||
|
@@ -13,11 +13,9 @@ What it can do:
|
|||||||
* Cuts (poorly) the Toot in half in it’s too long for Twitter and thread it (this is cut using a word count, not the best method, but it gets the job done);
|
* Cuts (poorly) the Toot in half in it’s too long for Twitter and thread it (this is cut using a word count, not the best method, but it gets the job done);
|
||||||
* Reuploads images/gifs/videos from Mastodon to Twitter
|
* Reuploads images/gifs/videos from Mastodon to Twitter
|
||||||
* Can reproduce threads from Mastodon to Twitter
|
* Can reproduce threads from Mastodon to Twitter
|
||||||
|
* Can reproduce poll from Mastodon to Twitter
|
||||||
* Can prevent a Toot from being tweeted by using the #NoTweet (case-insensitive) hashtag in Mastodon
|
* Can prevent a Toot from being tweeted by using the #NoTweet (case-insensitive) hashtag in Mastodon
|
||||||
|
|
||||||
What it can’t do:
|
|
||||||
* Poll (no idea on how to do it)
|
|
||||||
|
|
||||||
# Configuration file
|
# Configuration file
|
||||||
|
|
||||||
The configuration is relatively easy to follow:
|
The configuration is relatively easy to follow:
|
||||||
|
@@ -18,7 +18,7 @@ use utils::{generate_multi_tweets, strip_everything};
|
|||||||
|
|
||||||
mod twitter;
|
mod twitter;
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use twitter::{generate_media_ids, post_tweet};
|
use twitter::{generate_media_ids, post_tweet, transform_poll};
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
@@ -57,17 +57,20 @@ pub async fn run(config: &Config) {
|
|||||||
// if the toot is too long, we cut it in half here
|
// if the toot is too long, we cut it in half here
|
||||||
if let Some((first_half, second_half)) = generate_multi_tweets(&tweet_content) {
|
if let Some((first_half, second_half)) = generate_multi_tweets(&tweet_content) {
|
||||||
tweet_content = second_half;
|
tweet_content = second_half;
|
||||||
let reply_id = post_tweet(&config.twitter, &first_half, &[], &reply_to)
|
let reply_id = post_tweet(&config.twitter, first_half, vec![], reply_to, None)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e| panic!("Cannot post the first half of {}: {}", &toot.id, e));
|
.unwrap_or_else(|e| panic!("Cannot post the first half of {}: {}", &toot.id, e));
|
||||||
reply_to = Some(reply_id);
|
reply_to = Some(reply_id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// treats poll if any
|
||||||
|
let in_poll = toot.poll.map(|p| transform_poll(&p));
|
||||||
|
|
||||||
// treats medias
|
// treats medias
|
||||||
let medias = generate_media_ids(&config.twitter, &toot.media_attachments).await;
|
let medias = generate_media_ids(&config.twitter, &toot.media_attachments).await;
|
||||||
|
|
||||||
// posts corresponding tweet
|
// posts corresponding tweet
|
||||||
let tweet_id = post_tweet(&config.twitter, &tweet_content, &medias, &reply_to)
|
let tweet_id = post_tweet(&config.twitter, tweet_content, medias, reply_to, in_poll)
|
||||||
.await
|
.await
|
||||||
.unwrap_or_else(|e| panic!("Cannot Tweet {}: {}", toot.id, e));
|
.unwrap_or_else(|e| panic!("Cannot Tweet {}: {}", toot.id, e));
|
||||||
|
|
||||||
|
@@ -1,8 +1,12 @@
|
|||||||
use crate::config::TwitterConfig;
|
use crate::config::TwitterConfig;
|
||||||
use crate::error::OolatoocsError;
|
use crate::error::OolatoocsError;
|
||||||
|
use chrono::Utc;
|
||||||
use futures::{stream, StreamExt};
|
use futures::{stream, StreamExt};
|
||||||
use log::{debug, error, warn};
|
use log::{debug, error, warn};
|
||||||
use megalodon::entities::attachment::{Attachment, AttachmentType};
|
use megalodon::entities::{
|
||||||
|
attachment::{Attachment, AttachmentType},
|
||||||
|
Poll,
|
||||||
|
};
|
||||||
use oauth1_request::Token;
|
use oauth1_request::Token;
|
||||||
use reqwest::{
|
use reqwest::{
|
||||||
multipart::{Form, Part},
|
multipart::{Form, Part},
|
||||||
@@ -28,6 +32,8 @@ struct Tweet {
|
|||||||
media: Option<TweetMediasIds>,
|
media: Option<TweetMediasIds>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
reply: Option<TweetReply>,
|
reply: Option<TweetReply>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
poll: Option<TweetPoll>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
@@ -40,6 +46,12 @@ struct TweetReply {
|
|||||||
in_reply_to_tweet_id: String,
|
in_reply_to_tweet_id: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Debug)]
|
||||||
|
pub struct TweetPoll {
|
||||||
|
pub options: Vec<String>,
|
||||||
|
pub duration_minutes: i64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Debug)]
|
#[derive(Deserialize, Debug)]
|
||||||
struct TweetResponse {
|
struct TweetResponse {
|
||||||
data: TweetResponseData,
|
data: TweetResponseData,
|
||||||
@@ -416,24 +428,37 @@ async fn upload_chunk_media(
|
|||||||
Ok(orig_media_id.media_id)
|
Ok(orig_media_id.media_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn transform_poll(p: &Poll) -> TweetPoll {
|
||||||
|
let poll_end_datetime = p.expires_at.unwrap(); // should be safe at this point
|
||||||
|
let now = Utc::now();
|
||||||
|
let diff = poll_end_datetime.signed_duration_since(now);
|
||||||
|
|
||||||
|
TweetPoll {
|
||||||
|
options: p.options.iter().map(|i| i.title.clone()).collect(),
|
||||||
|
duration_minutes: diff.num_minutes(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This posts Tweets with all the associated medias
|
/// This posts Tweets with all the associated medias
|
||||||
pub async fn post_tweet(
|
pub async fn post_tweet(
|
||||||
config: &TwitterConfig,
|
config: &TwitterConfig,
|
||||||
content: &str,
|
content: String,
|
||||||
medias: &[u64],
|
medias: Vec<u64>,
|
||||||
reply_to: &Option<u64>,
|
reply_to: Option<u64>,
|
||||||
|
poll: Option<TweetPoll>,
|
||||||
) -> Result<u64, Box<dyn Error>> {
|
) -> Result<u64, Box<dyn Error>> {
|
||||||
let empty_request = EmptyRequest {}; // Why? Because fuck you, that’s why!
|
let empty_request = EmptyRequest {}; // Why? Because fuck you, that’s why!
|
||||||
let token = get_token(config);
|
let token = get_token(config);
|
||||||
|
|
||||||
let tweet = Tweet {
|
let tweet = Tweet {
|
||||||
text: content.to_string(),
|
text: content,
|
||||||
media: medias.is_empty().not().then(|| 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 {
|
reply: reply_to.map(|s| TweetReply {
|
||||||
in_reply_to_tweet_id: s.to_string(),
|
in_reply_to_tweet_id: s.to_string(),
|
||||||
}),
|
}),
|
||||||
|
poll,
|
||||||
};
|
};
|
||||||
|
|
||||||
let client = Client::new();
|
let client = Client::new();
|
||||||
|
Reference in New Issue
Block a user