ucas_iclass/
query.rs

1//! Query selected courses.
2
3use chrono::{DateTime, FixedOffset, NaiveDate};
4use std::fmt;
5
6use super::{IClass, IClassError, Response};
7use serde::Deserialize;
8
9/// A semester.
10#[derive(Clone, Debug, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct Semester {
13    /// Semester code.
14    pub code: String,
15    /// Semester name.
16    pub name: String,
17    /// Semester begin date.
18    #[serde(deserialize_with = "super::util::deserialize_str_to_date_hyphen")]
19    pub begin_date: NaiveDate,
20    /// Semester end date.
21    #[serde(deserialize_with = "super::util::deserialize_str_to_date_hyphen")]
22    pub end_date: NaiveDate,
23    /// Whether it is the current semester.
24    #[serde(
25        rename = "yearStatus",
26        deserialize_with = "super::util::deserialize_str_to_bool"
27    )]
28    pub is_current: bool,
29}
30
31/// A course.
32#[derive(Clone, Debug, Deserialize)]
33#[serde(rename_all = "camelCase")]
34pub struct Course {
35    // /// Course ID in iClass system.
36    // pub id: String,
37    /// Course ID as we all know.
38    #[serde(rename = "courseNum")]
39    pub course_id: String,
40    /// Course name. There may be courses with the same name.
41    pub course_name: String,
42    /// Classroom name.
43    pub classroom_name: String,
44    /// Teacher name.
45    pub teacher_name: String,
46}
47
48/// A daily schedule.
49#[derive(Clone, Debug, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct DailySchedule {
52    /// Date of this schedule.
53    #[serde(
54        rename = "dateStr",
55        deserialize_with = "super::util::deserialize_str_to_date"
56    )]
57    pub date: NaiveDate,
58    /// Schedules in this day.
59    #[serde(rename = "schedData")]
60    pub schedules: Vec<Schedule>,
61}
62
63/// A single schedule entry.
64#[derive(Clone, Debug, Deserialize)]
65pub struct Schedule {
66    /// The course scheduled.
67    #[serde(flatten)]
68    pub course: Course,
69    /// Id of this schedule.
70    pub id: String,
71    /// Unique id of this schedule.
72    pub uuid: String,
73    /// Check in status. Only work in [`query_daily_schedule`](IClass::query_daily_schedule), and does not work in [`query_weekly_schedule`](IClass::query_weekly_schedule) or when wrapped in [`DailySchedule`](DailySchedule).
74    #[serde(
75        rename = "signStatus",
76        deserialize_with = "super::util::deserialize_str_to_bool"
77    )]
78    pub checked_in: bool,
79    /// Begin time.
80    #[serde(
81        rename = "classBeginTime",
82        deserialize_with = "super::util::deserialize_str_to_datetime"
83    )]
84    pub begin_time: DateTime<FixedOffset>,
85    /// End time.
86    #[serde(
87        rename = "classEndTime",
88        deserialize_with = "super::util::deserialize_str_to_datetime"
89    )]
90    pub end_time: DateTime<FixedOffset>,
91}
92
93impl IClass {
94    /// Queries current semester.
95    ///
96    /// # Errors
97    ///
98    /// See [`IClassError`].
99    pub async fn query_semester(&self) -> Result<Vec<Semester>, IClassError> {
100        let url = self
101            .api_root
102            .join("app/course/get_base_school_year.action")?;
103        let response: Response<Vec<Semester>> = self
104            .client
105            .post(url)?
106            .form(&[("userId", &self.get_user_session()?.id)])?
107            .send()
108            .await?
109            .json()
110            .await?;
111        let semesters = response.into_result()?;
112        Ok(semesters)
113    }
114
115    // https://iclass.ucas.edu.cn:8181/app/choosecourse/get_myall_course.action?user_type=1
116
117    /// Queries selected courses for current semester.
118    ///
119    /// # Errors
120    ///
121    /// See [`IClassError`].
122    pub async fn query_courses(&self) -> Result<Vec<Course>, IClassError> {
123        let user_session = self.get_user_session()?;
124        let url = self.api_root.join("app/my/get_my_course.action")?;
125        let response: Response<Vec<Course>> = self
126            .client
127            .post(url)?
128            .header("sessionId", &user_session.session_id)?
129            .form(&[("id", &user_session.id)])?
130            .send()
131            .await?
132            .json()
133            .await?;
134        let courses = response.into_result()?;
135
136        Ok(courses)
137    }
138
139    /// Queries daily schedule.
140    ///
141    /// # Arguments
142    ///
143    /// - `date` - A date string in "YYYYMMDD" format within the week to query, like "20251013".
144    ///
145    /// # Errors
146    ///
147    /// See [`IClassError`].
148    pub async fn query_daily_schedule(
149        &self,
150        date: &NaiveDate,
151    ) -> Result<Vec<Schedule>, IClassError> {
152        let user_session = self.get_user_session()?;
153        let url = self
154            .api_root
155            .join("app/course/get_stu_course_sched.action")?;
156        let date_str = super::util::format_date_to_str(date);
157        let response: Response<Vec<Schedule>> = self
158            .client
159            .post(url)?
160            .header("sessionId", &user_session.session_id)?
161            .form(&[("id", &user_session.id), ("dateStr", &date_str)])?
162            .send()
163            .await?
164            .json()
165            .await?;
166        let daily_schedule = response.into_result()?;
167
168        Ok(daily_schedule)
169    }
170
171    /// Queries weekly schedule.
172    ///
173    /// # Arguments
174    ///
175    /// - `date` - A date string in "YYYYMMDD" format within the week to query, like "20251013".
176    ///
177    /// # Errors
178    ///
179    /// See [`IClassError`].
180    pub async fn query_weekly_schedule(
181        &self,
182        date: &NaiveDate,
183    ) -> Result<Vec<DailySchedule>, IClassError> {
184        let user_session = self.get_user_session()?;
185        let url = self
186            .api_root
187            .join("app/course/get_stu_course_sched_week.action")?;
188        let date_str = super::util::format_date_to_str(date);
189        let response: Response<Vec<DailySchedule>> = self
190            .client
191            .post(url)?
192            .header("sessionId", &user_session.session_id)?
193            .form(&[("id", &user_session.id), ("dateStr", &date_str)])?
194            .send()
195            .await?
196            .json()
197            .await?;
198        let week_schedule = response.into_result()?;
199
200        Ok(week_schedule)
201    }
202}
203
204impl fmt::Display for Semester {
205    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
206        let Self {
207            code,
208            name,
209            begin_date,
210            end_date,
211            is_current,
212        } = self;
213        let current_indicator = if *is_current { " (current)" } else { "" };
214        write!(
215            f,
216            "{name} ({code}): {begin_date} ~ {end_date}{current_indicator}"
217        )
218    }
219}
220
221impl fmt::Display for Course {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        let Self {
224            course_name,
225            course_id,
226            teacher_name,
227            classroom_name,
228            ..
229        } = self;
230        write!(
231            f,
232            "{course_name} ({course_id}) - {teacher_name} @ {classroom_name}"
233        )
234    }
235}
236
237impl fmt::Display for Schedule {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        let Self {
240            course,
241            id,
242            uuid,
243            checked_in,
244            begin_time,
245            end_time,
246            ..
247        } = self;
248        let indicator = if *checked_in { "[✓]" } else { "[ ]" };
249        let (begin_time, end_time) = (
250            super::util::format_datetime_to_str(begin_time),
251            super::util::format_datetime_to_str(end_time),
252        );
253        write!(
254            f,
255            "{indicator} [{begin_time} ~ {end_time}] id={id} uuid={uuid} {}",
256            course.course_name
257        )
258    }
259}
260
261impl fmt::Display for DailySchedule {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        let Self { date, schedules } = self;
264        writeln!(f, "Schedule on {date}:")?;
265        for schedule in schedules {
266            let Schedule {
267                id,
268                uuid,
269                begin_time,
270                end_time,
271                ..
272            } = schedule;
273            let (begin_time, end_time) = (
274                super::util::format_datetime_to_str(begin_time),
275                super::util::format_datetime_to_str(end_time),
276            );
277            writeln!(
278                f,
279                "  [{begin_time} ~ {end_time}] id={id} uuid={uuid} {}",
280                schedule.course.course_name
281            )?;
282        }
283        Ok(())
284    }
285}