mirror of
https://framagit.org/veretcle/oolatoocs.git
synced 2025-07-20 20:41:17 +02:00
199 lines
7.1 KiB
Rust
199 lines
7.1 KiB
Rust
use log::debug;
|
||
|
||
mod error;
|
||
pub use error::OolatoocsError;
|
||
|
||
mod config;
|
||
pub use config::{parse_toml, Config};
|
||
|
||
mod state;
|
||
use state::{delete_state, read_all_state, read_state, write_state, TootRecord};
|
||
pub use state::{init_db, migrate_db};
|
||
|
||
mod mastodon;
|
||
pub use mastodon::register;
|
||
use mastodon::{get_mastodon_instance, get_mastodon_timeline_since, get_status_edited_at};
|
||
|
||
mod utils;
|
||
use utils::{generate_multi_tweets, strip_everything};
|
||
|
||
mod bsky;
|
||
use bsky::{
|
||
build_post_record, generate_embed_records, generate_media_records, get_session, BskyReply,
|
||
};
|
||
|
||
use rusqlite::Connection;
|
||
|
||
#[tokio::main]
|
||
pub async fn run(config: &Config) {
|
||
let conn = Connection::open(&config.oolatoocs.db_path)
|
||
.unwrap_or_else(|e| panic!("Cannot open DB: {}", e));
|
||
|
||
let mastodon = get_mastodon_instance(&config.mastodon)
|
||
.unwrap_or_else(|e| panic!("Cannot instantiate Mastodon: {}", e));
|
||
|
||
let bluesky = get_session(&config.bluesky)
|
||
.await
|
||
.unwrap_or_else(|e| panic!("Cannot get Bsky session: {}", e));
|
||
|
||
let last_entry =
|
||
read_state(&conn, None).unwrap_or_else(|e| panic!("Cannot get last toot id: {}", e));
|
||
|
||
let last_toot_id: Option<u64> = match last_entry {
|
||
None => None, // Does not exist, this is the same as previously
|
||
Some(t) => {
|
||
match get_status_edited_at(&mastodon, t.toot_id).await {
|
||
None => Some(t.toot_id),
|
||
Some(d) => {
|
||
// a date has been found
|
||
if d > t.datetime.unwrap() {
|
||
debug!("Last toot date is posterior to the previously written tweet, deleting…");
|
||
let local_record_uris =
|
||
read_all_state(&conn, t.toot_id).unwrap_or_else(|e| {
|
||
panic!(
|
||
"Cannot fetch all records associated with Toot ID {}: {}",
|
||
t.toot_id, e
|
||
)
|
||
});
|
||
for local_record_uri in local_record_uris.into_iter() {
|
||
bluesky
|
||
.delete_record(&local_record_uri)
|
||
.await
|
||
.unwrap_or_else(|e| {
|
||
panic!("Cannot delete record ID ({}): {}", &t.record_uri, e)
|
||
});
|
||
}
|
||
delete_state(&conn, t.toot_id).unwrap_or_else(|e| {
|
||
panic!("Cannot delete Toot ID ({}): {}", t.toot_id, e)
|
||
});
|
||
read_state(&conn, None)
|
||
.unwrap_or_else(|e| panic!("Cannot get last toot id: {}", e))
|
||
.map(|a| a.toot_id)
|
||
} else {
|
||
Some(t.toot_id)
|
||
}
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
let timeline = get_mastodon_timeline_since(&mastodon, last_toot_id)
|
||
.await
|
||
.unwrap_or_else(|e| panic!("Cannot get instance: {}", e));
|
||
|
||
for toot in timeline {
|
||
// detecting tag #NoTweet and skipping the toot
|
||
if toot.tags.iter().any(|f| &f.name == "notweet") {
|
||
continue;
|
||
}
|
||
|
||
// form tweet_content and strip everything useless in it
|
||
let Ok(mut tweet_content) = strip_everything(&toot.content, &toot.tags) else {
|
||
continue; // skip in case we can’t strip something
|
||
};
|
||
|
||
// threads if necessary
|
||
let mut record_reply_to = toot.in_reply_to_id.and_then(|t| {
|
||
read_state(&conn, Some(t.parse::<u64>().unwrap()))
|
||
.ok()
|
||
.flatten()
|
||
.map(|s| BskyReply {
|
||
record_uri: s.record_uri.to_owned(),
|
||
root_record_uri: s.root_record_uri.to_owned(),
|
||
})
|
||
});
|
||
|
||
// 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;
|
||
// post the first half
|
||
let record = build_post_record(
|
||
&config.bluesky,
|
||
&first_half,
|
||
&toot.language,
|
||
None,
|
||
&record_reply_to,
|
||
)
|
||
.await
|
||
.unwrap_or_else(|e| panic!("Cannot create valid record for {}: {}", &toot.id, e));
|
||
let record_reply_id = bluesky.create_record(record).await.unwrap_or_else(|e| {
|
||
panic!(
|
||
"Cannot post the first half of {} for Bluesky: {}",
|
||
&toot.id, e
|
||
)
|
||
});
|
||
// write it to db
|
||
write_state(
|
||
&conn,
|
||
TootRecord {
|
||
toot_id: toot.id.parse::<u64>().unwrap(),
|
||
record_uri: record_reply_id.data.uri.to_owned(),
|
||
root_record_uri: record_reply_to
|
||
.as_ref()
|
||
.map_or(record_reply_id.data.uri.to_owned(), |v| {
|
||
v.root_record_uri.to_owned()
|
||
}),
|
||
datetime: None,
|
||
},
|
||
)
|
||
.unwrap_or_else(|e| {
|
||
panic!(
|
||
"Cannot store Toot/Tweet/Record ({}/{}): {}",
|
||
&toot.id, &record_reply_id.data.uri, e
|
||
)
|
||
});
|
||
|
||
record_reply_to = Some(BskyReply {
|
||
record_uri: record_reply_id.data.uri.to_owned(),
|
||
root_record_uri: record_reply_to
|
||
.as_ref()
|
||
.map_or(record_reply_id.data.uri.clone(), |v| {
|
||
v.root_record_uri.clone()
|
||
}),
|
||
});
|
||
};
|
||
|
||
// treats medias
|
||
let mut record_embed = generate_media_records(&bluesky, &toot.media_attachments).await;
|
||
|
||
// treats embed cards if any
|
||
if let Some(card) = &toot.card {
|
||
if record_embed.is_none() {
|
||
record_embed = generate_embed_records(&bluesky, card).await;
|
||
}
|
||
}
|
||
|
||
// posts corresponding tweet
|
||
let record = build_post_record(
|
||
&config.bluesky,
|
||
&tweet_content,
|
||
&toot.language,
|
||
record_embed,
|
||
&record_reply_to,
|
||
)
|
||
.await
|
||
.unwrap_or_else(|e| panic!("Cannot build record for {}: {}", &toot.id, e));
|
||
|
||
let created_record = bluesky
|
||
.create_record(record)
|
||
.await
|
||
.unwrap_or_else(|e| panic!("Cannot put record {}: {}", &toot.id, e));
|
||
|
||
// writes the current state of the tweet
|
||
write_state(
|
||
&conn,
|
||
TootRecord {
|
||
toot_id: toot.id.parse::<u64>().unwrap(),
|
||
record_uri: created_record.data.uri.clone(),
|
||
root_record_uri: record_reply_to
|
||
.as_ref()
|
||
.map_or(created_record.data.uri.clone(), |v| {
|
||
v.root_record_uri.clone()
|
||
}),
|
||
datetime: None,
|
||
},
|
||
)
|
||
.unwrap_or_else(|e| panic!("Cannot store Toot/Tweet ({}): {}", &toot.id, e));
|
||
}
|
||
}
|