Merge branch 'feature/unharcoding' into develop

This commit is contained in:
Alie 2023-07-14 11:12:36 +02:00
commit 985cea9894
6 changed files with 273 additions and 111 deletions

4
.gitignore vendored
View File

@ -1,4 +1,6 @@
/target
mastodon-data.toml
*.csv
.tmp
*.tmp
*.log
tmp/

129
Cargo.lock generated
View File

@ -30,7 +30,7 @@ checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@ -135,7 +135,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@ -146,7 +146,7 @@ checksum = "dcdbcee2d9941369faba772587a565f4f534e42cb8d17e5295871de730163b2b"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@ -164,6 +164,12 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "erased-serde"
version = "0.3.24"
@ -277,7 +283,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@ -339,7 +345,7 @@ dependencies = [
"futures-sink",
"futures-util",
"http",
"indexmap",
"indexmap 1.9.2",
"slab",
"tokio",
"tokio-util",
@ -352,6 +358,12 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hashbrown"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
[[package]]
name = "hermit-abi"
version = "0.2.6"
@ -449,7 +461,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown",
"hashbrown 0.12.3",
]
[[package]]
name = "indexmap"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
dependencies = [
"equivalent",
"hashbrown 0.14.0",
]
[[package]]
@ -542,7 +564,7 @@ dependencies = [
"time",
"tokio",
"tokio-util",
"toml",
"toml 0.5.11",
"url",
"uuid",
]
@ -567,8 +589,10 @@ version = "0.1.0"
dependencies = [
"mastodon-async",
"reqwest",
"serde",
"tokio",
"tokio-test",
"toml 0.7.6",
]
[[package]]
@ -662,7 +686,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@ -740,9 +764,9 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "proc-macro2"
version = "1.0.51"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
checksum = "78803b62cbf1f46fde80d7c0e803111524b9877184cfe7c3033659490ac7a7da"
dependencies = [
"unicode-ident",
]
@ -760,9 +784,9 @@ dependencies = [
[[package]]
name = "quote"
version = "1.0.23"
version = "1.0.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105"
dependencies = [
"proc-macro2",
]
@ -900,22 +924,22 @@ dependencies = [
[[package]]
name = "serde"
version = "1.0.152"
version = "1.0.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.152"
version = "1.0.171"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 2.0.25",
]
[[package]]
@ -949,6 +973,15 @@ dependencies = [
"thiserror",
]
[[package]]
name = "serde_spanned"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
dependencies = [
"serde",
]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
@ -1027,6 +1060,17 @@ dependencies = [
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e3fc8c0c74267e2df136e5e5fb656a464158aa57624053375eb9c8c6e25ae2"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tap-reader"
version = "1.0.1"
@ -1064,7 +1108,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@ -1135,7 +1179,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
]
[[package]]
@ -1195,6 +1239,40 @@ dependencies = [
"serde",
]
[[package]]
name = "toml"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
dependencies = [
"serde",
"serde_spanned",
"toml_datetime",
"toml_edit",
]
[[package]]
name = "toml_datetime"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
dependencies = [
"serde",
]
[[package]]
name = "toml_edit"
version = "0.19.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f8751d9c1b03c6500c387e96f81f815a4f8e72d142d2d4a9ffa6fedd51ddee7"
dependencies = [
"indexmap 2.0.0",
"serde",
"serde_spanned",
"toml_datetime",
"winnow",
]
[[package]]
name = "tower-service"
version = "0.3.2"
@ -1351,7 +1429,7 @@ dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
"wasm-bindgen-shared",
]
@ -1385,7 +1463,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
"syn",
"syn 1.0.107",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
@ -1531,6 +1609,15 @@ version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winnow"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81fac9742fd1ad1bd9643b991319f72dd031016d44b77039a26977eb667141e7"
dependencies = [
"memchr",
]
[[package]]
name = "winreg"
version = "0.10.1"

View File

@ -9,6 +9,8 @@ edition = "2021"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
tokio-test = "0.4.2"
reqwest = "0.11.14"
serde = "1.0.171"
toml = "0.7.1"
[dependencies.mastodon-async]
version = "1.0"

12
config.toml Normal file
View File

@ -0,0 +1,12 @@
[bot]
name = "dev-sleeping-girls-bot"
instance = "https://awoo.fai.st"
bio = "Bot who posts images of sleeping girls every 6 hours."
[files]
urls = "./urls.csv"
posted = "./posted.csv"
tempfile = "./.tmp"
[errors]
out_of_images = "@Sugui@awoo.fai.st @MeDueleLaTeta@awoo.fai.st me quedé sin chicas"

