mirror of
https://framagit.org/veretcle/oolatoocs.git
synced 2025-12-06 14:53:15 +01:00
Merge branch 'feat/add_quotes' into 'main'
Add quotes See merge request veretcle/oolatoocs!37
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
.last_tweet
|
.last_tweet
|
||||||
.config.toml
|
.config.toml
|
||||||
.config.json
|
.config.json
|
||||||
|
.bsky.json
|
||||||
|
|||||||
1071
Cargo.lock
generated
1071
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "oolatoocs"
|
name = "oolatoocs"
|
||||||
version = "4.3.1"
|
version = "4.4.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
|
||||||
@@ -12,7 +12,6 @@ env_logger = "^0.11"
|
|||||||
futures = "^0.3"
|
futures = "^0.3"
|
||||||
html-escape = "^0.2"
|
html-escape = "^0.2"
|
||||||
log = "^0.4"
|
log = "^0.4"
|
||||||
megalodon = "^1.0"
|
|
||||||
oauth1-request = "^0.6"
|
oauth1-request = "^0.6"
|
||||||
regex = "^1.10"
|
regex = "^1.10"
|
||||||
reqwest = { version = "^0.12", features = ["json", "stream", "multipart"] }
|
reqwest = { version = "^0.12", features = ["json", "stream", "multipart"] }
|
||||||
@@ -24,6 +23,7 @@ bsky-sdk = "^0.1"
|
|||||||
atrium-api = { version = "^0.25", features = ["namespace-appbsky"] }
|
atrium-api = { version = "^0.25", features = ["namespace-appbsky"] }
|
||||||
image = "^0.25"
|
image = "^0.25"
|
||||||
webp = "^0.3"
|
webp = "^0.3"
|
||||||
|
megalodon = "1.0.*"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
strip = true
|
strip = true
|
||||||
|
|||||||
25
src/bsky.rs
25
src/bsky.rs
@@ -148,6 +148,31 @@ async fn get_record(
|
|||||||
Ok(record)
|
Ok(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generate an quote embed record into Bsky
|
||||||
|
pub async fn generate_quote_records(
|
||||||
|
config: &BlueskyConfig,
|
||||||
|
quote_id: &str,
|
||||||
|
) -> Option<atrium_api::types::Union<atrium_api::app::bsky::feed::post::RecordEmbedRefs>> {
|
||||||
|
// if we can’t match the quote_id, simply return None
|
||||||
|
let quote_record = match get_record(&config.handle, &rkey(quote_id)).await {
|
||||||
|
Ok(a) => a,
|
||||||
|
Err(_) => return None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(atrium_api::types::Union::Refs(
|
||||||
|
atrium_api::app::bsky::feed::post::RecordEmbedRefs::AppBskyEmbedRecordMain(Box::new(
|
||||||
|
atrium_api::app::bsky::embed::record::MainData {
|
||||||
|
record: atrium_api::com::atproto::repo::strong_ref::MainData {
|
||||||
|
cid: quote_record.data.cid.unwrap(),
|
||||||
|
uri: quote_record.data.uri.to_owned(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
}
|
||||||
|
.into(),
|
||||||
|
)),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
/// Generate an embed card record into Bsky
|
/// Generate an embed card record into Bsky
|
||||||
/// If the preview image does not exist or fails to upload, it is simply ignored
|
/// If the preview image does not exist or fails to upload, it is simply ignored
|
||||||
pub async fn generate_embed_records(
|
pub async fn generate_embed_records(
|
||||||
|
|||||||
31
src/lib.rs
31
src/lib.rs
@@ -19,7 +19,8 @@ use utils::{generate_multi_tweets, strip_everything};
|
|||||||
|
|
||||||
mod bsky;
|
mod bsky;
|
||||||
use bsky::{
|
use bsky::{
|
||||||
build_post_record, generate_embed_records, generate_media_records, get_session, BskyReply,
|
build_post_record, generate_embed_records, generate_media_records, generate_quote_records,
|
||||||
|
get_session, BskyReply,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
@@ -93,7 +94,9 @@ pub async fn run(config: &Config) {
|
|||||||
true => toot.tags.clone(),
|
true => toot.tags.clone(),
|
||||||
false => vec![],
|
false => vec![],
|
||||||
};
|
};
|
||||||
let Ok(mut tweet_content) = strip_everything(&toot.content, &toot_tags) else {
|
let Ok(mut tweet_content) =
|
||||||
|
strip_everything(&toot.content, &toot_tags, &config.mastodon.base)
|
||||||
|
else {
|
||||||
continue; // skip in case we can’t strip something
|
continue; // skip in case we can’t strip something
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -158,15 +161,25 @@ pub async fn run(config: &Config) {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// treats medias
|
// Don’t know how to union things so…
|
||||||
let mut record_embed = generate_media_records(&bluesky, &toot.media_attachments).await;
|
// cards have the least priority (you cannot embed card and images anyway)
|
||||||
|
// images have a higher priority
|
||||||
|
// quotes have the highest priority
|
||||||
|
|
||||||
// treats embed cards if any
|
let record_embed = if toot.reblog.is_some() {
|
||||||
if let Some(card) = &toot.card {
|
let quote_record =
|
||||||
if record_embed.is_none() {
|
read_state(&conn, Some(toot.reblog.unwrap().id.parse::<u64>().unwrap()));
|
||||||
record_embed = generate_embed_records(&bluesky, card).await;
|
match quote_record {
|
||||||
|
Ok(Some(q)) => generate_quote_records(&config.bluesky, &q.record_uri).await,
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
} else if toot.media_attachments.len() > usize::from(0u8) {
|
||||||
|
generate_media_records(&bluesky, &toot.media_attachments).await
|
||||||
|
} else if toot.card.is_some() {
|
||||||
|
generate_embed_records(&bluesky, &toot.card.unwrap()).await
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
// posts corresponding tweet
|
// posts corresponding tweet
|
||||||
let record = build_post_record(
|
let record = build_post_record(
|
||||||
|
|||||||
@@ -55,9 +55,13 @@ pub async fn get_mastodon_timeline_since(
|
|||||||
.clone()
|
.clone()
|
||||||
.is_some_and(|r| r == t.account.id)
|
.is_some_and(|r| r == t.account.id)
|
||||||
})
|
})
|
||||||
.filter(|t| t.visibility == StatusVisibility::Public) // excludes everything that isn’t
|
.filter(|t| t.visibility == StatusVisibility::Public) // excludes everything that isn’t public
|
||||||
// public
|
.filter(|t| {
|
||||||
.filter(|t| t.reblog.is_none()) // excludes reblogs
|
t.reblog.is_none()
|
||||||
|
|| t.reblog
|
||||||
|
.clone()
|
||||||
|
.is_some_and(|r| r.account.id == t.account.id)
|
||||||
|
}) // excludes reblogs except by self
|
||||||
.cloned()
|
.cloned()
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
|||||||
32
src/utils.rs
32
src/utils.rs
@@ -53,10 +53,16 @@ fn twitter_count(content: &str) -> usize {
|
|||||||
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>,
|
||||||
|
mastodon_base: &str,
|
||||||
|
) -> 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"));
|
||||||
|
|
||||||
strip_mastodon_tags(&mut res, tags).unwrap();
|
strip_quote_header(&mut res, mastodon_base)?;
|
||||||
|
|
||||||
|
strip_mastodon_tags(&mut res, tags)?;
|
||||||
|
|
||||||
res = res.trim_end_matches('\n').trim_end_matches(' ').to_string();
|
res = res.trim_end_matches('\n').trim_end_matches(' ').to_string();
|
||||||
res = decode_html_entities(&res).to_string();
|
res = decode_html_entities(&res).to_string();
|
||||||
@@ -64,6 +70,16 @@ pub fn strip_everything(content: &str, tags: &Vec<Tag>) -> Result<String, Box<dy
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn strip_quote_header(content: &mut String, mastodon_base: &str) -> Result<(), Box<dyn Error>> {
|
||||||
|
let re = Regex::new(&format!(
|
||||||
|
r"^RE: {}\S+\n\n",
|
||||||
|
mastodon_base.replace(".", r"\.")
|
||||||
|
))?;
|
||||||
|
*content = re.replace(content, "").to_string();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn strip_mastodon_tags(content: &mut String, tags: &Vec<Tag>) -> Result<(), Box<dyn Error>> {
|
fn strip_mastodon_tags(content: &mut String, tags: &Vec<Tag>) -> Result<(), Box<dyn Error>> {
|
||||||
for tag in tags {
|
for tag in tags {
|
||||||
let re = Regex::new(&format!("(?i)(#{} ?)", &tag.name))?;
|
let re = Regex::new(&format!("(?i)(#{} ?)", &tag.name))?;
|
||||||
@@ -186,9 +202,19 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_strip_everything() {
|
fn test_strip_everything() {
|
||||||
|
// a classic toot
|
||||||
let content = "<p>Ce soir à 21h, c'est le Dojobar ! Au programme ce soir, une rétrospective sur la série Mario & Luigi.<br />Comme d'hab, le Twitch sera ici : <a href=\"https://twitch.tv/nintendojofr\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">twitch.tv/nintendojofr</span><span class=\"invisible\"></span></a><br />Ou juste l'audio là : <a href=\"https://nintendojo.fr/dojobar\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">nintendojo.fr/dojobar</span><span class=\"invisible\"></span></a><br />A toute !</p>";
|
let content = "<p>Ce soir à 21h, c'est le Dojobar ! Au programme ce soir, une rétrospective sur la série Mario & Luigi.<br />Comme d'hab, le Twitch sera ici : <a href=\"https://twitch.tv/nintendojofr\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">twitch.tv/nintendojofr</span><span class=\"invisible\"></span></a><br />Ou juste l'audio là : <a href=\"https://nintendojo.fr/dojobar\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">nintendojo.fr/dojobar</span><span class=\"invisible\"></span></a><br />A toute !</p>";
|
||||||
let expected_result = "Ce soir à 21h, c'est le Dojobar ! Au programme ce soir, une rétrospective sur la série Mario & Luigi.\nComme d'hab, le Twitch sera ici : https://twitch.tv/nintendojofr\nOu juste l'audio là : https://nintendojo.fr/dojobar\nA toute !".to_string();
|
let expected_result = "Ce soir à 21h, c'est le Dojobar ! Au programme ce soir, une rétrospective sur la série Mario & Luigi.\nComme d'hab, le Twitch sera ici : https://twitch.tv/nintendojofr\nOu juste l'audio là : https://nintendojo.fr/dojobar\nA toute !".to_string();
|
||||||
let result = strip_everything(content, &vec![]).unwrap();
|
let result = strip_everything(content, &vec![], "https://m.nintendojo.fr").unwrap();
|
||||||
|
|
||||||
|
assert_eq!(result, expected_result);
|
||||||
|
|
||||||
|
// a quoted toot
|
||||||
|
let content = "<p class=\"quote-inline\">RE: <a href=\"https://m.nintendojo.fr/@nintendojofr/115446347351491651\" target=\"_blank\" rel=\"nofollow noopener\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"ellipsis\">m.nintendojo.fr/@nintendojofr/</span><span class=\"invisible\">115446347351491651</span></a></p><p>Assassin’s Creed Shadows pèsera environ 62,8 Go sur Switch 2 (et un peu plus de 100 Go sur les autres supports), soit tout juste pour rentrer sur une cartouche de 64 Go.</p><p>Ou pas, pour rappel…</p><p><a href=\"https://m.nintendojo.fr/tags/AssassinsCreedShadows\" class=\"mention hashtag\" rel=\"tag\">#<span>AssassinsCreedShadows</span></a> <a href=\"https://m.nintendojo.fr/tags/Ubisoft\" class=\"mention hashtag\" rel=\"tag\">#<span>Ubisoft</span></a> <a href=\"https://m.nintendojo.fr/tags/NintendoSwitch2\" class=\"mention hashtag\" rel=\"tag\">#<span>NintendoSwitch2</span></a></p>";
|
||||||
|
|
||||||
|
let expected_result = "Assassin’s Creed Shadows pèsera environ 62,8 Go sur Switch 2 (et un peu plus de 100 Go sur les autres supports), soit tout juste pour rentrer sur une cartouche de 64 Go.\n\nOu pas, pour rappel…\n\n#AssassinsCreedShadows #Ubisoft #NintendoSwitch2";
|
||||||
|
|
||||||
|
let result = strip_everything(content, &vec![], "https://m.nintendojo.fr").unwrap();
|
||||||
|
|
||||||
assert_eq!(result, expected_result);
|
assert_eq!(result, expected_result);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user