1use crate::error::Result;
2use crate::{Client, course::Course, error::ManabaError};
3use chrono::NaiveDateTime;
4use scraper::{Html, Selector};
5
6#[derive(Debug)]
7pub struct Report {
8 pub title: String,
9 pub state: ReportState,
10 pub start_date: Option<ReportDate>,
11 pub due_date: Option<ReportDate>,
12}
13
14#[derive(Debug)]
15pub struct ReportDate {
16 pub date: NaiveDateTime,
17 pub importance_level: ReportImportanceLevel,
18}
19
20impl ReportDate {
21 fn new(date: &str) -> Self {
22 const DATE_FORMAT: &str = "%Y-%m-%d %H:%M";
23 let date = NaiveDateTime::parse_from_str(date, DATE_FORMAT).unwrap();
24 let importance_level = ReportImportanceLevel::from(date);
25
26 ReportDate {
27 date,
28 importance_level,
29 }
30 }
31}
32
33#[derive(Debug, PartialEq, Eq)]
34pub enum ReportState {
35 Todo,
36 Done,
37}
38
39#[derive(Debug)]
40pub enum ReportImportanceLevel {
41 None,
42 Low,
43 Medium,
44 High,
45}
46
47impl From<NaiveDateTime> for ReportImportanceLevel {
48 fn from(date: NaiveDateTime) -> Self {
49 let today: chrono::NaiveDate = chrono::Local::now().date_naive();
50 let due_date: chrono::NaiveDate = date.date();
51
52 if due_date < today {
53 ReportImportanceLevel::None
54 } else {
55 let tomorrow = today + chrono::Duration::days(1);
56 let one_week_later = today + chrono::Duration::days(7);
57
58 if due_date <= tomorrow {
59 ReportImportanceLevel::High
60 } else if due_date <= one_week_later {
61 ReportImportanceLevel::Medium
62 } else {
63 ReportImportanceLevel::Low
64 }
65 }
66 }
67}
68
69impl Client {
70 pub async fn get_reports(&self, course: &Course) -> Result<Vec<Report>> {
71 let url = format!("{}_report", course.id);
72
73 let request = self.request(reqwest::Method::GET, url);
74
75 let response = request
76 .send()
77 .await
78 .map_err(ManabaError::SendRequestError)?;
79
80 let body = response
81 .text()
82 .await
83 .map_err(ManabaError::HtmlBodyParseError)?;
84
85 let document = Html::parse_document(&body);
86
87 let selector = Selector::parse("table.stdlist tr:not(.title)").unwrap();
88 let reports_element = document.select(&selector);
89
90 let reports = reports_element
91 .map(|report_element| {
92 let selector = Selector::parse("td").unwrap();
93 let mut rows = report_element.select(&selector);
94
95 let title = {
96 let row = rows.next().unwrap();
97 let selector = Selector::parse("a").unwrap();
98 let report_title_element = row.select(&selector).next().unwrap();
99 report_title_element.inner_html()
100 };
101
102 let state = {
103 let row = rows.next().unwrap();
104 let selector = Selector::parse("strong").unwrap();
105
106 if row.select(&selector).next().is_some() {
107 ReportState::Done
108 } else {
109 ReportState::Todo
110 }
111 };
112
113 let start_date = {
114 let row = rows.next().unwrap();
115 let date = row.inner_html();
116 if date.is_empty() {
117 None
118 } else {
119 Some(ReportDate::new(&date))
120 }
121 };
122
123 let due_date = {
124 let row = rows.next().unwrap();
125 let date = row.inner_html();
126 if date.is_empty() {
127 None
128 } else {
129 Some(ReportDate::new(&date))
130 }
131 };
132
133 Report {
134 title,
135 state,
136 start_date,
137 due_date,
138 }
139 })
140 .collect::<Vec<Report>>();
141
142 Ok(reports)
143 }
144}