#![allow(dead_code)]
use core::fmt;
use serde_json::Value;
use std::{collections::HashMap, fmt::Display, str::FromStr};
pub struct ApiUrl {
pub api_url: String,
pub req_limit: usize,
pub tags: Vec<String>,
pub json: bool,
pub ids: HashMap<String, Option<usize>>,
}
impl ApiUrl {
pub fn new() -> ApiUrl {
ApiUrl::default()
}
pub fn set_limit(mut self, limit: usize) -> Self {
self.req_limit = limit;
self
}
pub fn add_tag(mut self, tag: &'static str) -> Self {
self.tags.push(tag.to_string());
self
}
pub fn add_tags(mut self, mut tags: Vec<String>) -> Self {
self.tags.append(&mut tags);
self
}
pub fn set_cid(mut self, cid: usize) -> Self {
self.ids.insert("cid".to_string(), Some(cid));
self
}
pub fn set_pid(mut self, pid: usize) -> Self {
self.ids.insert("pid".to_string(), Some(pid));
self
}
pub fn set_id(mut self, id: usize) -> Self {
self.ids.insert("id".to_string(), Some(id));
self
}
pub fn set_json_formatted(mut self, json: bool) -> Self {
self.json = json;
self
}
pub fn to_api_url(&mut self) -> String {
let api_url = self.api_url.clone();
let req_limit = self.req_limit;
let json = if self.json == true { r"&json=1" } else { "" };
let tags = format!(r"&tags={}", self.tags.join(" "));
let id = format_id(&self.ids, "id");
let pid = format_id(&self.ids, "pid");
let cid = format_id(&self.ids, "cid");
let req_string = format!(
r"{}{}&limit={}{}{}{}{}",
api_url, tags, req_limit, json, id, pid, cid
);
req_string
}
}
impl Default for ApiUrl {
fn default() -> Self {
let mut ids: HashMap<String, Option<usize>> = HashMap::new();
ids.insert("id".to_string(), None);
ids.insert("pid".to_string(), None);
ids.insert("cid".to_string(), None);
ApiUrl {
api_url: String::from("https://api.rule34.xxx/index.php?page=dapi&s=post&q=index"),
req_limit: 1000,
tags: Vec::new(),
json: true,
ids,
}
}
}
fn format_id(ids: &HashMap<String, Option<usize>>, key: &str) -> String {
match ids.get(key) {
Some(Some(value)) => format!("&{}={}", key, value),
_ => String::new(),
}
}
pub struct R34JsonParser {
pub conf: HashMap<&'static str, bool>,
}
impl Default for R34JsonParser {
fn default() -> Self {
let mut conf: HashMap<&str, bool> = HashMap::new();
conf.insert("file_url", true);
conf.insert("image", true);
conf.insert("tags", true);
conf.insert("width", true);
conf.insert("height", true);
conf.insert("sample", true);
conf.insert("samlpe_url", true);
conf.insert("sample_width", true);
conf.insert("sample_height", true);
conf.insert("source", true);
conf.insert("id", true);
conf.insert("score", true);
conf.insert("parent_id", true);
conf.insert("comment_count", true);
conf.insert("preview_url", true);
conf.insert("owner", true);
conf.insert("rating", true);
R34JsonParser { conf }
}
}
impl R34JsonParser {
pub fn new() -> R34JsonParser {
R34JsonParser::default()
}
pub fn from_api_response(&mut self, s: &str) -> Result<Vec<Post>, R34Error> {
if s == "" {
return Err(R34Error::R34EmptyReturn(String::from("One or more Tags didn't exist.")));
}
let value = match serde_json::Value::from_str(&s) {
Ok(value) => value,
Err(e) => {
return Err(R34Error::JsonParseError(e));
}
};
let pretty_json = serde_json::to_string_pretty(&value).unwrap();
Ok(self.parse_json(&pretty_json).unwrap())
}
pub fn parse_json(&mut self, s: &str) -> Result<Vec<Post>, R34Error> {
if s == "" {
return Err(R34Error::R34EmptyReturn(String::from("One or more Tags didn't exist.")));
}
let value: Value = match serde_json::Value::from_str(&s) {
Ok(value) => value,
Err(e) => {
return Err(R34Error::JsonParseError(e));
}
};
let mut post_vec: Vec<Post> = Vec::new();
if let Value::Array(a) = value {
for obj in a {
let mut post = Post::default();
for (k, b) in &mut self.conf {
match (k, b) {
(&"file_url", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::String(s)) = map.get("file_url") {
post.file_url = s.clone();
}
}
}
(&"image", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::String(s)) = map.get("image") {
post.image = s.clone();
}
}
}
(&"tags", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::String(s)) = map.get("tags") {
post.tags = s
.clone()
.split_whitespace()
.map(move |s| s.to_string())
.collect();
}
}
}
(&"width", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::Number(n)) = map.get("width") {
post.width = n.as_u64().unwrap();
}
}
}
(&"height", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::Number(n)) = map.get("height") {
post.height = n.as_u64().unwrap();
}
}
}
(&"sample", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::Bool(b)) = map.get("sample") {
post.sample = b.clone();
}
}
}
(&"sample_url", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::String(s)) = map.get("sample_url") {
post.sample_url = s.clone();
}
}
}
(&"sample_width", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::Number(n)) = map.get("sample_width") {
post.sample_width = n.as_u64().unwrap();
}
}
}
(&"sample_height", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::Number(n)) = map.get("sample_height") {
post.sample_height = n.as_u64().unwrap();
}
}
}
(&"source", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::String(s)) = map.get("source") {
post.source = s.clone();
}
}
}
(&"id", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::Number(n)) = map.get("id") {
post.id = n.as_u64().unwrap();
}
}
}
(&"score", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::Number(n)) = map.get("score") {
post.score = n.as_u64().unwrap();
}
}
}
(&"parent_id", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::Number(n)) = map.get("parent_id") {
post.parent_id = n.as_u64().unwrap();
}
}
}
(&"comment_count", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::Number(n)) = map.get("comment_count") {
post.comment_count = n.as_u64().unwrap();
}
}
}
(&"preview_url", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::String(s)) = map.get("preview_url") {
post.preview_url = s.clone();
}
}
}
(&"owner", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::String(s)) = map.get("owner") {
post.owner = s.clone();
}
}
}
(&"rating", true) => {
if let Value::Object(ref map) = obj {
if let Some(Value::String(s)) = map.get("rating") {
match s.as_str() {
"explicit" => post.rating = Some(Rating::Explicit),
"safe" => post.rating = Some(Rating::Safe),
"questionable" => post.rating = Some(Rating::Questionable),
_ => post.rating = None,
}
}
}
}
_ => (),
}
}
post_vec.push(post);
}
}
Ok(post_vec)
}
pub fn set_conf(mut self, key: &'static str, set: bool) -> Self {
*self.conf.get_mut(&key).unwrap() = set;
self
}
}
#[derive(Debug)]
pub enum R34Error {
JsonParseError(serde_json::Error),
R34EmptyReturn(String),
}
impl std::fmt::Display for R34Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
R34Error::JsonParseError(e) => write!(f, "{}", e),
R34Error::R34EmptyReturn(e) => write!(f, "{}", e)
}
}
}
#[derive(Clone, Copy)]
pub enum Rating {
Explicit,
Safe,
Questionable,
}
impl fmt::Display for Rating {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let r = match *self {
Self::Explicit => "explicit",
Self::Safe => "safe",
Self::Questionable => "questionable",
};
write!(f, "{}", r)
}
}
#[derive(Clone)]
pub struct Post {
pub file_url: String,
pub image: String,
pub tags: Vec<String>,
pub width: u64,
pub height: u64,
pub sample: bool,
pub sample_url: String,
pub sample_width: u64,
pub sample_height: u64,
pub source: String,
pub id: u64,
pub score: u64,
pub parent_id: u64,
pub comment_count: u64,
pub preview_url: String,
pub owner: String,
pub rating: Option<Rating>,
}
impl Display for Post {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f,
"image: {}\nfile_url: {}\nwidth: {}\nheight: {}\ntags: {:?}\nid: {}\nowner: {}\nrating: {}\nsample_url: {}\nsample_width: {}\nsample_height: {}\nsource: {}\nscore: {}\nparent_id: {}\ncomment_count: {}\npreview_url: {}\n",
self.image, self.file_url, self.width,
self.height, self.tags, self.id,
self.owner, self.rating.clone().unwrap(), self.sample_url,
self.sample_width, self.sample_height,
self.source, self.score, self.parent_id,
self.comment_count, self.preview_url)
}
}
impl Default for Post {
fn default() -> Self {
Post {
file_url: String::new(),
width: 0,
height: 0,
image: String::new(),
tags: vec![],
sample: false,
sample_url: String::new(),
sample_width: 0,
sample_height: 0,
source: String::new(),
id: 1,
score: 0,
parent_id: 0,
comment_count: 0,
preview_url: String::new(),
owner: String::new(),
rating: None,
}
}
}
#[cfg(test)]
mod tests {
use std::{
fs,
io::{Read, Write},
time,
};
#[test]
fn bench_json_parse_this() {
let mut buf = String::from("");
fs::File::open("./response.json")
.unwrap()
.read_to_string(&mut buf)
.unwrap();
let json = buf.as_str();
let now = time::Instant::now();
let _posts = super::R34JsonParser::default().parse_json(json);
let elapsed = now.elapsed();
println!("Full: {:?}", elapsed);
}
#[test]
fn test_json_parse_this() {
let mut buf = String::from("");
fs::File::open("./response.json")
.unwrap()
.read_to_string(&mut buf)
.unwrap();
let json = buf.as_str();
let posts = super::R34JsonParser::default().parse_json(json).unwrap();
let post1 = &posts[0].to_string();
let post2 = &posts[1].to_string();
let _file = fs::OpenOptions::new()
.write(true)
.append(true)
.open("./test.txt")
.unwrap()
.write_all(format!("{}\n{}\n", post1, post2).as_bytes()).unwrap();
}
}