Skip to content

Commit 580dcf0

Browse files
committed
feat: implement is_member, has_ears, and has_badge()
has_badge() is a function because it makes a new request each time also made the type checker happy with the typed dicts in _update_from_dict
1 parent 8cbee51 commit 580dcf0

File tree

3 files changed

+67
-9
lines changed

3 files changed

+67
-9
lines changed

scratchattach/site/typed_dicts.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ class UserProfileDict(TypedDict):
7878
bio: str
7979
country: str
8080
images: dict[str, str]
81+
membership_label: NotRequired[int]
82+
membership_avatar_badge: NotRequired[int]
8183

8284
class UserDict(TypedDict):
8385
id: NotRequired[int]

scratchattach/site/user.py

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ class User(BaseSiteComponent[typed_dicts.UserDict]):
9393
icon_url: str = field(kw_only=True, default="")
9494
id: int = field(kw_only=True, default=0)
9595
scratchteam: bool = field(kw_only=True, repr=False, default=False)
96+
is_member: bool = field(kw_only=True, repr=False, default=False)
97+
has_ears: bool = field(kw_only=True, repr=False, default=False)
9698
_classroom: tuple[bool, Optional[classroom.Classroom]] = field(init=False, default=(False, None))
9799
_headers: dict[str, str] = field(init=False, default_factory=headers.copy)
98100
_cookies: dict[str, str] = field(init=False, default_factory=dict)
@@ -141,14 +143,20 @@ def __post_init__(self):
141143

142144
def _update_from_dict(self, data: Union[dict, typed_dicts.UserDict]):
143145
data = cast(typed_dicts.UserDict, data)
144-
self.id = data["id"]
145-
self.username = data["username"]
146-
self.scratchteam = data["scratchteam"]
147-
self.join_date = data["history"]["joined"]
148-
self.about_me = data["profile"]["bio"]
149-
self.wiwo = data["profile"]["status"]
150-
self.country = data["profile"]["country"]
151-
self.icon_url = data["profile"]["images"]["90x90"]
146+
147+
self.id = data.get("id", self.id)
148+
self.username = data.get("username", self.username)
149+
self.scratchteam = data.get("scratchteam", self.scratchteam)
150+
if history := data.get("history"):
151+
self.join_date = history["joined"]
152+
153+
if profile := data.get("profile"):
154+
self.about_me = profile["bio"]
155+
self.wiwo = profile["status"]
156+
self.country = profile["country"]
157+
self.icon_url = profile["images"]["90x90"]
158+
self.is_member = bool(profile.get("membership_label", False))
159+
self.has_ears = bool(profile.get("membership_avatar_badge", False))
152160
return True
153161

154162
def _assert_permission(self):
@@ -612,6 +620,25 @@ def favorites_count(self):
612620
).text
613621
return commons.webscrape_count(text, "Favorites (", ")")
614622

623+
def has_badge(self) -> bool:
624+
"""
625+
Returns:
626+
bool: Whether the user has a scratch membership badge on their profile (located next to the follow button)
627+
"""
628+
with requests.no_error_handling():
629+
resp = requests.get(self.url)
630+
soup = BeautifulSoup(resp.text, "html.parser")
631+
head = soup.find("div", {"class": "box-head"})
632+
if not head:
633+
return False
634+
for child in head.children:
635+
if child.name == "img":
636+
if child["src"] == "//cdn.scratch.mit.edu/scratchr2/static/__ff7229f036c458728e45c39b0751aa44__/membership/membership-badge.svg":
637+
return True
638+
return False
639+
640+
641+
615642
def toggle_commenting(self):
616643
"""
617644
You can only use this function if this object was created using :meth:`scratchattach.session.Session.connect_user`

tests/test_memberships.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,33 @@
11
import scratchattach as sa
2+
import warnings
23

4+
warnings.filterwarnings("ignore", category=sa.UserAuthenticationWarning)
35
def test_memberships():
4-
...
6+
# unfortunately we don't have amazingly robust test-cases here.
7+
u1 = sa.get_user("-KittyMax-")
8+
assert u1.is_member
9+
assert not u1.has_ears
10+
assert u1.has_badge()
11+
12+
u2 = sa.get_user("ceebee")
13+
assert u2.is_member
14+
assert u2.has_ears
15+
assert u2.has_badge()
16+
17+
u3 = sa.get_user("scratchattachv2")
18+
assert not u3.is_member
19+
assert not u3.has_ears
20+
assert not u3.has_badge()
21+
22+
u4 = sa.get_user("peekir")
23+
assert u4.is_member
24+
assert u4.has_ears
25+
assert u4.has_badge()
26+
27+
u5 = sa.get_user("StardreamT2")
28+
assert u5.is_member
29+
assert u5.has_ears
30+
assert not u5.has_badge()
31+
32+
if __name__ == "__main__":
33+
test_memberships()

0 commit comments

Comments
 (0)