Initial commit
This commit is contained in:
commit
e1fcfd1b22
|
@ -0,0 +1 @@
|
||||||
|
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "imgurx"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["blank X <theblankx@protonmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
lto = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tokio = { version = "1.5", features = ["rt-multi-thread"] }
|
||||||
|
reqwest = "0.11"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
quick-xml = "0.22"
|
||||||
|
warp = "0.3"
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 blank X
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,54 @@
|
||||||
|
use std::convert::Infallible;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::env;
|
||||||
|
use warp::Filter;
|
||||||
|
mod structs;
|
||||||
|
mod utils;
|
||||||
|
use reqwest::Client;
|
||||||
|
use tokio::runtime;
|
||||||
|
extern crate warp;
|
||||||
|
|
||||||
|
async fn handle_album(id: String, client: Client) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
let (status_code, reply) = utils::generate_html(utils::get_album(client.clone(), &id).await);
|
||||||
|
Ok(warp::reply::with_status(
|
||||||
|
warp::reply::with_header(reply, "Content-Type", "text/html"),
|
||||||
|
status_code.try_into().unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_media(id: String, client: Client) -> Result<impl warp::Reply, Infallible> {
|
||||||
|
let (status_code, reply) = utils::generate_html(utils::get_media(client.clone(), &id).await);
|
||||||
|
Ok(warp::reply::with_status(
|
||||||
|
warp::reply::with_header(reply, "Content-Type", "text/html"),
|
||||||
|
status_code.try_into().unwrap(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn async_main(port: u16) {
|
||||||
|
let client = Client::new();
|
||||||
|
let client = warp::any().map(move || client.clone());
|
||||||
|
let album_path = warp::path!("a" / String)
|
||||||
|
.and(client.clone())
|
||||||
|
.and_then(handle_album);
|
||||||
|
let media_path = warp::path!(String).and(client).and_then(handle_media);
|
||||||
|
let not_found_handler = warp::any().map(|| warp::reply::with_status(
|
||||||
|
warp::reply::html("<html><head><style>body { background-color: black; color: white; text-align: center; }\ndiv { border: 1px solid #fcc; background: #fee; padding: 0.5em 1em 0.5em 1em; color: black; }</style></head><body><div><b>404: Not Found</b></div></body></html>"),
|
||||||
|
404.try_into().unwrap(),
|
||||||
|
));
|
||||||
|
let routes = warp::filters::method::get().and(album_path.or(media_path).or(not_found_handler));
|
||||||
|
eprintln!("Serving on 0.0.0.0:{}", port);
|
||||||
|
warp::serve(routes).run(([0u8, 0, 0, 0], port)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
let port = match env::var("PORT") {
|
||||||
|
Ok(port) => port.parse().unwrap(),
|
||||||
|
Err(env::VarError::NotPresent) => 8080,
|
||||||
|
Err(env::VarError::NotUnicode(_)) => panic!("Environment variable PORT isn't unicode"),
|
||||||
|
};
|
||||||
|
runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.block_on(async_main(port));
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
//use std::fmt;
|
||||||
|
use serde::Deserialize;
|
||||||
|
extern crate reqwest;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Album {
|
||||||
|
pub title: String,
|
||||||
|
pub is_mature: bool,
|
||||||
|
pub media: Vec<Media>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Media {
|
||||||
|
pub r#type: String,
|
||||||
|
pub url: String,
|
||||||
|
pub width: u32,
|
||||||
|
pub height: u32,
|
||||||
|
pub metadata: Metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct Metadata {
|
||||||
|
pub description: String,
|
||||||
|
pub duration: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Error {
|
||||||
|
Reqwest(reqwest::Error),
|
||||||
|
SerdeJSON(serde_json::Error),
|
||||||
|
APIErrors(APIErrors),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<reqwest::Error> for Error {
|
||||||
|
#[inline]
|
||||||
|
fn from(error: reqwest::Error) -> Error {
|
||||||
|
Error::Reqwest(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for Error {
|
||||||
|
#[inline]
|
||||||
|
fn from(error: serde_json::Error) -> Error {
|
||||||
|
Error::SerdeJSON(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//impl fmt::Display for Error {
|
||||||
|
// fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
// formatter.write_str(&match self {
|
||||||
|
// Error::Reqwest(err) => format!("reqwest: {}", err),
|
||||||
|
// Error::SerdeJSON(err) => format!("serde_json: {}", err),
|
||||||
|
// Error::ApiErrors(err) =>
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct APIErrors {
|
||||||
|
pub errors: Vec<APIError>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
pub struct APIError {
|
||||||
|
pub id: String,
|
||||||
|
pub code: String,
|
||||||
|
pub status: String,
|
||||||
|
pub detail: String,
|
||||||
|
}
|
|
@ -0,0 +1,212 @@
|
||||||
|
use crate::structs::{Album, Error};
|
||||||
|
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
|
||||||
|
use quick_xml::Writer;
|
||||||
|
use reqwest::Client;
|
||||||
|
use std::io::Cursor;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
pub async fn get_album(client: Client, id: &str) -> Result<Album, Error> {
|
||||||
|
let resp = client
|
||||||
|
.get(&format!(
|
||||||
|
"https://api.imgur.com/post/v1/albums/{}?client_id=546c25a59c58ad7&include=media",
|
||||||
|
id
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
let status = resp.status();
|
||||||
|
let text = resp.text().await?;
|
||||||
|
if status == 200 {
|
||||||
|
Ok(serde_json::from_str(&text)?)
|
||||||
|
} else {
|
||||||
|
Err(Error::APIErrors(serde_json::from_str(&text)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_media(client: Client, id: &str) -> Result<Album, Error> {
|
||||||
|
let resp = client
|
||||||
|
.get(&format!(
|
||||||
|
"https://api.imgur.com/post/v1/media/{}?client_id=546c25a59c58ad7&include=media",
|
||||||
|
id
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.await?;
|
||||||
|
let status = resp.status();
|
||||||
|
let text = resp.text().await?;
|
||||||
|
if status == 200 {
|
||||||
|
Ok(serde_json::from_str(&text)?)
|
||||||
|
} else {
|
||||||
|
Err(Error::APIErrors(serde_json::from_str(&text)?))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_html(album: Result<Album, Error>) -> (u16, Vec<u8>) {
|
||||||
|
let mut writer = Writer::new(Cursor::new(vec![]));
|
||||||
|
|
||||||
|
let elem = BytesStart::owned(b"html".to_vec(), 4);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesStart::owned(b"head".to_vec(), 4);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesStart::owned(b"style".to_vec(), 5);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesText::from_plain_str("body { background-color: black; color: white; text-align: center; }\ndiv { border: 1px solid #fcc; background: #fee; padding: 0.5em 1em 0.5em 1em; color: black; }");
|
||||||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"style".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"head".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesStart::owned(b"body".to_vec(), 4);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let status_code = match album {
|
||||||
|
Ok(album) => {
|
||||||
|
if album.is_mature {
|
||||||
|
let elem = BytesStart::owned(b"div".to_vec(), 3);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesStart::owned(b"b".to_vec(), 1);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesText::from_plain_str("Marked as NSFW");
|
||||||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"b".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"div".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !album.title.is_empty() {
|
||||||
|
let elem = BytesStart::owned(b"h1".to_vec(), 2);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesText::from_plain_str(&album.title);
|
||||||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"h1".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
for media in &album.media {
|
||||||
|
if media.r#type == "image" {
|
||||||
|
let mut elem = BytesStart::owned(b"img".to_vec(), 3);
|
||||||
|
elem.push_attribute(("width", media.width.to_string().as_str()));
|
||||||
|
elem.push_attribute(("height", media.height.to_string().as_str()));
|
||||||
|
elem.push_attribute(("src", media.url.as_str()));
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"img".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
} else if media.r#type == "video" {
|
||||||
|
let mut elem = BytesStart::owned(b"video".to_vec(), 5);
|
||||||
|
elem.push_attribute(("controls", "1"));
|
||||||
|
elem.push_attribute(("width", media.width.to_string().as_str()));
|
||||||
|
elem.push_attribute(("height", media.height.to_string().as_str()));
|
||||||
|
elem.push_attribute(("src", media.url.as_str()));
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"video".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
} else {
|
||||||
|
let elem = BytesStart::owned(b"div".to_vec(), 3);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesStart::owned(b"b".to_vec(), 1);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let text = format!("Unknown media type {}, URL is ", &media.r#type);
|
||||||
|
let elem = BytesText::from_plain_str(&text);
|
||||||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||||||
|
|
||||||
|
let mut elem = BytesStart::owned(b"a".to_vec(), 1);
|
||||||
|
elem.push_attribute(("href", media.url.as_str()));
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesText::from_plain_str(&media.url);
|
||||||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"a".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"b".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"div".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !media.metadata.description.is_empty() {
|
||||||
|
let elem = BytesStart::owned(b"p".to_vec(), 1);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesText::from_plain_str(&media.metadata.description);
|
||||||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"a".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
200
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
let show_details = match err {
|
||||||
|
Error::APIErrors(ref err)
|
||||||
|
if err.errors.len() == 1 && err.errors[0].code == "404" =>
|
||||||
|
{
|
||||||
|
false
|
||||||
|
}
|
||||||
|
_ => true,
|
||||||
|
};
|
||||||
|
let mut elem = BytesStart::owned(b"div".to_vec(), 3);
|
||||||
|
if show_details {
|
||||||
|
elem.push_attribute(("style", "text-align: left;"));
|
||||||
|
}
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesStart::owned(b"b".to_vec(), 1);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let to_return = match err {
|
||||||
|
Error::APIErrors(err) if !show_details => {
|
||||||
|
let text = format!("404: {}", err.errors[0].detail);
|
||||||
|
let elem = BytesText::from_plain_str(&text);
|
||||||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||||||
|
404
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let elem = BytesStart::owned(b"pre".to_vec(), 3);
|
||||||
|
writer.write_event(Event::Start(elem)).unwrap();
|
||||||
|
|
||||||
|
let text = format!("{:#?}", err);
|
||||||
|
let elem = BytesText::from_plain_str(&text);
|
||||||
|
writer.write_event(Event::Text(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"pre".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
500
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"b".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"div".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
to_return
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"body".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
|
||||||
|
let elem = BytesEnd::owned(b"html".to_vec());
|
||||||
|
writer.write_event(Event::End(elem)).unwrap();
|
||||||
|
|
||||||
|
(status_code, writer.into_inner().into_inner())
|
||||||
|
}
|
Loading…
Reference in New Issue