feat: split toot into 2 tweets when necessary

This commit is contained in:
VC
2023-11-20 12:03:54 +01:00
parent b6f87e829f
commit 87b0567b59
4 changed files with 111 additions and 14 deletions

2
Cargo.lock generated
View File

@@ -988,7 +988,7 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]] [[package]]
name = "oolatoocs" name = "oolatoocs"
version = "1.2.0" version = "1.3.0"
dependencies = [ dependencies = [
"clap", "clap",
"env_logger", "env_logger",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "oolatoocs" name = "oolatoocs"
version = "1.2.0" version = "1.3.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

View File

@@ -14,7 +14,7 @@ use mastodon::get_mastodon_timeline_since;
pub use mastodon::register; pub use mastodon::register;
mod utils; mod utils;
use utils::strip_everything; use utils::{generate_multi_tweets, strip_everything};
mod twitter; mod twitter;
#[allow(unused_imports)] #[allow(unused_imports)]
@@ -40,13 +40,29 @@ pub async fn run(config: &Config) {
.unwrap_or_else(|e| panic!("Cannot get instance: {}", e)); .unwrap_or_else(|e| panic!("Cannot get instance: {}", e));
for toot in timeline { for toot in timeline {
let Ok(tweet_content) = strip_everything(&toot.content, &toot.tags) else { let Ok(mut tweet_content) = strip_everything(&toot.content, &toot.tags) else {
continue; // skip in case we cant strip something continue; // skip in case we cant strip something
}; };
let mut medias: Vec<u64> = vec![]; // threads if necessary
let mut 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)
});
// if we wanted to cut toot in half, now would be the right time to do so // if the toot is too long, we cut it in half here
if let Some((first_half, second_half)) = generate_multi_tweets(&tweet_content) {
tweet_content = second_half;
let reply_id = post_tweet(&config.twitter, &first_half, &[], &reply_to)
.await
.unwrap_or_else(|e| panic!("Cannot post the first half of {}: {}", &toot.id, e));
reply_to = Some(reply_id);
};
// treats medias
let mut medias: Vec<u64> = vec![];
let media_attachments = toot.media_attachments.clone(); let media_attachments = toot.media_attachments.clone();
let mut stream = stream::iter(media_attachments) let mut stream = stream::iter(media_attachments)
@@ -84,14 +100,6 @@ pub async fn run(config: &Config) {
} }
} }
// 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)
});
// 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)
.await .await

View File

@@ -3,6 +3,50 @@ use megalodon::entities::status::Tag;
use regex::Regex; use regex::Regex;
use std::error::Error; use std::error::Error;
/// Generate 2 contents out of 1 if that content is > 280 chars, None else
pub fn generate_multi_tweets(content: &str) -> Option<(String, String)> {
// Twitter webforms are utf-8 encoded, so we cannot count on len(), we dont need
// encode_utf16().count()
if twitter_count(content) <= 280 {
return None;
}
let split_content = content.split(' ');
let split_count = split_content.clone().count();
let first_half: String = split_content
.clone()
.take(split_count / 2)
.collect::<Vec<_>>()
.join(" ");
let second_half: String = split_content
.clone()
.skip(split_count / 2)
.collect::<Vec<_>>()
.join(" ");
Some((first_half, second_half))
}
/// Twitter doesnt count words the same we do, so youll have to improvise
fn twitter_count(content: &str) -> usize {
let mut count = 0;
let split_content = content.split(' ');
count += split_content.clone().count() - 1; // count the spaces
for word in split_content {
if word.starts_with("http://") || word.starts_with("https://") {
count += 23;
} else {
count += word.chars().count();
}
}
count
}
pub fn strip_everything(content: &str, tags: &Vec<Tag>) -> Result<String, Box<dyn Error>> { pub fn strip_everything(content: &str, tags: &Vec<Tag>) -> Result<String, Box<dyn Error>> {
let mut res = strip_html_tags(&content.replace("</p><p>", "\n\n").replace("<br />", "\n")); let mut res = strip_html_tags(&content.replace("</p><p>", "\n\n").replace("<br />", "\n"));
@@ -48,6 +92,51 @@ fn strip_html_tags(input: &str) -> String {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn test_twitter_count() {
let content = "tamerelol?! 🐵";
assert_eq!(twitter_count(content), content.chars().count());
let content = "Shoot out to https://y.ml/ !";
assert_eq!(twitter_count(content), 38);
let content = "this is the link https://www.google.com/tamerelol/youpi/tonperemdr/tarace.html if you like! What if I shit a final";
assert_eq!(twitter_count(content), 76);
}
#[test]
fn test_generate_multi_tweets_to_none() {
// test «standard» text
let tweet_content =
"LOLOLOL, je suis bien trop petit pour être coupé en deux voyons :troll:".to_string();
let youpi = generate_multi_tweets(&tweet_content);
assert_eq!(None, youpi);
// test with «complex» emoji (2 utf-8 chars)
let tweet_content = "🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷🇫🇷".to_string();
let youpi = generate_multi_tweets(&tweet_content);
assert_eq!(None, youpi);
}
#[test]
fn test_generate_multi_tweets_to_some() {
let tweet_content = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ipsum dolor sit amet consectetur adipiscing elit pellentesque. Pharetra pharetra massa massa ultricies mi quis hendrerit dolor. Mauris nunc congue nisi vitae. Scelerisque varius morbi enim nunc faucibus a pellentesque sit amet. Morbi leo urna molestie at elementum. Tristique et egestas quis ipsum suspendisse ultrices gravida dictum fusce. Amet porttitor eget dolor morbi.".to_string();
let youpi = generate_multi_tweets(&tweet_content);
let first_half = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ipsum dolor sit amet consectetur adipiscing elit pellentesque. Pharetra pharetra massa massa ultricies mi quis hendrerit dolor.".to_string();
let second_half = "Mauris nunc congue nisi vitae. Scelerisque varius morbi enim nunc faucibus a pellentesque sit amet. Morbi leo urna molestie at elementum. Tristique et egestas quis ipsum suspendisse ultrices gravida dictum fusce. Amet porttitor eget dolor morbi.".to_string();
assert_eq!(youpi, Some((first_half, second_half)));
}
#[test] #[test]
fn test_strip_mastodon_tags() { fn test_strip_mastodon_tags() {
let tags = vec![ let tags = vec![