use std::collections::HashMap;
use std::rc::Rc;
use anyhow::Context;
use itertools::Itertools;
use scraper::{Html, Selector};
use serde::{Deserialize, Serialize};
use crate::course_modules::{COURSE_MODULE_REGISTRY, CourseModule, CourseModuleData, register_default_course_modules, REGISTERED_DEFAULT_COURSE_MODULES};
use crate::StudIpClient;
const MY_COURSES_URL: &str = "https://studip.example.com/dispatch.php/my_courses";
pub(crate) const COURSE_URL: &str = "https://studip.example.com/dispatch.php/course";
const MODULES_QUERY_URL : &str = "https://studip.example.com/seminar_main.php";
#[derive(Serialize, Deserialize, Debug)]
pub struct Course {
pub id: String,
pub name: String,
#[serde(rename = "number")]
_number: String, pub group: usize,
pub children: Vec<serde_json::Value>,
pub parent: Option<serde_json::Value>,
#[serde(rename = "avatar")]
pub icon_url: String,
pub navigation: Vec<serde_json::Value>,
pub admission_binding: bool,
pub is_teacher: bool,
pub is_studygroup: bool,
pub is_hidden: bool,
pub is_deputy: bool,
pub is_group: bool,
pub extra_navigation: bool,
#[serde(skip)]
pub modules: Vec<Box<dyn CourseModule>>,
#[serde(skip)]
client: Rc<StudIpClient>
}
impl Course {
pub fn query_modules(&mut self) -> anyhow::Result<()> {
REGISTERED_DEFAULT_COURSE_MODULES.get_or_init(register_default_course_modules);
let module_reg = COURSE_MODULE_REGISTRY.lock().unwrap();
let response = self.client.get(MODULES_QUERY_URL)
.query(&[("auswahl", &self.id)])
.send()?;
let html = Html::parse_document(&response.text()?);
let tabs_selector = Selector::parse("#tabs li").unwrap();
let module_data = Rc::new(CourseModuleData {
course_id: self.id.clone(),
client: self.client.clone(),
});
self.modules = html.select(&tabs_selector).filter_map(|tab_ref| {
let tab = tab_ref.value();
let module_name = tab.id().unwrap().replace("nav_course_", "");
module_reg.get(module_name.as_str())
.map(|module_constructor| module_constructor(module_data.clone()))
}).collect();
Ok(())
}
pub fn get_module<Module: CourseModule>(&mut self) -> Option<&mut Module> {
self.modules.iter_mut().find_map(|module| module.as_any().downcast_mut::<Module>())
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct SetGroup {
pub id: i64,
pub name: String,
#[serde(rename = "data")]
pub entries: Vec<CourseEntries>,
}
impl SetGroup {
pub fn get_courses<'a>(&self, courses: &'a mut HashMap<String, Course>) -> Vec<&'a mut Course> {
self.entries
.iter()
.flat_map(|entry| entry.ids.iter())
.unique() .filter_map(|id| {
courses.get_mut(id).map(|c_ref| c_ref as *mut Course)
})
.map(|raw| {
unsafe { &mut *raw }
})
.collect()
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CourseEntries {
pub id: String,
pub label: bool,
pub ids: Vec<String>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct MyCourses {
#[serde(rename = "setCourses")]
pub courses: HashMap<String, Course>,
#[serde(rename = "setGroups")]
pub set_groups: Vec<SetGroup>,
#[serde(rename = "setUserId")]
pub user_id: String,
#[serde(rename = "setConfig")]
pub config: HashMap<String, serde_json::Value>,
#[serde(skip)]
client: Rc<StudIpClient>
}
impl MyCourses {
pub(crate) fn from_client(client: Rc<StudIpClient>) -> Self {
Self {
courses: Default::default(),
set_groups: Default::default(),
user_id: Default::default(),
config: Default::default(),
client,
}
}
pub fn query(&mut self) -> anyhow::Result<()> {
let r = self.client.get(MY_COURSES_URL).send()?;
let text = r.text().context("failed to get response text")?;
let html = Html::parse_document(&text);
let script_tag_selector = Selector::parse("script#vue-vuex-store-data-mycourses").unwrap();
let script = html.select(&script_tag_selector).next().context("Expected MyCoursesData to be present in html")?;
let script_string = script.inner_html();
let mut new_my_courses: Self = serde_json::from_str(script_string.trim())
.context("Could not parse MyCoursesData")?;
for course in new_my_courses.courses.values_mut() {
course.client = self.client.clone();
}
*self = new_my_courses;
Ok(())
}
pub fn get_course_by_name(&self, name: &str) -> Option<&Course> {
self.courses.iter()
.find(|(_, course)| course.name == name)
.map(|(_, course)| course)
}
pub fn mut_course_by_name(&mut self, name: &str) -> Option<&mut Course> {
self.courses.iter_mut()
.find(|(_, course)| course.name == name)
.map(|(_, course)| course)
}
pub fn get_courses_by_set_group_name(&mut self, set_group_name: &str) -> Vec<&mut Course> {
let Some(set_group) = self.set_groups.iter()
.find(|set_group| set_group.name == set_group_name)
else { return vec![] };
set_group.get_courses(&mut self.courses)
}
}