17 Commits

Author SHA1 Message Date
VC
11b629203b Merge branch 'bump_versions' into 'master'
Bump versions of all dependencies

See merge request veretcle/scootaloo!15
2022-04-22 08:31:36 +00:00
VC
16792e515a refactor(ci): refactor .gitlab-ci to add tests 2022-04-22 09:41:53 +02:00
VC
d228ceaaf6 refactor(dependencies): bump version of all dependencies 2022-04-22 09:41:48 +02:00
VC
bd7d4dbbb5 Merge branch 'fix/unused_struct' into 'master'
fix: remove unused struct

See merge request veretcle/scootaloo!13
2022-04-12 11:44:04 +00:00
VC
ff03b32f9d fix: remove unused struct 2022-04-12 12:55:40 +02:00
VC
533a40f2c2 Merge branch 'noasync' into 'master'
getting async in a reasonnable way

See merge request veretcle/scootaloo!12
2021-04-25 05:32:09 +00:00
VC
c301649d49 last correction 2022-04-12 12:03:49 +02:00
VC
fd9cc31848 Update src/lib.rs 2021-04-24 08:04:52 +00:00
VC
4ef58bda0a Deleted Cargo.lock 2021-04-24 07:58:52 +00:00
VC
912ee25c50 Merge branch 'noasync' of framagit.org:veretcle/scootaloo into noasync 2021-04-24 09:45:50 +02:00
VC
4f03a1a6f3 Adding Cargo.lock 2021-04-24 09:43:51 +02:00
VC
ac80b67c9f Merge branch 'master' into 'noasync'
# Conflicts:
#   .gitlab-ci.yml
#   Cargo.lock
#   Cargo.toml
#   src/lib.rs
2021-04-24 07:40:04 +00:00
VC
7aec8e0e33 adding strip 2021-04-24 09:34:37 +02:00
VC
f58edf3c75 Backporting changes on gitlab-ci.yml 2021-04-24 09:15:31 +02:00
VC
394ec5d1f3 I consider this a good compromise between all async (that works but is pretty complex and honestly a bit useless) and nothing async that is not the most optimal way to deal with things as reqwest and egg-mode are async by nature 2021-04-24 09:08:09 +02:00
VC
c10de76854 Adding logging facility 2021-04-24 09:07:06 +02:00
VC
020af69fe0 Adding the necessary bits and pieces to make things work better 2021-04-24 09:04:50 +02:00
4 changed files with 572 additions and 562 deletions

View File

@@ -1,13 +1,15 @@
---
stages: stages:
- build - build
rust-latest: rust-latest:
stage: build stage: build
artifacts: artifacts:
paths: paths:
- target/release/scootaloo - target/release/scootaloo
image: rust:latest image: rust:latest
script: script:
- cargo build --release --verbose - cargo test
- strip target/release/${CI_PROJECT_NAME} - cargo build --release --verbose
- strip target/release/${CI_PROJECT_NAME}

994
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,21 @@
[package] [package]
name = "scootaloo" name = "scootaloo"
version = "0.3.3" version = "0.4.2"
authors = ["VC <veretcle+framagit@mateu.be>"] authors = ["VC <veretcle+framagit@mateu.be>"]
edition = "2018" 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
[dependencies] [dependencies]
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
toml = "^0.5" toml = "^0.5"
clap = "^2.33" clap = "^2.34"
egg-mode = { git = "https://github.com/egg-mode-rs/egg-mode", rev = "6b81073eba9c3b123ca0e80bdb5ef61d1758f131" } futures = "^0.3"
elefren = "^0.22" egg-mode = "^0.16"
tokio = { version = "1", features = ["full"]} tokio = { version = "1", features = ["full"]}
reqwest = "^0.11" elefren = "^0.22"
htmlescape = "^0.3" htmlescape = "^0.3"
reqwest = "^0.11"
log = "^0.4" log = "^0.4"
simple_logger = "^1.11" simple_logger = "^2.1"
[profile.release]
opt-level = 's' # Optimize for size.
lto = true # Link Time Optimization (LTO)
codegen-units = 1 # Set this to 1 to allow for maximum size reduction optimizations:
panic = 'abort' # removes the need for this extra unwinding code.

