manaba_sdk/
report.rs

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}