diff --git a/Cargo.toml b/Cargo.toml index 73383c9..71cb74f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,5 @@ [workspace] members = [ "tokencracker", + "cli", ] diff --git a/cli/Cargo.toml b/cli/Cargo.toml new file mode 100644 index 0000000..bd412a9 --- /dev/null +++ b/cli/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "cli" +version = "0.1.0" +authors = ["LordMZTE "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[[bin]] +name = "jm" +path = "src/main.rs" + +[dependencies] +anyhow = "1.0.34" +clap = "2.33.3" +opener = "0.4.1" +reqwest = { version = "0.10.9", features = ["stream"] } +serde = { version = "1.0.117", features = ["derive"] } +serde_json = "1.0.60" +structopt = "0.3.21" +tokio = { version = "0.2.23", features = ["macros", "fs", "process"] } diff --git a/cli/src/api.rs b/cli/src/api.rs new file mode 100644 index 0000000..34b9b97 --- /dev/null +++ b/cli/src/api.rs @@ -0,0 +1,6 @@ +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct UpResp { + pub files: Vec, +} diff --git a/cli/src/main.rs b/cli/src/main.rs new file mode 100644 index 0000000..fa70640 --- /dev/null +++ b/cli/src/main.rs @@ -0,0 +1,83 @@ +use anyhow::Result; +use reqwest::Client; +use structopt::StructOpt; + +mod api; +mod up; +mod util; + +const CATEGORIES: &[&str] = &[ + "toilet", + "jens", + "random", + "hendrik", + "realtox", + "chat", + "code", + "minecraft", + "jonas", + "server", + "uff", + "shithost", + "itbyhf", + "test", +]; + +#[derive(StructOpt)] +struct Opts { + #[structopt(subcommand)] + cmd: Cmd, +} + +#[derive(StructOpt)] +enum Cmd { + #[structopt(help = "uploads a meme")] + Up { + file: String, + category: String, + + #[structopt( + env = "JM_TOKEN", + short, + long, + hide_env_values = true, + help = "token to use in order to upload" + )] + token: String, + + #[structopt( + long, + short, + help = "the name the file should have. defaults to the name of the chosen file" + )] + name: Option, + + #[structopt(long, short, help = "open the files in the browser")] + open: bool, + }, + + #[structopt(help = "lists the available categories")] + Cats, +} + +#[tokio::main] +async fn main() -> Result<()> { + let Opts { cmd } = Opts::from_args(); + let http = Client::new(); + + match cmd { + Cmd::Up { + file, + category, + token, + name, + open, + } => { + let name = name.unwrap_or_else(|| file.clone()); + up::run(&http, token, file, name, category, open).await?; + }, + Cmd::Cats => CATEGORIES.iter().for_each(|c| println!("{}", c)), + } + + Ok(()) +} diff --git a/cli/src/up.rs b/cli/src/up.rs new file mode 100644 index 0000000..c54ea50 --- /dev/null +++ b/cli/src/up.rs @@ -0,0 +1,56 @@ +use anyhow::{bail, Result}; +use reqwest::{ + multipart::{Form, Part}, + Body, + Client, +}; +use tokio::{fs::File, io::reader_stream}; + +use crate::{api::UpResp, util::open_link, CATEGORIES}; + +pub async fn run( + http: &Client, + token: String, + path: String, + name: String, + category: String, + open: bool, +) -> Result<()> { + if !CATEGORIES.contains(&category.as_ref()) { + bail!(r#"category is invalid. use "cats" to list categories"#); + } + + let res = http + .post("https://data.tilera.xyz/api/jensmemes/upload") + .multipart( + Form::new() + .text("category", category) + .text("token", token) + .part( + "file", + Part::stream(Body::wrap_stream(reader_stream(File::open(path).await?))) + .file_name(name), + ), + ) + .send() + .await?; + + let status = res.status(); + let res = serde_json::from_slice::(&res.bytes().await?)?; + + println!("Server responded with code {}", status); + + if !open { + println!(); + } + + for f in res.files { + if open { + open_link(&f).await?; + } else { + println!("{}", f); + } + } + + Ok(()) +} diff --git a/cli/src/util.rs b/cli/src/util.rs new file mode 100644 index 0000000..67a4158 --- /dev/null +++ b/cli/src/util.rs @@ -0,0 +1,12 @@ +use tokio::process::Command; + +pub async fn open_link(url: &str) -> anyhow::Result<()> { + match std::env::var_os("BROWSER") { + Some(browser) => { + Command::new(&browser).arg(url).status().await?; + }, + None => opener::open(&url)?, + } + + Ok(()) +}