13 Commits

Author SHA1 Message Date
VC
3413f49d08 Update README.md 2023-07-13 09:02:57 +00:00
VC
d4fbccc69b Merge branch 'youpi' into 'master'
Update file README.md

See merge request veretcle/scootaloo!57
2023-07-13 08:57:08 +00:00
VC
058a07865d Update file README.md 2023-07-13 08:53:54 +00:00
VC
c6a29d1d7d Merge branch 'doc_correct' into 'master'
doc: update example regexp

See merge request veretcle/scootaloo!55
2023-02-10 08:53:08 +00:00
VC
3692d6e51f doc: update example regexp 2023-02-10 09:40:42 +01:00
VC
5fe57f189a Merge branch 'feat_wait_for_upload' into 'master'
Feat wait for upload

See merge request veretcle/scootaloo!54
2023-02-09 14:51:14 +00:00
VC
83c398cebf feat: wait 1 full sec between loop when uploading big media 2023-02-09 15:19:22 +01:00
VC
8f567ed6b4 chore: bump version 2023-02-09 15:18:12 +01:00
VC
d7431862ba Merge branch 'fix_copy_lang' into 'master'
Fix copy lang

See merge request veretcle/scootaloo!53
2023-02-09 14:12:25 +00:00
VC
f3b13eb62f refactor: conforms to clippy 1.67 recommandations 2023-02-09 11:32:22 +01:00
VC
6b68c8e299 fix: no need for defaults with clap v4 2023-02-09 11:31:58 +01:00
VC
0bb5eabdac fix: copy the original lang from Twitter to Mastodon 2023-02-09 10:58:34 +01:00
VC
3d44bbfb86 refactor: remove isolang, bump version 2023-02-09 10:58:12 +01:00
10 changed files with 34 additions and 70 deletions

36
Cargo.lock generated
View File

