mirror of https://github.com/MedzikUser/imgurs
Initial commit
commit
9cbb2ea149
@ -0,0 +1,12 @@
|
||||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
@ -0,0 +1 @@
|
||||
/target
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "imgurs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
dirs = "4.0.0"
|
||||
serde = "1.0.134"
|
||||
serde_derive = "1.0.134"
|
||||
shadow = "0.0.1"
|
||||
toml = "0.5.8"
|
||||
serde_json = "1.0.75"
|
||||
chrono = "0.4.19"
|
||||
anyhow = "1.0.53"
|
||||
base64 = "0.13.0"
|
||||
|
||||
[dependencies.clap]
|
||||
version = "3.0.7"
|
||||
features = ["derive", "cargo", "unicode"]
|
||||
|
||||
[dependencies.log]
|
||||
version = "0.4.14"
|
||||
features = ["release_max_level_info"]
|
||||
|
||||
[dependencies.simple_logger]
|
||||
version = "2.1.0"
|
||||
default-features = false
|
||||
features = ["colors"]
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11.9"
|
||||
features = ["json"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.15.0"
|
||||
features = ["full"]
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
@ -0,0 +1,29 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2022, MedzikUser
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
@ -0,0 +1,31 @@
|
||||
use std::fmt;
|
||||
use reqwest::Client;
|
||||
|
||||
macro_rules! api_url (
|
||||
($path: expr) => (
|
||||
format!("{}{}", "https://api.imgur.com/3/", $path)
|
||||
);
|
||||
);
|
||||
|
||||
pub(crate) use api_url;
|
||||
|
||||
pub struct ImgurHandle {
|
||||
pub client_id: String,
|
||||
pub client: Client,
|
||||
}
|
||||
|
||||
impl fmt::Debug for ImgurHandle {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "ImgurClient - client_id: {}", self.client_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl ImgurHandle {
|
||||
pub fn new(client_id: String) -> Self {
|
||||
let client = Client::new();
|
||||
ImgurHandle {
|
||||
client_id: client_id,
|
||||
client: client
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
use crate::api::configuration::{api_url, ImgurHandle};
|
||||
|
||||
pub async fn delete_image(c: ImgurHandle, delete_hash: String) -> Result<String, String> {
|
||||
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||
|
||||
let res = c
|
||||
.client
|
||||
.delete(api_url!(format!("image/{delete_hash}")))
|
||||
.header("Authorization", format!("Client-ID {}", c.client_id))
|
||||
.header(
|
||||
"User-Agent",
|
||||
format!("Imgur/{:?}", VERSION.unwrap_or("unknown")),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let status = res.status();
|
||||
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await.map_err(|err| err.to_string())?;
|
||||
Err(format!("server returned non-successful status code = {status}. body = {body}"))
|
||||
} else {
|
||||
Ok("If the delete hash was correct the image was deleted!".to_string())
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
pub mod rate_limit;
|
||||
pub mod configuration;
|
||||
pub mod upload_image;
|
||||
pub mod delete_image;
|
@ -0,0 +1,48 @@
|
||||
use crate::api::configuration::{api_url, ImgurHandle};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RateLimitInfo {
|
||||
pub data: RateLimitData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct RateLimitData {
|
||||
#[serde(rename = "UserLimit")]
|
||||
pub user_limit: i32,
|
||||
#[serde(rename = "UserRemaining")]
|
||||
pub user_remaining: i32,
|
||||
#[serde(rename = "UserReset")]
|
||||
pub user_reset: i32,
|
||||
#[serde(rename = "ClientLimit")]
|
||||
pub client_limit: i32,
|
||||
#[serde(rename = "ClientRemaining")]
|
||||
pub client_remaining: i32,
|
||||
}
|
||||
|
||||
pub async fn rate_limit(c: ImgurHandle) -> Result<RateLimitInfo, String> {
|
||||
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||
|
||||
let res = c
|
||||
.client
|
||||
.get(api_url!("credits"))
|
||||
.header("Authorization", format!("Client-ID {}", c.client_id))
|
||||
.header(
|
||||
"User-Agent",
|
||||
format!("Imgur/{:?}", VERSION.unwrap_or("unknown")),
|
||||
)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let status = res.status();
|
||||
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await.map_err(|err| err.to_string())?;
|
||||
Err(format!("server returned non-successful status code = {status}. body = {body}"))
|
||||
} else {
|
||||
let content: RateLimitInfo = res.json().await.map_err(|err| err.to_string())?;
|
||||
Ok(content)
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
use crate::api::configuration::{api_url, ImgurHandle};
|
||||
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ImageInfo {
|
||||
pub data: ImageInfoData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct ImageInfoData {
|
||||
pub id: String,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
pub datetime: i32,
|
||||
#[serde(rename = "type")]
|
||||
pub img_type: String,
|
||||
pub animated: bool,
|
||||
pub width: i32,
|
||||
pub height: i32,
|
||||
pub size: i32,
|
||||
pub views: i32,
|
||||
pub bandwidth: i64,
|
||||
pub favorite: bool,
|
||||
pub deletehash: String,
|
||||
pub link: String,
|
||||
}
|
||||
|
||||
pub async fn upload_image(c: ImgurHandle, image: &str) -> Result<ImageInfo, String> {
|
||||
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||
|
||||
let mut form = HashMap::new();
|
||||
|
||||
form.insert("image", image.to_string());
|
||||
|
||||
let res = c
|
||||
.client
|
||||
.post(api_url!("image"))
|
||||
.header("Authorization", format!("Client-ID {}", c.client_id))
|
||||
.header(
|
||||
"User-Agent",
|
||||
format!("Imgurs/{:?}", VERSION.unwrap_or("unknown")),
|
||||
)
|
||||
.form(&form)
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| err.to_string())?;
|
||||
|
||||
let status = res.status();
|
||||
|
||||
if status.is_client_error() || status.is_server_error() {
|
||||
let body = res.text().await.map_err(|err| err.to_string())?;
|
||||
Err(format!("server returned non-successful status code = {status}. body = {body}"))
|
||||
} else {
|
||||
let content: ImageInfo = res.json().await.map_err(|err| err.to_string())?;
|
||||
Ok(content)
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
use clap::{Parser, Subcommand, AppSettings};
|
||||
|
||||
use imgurs::api::configuration::ImgurHandle;
|
||||
|
||||
use crate::cli::*;
|
||||
|
||||
const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(
|
||||
name = "imgur",
|
||||
about = "Imgur API CLI", long_about = None,
|
||||
version = VERSION.unwrap_or("unknown")
|
||||
)]
|
||||
struct Cli {
|
||||
#[clap(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug)]
|
||||
enum Commands {
|
||||
#[clap(about = "Get API Rate Limit")]
|
||||
Credits,
|
||||
|
||||
#[clap(
|
||||
setting(AppSettings::ArgRequiredElseHelp),
|
||||
about = "Upload image to imgur"
|
||||
)]
|
||||
Upload {
|
||||
path: String
|
||||
},
|
||||
|
||||
#[clap(
|
||||
setting(AppSettings::ArgRequiredElseHelp),
|
||||
about = "Upload image to imgur"
|
||||
)]
|
||||
Delete {
|
||||
delete_hash: String
|
||||
},
|
||||
}
|
||||
|
||||
pub async fn parse(client: ImgurHandle) {
|
||||
let args = Cli::parse();
|
||||
|
||||
match &args.command {
|
||||
Commands::Credits => {
|
||||
credits::credits(client)
|
||||
.await;
|
||||
}
|
||||
|
||||
Commands::Upload { path } => {
|
||||
upload_image::upload_image(client, path)
|
||||
.await;
|
||||
}
|
||||
|
||||
Commands::Delete { delete_hash } => {
|
||||
delete_image::delete_image(client, delete_hash.to_string())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
use imgurs::api::configuration::ImgurHandle;
|
||||
use imgurs::api::rate_limit::*;
|
||||
|
||||
use log::{info, error};
|
||||
|
||||
use chrono::prelude::DateTime;
|
||||
use chrono::Utc;
|
||||
use std::time::{UNIX_EPOCH, Duration};
|
||||
|
||||
pub async fn credits(client: ImgurHandle) {
|
||||
match rate_limit(client).await {
|
||||
Ok(i) => {
|
||||
let d = UNIX_EPOCH + Duration::from_secs(i.data.user_reset.try_into().unwrap());
|
||||
let datetime = DateTime::<Utc>::from(d);
|
||||
let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
info!("User Limit {}", i.data.user_limit);
|
||||
info!("User Remaining {}", i.data.user_remaining);
|
||||
info!("User Reset {} (UTC)", timestamp_str);
|
||||
info!("Client Limit {}", i.data.client_limit);
|
||||
info!("Client Remaining {}", i.data.client_remaining);
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
use imgurs::api::configuration::ImgurHandle;
|
||||
use imgurs::api;
|
||||
|
||||
use log::{info, error};
|
||||
|
||||
pub async fn delete_image(client: ImgurHandle, delete_hash: String) {
|
||||
match api::delete_image::delete_image(client, delete_hash).await {
|
||||
Ok(i) => {
|
||||
info!("{i}")
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
pub mod cli;
|
||||
pub mod credits;
|
||||
pub mod upload_image;
|
||||
pub mod delete_image;
|
@ -0,0 +1,45 @@
|
||||
use imgurs::api::configuration::ImgurHandle;
|
||||
|
||||
use log::{info, error};
|
||||
use std::path::Path;
|
||||
use std::fs;
|
||||
use base64;
|
||||
|
||||
use chrono::prelude::DateTime;
|
||||
use chrono::Utc;
|
||||
use std::time::{UNIX_EPOCH, Duration};
|
||||
|
||||
pub async fn upload_image(client: ImgurHandle, path: &str) {
|
||||
let image: String;
|
||||
|
||||
if Path::new(path).exists() {
|
||||
let bytes = fs::read(path).map_err(|err| err.to_string()).unwrap();
|
||||
|
||||
image = base64::encode(bytes);
|
||||
} else {
|
||||
image = path.to_string()
|
||||
}
|
||||
|
||||
match imgurs::api::upload_image::upload_image(client, &image).await {
|
||||
Ok(i) => {
|
||||
let d = UNIX_EPOCH + Duration::from_secs(i.data.datetime.try_into().unwrap());
|
||||
let datetime = DateTime::<Utc>::from(d);
|
||||
let timestamp_str = datetime.format("%Y-%m-%d %H:%M:%S").to_string();
|
||||
|
||||
info!("ID {}", i.data.id);
|
||||
info!("Upload Date {} (UTC)", timestamp_str);
|
||||
info!("Type {}", i.data.img_type);
|
||||
info!("Width {}", i.data.width);
|
||||
info!("Height {}", i.data.height);
|
||||
info!("File Size {} KB", i.data.size / 1000);
|
||||
info!("Views {}", i.data.views);
|
||||
info!("Bandwidth {}", i.data.bandwidth);
|
||||
info!("Delete Hash {}", i.data.deletehash);
|
||||
info!("Link {}", i.data.link);
|
||||
}
|
||||
|
||||
Err(e) => {
|
||||
error!("{e}");
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
pub mod toml;
|
@ -0,0 +1,25 @@
|
||||
use toml::from_str;
|
||||
use serde_derive::Deserialize;
|
||||
use dirs::config_dir;
|
||||
use std::fs::read_to_string;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Config {
|
||||
pub imgur: ConfigImgur,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ConfigImgur {
|
||||
pub id: String,
|
||||
}
|
||||
|
||||
pub fn parse() -> Result<Config, String> {
|
||||
let config_dir = config_dir().unwrap();
|
||||
let file_dir: String = String::from(config_dir.to_string_lossy()) + "/imgur/config.toml";
|
||||
|
||||
let toml_str = read_to_string(file_dir).map_err(|err| err.to_string())?;
|
||||
|
||||
let decode = from_str(&toml_str).map_err(|err| err.to_string())?;
|
||||
|
||||
Ok(decode)
|
||||
}
|
@ -0,0 +1 @@
|
||||
pub mod api;
|
@ -0,0 +1,19 @@
|
||||
mod config;
|
||||
mod cli;
|
||||
|
||||
use cli::cli::parse;
|
||||
|
||||
use simple_logger::SimpleLogger;
|
||||
|
||||
use imgurs::api::configuration::ImgurHandle;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
SimpleLogger::new().init().unwrap();
|
||||
|
||||
let config = config::toml::parse().unwrap();
|
||||
|
||||
let client = ImgurHandle::new((&config.imgur.id).to_string());
|
||||
|
||||
parse(client).await;
|
||||
}
|
Loading…
Reference in New Issue