mirror of
https://github.com/Garmelon/PFERD.git
synced 2025-07-12 22:22:30 +02:00
Fix normalization of meeting names in cards
This commit is contained in:
@ -22,6 +22,9 @@ ambiguous situations.
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Normalization of meeting names in cards
|
||||||
|
|
||||||
## 3.5.2 - 2024-04-14
|
## 3.5.2 - 2024-04-14
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
@ -61,6 +61,47 @@ class IliasPageElement:
|
|||||||
log.warn(f"Didn't find identity for {self.name} - {self.url}. Please report this.")
|
log.warn(f"Didn't find identity for {self.name} - {self.url}. Please report this.")
|
||||||
return self.url
|
return self.url
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_new(
|
||||||
|
typ: IliasElementType,
|
||||||
|
url: str,
|
||||||
|
name: str,
|
||||||
|
mtime: Optional[datetime] = None,
|
||||||
|
description: Optional[str] = None
|
||||||
|
) -> 'IliasPageElement':
|
||||||
|
if typ == IliasElementType.MEETING:
|
||||||
|
normalized = _sanitize_path_name(IliasPageElement._normalize_meeting_name(name))
|
||||||
|
log.explain(f"Normalized meeting name from {name!r} to {normalized!r}")
|
||||||
|
name = normalized
|
||||||
|
return IliasPageElement(typ, url, name, mtime, description)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _normalize_meeting_name(meeting_name: str) -> str:
|
||||||
|
"""
|
||||||
|
Normalizes meeting names, which have a relative time as their first part,
|
||||||
|
to their date in ISO format.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This checks whether we can reach a `:` without passing a `-`
|
||||||
|
if re.search(r"^[^-]+: ", meeting_name):
|
||||||
|
# Meeting name only contains date: "05. Jan 2000:"
|
||||||
|
split_delimiter = ":"
|
||||||
|
else:
|
||||||
|
# Meeting name contains date and start/end times: "05. Jan 2000, 16:00 - 17:30:"
|
||||||
|
split_delimiter = ", "
|
||||||
|
|
||||||
|
# We have a meeting day without time
|
||||||
|
date_portion_str = meeting_name.split(split_delimiter)[0]
|
||||||
|
date_portion = demangle_date(date_portion_str)
|
||||||
|
|
||||||
|
# We failed to parse the date, bail out
|
||||||
|
if not date_portion:
|
||||||
|
return meeting_name
|
||||||
|
|
||||||
|
# Replace the first section with the absolute date
|
||||||
|
rest_of_name = split_delimiter.join(meeting_name.split(split_delimiter)[1:])
|
||||||
|
return datetime.strftime(date_portion, "%Y-%m-%d") + split_delimiter + rest_of_name
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class IliasDownloadForumData:
|
class IliasDownloadForumData:
|
||||||
@ -130,7 +171,7 @@ class IliasPage:
|
|||||||
attrs={"href": lambda x: x and "cmdClass=ilinfoscreengui" in x}
|
attrs={"href": lambda x: x and "cmdClass=ilinfoscreengui" in x}
|
||||||
)
|
)
|
||||||
if tab is not None:
|
if tab is not None:
|
||||||
return IliasPageElement(
|
return IliasPageElement.create_new(
|
||||||
IliasElementType.INFO_TAB,
|
IliasElementType.INFO_TAB,
|
||||||
self._abs_url_from_link(tab),
|
self._abs_url_from_link(tab),
|
||||||
"infos"
|
"infos"
|
||||||
@ -295,7 +336,7 @@ class IliasPage:
|
|||||||
if not element:
|
if not element:
|
||||||
return None
|
return None
|
||||||
link = self._abs_url_from_link(element)
|
link = self._abs_url_from_link(element)
|
||||||
return IliasPageElement(IliasElementType.FOLDER, link, "show all meetings")
|
return IliasPageElement.create_new(IliasElementType.FOLDER, link, "show all meetings")
|
||||||
|
|
||||||
def _is_content_tab_selected(self) -> bool:
|
def _is_content_tab_selected(self) -> bool:
|
||||||
return self._select_content_page_url() is None
|
return self._select_content_page_url() is None
|
||||||
@ -315,7 +356,7 @@ class IliasPage:
|
|||||||
link = tab.find("a")
|
link = tab.find("a")
|
||||||
if link:
|
if link:
|
||||||
link = self._abs_url_from_link(link)
|
link = self._abs_url_from_link(link)
|
||||||
return IliasPageElement(IliasElementType.FOLDER, link, "select content page")
|
return IliasPageElement.create_new(IliasElementType.FOLDER, link, "select content page")
|
||||||
|
|
||||||
_unexpected_html_warning()
|
_unexpected_html_warning()
|
||||||
log.warn_contd(f"Could not find content tab URL on {self._page_url!r}.")
|
log.warn_contd(f"Could not find content tab URL on {self._page_url!r}.")
|
||||||
@ -345,14 +386,16 @@ class IliasPage:
|
|||||||
# and just fetch the lone video url!
|
# and just fetch the lone video url!
|
||||||
if len(streams) == 1:
|
if len(streams) == 1:
|
||||||
video_url = streams[0]["sources"]["mp4"][0]["src"]
|
video_url = streams[0]["sources"]["mp4"][0]["src"]
|
||||||
return [IliasPageElement(IliasElementType.OPENCAST_VIDEO, video_url, self._source_name)]
|
return [
|
||||||
|
IliasPageElement.create_new(IliasElementType.OPENCAST_VIDEO, video_url, self._source_name)
|
||||||
|
]
|
||||||
|
|
||||||
log.explain(f"Found multiple videos for stream at {self._source_name}")
|
log.explain(f"Found multiple videos for stream at {self._source_name}")
|
||||||
items = []
|
items = []
|
||||||
for stream in sorted(streams, key=lambda stream: stream["content"]):
|
for stream in sorted(streams, key=lambda stream: stream["content"]):
|
||||||
full_name = f"{self._source_name.replace('.mp4', '')} ({stream['content']}).mp4"
|
full_name = f"{self._source_name.replace('.mp4', '')} ({stream['content']}).mp4"
|
||||||
video_url = stream["sources"]["mp4"][0]["src"]
|
video_url = stream["sources"]["mp4"][0]["src"]
|
||||||
items.append(IliasPageElement(IliasElementType.OPENCAST_VIDEO, video_url, full_name))
|
items.append(IliasPageElement.create_new(IliasElementType.OPENCAST_VIDEO, video_url, full_name))
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
@ -367,7 +410,7 @@ class IliasPage:
|
|||||||
|
|
||||||
link = self._abs_url_from_link(correct_link)
|
link = self._abs_url_from_link(correct_link)
|
||||||
|
|
||||||
return IliasPageElement(IliasElementType.FORUM, link, "show all forum threads")
|
return IliasPageElement.create_new(IliasElementType.FORUM, link, "show all forum threads")
|
||||||
|
|
||||||
def _find_personal_desktop_entries(self) -> List[IliasPageElement]:
|
def _find_personal_desktop_entries(self) -> List[IliasPageElement]:
|
||||||
items: List[IliasPageElement] = []
|
items: List[IliasPageElement] = []
|
||||||
@ -394,7 +437,7 @@ class IliasPage:
|
|||||||
url = re.sub(r"(target=file_\d+)", r"\1_download", url)
|
url = re.sub(r"(target=file_\d+)", r"\1_download", url)
|
||||||
log.explain("Rewired file URL to include download part")
|
log.explain("Rewired file URL to include download part")
|
||||||
|
|
||||||
items.append(IliasPageElement(type, url, name))
|
items.append(IliasPageElement.create_new(type, url, name))
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
@ -412,7 +455,7 @@ class IliasPage:
|
|||||||
log.warn_contd(f"Found unknown content page item {name!r} with url {url!r}")
|
log.warn_contd(f"Found unknown content page item {name!r} with url {url!r}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
items.append(IliasPageElement(IliasElementType.FILE, url, name))
|
items.append(IliasPageElement.create_new(IliasElementType.FILE, url, name))
|
||||||
|
|
||||||
return items
|
return items
|
||||||
|
|
||||||
@ -425,7 +468,7 @@ class IliasPage:
|
|||||||
continue
|
continue
|
||||||
if "cmd=sendfile" not in link["href"]:
|
if "cmd=sendfile" not in link["href"]:
|
||||||
continue
|
continue
|
||||||
items.append(IliasPageElement(
|
items.append(IliasPageElement.create_new(
|
||||||
IliasElementType.FILE,
|
IliasElementType.FILE,
|
||||||
self._abs_url_from_link(link),
|
self._abs_url_from_link(link),
|
||||||
_sanitize_path_name(link.getText())
|
_sanitize_path_name(link.getText())
|
||||||
@ -453,7 +496,9 @@ class IliasPage:
|
|||||||
query_params = {"limit": "800", "cmd": "asyncGetTableGUI", "cmdMode": "asynch"}
|
query_params = {"limit": "800", "cmd": "asyncGetTableGUI", "cmdMode": "asynch"}
|
||||||
url = url_set_query_params(url, query_params)
|
url = url_set_query_params(url, query_params)
|
||||||
log.explain("Found ILIAS video frame page, fetching actual content next")
|
log.explain("Found ILIAS video frame page, fetching actual content next")
|
||||||
return [IliasPageElement(IliasElementType.OPENCAST_VIDEO_FOLDER_MAYBE_PAGINATED, url, "")]
|
return [
|
||||||
|
IliasPageElement.create_new(IliasElementType.OPENCAST_VIDEO_FOLDER_MAYBE_PAGINATED, url, "")
|
||||||
|
]
|
||||||
|
|
||||||
is_paginated = self._soup.find(id=re.compile(r"tab_page_sel.+")) is not None
|
is_paginated = self._soup.find(id=re.compile(r"tab_page_sel.+")) is not None
|
||||||
|
|
||||||
@ -482,7 +527,7 @@ class IliasPage:
|
|||||||
url = url_set_query_params(self._page_url, query_params)
|
url = url_set_query_params(self._page_url, query_params)
|
||||||
|
|
||||||
log.explain("Disabled pagination, retrying folder as a new entry")
|
log.explain("Disabled pagination, retrying folder as a new entry")
|
||||||
return [IliasPageElement(IliasElementType.OPENCAST_VIDEO_FOLDER, url, "")]
|
return [IliasPageElement.create_new(IliasElementType.OPENCAST_VIDEO_FOLDER, url, "")]
|
||||||
|
|
||||||
def _find_opencast_video_entries_no_paging(self) -> List[IliasPageElement]:
|
def _find_opencast_video_entries_no_paging(self) -> List[IliasPageElement]:
|
||||||
"""
|
"""
|
||||||
@ -527,7 +572,7 @@ class IliasPage:
|
|||||||
video_url = self._abs_url_from_link(link)
|
video_url = self._abs_url_from_link(link)
|
||||||
|
|
||||||
log.explain(f"Found video {video_name!r} at {video_url}")
|
log.explain(f"Found video {video_name!r} at {video_url}")
|
||||||
return IliasPageElement(
|
return IliasPageElement.create_new(
|
||||||
IliasElementType.OPENCAST_VIDEO_PLAYER, video_url, video_name, modification_time
|
IliasElementType.OPENCAST_VIDEO_PLAYER, video_url, video_name, modification_time
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -563,7 +608,7 @@ class IliasPage:
|
|||||||
if date is None:
|
if date is None:
|
||||||
log.warn(f"Date parsing failed for exercise entry {name!r}")
|
log.warn(f"Date parsing failed for exercise entry {name!r}")
|
||||||
|
|
||||||
results.append(IliasPageElement(
|
results.append(IliasPageElement.create_new(
|
||||||
IliasElementType.FILE,
|
IliasElementType.FILE,
|
||||||
self._abs_url_from_link(link),
|
self._abs_url_from_link(link),
|
||||||
name,
|
name,
|
||||||
@ -600,7 +645,7 @@ class IliasPage:
|
|||||||
url = self._abs_url_from_link(file_link)
|
url = self._abs_url_from_link(file_link)
|
||||||
|
|
||||||
log.explain(f"Found exercise entry {file_name!r}")
|
log.explain(f"Found exercise entry {file_name!r}")
|
||||||
results.append(IliasPageElement(
|
results.append(IliasPageElement.create_new(
|
||||||
IliasElementType.FILE,
|
IliasElementType.FILE,
|
||||||
url,
|
url,
|
||||||
container_name + "/" + file_name,
|
container_name + "/" + file_name,
|
||||||
@ -625,7 +670,7 @@ class IliasPage:
|
|||||||
file_name = _sanitize_path_name(label_container.getText().strip())
|
file_name = _sanitize_path_name(label_container.getText().strip())
|
||||||
url = self._abs_url_from_link(listing)
|
url = self._abs_url_from_link(listing)
|
||||||
log.explain(f"Found exercise detail {file_name!r} at {url}")
|
log.explain(f"Found exercise detail {file_name!r} at {url}")
|
||||||
results.append(IliasPageElement(
|
results.append(IliasPageElement.create_new(
|
||||||
IliasElementType.EXERCISE_FILES,
|
IliasElementType.EXERCISE_FILES,
|
||||||
url,
|
url,
|
||||||
container_name + "/" + file_name,
|
container_name + "/" + file_name,
|
||||||
@ -660,16 +705,13 @@ class IliasPage:
|
|||||||
|
|
||||||
if not element_type:
|
if not element_type:
|
||||||
continue
|
continue
|
||||||
if element_type == IliasElementType.MEETING:
|
|
||||||
normalized = _sanitize_path_name(self._normalize_meeting_name(element_name))
|
|
||||||
log.explain(f"Normalized meeting name from {element_name!r} to {normalized!r}")
|
|
||||||
element_name = normalized
|
|
||||||
elif element_type == IliasElementType.FILE:
|
elif element_type == IliasElementType.FILE:
|
||||||
result.append(self._file_to_element(element_name, abs_url, link))
|
result.append(self._file_to_element(element_name, abs_url, link))
|
||||||
continue
|
continue
|
||||||
|
|
||||||
log.explain(f"Found {element_name!r}")
|
log.explain(f"Found {element_name!r}")
|
||||||
result.append(IliasPageElement(element_type, abs_url, element_name, description=description))
|
result.append(IliasPageElement.create_new(
|
||||||
|
element_type, abs_url, element_name, description=description))
|
||||||
|
|
||||||
result += self._find_cards()
|
result += self._find_cards()
|
||||||
result += self._find_mediacast_videos()
|
result += self._find_mediacast_videos()
|
||||||
@ -692,8 +734,8 @@ class IliasPage:
|
|||||||
log.warn_contd(f"No <video> element found for mediacast video '{element_name}'")
|
log.warn_contd(f"No <video> element found for mediacast video '{element_name}'")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
videos.append(IliasPageElement(
|
videos.append(IliasPageElement.create_new(
|
||||||
type=IliasElementType.MEDIACAST_VIDEO,
|
typ=IliasElementType.MEDIACAST_VIDEO,
|
||||||
url=self._abs_url_from_relative(video_element.get("src")),
|
url=self._abs_url_from_relative(video_element.get("src")),
|
||||||
name=element_name,
|
name=element_name,
|
||||||
mtime=self._find_mediacast_video_mtime(elem.findParent(name="td"))
|
mtime=self._find_mediacast_video_mtime(elem.findParent(name="td"))
|
||||||
@ -815,7 +857,7 @@ class IliasPage:
|
|||||||
full_path = name + "." + file_type
|
full_path = name + "." + file_type
|
||||||
|
|
||||||
log.explain(f"Found file {full_path!r}")
|
log.explain(f"Found file {full_path!r}")
|
||||||
return IliasPageElement(IliasElementType.FILE, url, full_path, modification_date)
|
return IliasPageElement.create_new(IliasElementType.FILE, url, full_path, modification_date)
|
||||||
|
|
||||||
def _find_cards(self) -> List[IliasPageElement]:
|
def _find_cards(self) -> List[IliasPageElement]:
|
||||||
result: List[IliasPageElement] = []
|
result: List[IliasPageElement] = []
|
||||||
@ -832,7 +874,7 @@ class IliasPage:
|
|||||||
log.warn_contd(f"Could not extract type for {title}")
|
log.warn_contd(f"Could not extract type for {title}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result.append(IliasPageElement(type, url, name))
|
result.append(IliasPageElement.create_new(type, url, name))
|
||||||
|
|
||||||
card_button_tiles: List[Tag] = self._soup.select(".card-title button")
|
card_button_tiles: List[Tag] = self._soup.select(".card-title button")
|
||||||
|
|
||||||
@ -861,7 +903,7 @@ class IliasPage:
|
|||||||
log.warn_contd(f"Could not extract type for {button}")
|
log.warn_contd(f"Could not extract type for {button}")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
result.append(IliasPageElement(type, url, name, description=description))
|
result.append(IliasPageElement.create_new(type, url, name, description=description))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@ -1038,33 +1080,6 @@ class IliasPage:
|
|||||||
|
|
||||||
return IliasElementType.FOLDER
|
return IliasElementType.FOLDER
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _normalize_meeting_name(meeting_name: str) -> str:
|
|
||||||
"""
|
|
||||||
Normalizes meeting names, which have a relative time as their first part,
|
|
||||||
to their date in ISO format.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# This checks whether we can reach a `:` without passing a `-`
|
|
||||||
if re.search(r"^[^-]+: ", meeting_name):
|
|
||||||
# Meeting name only contains date: "05. Jan 2000:"
|
|
||||||
split_delimiter = ":"
|
|
||||||
else:
|
|
||||||
# Meeting name contains date and start/end times: "05. Jan 2000, 16:00 - 17:30:"
|
|
||||||
split_delimiter = ", "
|
|
||||||
|
|
||||||
# We have a meeting day without time
|
|
||||||
date_portion_str = meeting_name.split(split_delimiter)[0]
|
|
||||||
date_portion = demangle_date(date_portion_str)
|
|
||||||
|
|
||||||
# We failed to parse the date, bail out
|
|
||||||
if not date_portion:
|
|
||||||
return meeting_name
|
|
||||||
|
|
||||||
# Replace the first section with the absolute date
|
|
||||||
rest_of_name = split_delimiter.join(meeting_name.split(split_delimiter)[1:])
|
|
||||||
return datetime.strftime(date_portion, "%Y-%m-%d") + split_delimiter + rest_of_name
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_logged_in(soup: BeautifulSoup) -> bool:
|
def is_logged_in(soup: BeautifulSoup) -> bool:
|
||||||
# Normal ILIAS pages
|
# Normal ILIAS pages
|
||||||
|
Reference in New Issue
Block a user