10
run_with_log.sh Executable file
View File

@ -0,0 +1,10 @@
#!/bin/sh
mastodon-image-uploader-bot >./tmp/log.out 2>./tmp/log.err
if [ ! -s ./tmp/log.err ]; then
echo -n "$(date +"[%Y-%M-%d %T]") success: " >> ./bot.log
cat ./tmp/log.out >> ./bot.log
else
echo -n "$(date +"[%Y-%M-%d %T]") errors: " >> ./bot.log
cat ./tmp/log.err >> ./bot.log
fi

View File

@ -1,116 +1,165 @@
use std::collections::HashSet;
use std::process::Command;
use mastodon_async::helpers::toml; use mastodon_async::scopes;
use mastodon_async::{prelude::*};
use mastodon_async::{entities::visibility::Visibility};
use mastodon_async::entities::visibility::Visibility;
use mastodon_async::helpers::toml as masto_toml;
use mastodon_async::prelude::*;
use mastodon_async::{helpers::cli, Result};
use reqwest;
use serde::Deserialize;
use std::collections::HashSet;
use std::io::Cursor;
use std::io::Write;
use std::process::Command;
use toml;
const CONFIG_FILENAME: &str = "config.toml";
#[derive(Deserialize)]
struct Config {
bot: Bot,
files: Files,
errors: Errors,
}
#[derive(Deserialize)]
struct Bot {
name: String,
instance: String,
bio: String,
}
#[derive(Deserialize)]
struct Files {
posted: String,
urls: String,
tempfile: String,
}
#[derive(Deserialize)]
struct Errors {
out_of_images: String,
}
/// Parses the given filename to a config struct
fn parse_config(filename: &str) -> Config {
let toml_file = std::fs::read_to_string(filename)
.expect("No config file, consider getting the original one and modifing it");
toml::from_str(&toml_file).expect("Malformed config file, check the original one for reference")
}
#[tokio::main] // requires `features = ["mt"]
async fn main() -> Result<()> {
let mastodon = if let Ok(data) = toml::from_file("mastodon-data.toml") {
Mastodon::from(data)
} else {
register().await?
};
let config: Config = parse_config(CONFIG_FILENAME);
let mastodon = if let Ok(data) = masto_toml::from_file("mastodon-data.toml") {
Mastodon::from(data)
} else {
register(&config).await?
};
/*
let data = Data::default();
let client = Mastodon::from(data);
let statuses = client.statuses(&AccountId::new("user-id"), Default::default()).await.unwrap();
dbg!(statuses);
*/
match get_next_url() {
Some(url) => {
post_image(&mastodon, &url).await;
update_bio(&mastodon).await;
},
None => post(&mastodon, "@Sugui@awoo.fai.st @MeDueleLaTeta@awoo.fai.st me quedé sin chicas").await
};
Ok(())
}
fn get_next_url() -> Option<String> {
let binding = std::fs::read("./posted.csv").expect("File not found").iter().map(|c| *c as char).collect::<String>();
let posted = binding.lines().collect::<HashSet<_>>();
let binding = std::fs::read("./urls.csv").expect("File not found").iter().map(|c| *c as char).collect::<String>();
let urls = binding.lines().collect::<HashSet<_>>();
let urls = urls.difference(&posted).collect::<Vec<_>>();
if urls.is_empty() {
None
} else {
let mut file = std::fs::OpenOptions::new()
.write(true)
.append(true) // This is needed to append to file
.open("./posted.csv")
.unwrap();
write!(file, "{}\n", urls[0]).unwrap();
Some(urls[0].to_string().clone())
match get_next_url(&config) {
Some(url) => {
post_image(&mastodon, &url, &config).await;
update_bio(&mastodon, &config).await;
}
None => post(&mastodon, &config.errors.out_of_images).await,
};
Ok(())
}
async fn post_image(account: &Mastodon, url: &String) {
fetch_url(url.to_string(), ".tmp".to_string()).await.unwrap();
let attachment = account.media("./.tmp", Some(url.to_string())).await.expect("upload");
let attachment = account.wait_for_processing(attachment, Default::default()).await.expect("processing");
let status = StatusBuilder::new()
.media_ids(&[attachment.id])
.visibility(Visibility::Unlisted)
.sensitive(true)
.build()
.unwrap();
account.new_status(status).await.unwrap();
fn get_next_url(config: &Config) -> Option<String> {
let binding = std::fs::read_to_string(&config.files.posted).expect("Posted file not found");
let posted = binding.lines().collect::<HashSet<&str>>();
let binding = std::fs::read_to_string(&config.files.urls).expect("Urls file not found");
let urls = binding.lines().collect::<HashSet<&str>>();
let urls = urls.difference(&posted).collect::<Vec<_>>();
if urls.is_empty() {
None
} else {
let mut file = std::fs::OpenOptions::new()
.write(true)
.append(true) // This is needed to append to file
.open(&config.files.posted)
.expect("Cannot open posted file"); // Maybe we should retry just in case
write!(file, "{}\n", urls[0]).expect("Cannot write to posted file"); // maybe we should retry tbh
Some(urls[0].to_string().clone())
}
}
async fn update_bio(account: &Mastodon) {
let binding = std::fs::read("./posted.csv").expect("File not found").iter().map(|c| *c as char).collect::<String>();
let posted = binding.lines().collect::<HashSet<_>>();
let binding = std::fs::read("./urls.csv").expect("File not found").iter().map(|c| *c as char).collect::<String>();
let urls = binding.lines().collect::<HashSet<_>>();
let remaining = urls.difference(&posted).count();
async fn post_image(account: &Mastodon, url: &String, config: &Config) {
fetch_url(url.to_string(), &config.files.tempfile)
.await
.expect("Error fetching url");
let attachment = account
.media(&config.files.tempfile, Some(url.to_string()))
.await
.expect("Attachment upload error");
let attachment = account
.wait_for_processing(attachment, Default::default())
.await
.expect("Attachment processing error");
let status = StatusBuilder::new()
.media_ids(&[attachment.id])
.visibility(Visibility::Unlisted)
.sensitive(true)
.build()
.expect("Could not build status"); // we should retry
account.new_status(status).await.expect("Error generating status"); // we should retry or delete last url in posted
println!("Status posted")
}
Command::new("curl")
.args([
"-X", "PATCH",
"https://awoo.fai.st/api/v1/accounts/update_credentials",
"-H", &format!("Authorization:Bearer {}", account.data.token),
"-d", &format!("note=Bot who posts images of sleeping girls every 6 hours.\n\n{} new images remaining", remaining),
]).spawn().unwrap().wait().unwrap();
async fn update_bio(account: &Mastodon, config: &Config) {
let binding = std::fs::read_to_string(&config.files.posted).expect("Posted file not found");
let posted = binding.lines().collect::<HashSet<&str>>();
let binding = std::fs::read_to_string(&config.files.urls).expect("Url file not found");
let urls = binding.lines().collect::<HashSet<&str>>();
let remaining = urls.difference(&posted).count();
Command::new("curl")
.args([
"-X",
"PATCH",
&format!("{}/api/v1/accounts/update_credentials", config.bot.instance),
"-H",
&format!("Authorization:Bearer {}", account.data.token),
"-d",
&format!(
"note={}\n\n{} new images remaining",
config.bot.bio, remaining
),
])
.spawn()
.expect("Could not spawn curl")
.wait()
.expect("Curl failed");
}
async fn post(account: &Mastodon, msg: &str) {
let status = StatusBuilder::new()
let status = StatusBuilder::new()
.visibility(Visibility::Direct)
.status(msg)
.build()
.unwrap();
account.new_status(status).await.unwrap();
.expect("Error building error status");
account.new_status(status).await.expect("Error posting error status");
println!("Error status posted")
}
async fn fetch_url(url: String, file_name: String) -> Result<()> {
let response = reqwest::get(url).await?;
let mut file = std::fs::File::create(file_name)?;
let mut content = Cursor::new(response.bytes().await?);
std::io::copy(&mut content, &mut file)?;
Ok(())
async fn fetch_url(url: String, file_name: &String) -> Result<()> {
let response = reqwest::get(url).await?;
let mut file = std::fs::File::create(file_name)?;
let mut content = Cursor::new(response.bytes().await?);
std::io::copy(&mut content, &mut file)?;
Ok(())
}
async fn register() -> Result<Mastodon> {
let registration = Registration::new("https://awoo.fai.st")
.client_name("sleeping-girls-bot")
.scopes(Scopes::write_all())
.build()
.await?;
let mastodon = cli::authenticate(registration).await?;
async fn register(config: &Config) -> Result<Mastodon> {
let registration = Registration::new(&config.bot.instance)
.client_name(&config.bot.name)
.scopes(Scopes::write_all())
.build()
.await?;
let mastodon = cli::authenticate(registration).await?;
// Save app data for using on the next run.
toml::to_file(&mastodon.data, "mastodon-data.toml")?;
// Save app data for using on the next run.
masto_toml::to_file(&mastodon.data, "mastodon-data.toml")?;
Ok(mastodon)
Ok(mastodon)
}