librus_apix.notifications
This module provides functionality to interact with the Librus site and extract notifications from the user's dashboard.
The notification bubbles are bound to token, and their amount doesn't change unless you retrieve a new Token, hence we have to request every individual last_login endpoint and retrieve stuff from there.
Classes: - NotificationAmount: Represents a notification with a destination and an amount. - NotificationData: Represents data of various notifications including grades, attendance, messages, announcements, schedule, and homework. - NotificationIds: Represents the IDs of various notifications to track seen notifications.
Functions: - get_initial_notification_data(client: Client) -> Tuple[NotificationData, NotificationIds]: Fetches and parses the initial notification data and their IDs for a new token. - get_new_notification_data(client: Client, seen_notifications: NotificationIds) -> Tuple[NotificationData, NotificationIds]: Fetches and parses new notifications using NotificationIds, returns data and updates seen notification IDs.
1""" 2This module provides functionality to interact with the Librus site and extract notifications from the user's dashboard. 3 4The notification bubbles are bound to token, and their amount doesn't change unless you retrieve a new Token, hence we have to 5request every individual last_login endpoint and retrieve stuff from there. 6 7Classes: 8 - NotificationAmount: Represents a notification with a destination and an amount. 9 - NotificationData: Represents data of various notifications including grades, attendance, messages, announcements, schedule, and homework. 10 - NotificationIds: Represents the IDs of various notifications to track seen notifications. 11 12Functions: 13 - get_initial_notification_data(client: Client) -> Tuple[NotificationData, NotificationIds]: Fetches and parses the initial notification data and their IDs for a new token. 14 - get_new_notification_data(client: Client, seen_notifications: NotificationIds) -> Tuple[NotificationData, NotificationIds]: Fetches and parses new notifications using NotificationIds, returns data and updates seen notification IDs. 15""" 16 17from dataclasses import dataclass 18from datetime import datetime, timedelta 19from hashlib import md5 20from typing import Any, DefaultDict, List, Tuple 21 22from bs4 import BeautifulSoup, Tag 23 24from librus_apix.announcements import Announcement, get_announcements 25from librus_apix.attendance import Attendance, get_attendance 26from librus_apix.client import Client 27from librus_apix.exceptions import ParseError 28from librus_apix.grades import Grade, get_grades 29from librus_apix.helpers import no_access_check 30from librus_apix.homework import Homework, get_homework 31from librus_apix.messages import Message, get_received 32from librus_apix.schedule import RecentEvent, get_recently_added_schedule 33 34 35@dataclass 36class NotificationAmount: 37 """ 38 Represents a notification with a destination identifier name and an amount. 39 40 Attributes: 41 name (str): Str representation of name ex: Oceny 42 destination (str): endpoint 43 amount (int): The count of notifications for the given destination. 44 """ 45 46 name: str 47 destination: str 48 amount: int 49 50 51def get_new_token_notification_amounts(client: Client) -> List[NotificationAmount]: 52 """ 53 Fetches and parses notification amounts from the user's dashboard on the Librus platform. 54 55 Args: 56 client (Client): An instance of `librus_apix.client.Client` used to make requests to the Librus platform. 57 58 Returns: 59 List[NotificationAmount]: A list of `NotificationAmount` objects representing the notifications found on the user's dashboard. 60 """ 61 soup = no_access_check(BeautifulSoup(client.get(client.INDEX_URL).text, "lxml")) 62 notifications = [] 63 circles = soup.select("div#graphic-menu > ul > li > a[class!='button counter']") 64 for circle in circles: 65 name = circle.text.replace("\n", "").strip() 66 destination = circle.attrs.get("href", "/") 67 amount = 0 68 if ( 69 name == "Widok alternatywny" 70 or "javascript" in destination 71 or destination 72 not in [ 73 "/ogloszenia", 74 "/moje_zadania", 75 "/wiadomosci", 76 "/przegladaj_oceny/uczen", 77 "/przegladaj_nb/uczen", 78 "/terminarz", 79 ] 80 ): 81 continue 82 83 if isinstance(circle.parent, Tag): 84 counter = circle.parent.select_one("a.button.counter") 85 if isinstance(counter, Tag): 86 try: 87 amount = int(counter.text) 88 except ValueError: 89 pass 90 91 notifications.append(NotificationAmount(name, destination, amount)) 92 93 return notifications 94 95 96def _compare_hrefs(object_href: str, href_ids: List[str]): 97 return object_href in href_ids 98 99 100def _parse_recent_schedule_notification( 101 schedule: List[RecentEvent], seen_ids: List[str] = [] 102): 103 new_schedule = [] 104 for event in schedule: 105 data_bytes = event.data.encode("utf-8") 106 _id = md5(data_bytes).hexdigest() 107 if _compare_hrefs(_id, seen_ids): 108 continue 109 seen_ids.append(_id) 110 new_schedule.append(event) 111 return new_schedule, seen_ids 112 113 114def _parse_announcements_notification( 115 announcements: List[Announcement], seen_ids: List[str] = [] 116): 117 new_announcements = [] 118 for announcement in announcements: 119 _id = announcement.title + announcement.date 120 if _compare_hrefs(_id, seen_ids): 121 break 122 seen_ids.append(_id) 123 new_announcements.append(announcement) 124 return new_announcements, seen_ids 125 126 127def _parse_homework_notification(homework: List[Homework], seen_ids: List[str] = []): 128 new_homework = [] 129 for hw in homework: 130 href = hw.href 131 if _compare_hrefs(href, seen_ids): 132 break 133 seen_ids.append(href) 134 new_homework.append(hw) 135 return new_homework, seen_ids 136 137 138def _parse_messages_notification(messages: List[Message], seen_ids: List[str] = []): 139 new_messages = [] 140 new_ids = [] 141 for message in messages: 142 href = message.href 143 if _compare_hrefs(href, seen_ids): 144 break 145 if message.unread == False: 146 continue 147 new_ids.append(href) 148 new_messages.append(message) 149 if len(messages) > 0 and len(seen_ids) == 0: 150 seen_ids.append(messages[0].href) 151 else: 152 seen_ids.extend(new_ids) 153 return new_messages, seen_ids 154 155 156def _parse_attendance_notification( 157 attendance: List[List[Attendance]], seen_ids: List[str] = [] 158): 159 new_attendance = [] 160 for semester in attendance: 161 for semester_attendance in semester: 162 href = semester_attendance.href 163 if _compare_hrefs(href, seen_ids): 164 continue 165 seen_ids.append(href) 166 new_attendance.append(semester_attendance) 167 return new_attendance, seen_ids 168 169 170def _parse_grades_notifications( 171 grades: List[DefaultDict[str, List[Grade]]], seen_ids: List[str] = [] 172): 173 new_grades = [] 174 for semester in grades: 175 for subject_grades in semester.values(): 176 for grade in subject_grades: 177 href = grade.href 178 if _compare_hrefs(href, seen_ids): 179 continue 180 new_grades.append(grade) 181 seen_ids.append(href) 182 return new_grades, seen_ids 183 184 185def parse_basic_amount( 186 client: Client, amount: NotificationAmount 187) -> Tuple[List[Any], List[str]]: 188 if amount.amount == 0 and amount.destination not in [ 189 "/ogloszenia", 190 "/moje_zadania", 191 "/wiadomosci", 192 ]: 193 return [], [] 194 match amount.destination: 195 case "/przegladaj_oceny/uczen": 196 grades, _averages, _descriptive = get_grades(client, "last_login") 197 return _parse_grades_notifications(grades) 198 case "/przegladaj_nb/uczen": 199 attendance = get_attendance(client, "last_login") 200 return _parse_attendance_notification(attendance) 201 case "/wiadomosci": 202 messages = get_received(client, 0) 203 top_two_msgs = messages[:2] 204 if len(top_two_msgs) == 0: 205 return [], [] 206 else: 207 return _parse_messages_notification(top_two_msgs) 208 209 case "/ogloszenia": 210 announcements = get_announcements(client) 211 newest = announcements[: amount.amount] 212 if len(newest) == 0: 213 newest = [announcements[0]] 214 _, ids = _parse_announcements_notification(newest) 215 return [], ids 216 else: 217 return _parse_announcements_notification(newest) 218 219 case "/terminarz": 220 schedule = get_recently_added_schedule(client) 221 return schedule, [] 222 case "/moje_zadania": 223 today = datetime.now() 224 hw_amount = -amount.amount 225 if hw_amount == 0: 226 hw_amount = -1 227 homework = get_homework( 228 client, 229 (today - timedelta(days=7)).strftime("%Y-%m-%d"), 230 today.strftime("%Y-%m-%d"), 231 )[hw_amount:] 232 if amount.amount == 0: 233 _, ids = _parse_homework_notification(homework) 234 return [], ids 235 else: 236 return _parse_homework_notification(homework) 237 238 case _: 239 return [], [] 240 241 242@dataclass 243class NotificationData: 244 """ 245 Represents data of various notifications. 246 247 Attributes: 248 grades (List[Grade]): A list of grade notifications. 249 attendance (List[Attendance]): A list of attendance notifications. 250 messages (List[Message]): A list of message notifications. 251 announcements (List[Announcement]): A list of announcement notifications. 252 schedule (List[RecentEvent]): A list of schedule notifications. 253 homework (List[Homework]): A list of homework notifications. 254 """ 255 256 grades: List[Grade] 257 attendance: List[Attendance] 258 messages: List[Message] 259 announcements: List[Announcement] 260 schedule: List[RecentEvent] 261 homework: List[Homework] 262 263 264@dataclass 265class NotificationIds: 266 """ 267 Represents the IDs (mostly .href) of various notifications to track seen notifications. 268 269 Attributes: 270 grades (List[str]): A list of grade notification IDs. 271 attendance (List[str]): A list of attendance notification IDs. 272 messages (List[str]): A list of message notification IDs. 273 announcements (List[str]): A list of announcement notification IDs (title+data) concat. 274 schedule (List[str]): A list of schedule notification IDs. 275 homework (List[str]): A list of homework notification IDs. 276 """ 277 278 grades: List[str] 279 attendance: List[str] 280 messages: List[str] 281 announcements: List[str] 282 schedule: List[str] 283 homework: List[str] 284 285 286def get_initial_notification_data(client: Client): 287 """ 288 Fetches and parses the initial notification data and their IDs for a new token. 289 ! Should only be ran once on every new Token. The notifications are stored inside Token and won't update. 290 291 Args: 292 client (Client): An instance of `librus_apix.client.Client`. 293 294 Returns: 295 Tuple[NotificationData, NotificationIds]: A tuple containing the initial notification data and their IDs. 296 """ 297 amounts = get_new_token_notification_amounts(client) 298 amounts = map(lambda amount: parse_basic_amount(client, amount), amounts) 299 notify_data = [] 300 notify_ids = [] 301 for data, ids in amounts: 302 notify_data.append(data) 303 notify_ids.append(ids) 304 305 if len(notify_data) != 6: 306 raise ParseError("notification length doenst match expected 6") 307 return NotificationData(*notify_data), NotificationIds(*notify_ids) 308 309 310def get_new_notification_data(client: Client, seen_notifications: NotificationIds): 311 """ 312 Fetches and parses new notification data and updates seen notification IDs based on given NotificationIds. 313 314 Args: 315 client (Client): An instance of `librus_apix.client.Client`. 316 seen_notifications (NotificationIds): A `NotificationIds` object representing the seen notifications. 317 318 Returns: 319 Tuple[NotificationData, NotificationIds]: A tuple containing the new notification data and updated seen notification IDs. 320 """ 321 grades, _, _ = get_grades(client, "last_login") 322 attendance = get_attendance(client, "last_login") 323 messages = get_received(client, 0) 324 announcements = get_announcements(client) 325 today = datetime.now() 326 homework = get_homework( 327 client, 328 (today - timedelta(days=7)).strftime("%Y-%m-%d"), 329 today.strftime("%Y-%m-%d"), 330 )[::-1] 331 332 schedule = get_recently_added_schedule(client) 333 334 new_schedule, seen_events = _parse_recent_schedule_notification( 335 schedule, seen_notifications.schedule 336 ) 337 new_grades, seen_grades = _parse_grades_notifications( 338 grades, seen_notifications.grades 339 ) 340 new_attendance, seen_attendance = _parse_attendance_notification( 341 attendance, seen_notifications.attendance 342 ) 343 new_messages, seen_messages = _parse_messages_notification( 344 messages, seen_notifications.messages 345 ) 346 new_announcements, seen_announcements = _parse_announcements_notification( 347 announcements, seen_notifications.announcements 348 ) 349 new_homework, seen_homework = _parse_homework_notification( 350 homework, seen_notifications.homework 351 ) 352 353 return NotificationData( 354 new_grades, 355 new_attendance, 356 new_messages, 357 new_announcements, 358 new_schedule, 359 new_homework, 360 ), NotificationIds( 361 seen_grades, 362 seen_attendance, 363 seen_messages, 364 seen_announcements, 365 seen_events, 366 seen_homework, 367 )
36@dataclass 37class NotificationAmount: 38 """ 39 Represents a notification with a destination identifier name and an amount. 40 41 Attributes: 42 name (str): Str representation of name ex: Oceny 43 destination (str): endpoint 44 amount (int): The count of notifications for the given destination. 45 """ 46 47 name: str 48 destination: str 49 amount: int
Represents a notification with a destination identifier name and an amount.
Attributes: name (str): Str representation of name ex: Oceny destination (str): endpoint amount (int): The count of notifications for the given destination.
52def get_new_token_notification_amounts(client: Client) -> List[NotificationAmount]: 53 """ 54 Fetches and parses notification amounts from the user's dashboard on the Librus platform. 55 56 Args: 57 client (Client): An instance of `librus_apix.client.Client` used to make requests to the Librus platform. 58 59 Returns: 60 List[NotificationAmount]: A list of `NotificationAmount` objects representing the notifications found on the user's dashboard. 61 """ 62 soup = no_access_check(BeautifulSoup(client.get(client.INDEX_URL).text, "lxml")) 63 notifications = [] 64 circles = soup.select("div#graphic-menu > ul > li > a[class!='button counter']") 65 for circle in circles: 66 name = circle.text.replace("\n", "").strip() 67 destination = circle.attrs.get("href", "/") 68 amount = 0 69 if ( 70 name == "Widok alternatywny" 71 or "javascript" in destination 72 or destination 73 not in [ 74 "/ogloszenia", 75 "/moje_zadania", 76 "/wiadomosci", 77 "/przegladaj_oceny/uczen", 78 "/przegladaj_nb/uczen", 79 "/terminarz", 80 ] 81 ): 82 continue 83 84 if isinstance(circle.parent, Tag): 85 counter = circle.parent.select_one("a.button.counter") 86 if isinstance(counter, Tag): 87 try: 88 amount = int(counter.text) 89 except ValueError: 90 pass 91 92 notifications.append(NotificationAmount(name, destination, amount)) 93 94 return notifications
Fetches and parses notification amounts from the user's dashboard on the Librus platform.
Args:
client (Client): An instance of librus_apix.client.Client
used to make requests to the Librus platform.
Returns:
List[NotificationAmount]: A list of NotificationAmount
objects representing the notifications found on the user's dashboard.
186def parse_basic_amount( 187 client: Client, amount: NotificationAmount 188) -> Tuple[List[Any], List[str]]: 189 if amount.amount == 0 and amount.destination not in [ 190 "/ogloszenia", 191 "/moje_zadania", 192 "/wiadomosci", 193 ]: 194 return [], [] 195 match amount.destination: 196 case "/przegladaj_oceny/uczen": 197 grades, _averages, _descriptive = get_grades(client, "last_login") 198 return _parse_grades_notifications(grades) 199 case "/przegladaj_nb/uczen": 200 attendance = get_attendance(client, "last_login") 201 return _parse_attendance_notification(attendance) 202 case "/wiadomosci": 203 messages = get_received(client, 0) 204 top_two_msgs = messages[:2] 205 if len(top_two_msgs) == 0: 206 return [], [] 207 else: 208 return _parse_messages_notification(top_two_msgs) 209 210 case "/ogloszenia": 211 announcements = get_announcements(client) 212 newest = announcements[: amount.amount] 213 if len(newest) == 0: 214 newest = [announcements[0]] 215 _, ids = _parse_announcements_notification(newest) 216 return [], ids 217 else: 218 return _parse_announcements_notification(newest) 219 220 case "/terminarz": 221 schedule = get_recently_added_schedule(client) 222 return schedule, [] 223 case "/moje_zadania": 224 today = datetime.now() 225 hw_amount = -amount.amount 226 if hw_amount == 0: 227 hw_amount = -1 228 homework = get_homework( 229 client, 230 (today - timedelta(days=7)).strftime("%Y-%m-%d"), 231 today.strftime("%Y-%m-%d"), 232 )[hw_amount:] 233 if amount.amount == 0: 234 _, ids = _parse_homework_notification(homework) 235 return [], ids 236 else: 237 return _parse_homework_notification(homework) 238 239 case _: 240 return [], []
243@dataclass 244class NotificationData: 245 """ 246 Represents data of various notifications. 247 248 Attributes: 249 grades (List[Grade]): A list of grade notifications. 250 attendance (List[Attendance]): A list of attendance notifications. 251 messages (List[Message]): A list of message notifications. 252 announcements (List[Announcement]): A list of announcement notifications. 253 schedule (List[RecentEvent]): A list of schedule notifications. 254 homework (List[Homework]): A list of homework notifications. 255 """ 256 257 grades: List[Grade] 258 attendance: List[Attendance] 259 messages: List[Message] 260 announcements: List[Announcement] 261 schedule: List[RecentEvent] 262 homework: List[Homework]
Represents data of various notifications.
Attributes: grades (List[Grade]): A list of grade notifications. attendance (List[Attendance]): A list of attendance notifications. messages (List[Message]): A list of message notifications. announcements (List[Announcement]): A list of announcement notifications. schedule (List[RecentEvent]): A list of schedule notifications. homework (List[Homework]): A list of homework notifications.
265@dataclass 266class NotificationIds: 267 """ 268 Represents the IDs (mostly .href) of various notifications to track seen notifications. 269 270 Attributes: 271 grades (List[str]): A list of grade notification IDs. 272 attendance (List[str]): A list of attendance notification IDs. 273 messages (List[str]): A list of message notification IDs. 274 announcements (List[str]): A list of announcement notification IDs (title+data) concat. 275 schedule (List[str]): A list of schedule notification IDs. 276 homework (List[str]): A list of homework notification IDs. 277 """ 278 279 grades: List[str] 280 attendance: List[str] 281 messages: List[str] 282 announcements: List[str] 283 schedule: List[str] 284 homework: List[str]
Represents the IDs (mostly .href) of various notifications to track seen notifications.
Attributes: grades (List[str]): A list of grade notification IDs. attendance (List[str]): A list of attendance notification IDs. messages (List[str]): A list of message notification IDs. announcements (List[str]): A list of announcement notification IDs (title+data) concat. schedule (List[str]): A list of schedule notification IDs. homework (List[str]): A list of homework notification IDs.
287def get_initial_notification_data(client: Client): 288 """ 289 Fetches and parses the initial notification data and their IDs for a new token. 290 ! Should only be ran once on every new Token. The notifications are stored inside Token and won't update. 291 292 Args: 293 client (Client): An instance of `librus_apix.client.Client`. 294 295 Returns: 296 Tuple[NotificationData, NotificationIds]: A tuple containing the initial notification data and their IDs. 297 """ 298 amounts = get_new_token_notification_amounts(client) 299 amounts = map(lambda amount: parse_basic_amount(client, amount), amounts) 300 notify_data = [] 301 notify_ids = [] 302 for data, ids in amounts: 303 notify_data.append(data) 304 notify_ids.append(ids) 305 306 if len(notify_data) != 6: 307 raise ParseError("notification length doenst match expected 6") 308 return NotificationData(*notify_data), NotificationIds(*notify_ids)
Fetches and parses the initial notification data and their IDs for a new token. ! Should only be ran once on every new Token. The notifications are stored inside Token and won't update.
Args:
client (Client): An instance of librus_apix.client.Client
.
Returns: Tuple[NotificationData, NotificationIds]: A tuple containing the initial notification data and their IDs.
311def get_new_notification_data(client: Client, seen_notifications: NotificationIds): 312 """ 313 Fetches and parses new notification data and updates seen notification IDs based on given NotificationIds. 314 315 Args: 316 client (Client): An instance of `librus_apix.client.Client`. 317 seen_notifications (NotificationIds): A `NotificationIds` object representing the seen notifications. 318 319 Returns: 320 Tuple[NotificationData, NotificationIds]: A tuple containing the new notification data and updated seen notification IDs. 321 """ 322 grades, _, _ = get_grades(client, "last_login") 323 attendance = get_attendance(client, "last_login") 324 messages = get_received(client, 0) 325 announcements = get_announcements(client) 326 today = datetime.now() 327 homework = get_homework( 328 client, 329 (today - timedelta(days=7)).strftime("%Y-%m-%d"), 330 today.strftime("%Y-%m-%d"), 331 )[::-1] 332 333 schedule = get_recently_added_schedule(client) 334 335 new_schedule, seen_events = _parse_recent_schedule_notification( 336 schedule, seen_notifications.schedule 337 ) 338 new_grades, seen_grades = _parse_grades_notifications( 339 grades, seen_notifications.grades 340 ) 341 new_attendance, seen_attendance = _parse_attendance_notification( 342 attendance, seen_notifications.attendance 343 ) 344 new_messages, seen_messages = _parse_messages_notification( 345 messages, seen_notifications.messages 346 ) 347 new_announcements, seen_announcements = _parse_announcements_notification( 348 announcements, seen_notifications.announcements 349 ) 350 new_homework, seen_homework = _parse_homework_notification( 351 homework, seen_notifications.homework 352 ) 353 354 return NotificationData( 355 new_grades, 356 new_attendance, 357 new_messages, 358 new_announcements, 359 new_schedule, 360 new_homework, 361 ), NotificationIds( 362 seen_grades, 363 seen_attendance, 364 seen_messages, 365 seen_announcements, 366 seen_events, 367 seen_homework, 368 )
Fetches and parses new notification data and updates seen notification IDs based on given NotificationIds.
Args:
client (Client): An instance of librus_apix.client.Client
.
seen_notifications (NotificationIds): A NotificationIds
object representing the seen notifications.
Returns: Tuple[NotificationData, NotificationIds]: A tuple containing the new notification data and updated seen notification IDs.