View File

@@ -6,7 +6,6 @@ use std::{
fmt, fmt,
fs::{read_to_string, write}, fs::{read_to_string, write},
error::Error, error::Error,
sync::{Arc, Mutex},
}; };
// toml // toml
@@ -39,7 +38,6 @@ use reqwest::Url;
use tokio::{ use tokio::{
io::copy, io::copy,
fs::{File, create_dir_all, remove_file}, fs::{File, create_dir_all, remove_file},
sync::mpsc,
}; };
// htmlescape // htmlescape
@@ -133,10 +131,10 @@ async fn get_tweet_media(m: &MediaEntity, t: &str) -> Result<String, Box<dyn Err
return cache_media(&variant.url, t).await; return cache_media(&variant.url, t).await;
} }
} }
return Err(Box::new(ScootalooError::new(format!("Media Type for {} is video but no mp4 file URL is available", &m.url).as_str()))); return Err(ScootalooError::new(&format!("Media Type for {} is video but no mp4 file URL is available", &m.url)).into());
}, },
None => { None => {
return Err(Box::new(ScootalooError::new(format!("Media Type for {} is video but does not contain any video_info", &m.url).as_str()))); return Err(ScootalooError::new(&format!("Media Type for {} is video but does not contain any video_info", &m.url)).into());
}, },
} }
}, },
@@ -185,7 +183,6 @@ fn build_basic_status(tweet: &Tweet) -> Result<String, Box<dyn Error>> {
/* /*
* Generic private functions * Generic private functions
*/ */
/// Gets and caches Twitter Media inside the determined temp dir /// Gets and caches Twitter Media inside the determined temp dir
async fn cache_media(u: &str, t: &str) -> Result<String, Box<dyn Error>> { async fn cache_media(u: &str, t: &str) -> Result<String, Box<dyn Error>> {
// create dir // create dir
@@ -196,8 +193,8 @@ async fn cache_media(u: &str, t: &str) -> Result<String, Box<dyn Error>> {
// create local file // create local file
let url = Url::parse(u)?; let url = Url::parse(u)?;
let dest_filename = url.path_segments().ok_or_else(|| Box::new(ScootalooError::new(format!("Cannot determine the destination filename for {}", u).as_str())))? let dest_filename = url.path_segments().ok_or_else(|| ScootalooError::new(&format!("Cannot determine the destination filename for {}", u)))?
.last().ok_or_else(|| Box::new(ScootalooError::new(format!("Cannot determine the destination filename for {}", u).as_str())))?; .last().ok_or_else(|| ScootalooError::new(&format!("Cannot determine the destination filename for {}", u)))?;
let dest_filepath = format!("{}/{}", t, dest_filename); let dest_filepath = format!("{}/{}", t, dest_filename);
@@ -210,15 +207,6 @@ async fn cache_media(u: &str, t: &str) -> Result<String, Box<dyn Error>> {
Ok(dest_filepath) Ok(dest_filepath)
} }
/**********
* This is the struct that holds the Mastodon Media ID and the Twitter Media URL at the same Time
**********/
#[derive(Debug)]
struct ScootalooSpawnResponse {
mastodon_media_id: String,
twitter_media_url: String,
}
/********** /**********
* local error handler * local error handler
**********/ **********/
@@ -337,7 +325,7 @@ pub async fn run(config: Config) {
let token = get_oauth2_token(&config); let token = get_oauth2_token(&config);
// get Mastodon instance // get Mastodon instance
let mastodon = Arc::new(Mutex::new(get_mastodon_token(&config.mastodon))); let mastodon = get_mastodon_token(&config.mastodon);
// get user timeline feed (Vec<tweet>) // get user timeline feed (Vec<tweet>)
let mut feed = get_user_timeline(&config, token, last_tweet_id) let mut feed = get_user_timeline(&config, token, last_tweet_id)
@@ -377,61 +365,34 @@ pub async fn run(config: Config) {
let mut status_medias: Vec<String> = vec![]; let mut status_medias: Vec<String> = vec![];
// reupload the attachments if any // reupload the attachments if any
if let Some(m) = &tweet.extended_entities { if let Some(m) = &tweet.extended_entities {
let (tx, mut rx) = mpsc::channel(4);
for media in &m.media { for media in &m.media {
// creating a new tx for this initial loop let local_tweet_media_path = match get_tweet_media(&media, &config.scootaloo.cache_path).await {
let tx = tx.clone(); Ok(m) => m,
// creating a new mastodon from the original mutex Err(e) => {
let mastodon = mastodon.clone(); error!("Cannot get tweet media for {}: {}", &media.url, e);
// unfortunately for this to be thread safe, we need to clone a lot of structures continue;
let media = media.clone(); },
let cache_path = config.scootaloo.cache_path.clone(); };
tokio::spawn(async move { let mastodon_media_ids = match mastodon.media(Cow::from(String::from(&local_tweet_media_path))) {
debug!("Spawing new async thread to treat {}", &media.id); Ok(m) => {
let local_tweet_media_path = match get_tweet_media(&media, &cache_path).await { remove_file(&local_tweet_media_path).await.unwrap_or_else(|e|
Ok(m) => m, warn!("Attachment for {} has been uploaded, but Im unable to remove the existing file: {}", &local_tweet_media_path, e)
Err(e) => { );
// we could have panicked here, no issue, but Im not confortable using m.id
// that for now },
warn!("Cannot get tweet media for {}: {}", &media.url, e); Err(e) => {
return; error!("Attachment {} cannot be uploaded to Mastodon Instance: {}", &local_tweet_media_path, e);
} continue;
};
// we cannot directly do all the stuff inside here because mastodon lock can
// live outside this
let mas_result = mastodon.lock().unwrap().media(Cow::from(String::from(&local_tweet_media_path)));
match mas_result {
Ok(m) => {
remove_file(&local_tweet_media_path).await.unwrap_or_else(|e|
warn!("Attachment {} has been uploaded but Im unable to remove the existing file: {}", &local_tweet_media_path, e)
);
// we can unwrap here because were in a thread
tx.send(ScootalooSpawnResponse {
mastodon_media_id: m.id.clone(),
twitter_media_url: local_tweet_media_path.clone()
}).await.unwrap();
},
Err(e) => {
error!("Attachment {} cannot be uploaded to Mastodon Instance: {}", &local_tweet_media_path, e);
}
} }
}); };
}
// dropping the last tx otherwise recv() will wait indefinitely status_medias.push(mastodon_media_ids);
drop(tx);
while let Some(i) = rx.recv().await { // last step, removing the reference to the media from with the toots text
// pushes the media into the media vec status_text = status_text.replace(&media.url, "");
status_medias.push(i.mastodon_media_id);
// removes the URL from the original Tweet text
status_text = status_text.replace(&i.twitter_media_url, "");
} }
} }
// finished reuploading attachments, now lets do the toot baby! // finished reuploading attachments, now lets do the toot baby!
@@ -441,11 +402,11 @@ pub async fn run(config: Config) {
.status(&status_text) .status(&status_text)
.media_ids(status_medias) .media_ids(status_medias)
.build() .build()
.expect(format!("Cannot build status with text {}", &status_text).as_str()); .expect(&format!("Cannot build status with text {}", &status_text));
// publish status // publish status
// again unwrap is safe here as we are in the main thread // again unwrap is safe here as we are in the main thread
mastodon.lock().unwrap().new_status(status).unwrap(); mastodon.new_status(status).unwrap();
// this will panic if it cannot publish the status, which is a good thing, it allows the // this will panic if it cannot publish the status, which is a good thing, it allows the
// last_tweet gathered not to be written // last_tweet gathered not to be written