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!(
329 ChapterInfo {
330 payment_required: Some(true),
331 ..Default::default()
332 }
333 .payment_required()
334 );
335
336 assert!(
337 !ChapterInfo {
338 payment_required: Some(false),
339 ..Default::default()
340 }
341 .payment_required()
342 );
343
344 assert!(
345 !ChapterInfo {
346 payment_required: None,
347 ..Default::default()
348 }
349 .payment_required()
350 );
351 }
352
353 #[test]
354 fn test_is_valid() {
355 use crate::ChapterInfo;
356
357 assert!(
358 ChapterInfo {
359 is_valid: Some(true),
360 ..Default::default()
361 }
362 .is_valid()
363 );
364
365 assert!(
366 !ChapterInfo {
367 is_valid: Some(false),
368 ..Default::default()
369 }
370 .is_valid()
371 );
372
373 assert!(
374 ChapterInfo {
375 is_valid: None,
376 ..Default::default()
377 }
378 .is_valid()
379 );
380 }
381}