diff --git a/scratchattach/site/_base.py b/scratchattach/site/_base.py index 30df01b7..41214cb5 100644 --- a/scratchattach/site/_base.py +++ b/scratchattach/site/_base.py @@ -13,9 +13,10 @@ class BaseSiteComponent(ABC): update_api: str _headers: dict[str, str] _cookies: dict[str, str] - @abstractmethod - def __init__(self): - pass + + # @abstractmethod + # def __init__(self): # dataclasses do not implement __init__ directly + # pass def update(self): """ diff --git a/scratchattach/site/activity.py b/scratchattach/site/activity.py index d7cdd702..15555b8f 100644 --- a/scratchattach/site/activity.py +++ b/scratchattach/site/activity.py @@ -20,7 +20,6 @@ def __str__(self): return str(self.raw) def __init__(self, **entries): - # Set attributes every Activity object needs to have: self._session = None self.raw = None @@ -80,7 +79,7 @@ def _update_from_json(self, data: dict): else: recipient_username = None - default_case = True + default_case = False # Even if `activity_type` is an invalid value; it will default to 'user performed an action' if activity_type == 0: @@ -283,7 +282,8 @@ def _update_from_json(self, data: dict): self.comment_obj_id = comment_obj_id self.comment_obj_title = comment_obj_title self.comment_id = comment_id - + else: + default_case = True if default_case: # This is coded in the scratch HTML, haven't found an example of it though diff --git a/scratchattach/site/classroom.py b/scratchattach/site/classroom.py index f04d96b3..82fec6f3 100644 --- a/scratchattach/site/classroom.py +++ b/scratchattach/site/classroom.py @@ -2,9 +2,12 @@ import datetime import warnings -from typing import Optional, TYPE_CHECKING, Any +from dataclasses import dataclass, field +from datetime import datetime +from typing import Optional, TYPE_CHECKING, Any, Callable import bs4 +from bs4 import BeautifulSoup if TYPE_CHECKING: from scratchattach.site.session import Session @@ -13,46 +16,50 @@ from . import user, activity from ._base import BaseSiteComponent from scratchattach.utils import exceptions, commons -from scratchattach.utils.commons import headers - -from bs4 import BeautifulSoup +@dataclass class Classroom(BaseSiteComponent): - def __init__(self, **entries): + title: str = None + id: int = None + classtoken: str = None + + author: user.User = None + about_class: str = None + working_on: str = None + + is_closed: bool = False + datetime: datetime = None + + + update_function: Callable = field(repr=False, default=requests.get) + _session: Optional[Session] = field(repr=False, default=None) + + def __post_init__(self): # Info on how the .update method has to fetch the data: # NOTE: THIS DOESN'T WORK WITH CLOSED CLASSES! - self.update_function = requests.get - if "id" in entries: - self.update_api = f"https://api.scratch.mit.edu/classrooms/{entries['id']}" - elif "classtoken" in entries: - self.update_api = f"https://api.scratch.mit.edu/classtoken/{entries['classtoken']}" + if self.id: + self.update_api = f"https://api.scratch.mit.edu/classrooms/{self.id}" + elif self.classtoken: + self.update_api = f"https://api.scratch.mit.edu/classtoken/{self.classtoken}" else: - raise KeyError(f"No class id or token provided! Entries: {entries}") - - # Set attributes every Classroom object needs to have: - self._session: Session = None - self.id = None - self.classtoken = None - self.is_closed = False - - self.__dict__.update(entries) + raise KeyError(f"No class id or token provided! {self.__dict__ = }") # Headers and cookies: if self._session is None: - self._headers = headers + self._headers = commons.headers self._cookies = {} else: self._headers = self._session._headers self._cookies = self._session._cookies # Headers for operations that require accept and Content-Type fields: - self._json_headers = dict(self._headers) - self._json_headers["accept"] = "application/json" - self._json_headers["Content-Type"] = "application/json" + self._json_headers = {**self._headers, + "accept": "application/json", + "Content-Type": "application/json"} - def __repr__(self) -> str: - return f"classroom called {self.title!r}" + def __str__(self) -> str: + return f"" def update(self): try: @@ -305,7 +312,8 @@ def close(self) -> None: warnings.warn(f"{self._session} may not be authenticated to edit {self}") raise e - def register_student(self, username: str, password: str = '', birth_month: Optional[int] = None, birth_year: Optional[int] = None, + def register_student(self, username: str, password: str = '', birth_month: Optional[int] = None, + birth_year: Optional[int] = None, gender: Optional[str] = None, country: Optional[str] = None, is_robot: bool = False) -> None: return register_by_token(self.id, self.classtoken, username, password, birth_month, birth_year, gender, country, is_robot) @@ -346,7 +354,8 @@ def public_activity(self, *, limit=20): return activities - def activity(self, student: str = "all", mode: str = "Last created", page: Optional[int] = None) -> list[dict[str, Any]]: + def activity(self, student: str = "all", mode: str = "Last created", page: Optional[int] = None) -> list[ + dict[str, Any]]: """ Get a list of private activity, only available to the class owner. Returns: @@ -421,7 +430,7 @@ def register_by_token(class_id: int, class_token: str, username: str, password: "is_robot": is_robot} response = requests.post("https://scratch.mit.edu/classes/register_new_student/", - data=data, headers=headers, cookies={"scratchcsrftoken": 'a'}) + data=data, headers=commons.headers, cookies={"scratchcsrftoken": 'a'}) ret = response.json()[0] if "username" in ret: diff --git a/scratchattach/site/session.py b/scratchattach/site/session.py index e2935ea1..c0ae9d37 100644 --- a/scratchattach/site/session.py +++ b/scratchattach/site/session.py @@ -98,6 +98,7 @@ def __init__(self, **entries): self.username = None self.xtoken = None self.new_scratcher = None + self.is_teacher = None # Set attributes that Session object may get self._user: user.User = None @@ -704,6 +705,9 @@ def mystuff_studios(self, filter_arg: str = "all", *, page: int = 1, sort_by: st raise exceptions.FetchError() def mystuff_classes(self, mode: str = "Last created", page: Optional[int] = None) -> list[classroom.Classroom]: + if self.is_teacher is None: + self.update() + if not self.is_teacher: raise exceptions.Unauthorized(f"{self.username} is not a teacher; can't have classes") ascsort, descsort = get_class_sort_mode(mode)