@@ -776,15 +776,6 @@ dependencies = [
"windows-sys 0.42.0", "windows-sys 0.42.0",
] ]
[[package]]
name = "isolang"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b64fd6448ee8a45ce6e4365c58e4fa7d8740cba2ed70db3e9ab4879ebd93eaaa"
dependencies = [
"phf",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.5" version = "1.0.5"
@@ -1082,24 +1073,6 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "phf"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [
"phf_shared",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [
"siphasher",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.9" version = "0.2.9"
@@ -1343,14 +1316,13 @@ dependencies = [
[[package]] [[package]]
name = "scootaloo" name = "scootaloo"
version = "1.1.4" version = "1.1.6"
dependencies = [ dependencies = [
"base64", "base64",
"clap", "clap",
"egg-mode", "egg-mode",
"futures", "futures",
"html-escape", "html-escape",
"isolang",
"log", "log",
"megalodon", "megalodon",
"mime", "mime",
@@ -1523,12 +1495,6 @@ dependencies = [
"winapi", "winapi",
] ]
[[package]]
name = "siphasher"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.7" version = "0.4.7"

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "scootaloo" name = "scootaloo"
version = "1.1.4" version = "1.1.6"
authors = ["VC <veretcle+framagit@mateu.be>"] authors = ["VC <veretcle+framagit@mateu.be>"]
edition = "2021" edition = "2021"
@@ -14,7 +14,6 @@ toml = "^0.5"
clap = "^4" clap = "^4"
egg-mode = "^0.16" egg-mode = "^0.16"
rusqlite = "^0.27" rusqlite = "^0.27"
isolang = "^2"
tokio = { version = "^1", features = ["rt"]} tokio = { version = "^1", features = ["rt"]}
futures = "^0.3" futures = "^0.3"
megalodon = "^0.3.6" megalodon = "^0.3.6"

View File

@@ -1,3 +1,9 @@
**Due to the new Twitter policy about API v1.1 and API v2, this project will no longer be updated as it no longer can work for free any way, shape or form.**
First of all, Im deeply sorry about that. It worked pretty great for what it did with a level of quality Im very proud to have achieved.
Secondly, fuck you Musk, fuck you.
A Twitter to Mastodon copy bot written in Rust A Twitter to Mastodon copy bot written in Rust
It: It:
@@ -25,7 +31,7 @@ rate_limiting = 4 ## optional, default 4, number of accounts handled simultaneou
## this parameter allows you to catch such URLs and apply the `display_url` (i.e. `tout.es`) instead of the `expanded_url` (i.e. `http://tout.es`) ## this parameter allows you to catch such URLs and apply the `display_url` (i.e. `tout.es`) instead of the `expanded_url` (i.e. `http://tout.es`)
## in those particular cases ## in those particular cases
## (!) use with caution, it might have some undesired effects ## (!) use with caution, it might have some undesired effects
show_url_as_display_url_for = "^http(s)://(.+)\\.es$" show_url_as_display_url_for = "^https?://(.+)\\.es$"
## optional, this allows you to replace the host for popular services such as YouTube of Twitter, or any other ## optional, this allows you to replace the host for popular services such as YouTube of Twitter, or any other
## with their more freely accessible equivalent ## with their more freely accessible equivalent
[scootaloo.alternative_services_for] [scootaloo.alternative_services_for]

View File

@@ -43,10 +43,10 @@ pub struct ScootalooConfig {
/// Parses the TOML file into a Config Struct /// Parses the TOML file into a Config Struct
pub fn parse_toml(toml_file: &str) -> Config { pub fn parse_toml(toml_file: &str) -> Config {
let toml_config = read_to_string(toml_file) let toml_config = read_to_string(toml_file)
.unwrap_or_else(|e| panic!("Cannot open config file {}: {}", toml_file, e)); .unwrap_or_else(|e| panic!("Cannot open config file {toml_file}: {e}"));
let config: Config = toml::from_str(&toml_config) let config: Config = toml::from_str(&toml_config)
.unwrap_or_else(|e| panic!("Cannot parse TOML file {}: {}", toml_file, e)); .unwrap_or_else(|e| panic!("Cannot parse TOML file {toml_file}: {e}"));
config config
} }

View File

@@ -30,12 +30,12 @@ impl Display for ScootalooError {
impl From<Box<dyn Error>> for ScootalooError { impl From<Box<dyn Error>> for ScootalooError {
fn from(error: Box<dyn Error>) -> Self { fn from(error: Box<dyn Error>) -> Self {
ScootalooError::new(&format!("Error in a subset crate: {}", error)) ScootalooError::new(&format!("Error in a subset crate: {error}"))
} }
} }
impl From<megalodonError> for ScootalooError { impl From<megalodonError> for ScootalooError {
fn from(error: megalodonError) -> Self { fn from(error: megalodonError) -> Self {
ScootalooError::new(&format!("Error in megalodon crate: {}", error)) ScootalooError::new(&format!("Error in megalodon crate: {error}"))
} }
} }

View File

@@ -21,7 +21,6 @@ use state::{read_state, write_state, TweetToToot};
use futures::StreamExt; use futures::StreamExt;
use html_escape::decode_html_entities; use html_escape::decode_html_entities;
use isolang::Language;
use log::info; use log::info;
use megalodon::{ use megalodon::{
megalodon::PostStatusInputOptions, megalodon::UpdateCredentialsInputOptions, Megalodon, megalodon::PostStatusInputOptions, megalodon::UpdateCredentialsInputOptions, Megalodon,
@@ -204,9 +203,7 @@ pub async fn run(config: Config) {
// language if any // language if any
if let Some(l) = &tweet.lang { if let Some(l) = &tweet.lang {
if let Some(r) = Language::from_639_1(l) { post_status.language = Some(l.to_string());
post_status.language = Some(r.to_string());
}
} }
// can be activated for test purposes // can be activated for test purposes
@@ -238,8 +235,8 @@ pub async fn run(config: Config) {
// launch and wait for every handle // launch and wait for every handle
while let Some(result) = stream.next().await { while let Some(result) = stream.next().await {
match result { match result {
Ok(Err(e)) => eprintln!("Error within thread: {}", e), Ok(Err(e)) => eprintln!("Error within thread: {e}"),
Err(e) => eprintln!("Error with thread: {}", e), Err(e) => eprintln!("Error with thread: {e}"),
_ => (), _ => (),
} }
} }
@@ -307,8 +304,8 @@ pub async fn profile(config: Config, bot: Option<bool>) {
while let Some(result) = stream.next().await { while let Some(result) = stream.next().await {
match result { match result {
Ok(Err(e)) => eprintln!("Error within thread: {}", e), Ok(Err(e)) => eprintln!("Error within thread: {e}"),
Err(e) => eprintln!("Error with thread: {}", e), Err(e) => eprintln!("Error with thread: {e}"),
_ => (), _ => (),
} }
} }

View File

@@ -63,10 +63,7 @@ fn main() {
.short('c') .short('c')
.long("config") .long("config")
.value_name("CONFIG_FILE") .value_name("CONFIG_FILE")
.help(format!( .help("TOML config file for scootaloo")
"TOML config file for scootaloo (default {})",
DEFAULT_CONFIG_PATH
))
.default_value(DEFAULT_CONFIG_PATH) .default_value(DEFAULT_CONFIG_PATH)
.num_args(1) .num_args(1)
.display_order(1), .display_order(1),
@@ -81,7 +78,7 @@ fn main() {
.short('c') .short('c')
.long("config") .long("config")
.value_name("CONFIG_FILE") .value_name("CONFIG_FILE")
.help(format!("TOML config file for scootaloo (default {})", DEFAULT_CONFIG_PATH)) .help("TOML config file for scootaloo")
.default_value(DEFAULT_CONFIG_PATH) .default_value(DEFAULT_CONFIG_PATH)
.num_args(1) .num_args(1)
.display_order(1), .display_order(1),
@@ -104,7 +101,7 @@ fn main() {
.short('c') .short('c')
.long("config") .long("config")
.value_name("CONFIG_FILE") .value_name("CONFIG_FILE")
.help(format!("TOML config file for scootaloo (default {})", DEFAULT_CONFIG_PATH)) .help("TOML config file for scootaloo")
.default_value(DEFAULT_CONFIG_PATH) .default_value(DEFAULT_CONFIG_PATH)
.num_args(1) .num_args(1)
.display_order(1), .display_order(1),

View File

@@ -83,7 +83,7 @@ pub fn associate_urls(urls: &[UrlEntity], re: &Option<Regex>) -> HashMap<String,
pub fn replace_alt_services(urls: &mut HashMap<String, String>, alts: &HashMap<String, String>) { pub fn replace_alt_services(urls: &mut HashMap<String, String>, alts: &HashMap<String, String>) {
for val in urls.values_mut() { for val in urls.values_mut() {
for (k, v) in alts { for (k, v) in alts {
*val = val.replace(&format!("/{}/", k), &format!("/{}/", v)); *val = val.replace(&format!("/{k}/"), &format!("/{v}/"));
} }
} }
} }
@@ -120,7 +120,7 @@ pub fn replace_tweet_by_toot(
twitter_screen_name.to_lowercase(), twitter_screen_name.to_lowercase(),
tweet_id tweet_id
)) { )) {
*val = format!("{}/@{}/{}", base_url, mastodon_screen_name, toot_id); *val = format!("{base_url}/@{mastodon_screen_name}/{toot_id}");
} }
} }
} }
@@ -184,7 +184,7 @@ pub async fn register(host: &str, screen_name: &str) {
let url = app_data.url.expect("Cannot generate registration URI!"); let url = app_data.url.expect("Cannot generate registration URI!");
println!("Click this link to authorize on Mastodon: {}", url); println!("Click this link to authorize on Mastodon: {url}");
println!("Paste the returned authorization code: "); println!("Paste the returned authorization code: ");
let mut input = String::new(); let mut input = String::new();

View File

@@ -21,8 +21,8 @@ pub fn read_state(
) -> Result<Option<TweetToToot>, Box<dyn Error>> { ) -> Result<Option<TweetToToot>, Box<dyn Error>> {
debug!("Reading tweet_id {:?}", s); debug!("Reading tweet_id {:?}", s);
let query: String = match s { let query: String = match s {
Some(i) => format!("SELECT * FROM tweet_to_toot WHERE tweet_id = {} and twitter_screen_name = \"{}\"", i, n), Some(i) => format!("SELECT * FROM tweet_to_toot WHERE tweet_id = {i} and twitter_screen_name = \"{n}\""),
None => format!("SELECT * FROM tweet_to_toot WHERE twitter_screen_name = \"{}\" ORDER BY tweet_id DESC LIMIT 1", n), None => format!("SELECT * FROM tweet_to_toot WHERE twitter_screen_name = \"{n}\" ORDER BY tweet_id DESC LIMIT 1"),
}; };
let mut stmt = conn.prepare(&query)?; let mut stmt = conn.prepare(&query)?;
@@ -78,8 +78,7 @@ pub fn migrate_db(d: &str, s: &str) -> Result<(), Box<dyn Error>> {
&format!( &format!(
"ALTER TABLE tweet_to_toot "ALTER TABLE tweet_to_toot
ADD COLUMN twitter_screen_name TEXT NOT NULL ADD COLUMN twitter_screen_name TEXT NOT NULL
DEFAULT \"{}\"", DEFAULT \"{s}\""
s
), ),
[], [],
); );

View File

@@ -15,6 +15,7 @@ use std::error::Error;
use tokio::{ use tokio::{
fs::{create_dir_all, remove_file, File}, fs::{create_dir_all, remove_file, File},
io::copy, io::copy,
time::{sleep, Duration},
}; };
/// Generate associative table between media ids and tweet extended entities /// Generate associative table between media ids and tweet extended entities
@@ -85,6 +86,7 @@ pub async fn generate_media_ids(
/// Wait on uploaded medias when necessary /// Wait on uploaded medias when necessary
async fn wait_until_uploaded(client: &Mastodon, id: &str) -> Result<String, error::Error> { async fn wait_until_uploaded(client: &Mastodon, id: &str) -> Result<String, error::Error> {
loop { loop {
sleep(Duration::from_secs(1)).await;
let res = client.get_media(id.to_string()).await; let res = client.get_media(id.to_string()).await;
return match res { return match res {
Ok(res) => Ok(res.json.id), Ok(res) => Ok(res.json.id),
@@ -112,12 +114,12 @@ pub async fn base64_media(u: &str) -> Result<String, Box<dyn Error>> {
let content_type = response let content_type = response
.headers() .headers()
.get("content-type") .get("content-type")
.ok_or_else(|| ScootalooError::new(&format!("Cannot get media content type for {}", u)))? .ok_or_else(|| ScootalooError::new(&format!("Cannot get media content type for {u}")))?
.to_str()?; .to_str()?;
let encoded_f = encode(buffer); let encoded_f = encode(buffer);
Ok(format!("data:{};base64,{}", content_type, encoded_f)) Ok(format!("data:{content_type};base64,{encoded_f}"))
} }
/// Gets and caches Twitter Media inside the determined temp dir /// Gets and caches Twitter Media inside the determined temp dir
@@ -134,19 +136,17 @@ pub async fn cache_media(u: &str, t: &str) -> Result<String, Box<dyn Error>> {
.path_segments() .path_segments()
.ok_or_else(|| { .ok_or_else(|| {
ScootalooError::new(&format!( ScootalooError::new(&format!(
"Cannot determine the destination filename for {}", "Cannot determine the destination filename for {u}"
u
)) ))
})? })?
.last() .last()
.ok_or_else(|| { .ok_or_else(|| {
ScootalooError::new(&format!( ScootalooError::new(&format!(
"Cannot determine the destination filename for {}", "Cannot determine the destination filename for {u}"
u
)) ))
})?; })?;
let dest_filepath = format!("{}/{}", t, dest_filename); let dest_filepath = format!("{t}/{dest_filename}");
let mut dest_file = File::create(&dest_filepath).await?; let mut dest_file = File::create(&dest_filepath).await?;