@@ -58,7 +58,9 @@ class _OcularStatus(TypedDict):
5858
5959class Verificator :
6060 def __init__ (self , user : User , project_id : int ):
61- self .project = user ._make_linked_object ("id" , project_id , project .Project , exceptions .ProjectNotFound )
61+ self .project = user ._make_linked_object (
62+ "id" , project_id , project .Project , exceptions .ProjectNotFound
63+ )
6264 self .projecturl = self .project .url
6365 self .code = "" .join (random .choices (string .ascii_letters + string .digits , k = 8 ))
6466 self .username = user .username
@@ -69,7 +71,11 @@ def check(self) -> bool:
6971 filter (
7072 lambda x : (
7173 x .author_name == self .username
72- and (x .content == self .code or x .content .startswith (self .code ) or x .content .endswith (self .code ))
74+ and (
75+ x .content == self .code
76+ or x .content .startswith (self .code )
77+ or x .content .endswith (self .code )
78+ )
7379 ),
7480 self .project .comments (),
7581 )
@@ -109,9 +115,11 @@ class User(BaseSiteComponent[typed_dicts.UserDict]):
109115 icon_url : str = field (kw_only = True , default = "" )
110116 id : int = field (kw_only = True , default = 0 )
111117 scratchteam : bool = field (kw_only = True , repr = False , default = False )
112- is_member : bool = field (kw_only = True , repr = False , default = False )
113- has_ears : bool = field (kw_only = True , repr = False , default = False )
114- _classroom : tuple [bool , Optional [classroom .Classroom ]] = field (init = False , default = (False , None ))
118+ _is_member : bool = field (kw_only = True , repr = False , default = False )
119+ _has_ears : bool = field (kw_only = True , repr = False , default = False )
120+ _classroom : tuple [bool , Optional [classroom .Classroom ]] = field (
121+ init = False , default = (False , None )
122+ )
115123 _headers : dict [str , str ] = field (init = False , default_factory = headers .copy )
116124 _cookies : dict [str , str ] = field (init = False , default_factory = dict )
117125 _json_headers : dict [str , str ] = field (init = False , default_factory = dict )
@@ -137,6 +145,29 @@ def icon(self) -> bytes:
137145 def name (self ) -> str :
138146 return self .username
139147
148+ @property
149+ @deprecated ("memberships are over" )
150+ def is_member (self ) -> bool :
151+ return self ._is_member
152+
153+ @is_member .setter
154+ @deprecated ("memberships are over" )
155+ def is_member (self , value : bool ):
156+ "deprecated: memberships are over"
157+ self ._is_member = value
158+
159+ @property
160+ @deprecated ("memberships are over" )
161+ def has_ears (self ) -> bool :
162+ "deprecated: memberships are over"
163+ return self ._has_ears
164+
165+ @has_ears .setter
166+ @deprecated ("memberships are over" )
167+ def has_ears (self , value : bool ):
168+ "deprecated: memberships are over"
169+ self ._has_ears = value
170+
140171 def __post_init__ (self ):
141172
142173 # Info on how the .update method has to fetch the data:
@@ -178,7 +209,9 @@ def _update_from_dict(self, data: Union[dict, typed_dicts.UserDict]):
178209 def _assert_permission (self ):
179210 self ._assert_auth ()
180211 if self ._session .username != self .username :
181- raise exceptions .Unauthorized ("You need to be authenticated as the profile owner to do this." )
212+ raise exceptions .Unauthorized (
213+ "You need to be authenticated as the profile owner to do this."
214+ )
182215
183216 @property
184217 def url (self ):
@@ -266,7 +299,9 @@ def classroom(self) -> classroom.Classroom | None:
266299 href = str (a .get ("href" ))
267300 if re .match (r"/classes/\d*/" , href ):
268301 class_name = a .text .strip ()[len ("Student of: " ) :]
269- is_closed = bool (re .search (r"\n *\(ended\)" , class_name )) # as this has a \n, we can be sure
302+ is_closed = bool (
303+ re .search (r"\n *\(ended\)" , class_name )
304+ ) # as this has a \n, we can be sure
270305 if is_closed :
271306 class_name = re .sub (r"\n *\(ended\)" , "" , class_name ).strip ()
272307
@@ -294,7 +329,9 @@ def does_exist(self) -> Optional[bool]:
294329 boolean : True if the user exists, False if the user is deleted, None if an error occured
295330 """
296331 with requests .no_error_handling ():
297- status_code = requests .get (f"https://scratch.mit.edu/users/{ self .username } /" ).status_code
332+ status_code = requests .get (
333+ f"https://scratch.mit.edu/users/{ self .username } /"
334+ ).status_code
298335 if status_code == 200 :
299336 return True
300337 elif status_code == 404 :
@@ -335,13 +372,16 @@ def featured_data(self):
335372 dict: Gets info on the user's featured project and featured label (like "Featured project", "My favorite things", etc.)
336373 """
337374 try :
338- response = requests .get (f"https://scratch.mit.edu/site-api/users/all/{ self .username } /" ).json ()
375+ response = requests .get (
376+ f"https://scratch.mit.edu/site-api/users/all/{ self .username } /"
377+ ).json ()
339378 return {
340379 "label" : response ["featured_project_label_name" ],
341380 "project" : dict (
342381 id = str (response ["featured_project_data" ]["id" ]),
343382 author = response ["featured_project_data" ]["creator" ],
344- thumbnail_url = "https://" + response ["featured_project_data" ]["thumbnail_url" ][2 :],
383+ thumbnail_url = "https://"
384+ + response ["featured_project_data" ]["thumbnail_url" ][2 :],
345385 title = response ["featured_project_data" ]["title" ],
346386 ),
347387 }
@@ -371,7 +411,11 @@ def unfollowers(self) -> list[User]:
371411 # api response contains all-time followers, including deleted and unfollowed
372412 unfollowers = []
373413 for offset in range (0 , follower_count , 40 ):
374- unfollowers .extend (user for user in self .followers (offset = offset , limit = 40 ) if user .username not in usernames )
414+ unfollowers .extend (
415+ user
416+ for user in self .followers (offset = offset , limit = 40 )
417+ if user .username not in usernames
418+ )
375419
376420 return unfollowers
377421
@@ -629,7 +673,9 @@ def loves(self, *, limit=40, offset=0, get_full_project: bool = False) -> list[p
629673 break
630674
631675 # Each project element is a list item with the class name 'project thumb item' so we can just use that
632- for i , project_element in enumerate (soup .find_all ("li" , {"class" : "project thumb item" })):
676+ for i , project_element in enumerate (
677+ soup .find_all ("li" , {"class" : "project thumb item" })
678+ ):
633679 # Remember we only want certain projects:
634680 # The current project idx = first_idx + i
635681 # We want to start at {offset} and end at {offset + limit}
@@ -656,7 +702,9 @@ def loves(self, *, limit=40, offset=0, get_full_project: bool = False) -> list[p
656702 assert isinstance (first_anchor , Tag )
657703 assert isinstance (second_anchor , Tag )
658704 assert isinstance (third_anchor , Tag )
659- project_id = commons .webscrape_count (first_anchor .attrs ["href" ], "/projects/" , "/" )
705+ project_id = commons .webscrape_count (
706+ first_anchor .attrs ["href" ], "/projects/" , "/"
707+ )
660708 title = second_anchor .text
661709 author = third_anchor .contents [0 ]
662710
@@ -715,10 +763,12 @@ def favorites_count(self):
715763 ).text
716764 return commons .webscrape_count (text , "Favorites (" , ")" )
717765
766+ @deprecated ("memberships are over" )
718767 def has_badge (self ) -> bool :
719768 """
720769 Returns:
721770 bool: Whether the user has a scratch membership badge on their profile (located next to the follow button)
771+ deprecated: memberships are over
722772 """
723773 with requests .no_error_handling ():
724774 resp = requests .get (self .url )
@@ -876,7 +926,9 @@ def post_comment(self, content, *, parent_id="", commentee_id=""):
876926 try :
877927 data = {
878928 "id" : text .split ('<div id="comments-' )[1 ].split ('" class="comment' )[0 ],
879- "author" : {"username" : text .split ('" data-comment-user="' )[1 ].split ('"><img class' )[0 ]},
929+ "author" : {
930+ "username" : text .split ('" data-comment-user="' )[1 ].split ('"><img class' )[0 ]
931+ },
880932 "content" : text .split ('<div class="content">' )[1 ].split ("</div>" )[0 ].strip (),
881933 "reply_count" : 0 ,
882934 "cached_replies" : [],
@@ -900,11 +952,15 @@ def post_comment(self, content, *, parent_id="", commentee_id=""):
900952 )
901953 ) from e
902954 elif '<script id="error-data" type="application/json">' in text :
903- raw_error_data = text .split ('<script id="error-data" type="application/json">' )[1 ].split ("</script>" )[0 ]
955+ raw_error_data = text .split ('<script id="error-data" type="application/json">' )[
956+ 1
957+ ].split ("</script>" )[0 ]
904958 error_data = json .loads (raw_error_data )
905959 expires = error_data ["mute_status" ]["muteExpiresAt" ]
906960 expires = datetime .fromtimestamp (expires , timezone .utc )
907- raise (exceptions .CommentPostFailure (f"You have been muted. Mute expires on { expires } " )) from e
961+ raise (
962+ exceptions .CommentPostFailure (f"You have been muted. Mute expires on { expires } " )
963+ ) from e
908964 else :
909965 raise (exceptions .FetchError (f"Couldn't parse API response: { r .text !r} " )) from e
910966
@@ -933,7 +989,9 @@ def activity(self, *, limit=1000):
933989 """
934990 with requests .no_error_handling ():
935991 soup = BeautifulSoup (
936- requests .get (f"https://scratch.mit.edu/messages/ajax/user-activity/?user={ self .username } &max={ limit } " ).text ,
992+ requests .get (
993+ f"https://scratch.mit.edu/messages/ajax/user-activity/?user={ self .username } &max={ limit } "
994+ ).text ,
937995 "html.parser" ,
938996 )
939997
@@ -953,7 +1011,9 @@ def activity_html(self, *, limit=1000):
9531011 str: The raw user activity HTML data
9541012 """
9551013 with requests .no_error_handling ():
956- return requests .get (f"https://scratch.mit.edu/messages/ajax/user-activity/?user={ self .username } &max={ limit } " ).text
1014+ return requests .get (
1015+ f"https://scratch.mit.edu/messages/ajax/user-activity/?user={ self .username } &max={ limit } "
1016+ ).text
9571017
9581018 def follow (self ):
9591019 """
@@ -1021,7 +1081,9 @@ def comments(self, *, page=1) -> list[comment.Comment] | None:
10211081 data = []
10221082
10231083 with requests .no_error_handling ():
1024- resp = requests .get (f"https://scratch.mit.edu/site-api/comments/user/{ self .username } /?page={ page } " )
1084+ resp = requests .get (
1085+ f"https://scratch.mit.edu/site-api/comments/user/{ self .username } /?page={ page } "
1086+ )
10251087
10261088 if resp .status_code == 404 :
10271089 # Profile comments seem to end at page 67, and afterwards give 404.
@@ -1137,7 +1199,9 @@ def stats(self):
11371199 dict: A dict containing the user's stats. If the stats aren't available, all values will be -1.
11381200 """
11391201 try :
1140- stats = requests .get (f"https://scratchdb.lefty.one/v3/user/info/{ self .username } " ).json ()["statistics" ]
1202+ stats = requests .get (
1203+ f"https://scratchdb.lefty.one/v3/user/info/{ self .username } "
1204+ ).json ()["statistics" ]
11411205 stats .pop ("ranks" )
11421206 except Exception :
11431207 stats = {
@@ -1162,7 +1226,9 @@ def ranks(self):
11621226 dict: A dict containing the user's ranks. If the ranks aren't available, all values will be -1.
11631227 """
11641228 try :
1165- return requests .get (f"https://scratchdb.lefty.one/v3/user/info/{ self .username } " ).json ()["statistics" ]["ranks" ]
1229+ return requests .get (f"https://scratchdb.lefty.one/v3/user/info/{ self .username } " ).json ()[
1230+ "statistics"
1231+ ]["ranks" ]
11661232 except Exception :
11671233 return {
11681234 "country" : {
0 commit comments