forked from Anri/cal8tor
find the right timetable for the current semester/year combo
This commit is contained in:
parent
ade74b4e4c
commit
16a9bba6dd
3 changed files with 64 additions and 108 deletions
41
src/main.rs
41
src/main.rs
|
@ -9,14 +9,18 @@ mod utils;
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(version, about, long_about = None)]
|
#[clap(version, about, long_about = None)]
|
||||||
struct Args {
|
struct Args {
|
||||||
/// The class you want to get the timetable, i.e.: L2-A
|
/// The class you want to get the timetable, i.e.: M1-LP
|
||||||
#[clap(value_parser)]
|
#[clap(value_parser)]
|
||||||
class: String,
|
class: String,
|
||||||
|
|
||||||
/// The semester you want (useful only in 3rd year, 1-2 use letter in class)
|
/// The semester you want (1 or 2)
|
||||||
#[clap(short, long, value_parser, value_name = "SEMESTER NUMBER")]
|
#[clap(short, long, value_parser, value_name = "SEMESTER NUMBER")]
|
||||||
semester: Option<i8>,
|
semester: Option<i8>,
|
||||||
|
|
||||||
|
/// The year, default to the current year
|
||||||
|
#[clap(short, long, value_parser, value_name = "YEAR")]
|
||||||
|
year: Option<i32>,
|
||||||
|
|
||||||
/// Export to iCalendar format (.ics)
|
/// Export to iCalendar format (.ics)
|
||||||
#[clap(short, long, value_name = "FILE NAME")]
|
#[clap(short, long, value_name = "FILE NAME")]
|
||||||
export: Option<String>,
|
export: Option<String>,
|
||||||
|
@ -30,38 +34,31 @@ struct Args {
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let args = Args::parse();
|
let args = Args::parse();
|
||||||
|
|
||||||
let matches = Regex::new(r"[Ll](?P<year>\d)[-–•·]?(?P<letter>.)?")
|
let matches =
|
||||||
|
Regex::new(r"(?i)M(?P<level>[1,2])[-–•·]?(?P<pathway>(LP|IMPAIRS|DATA|GENIAL|MPRI))?")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.captures(&args.class)
|
.captures(&args.class)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let year = matches
|
let level = matches
|
||||||
.name("year")
|
.name("level")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_str()
|
.as_str()
|
||||||
.parse::<i8>()
|
.parse::<i8>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let letter = matches
|
let pathway = matches.name("pathway").unwrap().as_str();
|
||||||
.name("letter")
|
|
||||||
.map(|c| c.as_str().chars().next().expect("Error in letter"));
|
|
||||||
|
|
||||||
// Show a separator only if we need one
|
let user_agent = format!("cal7tor/{}", env!("CARGO_PKG_VERSION"));
|
||||||
let seperator = match letter {
|
|
||||||
Some(_) => "-",
|
|
||||||
None => "",
|
|
||||||
};
|
|
||||||
|
|
||||||
let user_agent = format!("cal8tor/{}", env!("CARGO_PKG_VERSION"));
|
|
||||||
|
|
||||||
println!(
|
println!(
|
||||||
"Récupération de l'emploi du temps des L{}{}{}...",
|
"Récupération de l'emploi du temps des M{}-{}...",
|
||||||
year,
|
level,
|
||||||
seperator,
|
pathway.to_uppercase()
|
||||||
letter.unwrap_or_default().to_uppercase()
|
|
||||||
);
|
);
|
||||||
let timetable = timetable::timetable(year, args.semester, letter, &user_agent).await;
|
let timetable =
|
||||||
|
timetable::timetable(level, args.semester, args.year, pathway, &user_agent).await;
|
||||||
|
|
||||||
println!("Récupération des informations par rapport à l'année...");
|
/* println!("Récupération des informations par rapport à l'année...");
|
||||||
let info = info::info(&user_agent).await;
|
let info = info::info(&user_agent).await;
|
||||||
|
|
||||||
if args.export.is_some() {
|
if args.export.is_some() {
|
||||||
|
@ -77,5 +74,5 @@ async fn main() {
|
||||||
println!("Affichage...");
|
println!("Affichage...");
|
||||||
timetable::display(timetable, args.cl);
|
timetable::display(timetable, args.cl);
|
||||||
println!("Vous devrez peut-être mettre votre terminal en plein écran si ce n'est pas déjà le cas.");
|
println!("Vous devrez peut-être mettre votre terminal en plein écran si ce n'est pas déjà le cas.");
|
||||||
}
|
} */
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,19 +12,24 @@ pub mod models;
|
||||||
|
|
||||||
/// Fetch the timetable for a class
|
/// Fetch the timetable for a class
|
||||||
pub async fn timetable(
|
pub async fn timetable(
|
||||||
year: i8,
|
level: i8,
|
||||||
semester_opt: Option<i8>,
|
semester_opt: Option<i8>,
|
||||||
letter: Option<char>,
|
year_opt: Option<i32>,
|
||||||
|
pathway: &str,
|
||||||
user_agent: &str,
|
user_agent: &str,
|
||||||
) -> (Vec<String>, (usize, Vec<models::Day>)) {
|
) -> (Vec<String>, (usize, Vec<models::Day>)) {
|
||||||
let semester = get_semester(semester_opt, letter);
|
let semester = get_semester(semester_opt);
|
||||||
|
|
||||||
let document = get_webpage(year, semester, letter, user_agent)
|
let year = get_year(year_opt, semester);
|
||||||
|
|
||||||
|
let document = get_webpage(level, semester, &year, user_agent)
|
||||||
.await
|
.await
|
||||||
.expect("Can't reach timetable website.");
|
.expect("Can't reach timetable website.");
|
||||||
|
|
||||||
|
(vec![], (0, vec![]))
|
||||||
|
|
||||||
// Selectors
|
// Selectors
|
||||||
let sel_table = Selector::parse("table").unwrap();
|
/* let sel_table = Selector::parse("table").unwrap();
|
||||||
let sel_tr = Selector::parse("tr").unwrap();
|
let sel_tr = Selector::parse("tr").unwrap();
|
||||||
let sel_tbody = Selector::parse("tbody").unwrap();
|
let sel_tbody = Selector::parse("tbody").unwrap();
|
||||||
let sel_th = Selector::parse("th").unwrap();
|
let sel_th = Selector::parse("th").unwrap();
|
||||||
|
@ -116,59 +121,17 @@ pub async fn timetable(
|
||||||
panic!("Error when building the timetable.");
|
panic!("Error when building the timetable.");
|
||||||
}
|
}
|
||||||
|
|
||||||
(schedules, (semester as usize, timetable))
|
(schedules, (semester as usize, timetable)) */
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get timetable webpage
|
/// Get timetable webpage
|
||||||
async fn get_webpage(
|
async fn get_webpage(
|
||||||
year: i8,
|
level: i8,
|
||||||
semester: i8,
|
semester: i8,
|
||||||
letter: Option<char>,
|
year: &str,
|
||||||
user_agent: &str,
|
user_agent: &str,
|
||||||
) -> Result<Html, Box<dyn std::error::Error>> {
|
) -> Result<Html, Box<dyn std::error::Error>> {
|
||||||
let url = {
|
let url = format!("https://silice.informatique.univ-paris-diderot.fr/ufr/U{}/EDT/visualiserEmploiDuTemps.php?quoi=M{},{}", year, level, semester);
|
||||||
let panic_semester_message = "Unknown semester.";
|
|
||||||
let panic_letter_message = "Unknown letter.";
|
|
||||||
|
|
||||||
let base_url = "https://informatique.up8.edu/licence-iv/edt";
|
|
||||||
let allow_letters_1 = match semester {
|
|
||||||
1 => ['a', 'b', 'c'],
|
|
||||||
2 => ['x', 'y', 'z'],
|
|
||||||
_ => panic!("{}", panic_semester_message),
|
|
||||||
};
|
|
||||||
let allow_letters_2_3 = match semester {
|
|
||||||
1 => ['a', 'b'],
|
|
||||||
2 => ['x', 'y'],
|
|
||||||
_ => panic!("{}", panic_semester_message),
|
|
||||||
};
|
|
||||||
match year {
|
|
||||||
1 => {
|
|
||||||
let c = letter.expect(panic_letter_message).to_ascii_lowercase();
|
|
||||||
if allow_letters_1.contains(&c) {
|
|
||||||
format!("{}/l1-{}.html", base_url, c)
|
|
||||||
} else {
|
|
||||||
panic!("{}", panic_letter_message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
2 => {
|
|
||||||
let c = letter.expect(panic_letter_message).to_ascii_lowercase();
|
|
||||||
if allow_letters_2_3.contains(&c) {
|
|
||||||
format!("{}/l2-{}.html", base_url, c)
|
|
||||||
} else {
|
|
||||||
panic!("{}", panic_letter_message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
3 => {
|
|
||||||
let c = letter.expect(panic_letter_message).to_ascii_lowercase();
|
|
||||||
if allow_letters_2_3.contains(&c) {
|
|
||||||
format!("{}/l3-{}.html", base_url, c)
|
|
||||||
} else {
|
|
||||||
panic!("{}", panic_letter_message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => panic!("Unknown year."),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use custom User-Agent
|
// Use custom User-Agent
|
||||||
let client = reqwest::Client::builder().user_agent(user_agent).build()?;
|
let client = reqwest::Client::builder().user_agent(user_agent).build()?;
|
||||||
|
@ -314,24 +277,12 @@ pub fn build(timetable: T, dates: D) -> Vec<models::Course> {
|
||||||
semester
|
semester
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current semester depending on the letter or the current date
|
/// Get the current semester depending on the current date
|
||||||
fn get_semester(semester: Option<i8>, letter: Option<char>) -> i8 {
|
fn get_semester(semester: Option<i8>) -> i8 {
|
||||||
match semester {
|
match semester {
|
||||||
// Force the asked semester
|
// Force the asked semester
|
||||||
Some(n) => n,
|
Some(n) => n,
|
||||||
// Find the potential semester
|
// Find the potential semester
|
||||||
None => match letter {
|
|
||||||
// Based on letter (kinda accurate)
|
|
||||||
Some(c) => {
|
|
||||||
if c.to_ascii_uppercase() as i8 > 77 {
|
|
||||||
// If letter is N or after
|
|
||||||
2
|
|
||||||
} else {
|
|
||||||
// If letter is before N
|
|
||||||
1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Based on the time (kinda less accurate)
|
|
||||||
None => {
|
None => {
|
||||||
if Utc::now().month() > 6 {
|
if Utc::now().month() > 6 {
|
||||||
// From july to december
|
// From july to december
|
||||||
|
@ -341,7 +292,22 @@ fn get_semester(semester: Option<i8>, letter: Option<char>) -> i8 {
|
||||||
2
|
2
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the current year depending on the current date
|
||||||
|
fn get_year(year: Option<i32>, semester: i8) -> String {
|
||||||
|
let wanted_year = match year {
|
||||||
|
// Force the asked semester
|
||||||
|
Some(n) => n,
|
||||||
|
// Find the potential semester
|
||||||
|
None => Utc::now().year(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if semester == 1 {
|
||||||
|
format!("{}-{}", wanted_year, wanted_year + 1)
|
||||||
|
} else {
|
||||||
|
format!("{}-{}", wanted_year - 1, wanted_year)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
src/utils.rs
11
src/utils.rs
|
@ -2,20 +2,13 @@ pub mod models;
|
||||||
|
|
||||||
/// Panic if an error happened
|
/// Panic if an error happened
|
||||||
pub fn check_errors(html: &String, loc: &str) {
|
pub fn check_errors(html: &String, loc: &str) {
|
||||||
|
let no_timetable = "Aucun créneau horaire affecté";
|
||||||
match html {
|
match html {
|
||||||
t if t.contains(&err_code(429)) => panic!(
|
t if t.contains(no_timetable) => panic!("URL: {} • {}", loc, no_timetable),
|
||||||
"URL: {} • HTTP 429: Slow down - Rate limited (too many access attempts detected)",
|
|
||||||
loc
|
|
||||||
),
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create String error code
|
|
||||||
fn err_code(code: i32) -> String {
|
|
||||||
format!("HTTP Code : {}", code)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print a line for the table
|
/// Print a line for the table
|
||||||
pub fn line_table(
|
pub fn line_table(
|
||||||
cell_length_hours: usize,
|
cell_length_hours: usize,
|
||||||
|
|
Loading…
Reference in a new issue