1use std::{
2 fmt::{self, Display},
3 ops::{Range, RangeFrom, RangeTo},
4 path::PathBuf,
5};
6
7use chrono::NaiveDateTime;
8use image::DynamicImage;
9use url::Url;
10
11use crate::Error;
12
13#[must_use]
15#[derive(Debug)]
16pub struct UserInfo {
17 pub nickname: String,
19 pub avatar: Option<Url>,
21}
22
23#[must_use]
25#[derive(Debug, Default)]
26pub struct NovelInfo {
27 pub id: u32,
29 pub name: String,
31 pub author_name: String,
33 pub cover_url: Option<Url>,
35 pub introduction: Option<Vec<String>>,
37 pub word_count: Option<u32>,
39 pub is_vip: Option<bool>,
41 pub is_finished: Option<bool>,
43 pub create_time: Option<NaiveDateTime>,
45 pub update_time: Option<NaiveDateTime>,
47 pub category: Option<Category>,
49 pub tags: Option<Vec<Tag>>,
51}
52
53impl PartialEq for NovelInfo {
54 fn eq(&self, other: &Self) -> bool {
55 self.id == other.id
56 }
57}
58
59#[must_use]
61#[derive(Debug, Clone, PartialEq)]
62pub struct Category {
63 pub id: Option<u16>,
65 pub parent_id: Option<u16>,
67 pub name: String,
69}
70
71impl Display for Category {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(f, "{}", self.name)
74 }
75}
76
77#[must_use]
79#[derive(Debug, Clone, PartialEq)]
80pub struct Tag {
81 pub id: Option<u16>,
83 pub name: String,
85}
86
87impl Display for Tag {
88 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89 write!(f, "{}", self.name)
90 }
91}
92
93pub type VolumeInfos = Vec<VolumeInfo>;
95
96#[must_use]
98#[derive(Debug)]
99pub struct VolumeInfo {
100 pub id: u32,
102 pub title: String,
104 pub chapter_infos: Vec<ChapterInfo>,
106}
107
108#[must_use]
110#[derive(Debug, Default)]
111pub struct ChapterInfo {
112 pub novel_id: Option<u32>,
114 pub id: u32,
116 pub title: String,
118 pub is_vip: Option<bool>,
120 pub price: Option<u16>,
122 pub payment_required: Option<bool>,
124 pub is_valid: Option<bool>,
126 pub word_count: Option<u32>,
128 pub create_time: Option<NaiveDateTime>,
130 pub update_time: Option<NaiveDateTime>,
132}
133
134impl ChapterInfo {
135 pub fn payment_required(&self) -> bool {
137 self.payment_required.as_ref().is_some_and(|x| *x)
138 }
139
140 pub fn is_valid(&self) -> bool {
142 self.is_valid.as_ref().is_none_or(|x| *x)
143 }
144
145 pub fn can_download(&self) -> bool {
147 !self.payment_required() && self.is_valid()
148 }
149}
150
151pub type ContentInfos = Vec<ContentInfo>;
153
154#[must_use]
156#[derive(Debug)]
157pub enum ContentInfo {
158 Text(String),
160 Image(Url),
162}
163
164#[derive(Debug, Default)]
166pub struct Options {
167 pub keyword: Option<String>,
169 pub is_finished: Option<bool>,
171 pub is_vip: Option<bool>,
173 pub category: Option<Category>,
175 pub tags: Option<Vec<Tag>>,
177 pub excluded_tags: Option<Vec<Tag>>,
179 pub update_days: Option<u8>,
181 pub word_count: Option<WordCountRange>,
183}
184
185#[derive(Debug)]
187pub enum WordCountRange {
188 Range(Range<u32>),
190 RangeFrom(RangeFrom<u32>),
192 RangeTo(RangeTo<u32>),
194}
195
196#[derive(Debug)]
197pub enum Comment {
198 Short(ShortComment),
199 Long(LongComment),
200}
201
202#[derive(Debug)]
203pub struct ShortComment {
204 pub id: u32,
205 pub user: UserInfo,
206 pub content: Vec<String>,
207 pub create_time: Option<NaiveDateTime>,
208 pub like_count: Option<u16>,
209 pub replies: Option<Vec<ShortComment>>,
210}
211
212impl PartialEq for ShortComment {
213 fn eq(&self, other: &Self) -> bool {
214 self.id == other.id
215 }
216}
217
218#[derive(Debug)]
219pub struct LongComment {
220 pub id: u32,
221 pub user: UserInfo,
222 pub title: String,
223 pub content: Vec<String>,
224 pub create_time: Option<NaiveDateTime>,
225 pub like_count: Option<u16>,
226 pub replies: Option<Vec<ShortComment>>,
227}
228
229impl PartialEq for LongComment {
230 fn eq(&self, other: &Self) -> bool {
231 self.id == other.id
232 }
233}
234
235#[derive(Clone, Copy)]
236pub enum CommentType {
237 Short,
238 Long,
239}
240
241#[trait_variant::make(Send)]
243pub trait Client {
244 fn proxy(&mut self, proxy: Url);
246
247 fn no_proxy(&mut self);
249
250 fn cert(&mut self, cert_path: PathBuf);
252
253 async fn shutdown(&self) -> Result<(), Error>;
255
256 async fn add_cookie(&self, cookie_str: &str, url: &Url) -> Result<(), Error>;
258
259 async fn log_in(&self, username: String, password: Option<String>) -> Result<(), Error>;
261
262 async fn logged_in(&self) -> Result<bool, Error>;
264
265 async fn user_info(&self) -> Result<UserInfo, Error>;
267
268 async fn money(&self) -> Result<u32, Error>;
270
271 async fn sign_in(&self) -> Result<(), Error>;
273
274 async fn bookshelf_infos(&self) -> Result<Vec<u32>, Error>;
276
277 async fn novel_info(&self, id: u32) -> Result<Option<NovelInfo>, Error>;
279
280 async fn comments(
282 &self,
283 id: u32,
284 comment_type: CommentType,
285 need_replies: bool,
286 page: u16,
287 size: u16,
288 ) -> Result<Option<Vec<Comment>>, Error>;
289
290 async fn volume_infos(&self, id: u32) -> Result<Option<VolumeInfos>, Error>;
292
293 async fn content_infos(&self, info: &ChapterInfo) -> Result<ContentInfos, Error>;
295
296 async fn order_chapter(&self, info: &ChapterInfo) -> Result<(), Error>;
298
299 async fn order_novel(&self, id: u32, infos: &VolumeInfos) -> Result<(), Error>;
301
302 async fn image(&self, url: &Url) -> Result<DynamicImage, Error>;
304
305 async fn categories(&self) -> Result<&Vec<Category>, Error>;
307
308 async fn tags(&self) -> Result<&Vec<Tag>, Error>;
310
311 async fn search_infos(
313 &self,
314 option: &Options,
315 page: u16,
316 size: u16,
317 ) -> Result<Option<Vec<u32>>, Error>;
318
319 fn has_this_type_of_comments(comment_type: CommentType) -> bool;
321}
322
323mod tests {
324 #[test]
325 fn test_payment_required() {
326 use crate::ChapterInfo;
327
328 assert!(ChapterInfo {
329 payment_required: Some(true),
330 ..Default::default()
331 }
332 .payment_required());
333
334 assert!(!ChapterInfo {
335 payment_required: Some(false),
336 ..Default::default()
337 }
338 .payment_required());
339
340 assert!(!ChapterInfo {
341 payment_required: None,
342 ..Default::default()
343 }
344 .payment_required());
345 }
346
347 #[test]
348 fn test_is_valid() {
349 use crate::ChapterInfo;
350
351 assert!(ChapterInfo {
352 is_valid: Some(true),
353 ..Default::default()
354 }
355 .is_valid());
356
357 assert!(!ChapterInfo {
358 is_valid: Some(false),
359 ..Default::default()
360 }
361 .is_valid());
362
363 assert!(ChapterInfo {
364 is_valid: None,
365 ..Default::default()
366 }
367 .is_valid());
368 }
369}