1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
//! Main representation of a imageboard post
//!
//! # Post
//! A [`Post` struct](Post) is a generic representation of an imageboard post.
//!
//! Most imageboard APIs have a common set of info from the files we want to download.
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::{cmp::Ordering, fmt::Debug, ops::Not};
use crate::ImageBoards;
use self::{rating::Rating, tags::Tag};
pub mod error;
pub mod extension;
pub mod rating;
pub mod tags;
/// Special enum to simplify the selection of the output file name when downloading a [`Post`]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum NameType {
ID,
MD5,
}
impl Not for NameType {
type Output = Self;
fn not(self) -> Self::Output {
match self {
NameType::ID => NameType::MD5,
NameType::MD5 => NameType::ID,
}
}
}
/// Queue that combines all posts collected, with which tags and with a user-defined blacklist in case an Extractor implements [Auth](ibdl-extractors::websites::Auth).
#[derive(Debug)]
pub struct PostQueue {
/// The imageboard where the posts come from.
pub imageboard: ImageBoards,
/// The internal `Client` used by the extractor.
pub client: Client,
/// A list containing all `Post`s collected.
pub posts: Vec<Post>,
/// The tags used to search the collected posts.
pub tags: Vec<String>,
}
impl PostQueue {
pub fn prepare(&mut self, limit: Option<u16>) {
if let Some(max) = limit {
self.posts.truncate(max as usize);
} else {
self.posts.shrink_to_fit()
}
}
}
/// Catchall model for the necessary parts of the imageboard post to properly identify, download and save it.
#[derive(Clone, Serialize, Deserialize, Eq)]
pub struct Post {
/// ID number of the post given by the imageboard
pub id: u64,
/// The imageboard where this post was extracted from
pub website: ImageBoards,
/// Direct URL of the original image file located inside the imageboard's server
pub url: String,
/// Instead of calculating the downloaded file's MD5 hash on the fly, it uses the one provided by the API.
pub md5: String,
/// The original file extension provided by the imageboard.
///
/// ```https://konachan.com``` (Moebooru) and some other imageboards don't provide this field. So, additional work is required to get the file extension from the url
pub extension: String,
/// Rating of the post. Can be:
///
/// * `Rating::Safe` for SFW posts
/// * `Rating::Questionable` for a not necessarily SFW post
/// * `Rating::Explicit` for NSFW posts
/// * `Rating::Unknown` in case none of the above are correctly parsed
pub rating: Rating,
/// Set of tags associated with the post.
///
/// Used to exclude posts according to a blacklist
pub tags: Vec<Tag>,
}
impl Debug for Post {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Post")
.field("Post ID", &self.id)
.field("Website", &self.website)
.field("Download URL", &self.url)
.field("MD5 Hash", &self.md5)
.field("File Extension", &self.extension)
.field("Rating", &self.rating)
.field("Tag List", &self.tags)
.finish()
}
}
impl Ord for Post {
fn cmp(&self, other: &Self) -> Ordering {
self.id.cmp(&other.id)
}
}
impl PartialOrd for Post {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Post {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Post {
/// Get the final file name of the post for saving.
#[inline]
pub fn file_name(&self, name_type: NameType) -> String {
let name = match name_type {
NameType::ID => self.id.to_string(),
NameType::MD5 => self.md5.to_string(),
};
format!("{}.{}", name, self.extension)
}
/// Get the generic name of the post. Can be it's MD5 hash or ID
#[inline]
pub fn name(&self, name_type: NameType) -> String {
match name_type {
NameType::ID => self.id.to_string(),
NameType::MD5 => self.md5.to_string(),
}
}
#[inline]
pub fn seq_file_name(&self, num_digits: usize) -> String {
format!("{:0num_digits$}.{}", self.id, self.extension)
}
}