: add bluesky support

This commit is contained in:
VC
2024-09-26 16:32:39 +02:00
parent f7e2aafa7b
commit ac8af5ce95
8 changed files with 1090 additions and 245 deletions

View File

@@ -1,3 +1,5 @@
use log::debug;
mod error;
pub use error::OolatoocsError;
@@ -5,8 +7,7 @@ mod config;
pub use config::{parse_toml, Config};
mod state;
#[allow(unused_imports)]
use state::{delete_state, read_all_tweet_state, read_state, write_state, TweetToToot};
use state::{delete_state, read_all_state, read_state, write_state, TootTweetRecord};
pub use state::{init_db, migrate_db};
mod mastodon;
@@ -17,9 +18,11 @@ mod utils;
use utils::{generate_multi_tweets, strip_everything};
mod twitter;
#[allow(unused_imports)]
use twitter::{delete_tweet, generate_media_ids, post_tweet, transform_poll};
mod bsky;
use bsky::{build_post_record, generate_media_records, get_session, BskyReply};
use rusqlite::Connection;
#[tokio::main]
@@ -29,6 +32,10 @@ pub async fn run(config: &Config) {
let mastodon = get_mastodon_instance(&config.mastodon);
let bluesky = get_session(&config.bluesky.handle, &config.bluesky.password)
.await
.unwrap_or_else(|e| panic!("Cannot connect to Bsky: {}", e));
let last_entry =
read_state(&conn, None).unwrap_or_else(|e| panic!("Cannot get last toot id: {}", e));
@@ -40,23 +47,29 @@ pub async fn run(config: &Config) {
Some(d) => {
// a date has been found
if d > t.datetime.unwrap() {
// said date is posterior to the previously
// written tweet, we need to delete/rewrite
for local_tweet_id in read_all_tweet_state(&conn, t.toot_id)
debug!("Last toot date is posterior to the previously written tweet, deleting…");
let (local_tweet_ids, local_record_uris) = read_all_state(&conn, t.toot_id)
.unwrap_or_else(|e| {
panic!(
"Cannot fetch all tweets associated with Toot ID {}: {}",
t.toot_id, e
)
})
.into_iter()
{
});
for local_tweet_id in local_tweet_ids.into_iter() {
delete_tweet(&config.twitter, local_tweet_id)
.await
.unwrap_or_else(|e| {
panic!("Cannot delete Tweet ID ({}): {}", t.tweet_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)
});
@@ -87,52 +100,132 @@ pub async fn run(config: &Config) {
};
// 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)
});
let (mut tweet_reply_to, mut record_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,
BskyReply {
record_uri: s.record_uri.to_owned(),
root_record_uri: s.root_record_uri.to_owned(),
},
)
})
})
.unzip();
// 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 reply_id = post_tweet(&config.twitter, first_half, vec![], reply_to, None)
.await
.unwrap_or_else(|e| panic!("Cannot post the first half of {}: {}", &toot.id, e));
let tweet_reply_id =
post_tweet(&config.twitter, &first_half, vec![], tweet_reply_to, None)
.await
.unwrap_or_else(|e| {
panic!(
"Cannot post the first half of {} for Twitter: {}",
&toot.id, e
)
});
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,
TweetToToot {
tweet_id: reply_id,
TootTweetRecord {
toot_id: toot.id.parse::<u64>().unwrap(),
tweet_id: tweet_reply_id,
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 ({}/{}): {}", &toot.id, reply_id, e)
panic!(
"Cannot store Toot/Tweet/Record ({}/{}/{}): {}",
&toot.id, tweet_reply_id, &record_reply_id.data.uri, e
)
});
reply_to = Some(reply_id);
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()
}),
});
tweet_reply_to = Some(tweet_reply_id);
};
// treats poll if any
let in_poll = toot.poll.map(|p| transform_poll(&p));
// treats medias
let medias = generate_media_ids(&config.twitter, &toot.media_attachments).await;
let record_medias = generate_media_records(&bluesky, &toot.media_attachments).await;
let tweet_medias = generate_media_ids(&config.twitter, &toot.media_attachments).await;
// posts corresponding tweet
let tweet_id = post_tweet(&config.twitter, tweet_content, medias, reply_to, in_poll)
let tweet_id = post_tweet(
&config.twitter,
&tweet_content,
tweet_medias,
tweet_reply_to,
in_poll,
)
.await
.unwrap_or_else(|e| panic!("Cannot Tweet {}: {}", toot.id, e));
let record = build_post_record(
&config.bluesky,
&tweet_content,
&toot.language,
record_medias,
&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 Tweet {}: {}", toot.id, e));
.unwrap_or_else(|e| panic!("Cannot put record {}: {}", &toot.id, e));
// writes the current state of the tweet
write_state(
&conn,
TweetToToot {
tweet_id,
TootTweetRecord {
toot_id: toot.id.parse::<u64>().unwrap(),
tweet_id,
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,
},
)