雖然現在重郵課表查看可以通過掌上重郵 app 和 We 重郵微信小程序,但是這兩個應用都不能有課程的時候提前通知。因此不如通過這兩個應用的接口提供課表數據,然後轉化為通用的 iCalendar格式導入日曆。就可以體驗到系統級的日曆通知了。
此前已經有一個 Python 版本的 CQUPT-ics 是重郵的學長寫的,但是目前這兩個數據源的接口都已經發生改變,已經無法正常使用。
因此我就打算寫一個新的項目來實現上述目標。按照先前學長項目的思路來一個重寫 RIIR (Rewrite It In Rust)
新屎山地址:https://github.com/jizizr/cqupt-ics-rs 項目源碼在這裡
使用地址:https://cqupt.op.wiki/ 要用這個項目點這裡
基本實現了先前的 Python 版本的功能,此外,也實現了調休的自動課程調節。避免了放假時候還收到課程的通知,和忘記今天調休要上課(重郵官方課表是無法根據放假去改的,需要自己手動查看調休安排照著去看)
當然,純靠憑拼接訂閱鏈接還是不夠友好,當然要來一個前端的使用界面(AI 搓的),應該可以友好地使用本項目了。學校晚上會關閉課表查詢,因此 大概 0-7 點查不了課表,屬於正常現象
PS: 一鍵導入的實現只實現了 Android 和 apple 系列系統。其他系統自行在支持 ics 訂閱的日曆裡複製鏈接添加
本來是打算使用 webcal:// 跳轉日曆添加,理論上 webcal:// 協議頭跳轉基本系統都實現了,但是在 apple (macos ios) 之外其他系統由於實際提供日曆的軟件不同,具體有著極大的差異,而且基本會出現奇奇怪怪的問題。因此只提供給了 apple 使用,然後 Android 使用 intent:// 去跳轉系統日曆。因此只實現了 apple 系列系統和 Android。
如果有更好的實現歡迎提 PR,有 bug 歡迎提 issue。
最後來看一下導入的效果圖
在國慶節(休)去除了所有的課,在國慶節(班)的有對應調休的課。
手機上的效果
要增加新的 provider 源只要實現一個 trait 就行(非常簡單)
#[async_trait]
pub trait Provider: Send + Sync {
/// Token type for this provider
type Token: Send + Sync + Serialize + DeserializeOwned;
type ContextType: Send + Sync;
/// Provider 的名字
fn name(&self) -> &str;
/// Provider 的描述
fn description(&self) -> &str;
/// Get timezone for this provider
///
/// Returns the timezone used by this provider for time calculations.
/// This is used to ensure consistent timezone handling across all
/// provider operations.
/// provider 的時區
fn timezone(&self) -> FixedOffset;
/// Authenticate and get token
/// 獲取 token 的方法
async fn authenticate<'a, 'b>(
&'a self,
context: ParamContext<'b, Self::ContextType>,
request: &CourseRequest,
) -> Result<Self::Token>;
/// Validate existing token
/// 驗證 token 是否有效
async fn validate_token(&self, token: &Self::Token) -> Result<bool>;
/// Refresh token
/// 刷新 token 的方法(如果 provider 不存在則直接返回 Err 即可)
async fn refresh_token(&self, token: &Self::Token) -> Result<Self::Token>;
/// Get courses using token
/// request.semester should be Some before calling this method
/// If use crate::providers::Wrapper, it will ensure semester is Some
/// 獲取課程數據的方法
async fn get_courses<'a, 'b>(
&'a self,
context: ParamContext<'b, Self::ContextType>,
request: &mut CourseRequest,
token: &Self::Token,
) -> Result<CourseResponse>;
/// Get semester start date
/// This is called if request.semester is None before get_courses
/// If you use crate::providers::Wrapper, it will call this method automatically if request.semester is None
/// You can use the context to store intermediate data if needed
/// 獲取學期開始日期的方法
async fn get_semester_start<'a, 'b>(
&'a self,
context: ParamContext<'b, Self::ContextType>,
request: &mut CourseRequest,
token: &Self::Token,
) -> Result<chrono::DateTime<FixedOffset>>;
/// Token TTL
/// 返回 token 的有效期,用於控制緩存
fn token_ttl(&self) -> Duration {
Duration::from_secs(3600 * 24) // 24 hours default
}
}