Skip to content

Commit b9f657f

Browse files
committed
feat: unfollowers
NOTE: this method requests ALL followers twice. So it can be slow. Don't spam. solves #461
1 parent b58a8cf commit b9f657f

File tree

2 files changed

+34
-1
lines changed

2 files changed

+34
-1
lines changed

scratchattach/site/user.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,33 @@ def featured_data(self):
247247
except Exception:
248248
return None
249249

250+
def unfollowers(self) -> list[User]:
251+
"""
252+
Get all unfollowers by comparing API response and HTML response.
253+
NOTE: This method can take a long time to run.
254+
255+
Based on https://juegostrower.github.io/unfollowers/
256+
"""
257+
follower_count = self.follower_count()
258+
259+
# regular followers
260+
usernames = []
261+
for i in range(1, 2 + follower_count // 60):
262+
with requests.no_error_handling():
263+
resp = requests.get(f"https://scratch.mit.edu/users/{self.username}/followers/", params={"page": i})
264+
soup = BeautifulSoup(resp.text, "html.parser")
265+
usernames.extend(span.text.strip() for span in soup.select("span.title"))
266+
267+
# api response contains all-time followers, including deleted and unfollowed
268+
unfollowers = []
269+
for offset in range(0, follower_count, 40):
270+
unfollowers.extend(user for user in self.followers(offset=offset, limit=40) if user.username not in usernames)
271+
272+
return unfollowers
273+
274+
def unfollower_usernames(self) -> list[str]:
275+
return [user.username for user in self.unfollowers()]
276+
250277
def follower_count(self):
251278
with requests.no_error_handling():
252279
text = requests.get(

tests/test_user.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
import sys
33

44

5-
def test_import():
5+
def test_user():
66
sys.path.insert(0, ".")
77
import scratchattach as sa
88
from util import session
99
sess = session()
1010

11+
user = sess.connect_user("faretek1")
12+
assert "mochipiyo" in user.unfollower_usernames()
13+
1114
user = sess.connect_user("ScratchAttachV2")
1215

1316
assert user.id == 147905216
@@ -87,3 +90,6 @@ def test_import():
8790
status_data = user.ocular_status()
8891
assert status_data["status"] == "Sample status"
8992
assert status_data["color"] == "#855cd6"
93+
94+
if __name__ == '__main__':
95+
test_user()

0 commit comments

Comments
 (0)