7 Commits

Author SHA1 Message Date
VC
e99a666b18 Merge branch 'fix_rand_doc' into 'main'
Fix rand doc

See merge request veretcle/oolatoocs!22
2024-10-09 09:26:25 +00:00
VC
3b18dac2fb 📝: update doc 2024-10-09 11:22:06 +02:00
VC
af977a1ee0 : remove rand 2024-10-09 11:14:07 +02:00
VC
b90b727783 Merge branch 'feat_www' into 'main'
💄: remove www. from url

See merge request veretcle/oolatoocs!21
2024-10-08 08:33:03 +00:00
VC
f8227f99c1 💄: remove www. from url 2024-10-08 10:24:22 +02:00
VC
9f2ff119ff Merge branch 'feat_better_session_handling' into 'main'
♻️: refactor bsky session

See merge request veretcle/oolatoocs!20
2024-10-07 12:50:34 +00:00
VC
c0244c8c30 ♻️: refactor bsky session 2024-10-07 11:55:20 +02:00
7 changed files with 109 additions and 94 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/target
.last_tweet
.config.toml
.config.json

106
Cargo.lock generated
View File

@@ -4,9 +4,9 @@ version = 3
[[package]]
name = "addr2line"
version = "0.24.1"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
@@ -116,9 +116,9 @@ checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6"
[[package]]
name = "async-compression"
version = "0.4.12"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fec134f64e2bc57411226dfc4e52dec859ddfc7e711fc5e07b612584f000e4aa"
checksum = "7e614738943d3f68c628ae3dbce7c3daffb196665f82f8c8ea6b65de73c79429"
dependencies = [
"flate2",
"futures-core",
@@ -287,9 +287,9 @@ checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3"
[[package]]
name = "cc"
version = "1.1.24"
version = "1.1.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938"
checksum = "2e80e3b6a3ab07840e1cae9b0666a63970dc28e8ed5ffbcdacbfc760c281bfc1"
dependencies = [
"shlex",
]
@@ -556,9 +556,9 @@ dependencies = [
[[package]]
name = "futures"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
@@ -571,9 +571,9 @@ dependencies = [
[[package]]
name = "futures-channel"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
dependencies = [
"futures-core",
"futures-sink",
@@ -581,15 +581,15 @@ dependencies = [
[[package]]
name = "futures-core"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-executor"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
dependencies = [
"futures-core",
"futures-task",
@@ -598,15 +598,15 @@ dependencies = [
[[package]]
name = "futures-io"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6"
[[package]]
name = "futures-macro"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
@@ -615,21 +615,21 @@ dependencies = [
[[package]]
name = "futures-sink"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.30"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-channel",
"futures-core",
@@ -668,9 +668,9 @@ dependencies = [
[[package]]
name = "gimli"
version = "0.31.0"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "h2"
@@ -893,7 +893,7 @@ dependencies = [
"http 1.1.0",
"hyper 1.4.1",
"hyper-util",
"rustls 0.23.13",
"rustls 0.23.14",
"rustls-pki-types",
"tokio",
"tokio-rustls 0.26.0",
@@ -1005,9 +1005,9 @@ dependencies = [
[[package]]
name = "ipnet"
version = "2.10.0"
version = "2.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4"
checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708"
[[package]]
name = "is-terminal"
@@ -1265,25 +1265,22 @@ dependencies = [
[[package]]
name = "object"
version = "0.36.4"
version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a"
checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.20.1"
version = "1.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82881c4be219ab5faaf2ad5e5e5ecdff8c66bd7402ca3160975c93b24961afd1"
dependencies = [
"portable-atomic",
]
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
[[package]]
name = "oolatoocs"
version = "3.0.3"
version = "3.1.1"
dependencies = [
"atrium-api",
"bsky-sdk",
@@ -1295,7 +1292,6 @@ dependencies = [
"log",
"megalodon",
"oauth1-request",
"rand",
"regex",
"reqwest 0.11.27",
"rusqlite",
@@ -1395,12 +1391,6 @@ version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "portable-atomic"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc9c68a3f6da06753e9335d63e27f6b9754dd1920d941135b7ea8224f141adb2"
[[package]]
name = "ppv-lite86"
version = "0.2.20"
@@ -1444,9 +1434,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.86"
version = "1.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a"
dependencies = [
"unicode-ident",
]
@@ -1477,7 +1467,7 @@ dependencies = [
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls 0.23.13",
"rustls 0.23.14",
"socket2",
"thiserror",
"tokio",
@@ -1494,7 +1484,7 @@ dependencies = [
"rand",
"ring",
"rustc-hash",
"rustls 0.23.13",
"rustls 0.23.14",
"slab",
"thiserror",
"tinyvec",
@@ -1666,7 +1656,7 @@ dependencies = [
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls 0.23.13",
"rustls 0.23.14",
"rustls-pemfile 2.2.0",
"rustls-pki-types",
"serde",
@@ -1756,9 +1746,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.13"
version = "0.23.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8"
dependencies = [
"once_cell",
"ring",
@@ -1834,9 +1824,9 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "schannel"
version = "0.1.24"
version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b"
checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1"
dependencies = [
"windows-sys 0.59.0",
]
@@ -2215,7 +2205,7 @@ version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls 0.23.13",
"rustls 0.23.14",
"rustls-pki-types",
"tokio",
]
@@ -2228,7 +2218,7 @@ checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd"
dependencies = [
"futures-util",
"log",
"rustls 0.23.13",
"rustls 0.23.14",
"rustls-native-certs",
"rustls-pki-types",
"tokio",
@@ -2350,7 +2340,7 @@ dependencies = [
"httparse",
"log",
"rand",
"rustls 0.23.13",
"rustls 0.23.14",
"rustls-pki-types",
"sha1",
"thiserror",
@@ -2375,9 +2365,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
version = "0.3.15"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75"
checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
[[package]]
name = "unicode-ident"

View File

@@ -1,12 +1,11 @@
[package]
name = "oolatoocs"
version = "3.0.3"
version = "3.1.1"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rand = "^0.8" # /!\ to be removed
chrono = "^0.4"
clap = "^4"
env_logger = "^0.10"

View File

@@ -1,10 +1,13 @@
# oolatoocs, a Mastodon to Twitter bot
# oolatoocs, a Mastodon to Twitter/Bluesky bot
## A little bit of history
So what is it? Originally, I wrote, with some help, [Scootaloo](https://framagit.org/veretcle/scootaloo/) which was a Twitter to Mastodon Bot to help the [writers at NintendojoFR](https://www.nintendojo.fr) not to worry about Mastodon: the vast majority of writers were posting to Twitter, the bot scooped everything and arranged it properly for Mastodon and everything was fine and dandy. It was also used, in an altered beefed-up version, for [Nupes.social](https://nupes.social) to make the tweets from the NUPES political alliance on Twitter, more easily accessible in Mastodon.
So what is it? Originally, I wrote, with some help, [Scootaloo](https://framagit.org/veretcle/scootaloo/) which was a Twitter to Mastodon Bot to help the [writers at NintendojoFR](https://www.nintendojo.fr) not to worry about Mastodon: the vast majority of writers were posting to Twitter, the bot scooped everything and arranged it properly for Mastodon and everything was fine and dandy. It was also used, in an altered beefed-up version, for the (now defunct) Mastodon Instance [Nupes.social](https://nupes.social) to make the tweets from the NUPES political alliance on Twitter, more easily accessible for Mastodon users.
But then Elon came, and we couldnt read data from Twitter anymore. So we had to rely on copy/pasting things from one to another, which is not fun nor efficient.
Hence `oolatoocs`, which takes a Mastodon Timeline and reposts it to Twitter as properly as possible. And since Bluesky seems to be hype right now, it also incorporates Bluesky support since v3.0.0.
## And now…
Hence `oolatoocs`, which takes a Mastodon Timeline and reposts it to Twitter as properly as possible. And since Bluesky seems to be hype right now, it also incorporates Bluesky support since v3.
Bluesky support is mandatory for now on: you cant have Twitter or Bluesky, you must have both. I might change this behaviour in a near future, especially when I will inevitably have to drop support for Twitter. If you just want Twitter support, just stick with v2.4.x release, itll get the job done exactly as the newer version for now.
@@ -16,8 +19,11 @@ What it can do:
* Reproduces the Toot content into the Tweet/Record;
* Cuts (poorly) the Toot in half in its too long for Twitter/Bluesky 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/Bluesky
* ⚠️ Bluesky does not support mixing images and videos. You can have up to 4 images on a Bsky record **or** 1 video but not mix around. If you do so, only the video will be posted on Bluesky.
* ⚠️ Bluesky does not support images greater than 1Mb (that is 1,000,000,000 bytes or 976.6 KiB). I might incorporate soon a image quality reducer or WebP transcoding to avoid this issue.
* Can reproduce threads from Mastodon to Twitter/Bluesky
* Can reproduce poll from Mastodon to Twitter/Bluesky
* Can reproduce poll from Mastodon to Twitter
* ⚠️ Bluesky does support polls for now. So the poll itself is just presented as text from Mastodon instead which is not the most elegant.
* Can prevent a Toot from being tweeted/recorded to Bluesky by using the #NoTweet (case-insensitive) hashtag in Mastodon
# Configuration file
@@ -26,7 +32,7 @@ The configuration is relatively easy to follow:
```toml
[oolatoocs]
db_path = "/var/lib/oolatoocs/db.sqlite3" # the path to the DB where toot/tweet are stored
db_path = "/var/lib/oolatoocs/db.sqlite3" # the path to the DB where toots/tweets/records are stored
[mastodon] # This part can be generated, see below
base = "https://m.nintendojo.fr"
@@ -41,9 +47,10 @@ consumer_secret = "<REDACTED>"
oauth_token = "<REDACTED>"
oauth_token_secret = "<REDACTED>"
[bluesky]
[bluesky] # this is your Bsky handle and password + a writable path for the session handling
handle = "nintendojofr.bsky.social"
password = "<REDACTED>"
config_path = "/var/lib/oolatoocs/bsky.json"
```
## How to generate the Mastodon keys?
@@ -64,7 +71,7 @@ Will I some day make a subcommand to generate it? Maybe…
## How to generate the Bluesky part?
Youll need your handle and password. I strongly recommend a dedicated application password.
Youll need your handle and password. I strongly recommend a dedicated application password. Youll also need a writable path to store the Bsky session.
# How to run

View File

@@ -3,11 +3,15 @@ use atrium_api::{
app::bsky::feed::post::RecordData, com::atproto::repo::upload_blob::Output,
types::string::Datetime, types::string::Language,
};
use bsky_sdk::{rich_text::RichText, BskyAgent};
use bsky_sdk::{
agent::config::{Config, FileStore},
rich_text::RichText,
BskyAgent,
};
use log::error;
use megalodon::entities::attachment::{Attachment, AttachmentType};
use regex::Regex;
use std::error::Error;
use std::{error::Error, fs::exists};
/// Intermediary struct to deal with replies more easily
#[derive(Debug)]
@@ -16,6 +20,34 @@ pub struct BskyReply {
pub root_record_uri: String,
}
pub async fn get_session(config: &BlueskyConfig) -> Result<BskyAgent, Box<dyn Error>> {
if exists(&config.config_path)? {
let bluesky = BskyAgent::builder()
.config(Config::load(&FileStore::new(&config.config_path)).await?)
.build()
.await?;
if bluesky.api.com.atproto.server.get_session().await.is_ok() {
bluesky
.to_config()
.await
.save(&FileStore::new(&config.config_path))
.await?;
return Ok(bluesky);
}
}
let bluesky = BskyAgent::builder().build().await?;
bluesky.login(&config.handle, &config.password).await?;
bluesky
.to_config()
.await
.save(&FileStore::new(&config.config_path))
.await?;
Ok(bluesky)
}
pub async fn build_post_record(
config: &BlueskyConfig,
text: &str,
@@ -27,10 +59,10 @@ pub async fn build_post_record(
let insert_chars = "";
let re = Regex::new(r#"(https?://)(\S{1,26})(\S*)"#).unwrap();
let re = Regex::new(r#"(https?://)(www\.)?(\S{1,26})(\S*)"#).unwrap();
while let Some(found) = re.captures(&rt.text.clone()) {
if let Some(group) = found.get(3) {
if let Some(group) = found.get(4) {
if !group.is_empty() {
rt.insert(group.start(), insert_chars);
rt.delete(
@@ -40,7 +72,8 @@ pub async fn build_post_record(
}
}
if let Some(group) = found.get(1) {
rt.delete(group.start(), group.start() + group.len());
let www: usize = found.get(2).map_or(0, |x| x.len());
rt.delete(group.start(), group.start() + www + group.len());
}
}
@@ -190,12 +223,13 @@ mod tests {
#[tokio::test]
async fn test_build_post_record() {
let text = "@factornews@piaille.fr Retrouvez-nous ici https://www.nintendojo.fr/articles/editos/le-mod-renovation-de-8bitdo-pour-manette-n64 et là https://www.nintendojo.fr/articles/analyses/vite-vu/vite-vu-morbid-the-lords-of-ire et un lien très court http://vsl.ie/TaMere";
let expected_text = "@factornews@piaille.fr Retrouvez-nous ici www.nintendojo.fr/articles… et là www.nintendojo.fr/articles… et un lien très court vsl.ie/TaMere";
let text = "@factornews@piaille.fr Retrouvez-nous ici https://www.nintendojo.fr/articles/editos/le-mod-renovation-de-8bitdo-pour-manette-n64 et là https://www.nintendojo.fr/articles/analyses/vite-vu/vite-vu-morbid-the-lords-of-ire et un lien très court http://vsl.ie/TaMere et un autre https://p.nintendojo.fr/w/kV3CBbKKt1nPEChHhZiNve + http://www.xxx.com + https://www.youtube.com/watch?v=dQw4w9WgXcQ&pp=ygUJcmljayByb2xs";
let expected_text = "@factornews@piaille.fr Retrouvez-nous ici nintendojo.fr/articles/edi… et là nintendojo.fr/articles/ana… et un lien très court vsl.ie/TaMere et un autre p.nintendojo.fr/w/kV3CBbKK… + xxx.com + youtube.com/watch?v=dQw4w9…";
let bsky_conf = BlueskyConfig {
handle: "tamerelol.bsky.social".to_string(),
password: "dtc".to_string(),
config_path: "nope".to_string(),
};
let created_record_data = build_post_record(&bsky_conf, text, &None, None, &None)

View File

@@ -35,6 +35,7 @@ pub struct MastodonConfig {
pub struct BlueskyConfig {
pub handle: String,
pub password: String,
pub config_path: String,
}
/// parses TOML file into Config struct

View File

@@ -1,4 +1,3 @@
use bsky_sdk::BskyAgent;
use log::debug;
mod error;
@@ -22,7 +21,7 @@ mod twitter;
use twitter::{delete_tweet, generate_media_ids, post_tweet, transform_poll};
mod bsky;
use bsky::{build_post_record, generate_media_records, BskyReply};
use bsky::{build_post_record, generate_media_records, get_session, BskyReply};
use rusqlite::Connection;
@@ -33,10 +32,9 @@ pub async fn run(config: &Config) {
let mastodon = get_mastodon_instance(&config.mastodon);
let bluesky = BskyAgent::builder()
.build()
let bluesky = get_session(&config.bluesky)
.await
.unwrap_or_else(|e| panic!("Cant build Bsky Agent: {}", e));
.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));
@@ -64,13 +62,6 @@ pub async fn run(config: &Config) {
panic!("Cannot delete Tweet ID ({}): {}", t.tweet_id, e)
});
}
debug!("Create Bsky session prior to deletion");
bluesky
.login(&config.bluesky.handle, &config.bluesky.password)
.await
.unwrap_or_else(|e| panic!("Cannot login to Bsky: {}", e));
for local_record_uri in local_record_uris.into_iter() {
bluesky
.delete_record(&local_record_uri)
@@ -108,14 +99,6 @@ pub async fn run(config: &Config) {
continue; // skip in case we cant strip something
};
debug!("Now we need a valid Bsky session, creating it");
if bluesky.api.com.atproto.server.get_session().await.is_err() {
bluesky
.login(&config.bluesky.handle, &config.bluesky.password)
.await
.unwrap_or_else(|e| panic!("Cannot login to Bsky: {}", e));
}
// threads if necessary
let (mut tweet_reply_to, mut record_reply_to) = toot
.in_reply_to_id