1use std::fmt::{self, Display};
2use std::ops::{Range, RangeFrom, RangeTo};
3use std::path::PathBuf;
4
5use chrono::NaiveDateTime;
6use image::DynamicImage;
7use url::Url;
8
9use crate::Error;
10
11#[must_use]
13#[derive(Debug)]
14pub struct UserInfo {
15 pub nickname: String,
17 pub avatar: Option<Url>,
19}
20
21#[must_use]
23#[derive(Debug, Default)]
24pub struct NovelInfo {
25 pub id: u32,
27 pub name: String,
29 pub author_name: String,
31 pub cover_url: Option<Url>,
33 pub introduction: Option<Vec<String>>,
35 pub word_count: Option<u32>,
37 pub is_vip: Option<bool>,
39 pub is_finished: Option<bool>,
41 pub create_time: Option<NaiveDateTime>,
43 pub update_time: Option<NaiveDateTime>,
45 pub category: Option<Category>,
47 pub tags: Option<Vec<Tag>>,
49}
50
51impl PartialEq for NovelInfo {
52 fn eq(&self, other: &Self) -> bool {
53 self.id == other.id
54 }
55}
56
57#[must_use]
59#[derive(Debug, Clone, PartialEq)]
60pub struct Category {
61 pub id: Option<u16>,
63 pub parent_id: Option<u16>,
65 pub name: String,
67}
68
69impl Display for Category {
70 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 write!(f, "{}", self.name)
72 }
73}
74
75#[must_use]
77#[derive(Debug, Clone, PartialEq)]
78pub struct Tag {
79 pub id: Option<u16>,
81 pub name: String,
83}
84
85impl Display for Tag {
86 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87 write!(f, "{}", self.name)
88 }
89}
90
91pub type VolumeInfos = Vec<VolumeInfo>;
93
94#[must_use]
96#[derive(Debug)]
97pub struct VolumeInfo {
98 pub id: u32,
100 pub title: String,
102 pub chapter_infos: Vec<ChapterInfo>,
104}
105
106#[must_use]
108#[derive(Debug, Default, Clone)]
109pub struct ChapterInfo {
110 pub novel_id: Option<u32>,
112 pub id: u32,
114 pub title: String,
116 pub is_vip: Option<bool>,
118 pub price: Option<u16>,
120 pub payment_required: Option<bool>,
122 pub is_valid: Option<bool>,
124 pub word_count: Option<u32>,
126 pub create_time: Option<NaiveDateTime>,
128 pub update_time: Option<NaiveDateTime>,
130}
131
132impl ChapterInfo {
133 pub fn payment_required(&self) -> bool {
135 self.payment_required.as_ref().is_some_and(|x| *x)
136 }
137
138 pub fn is_valid(&self) -> bool {
140 self.is_valid.as_ref().is_none_or(|x| *x)
141 }
142
143 pub fn can_download(&self) -> bool {
145 !self.payment_required() && self.is_valid()
146 }
147}
148
149pub type ContentInfos = Vec<ContentInfo>;
151
152#[must_use]
154#[derive(Debug)]
155pub enum ContentInfo {
156 Text(String),
158 Image(Url),
160}
161
162#[derive(Debug, Default)]
164pub struct Options {
165 pub keyword: Option<String>,
167 pub is_finished: Option<bool>,
169 pub is_vip: Option<bool>,
171 pub category: Option<Category>,
173 pub tags: Option<Vec<Tag>>,
175 pub excluded_tags: Option<Vec<Tag>>,
177 pub update_days: Option<u8>,
179 pub word_count: Option<WordCountRange>,
181}
182
183#[derive(Debug)]
185pub enum WordCountRange {
186 Range(Range<u32>),
188 RangeFrom(RangeFrom<u32>),
190 RangeTo(RangeTo<u32>),
192}
193
194#[derive(Debug)]
195pub enum Comment {
196 Short(ShortComment),
197 Long(LongComment),
198}
199
200#[derive(Debug)]
201pub struct ShortComment {
202 pub id: u32,
203 pub user: UserInfo,
204 pub content: Vec<String>,
205 pub create_time: Option<NaiveDateTime>,
206 pub like_count: Option<u16>,
207 pub replies: Option<Vec<ShortComment>>,
208}
209
210impl PartialEq for ShortComment {
211 fn eq(&self, other: &Self) -> bool {
212 self.id == other.id
213 }
214}
215
216#[derive(Debug)]
217pub struct LongComment {
218 pub id: u32,
219 pub user: UserInfo,
220 pub title: String,
221 pub content: Vec<String>,
222 pub create_time: Option<NaiveDateTime>,
223 pub like_count: Option<u16>,
224 pub replies: Option<Vec<ShortComment>>,
225}
226
227impl PartialEq for LongComment {
228 fn eq(&self, other: &Self) -> bool {
229 self.id == other.id
230 }
231}
232
233#[derive(Clone, Copy)]
234pub enum CommentType {
235 Short,
236 Long,
237}
238
239#[trait_variant::make(Send)]
241pub trait Client {
242 fn proxy(&mut self, proxy: Url);
244
245 fn no_proxy(&mut self);
247
248 fn cert(&mut self, cert_path: PathBuf);
250
251 async fn shutdown(&self) -> Result<(), Error>;
253
254 async fn add_cookie(&self, cookie_str: &str, url: &Url) -> Result<(), Error>;
256
257 async fn log_in(&self, username: String, password: Option<String>) -> Result<(), Error>;
259
260 async fn logged_in(&self) -> Result<bool, Error>;
262
263 async fn user_info(&self) -> Result<UserInfo, Error>;
265
266 async fn money(&self) -> Result<u32, Error>;
268
269 async fn sign_in(&self) -> Result<(), Error>;
271
272 async fn bookshelf_infos(&self) -> Result<Vec<u32>, Error>;
274
275 async fn novel_info(&self, id: u32) -> Result<Option<NovelInfo>, Error>;
277
278 async fn comments(
280 &self,
281 id: u32,
282 comment_type: CommentType,
283 need_replies: bool,
284 page: u16,
285 size: u16,
286 ) -> Result<Option<Vec<Comment>>, Error>;
287
288 async fn volume_infos(&self, id: u32) -> Result<Option<VolumeInfos>, Error>;
290
291 async fn content_infos(&self, info: &ChapterInfo) -> Result<ContentInfos, Error>;
293
294 async fn content_infos_multiple(
296 &self,
297 infos: &[ChapterInfo],
298 ) -> Result<Vec<ContentInfos>, Error>;
299
300 async fn order_chapter(&self, info: &ChapterInfo) -> Result<(), Error>;
302
303 async fn order_novel(&self, id: u32, infos: &VolumeInfos) -> Result<(), Error>;
305
306 async fn image(&self, url: &Url) -> Result<DynamicImage, Error>;
308
309 async fn categories(&self) -> Result<&Vec<Category>, Error>;
311
312 async fn tags(&self) -> Result<&Vec<Tag>, Error>;
314
315 async fn search_infos(
317 &self,
318 option: &Options,
319 page: u16,
320 size: u16,
321 ) -> Result<Option<Vec<u32>>, Error>;
322
323 fn has_this_type_of_comments(comment_type: CommentType) -> bool;
325}
326
327mod tests {
328 #[test]
329 fn test_payment_required() {
330 use crate::ChapterInfo;
331
332 assert!(
333 ChapterInfo {
334 payment_required: Some(true),
335 ..Default::default()
336 }
337 .payment_required()
338 );
339
340 assert!(
341 !ChapterInfo {
342 payment_required: Some(false),
343 ..Default::default()
344 }
345 .payment_required()
346 );
347
348 assert!(
349 !ChapterInfo {
350 payment_required: None,
351 ..Default::default()
352 }
353 .payment_required()
354 );
355 }
356
357 #[test]
358 fn test_is_valid() {
359 use crate::ChapterInfo;
360
361 assert!(
362 ChapterInfo {
363 is_valid: Some(true),
364 ..Default::default()
365 }
366 .is_valid()
367 );
368
369 assert!(
370 !ChapterInfo {
371 is_valid: Some(false),
372 ..Default::default()
373 }
374 .is_valid()
375 );
376
377 assert!(
378 ChapterInfo {
379 is_valid: None,
380 ..Default::default()
381 }
382 .is_valid()
383 );
384 }
385}