虽然现在重邮课表查看可以通过掌上重邮 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
}
}