"""Activity and CloudActivity class"""
from __future__ import annotations
from bs4 import PageElement
from . import user, project, studio
from ._base import BaseSiteComponent
from scratchattach.utils import exceptions
[docs]
class Activity(BaseSiteComponent):
"""
Represents a Scratch activity (message or other user page activity)
"""
def __repr__(self):
return f"Activity({repr(self.raw)})"
def __str__(self):
return str(self.raw)
[docs]
def __init__(self, **entries):
# Set attributes every Activity object needs to have:
self._session = None
self.raw = None
# Possible attributes
self.project_id = None
self.gallery_id = None
self.username = None
self.followed_username = None
self.recipient_username = None
self.comment_type = None
self.comment_obj_id = None
self.comment_obj_title = None
self.comment_id = None
self.datetime_created = None
self.time = None
self.type = None
# Update attributes from entries dict:
self.__dict__.update(entries)
[docs]
def update(self):
print("Warning: Activity objects can't be updated")
return False # Objects of this type cannot be updated
[docs]
def _update_from_dict(self, data):
self.raw = data
self.__dict__.update(data)
return True
[docs]
def _update_from_json(self, data: dict):
"""
Update using JSON, used in the classroom API.
"""
activity_type = data["type"]
_time = data["datetime_created"] if "datetime_created" in data else None
if "actor" in data:
username = data["actor"]["username"]
elif "actor_username" in data:
username = data["actor_username"]
else:
username = None
if data.get("recipient") is not None:
recipient_username = data["recipient"]["username"]
elif data.get("recipient_username") is not None:
recipient_username = data["recipient_username"]
elif data.get("project_creator") is not None:
recipient_username = data["project_creator"]["username"]
else:
recipient_username = None
default_case = False
# Even if `activity_type` is an invalid value; it will default to 'user performed an action'
if activity_type == 0:
# follow
followed_username = data["followed_username"]
self.raw = f"{username} followed user {followed_username}"
self.datetime_created = _time
self.type = "followuser"
self.username = username
self.followed_username = followed_username
elif activity_type == 1:
# follow studio
studio_id = data["gallery"]
raw = f"{username} followed studio https://scratch.mit.edu/studios/{studio_id}"
self.raw = raw
self.datetime_created = _time
self.type = "followstudio"
self.username = username
self.gallery_id = studio_id
elif activity_type == 2:
# love project
project_id = data["project"]
raw = f"{username} loved project https://scratch.mit.edu/projects/{project_id}"
self.raw = raw
self.datetime_created = _time,
self.type = "loveproject"
self.username = username
self.project_id = project_id
self.recipient_username = recipient_username
elif activity_type == 3:
# Favorite project
project_id = data["project"]
raw = f"{username} favorited project https://scratch.mit.edu/projects/{project_id}"
self.raw = raw
self.datetime_created = _time
self.type = "favoriteproject"
self.username = username
self.project_id = project_id
self.recipient_username = recipient_username
elif activity_type == 7:
# Add project to studio
project_id = data["project"]
studio_id = data["gallery"]
raw = f"{username} added the project https://scratch.mit.edu/projects/{project_id} to studio https://scratch.mit.edu/studios/{studio_id}"
self.raw = raw
self.datetime_created = _time
self.type = "addprojecttostudio"
self.username = username
self.project_id = project_id
self.recipient_username = recipient_username
elif activity_type in (8, 9, 10):
# Share/Reshare project
project_id = data["project"]
is_reshare = data["is_reshare"]
raw_reshare = "reshared" if is_reshare else "shared"
raw = f"{username} {raw_reshare} the project https://scratch.mit.edu/projects/{project_id}"
self.raw = raw
self.datetime_created = _time
self.type = "shareproject"
self.username = username
self.project_id = project_id
self.recipient_username = recipient_username
elif activity_type == 11:
# Remix
parent_id = data["parent"]
raw = f"{username} remixed the project https://scratch.mit.edu/projects/{parent_id}"
self.raw = raw
self.datetime_created = _time
self.type = "remixproject"
self.username = username
self.project_id = parent_id
self.recipient_username = recipient_username
# type 12 does not exist in the HTML. That's why it was removed, not merged with type 13.
elif activity_type == 13:
# Create ('add') studio
studio_id = data["gallery"]
raw = f"{username} created the studio https://scratch.mit.edu/studios/{studio_id}"
self.raw = raw
self.datetime_created = _time
self.type = "createstudio"
self.username = username
self.gallery_id = studio_id
elif activity_type == 15:
# Update studio
studio_id = data["gallery"]
raw = f"{username} updated the studio https://scratch.mit.edu/studios/{studio_id}"
self.raw = raw
self.datetime_created = _time
self.type = "updatestudio"
self.username = username
self.gallery_id = studio_id
elif activity_type in (16, 17, 18, 19):
# Remove project from studio
project_id = data["project"]
studio_id = data["gallery"]
raw = f"{username} removed the project https://scratch.mit.edu/projects/{project_id} from studio https://scratch.mit.edu/studios/{studio_id}"
self.raw = raw
self.datetime_created = _time
self.type = "removeprojectfromstudio"
self.username = username
self.project_id = project_id
elif activity_type in (20, 21, 22):
# Was promoted to manager for studio
studio_id = data["gallery"]
raw = f"{recipient_username} was promoted to manager by {username} for studio https://scratch.mit.edu/studios/{studio_id}"
self.raw = raw
self.datetime_created = _time
self.type = "promotetomanager"
self.username = username
self.recipient_username = recipient_username
self.gallery_id = studio_id
elif activity_type in (23, 24, 25):
# Update profile
raw = f"{username} made a profile update"
self.raw = raw
self.datetime_created = _time
self.type = "updateprofile"
self.username = username
elif activity_type in (26, 27):
# Comment (quite complicated)
comment_type: int = data["comment_type"]
fragment = data["comment_fragment"]
comment_id = data["comment_id"]
comment_obj_id = data["comment_obj_id"]
comment_obj_title = data["comment_obj_title"]
if comment_type == 0:
# Project comment
raw = f"{username} commented on project https://scratch.mit.edu/projects/{comment_obj_id}/#comments-{comment_id} {fragment!r}"
elif comment_type == 1:
# Profile comment
raw = f"{username} commented on user https://scratch.mit.edu/users/{comment_obj_title}/#comments-{comment_id} {fragment!r}"
elif comment_type == 2:
# Studio comment
# Scratch actually provides an incorrect link, but it is fixed here:
raw = f"{username} commented on studio https://scratch.mit.edu/studios/{comment_obj_id}/comments/#comments-{comment_id} {fragment!r}"
else:
raw = f"{username} commented {fragment!r}" # This should never happen
self.raw = raw
self.datetime_created = _time
self.type = "addcomment"
self.username = username
self.comment_type = comment_type
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
raw = f"{username} performed an action."
self.raw = raw
self.datetime_created = _time
self.type = "performaction"
self.username = username
[docs]
def _update_from_html(self, data: PageElement):
self.raw = data
_time = data.find('div').find('span').findNext().findNext().text.strip()
if '\xa0' in _time:
while '\xa0' in _time:
_time = _time.replace('\xa0', ' ')
self.datetime_created = _time
self.actor_username = data.find('div').find('span').text
self.target_name = data.find('div').find('span').findNext().text
self.target_link = data.find('div').find('span').findNext()["href"]
self.target_id = data.find('div').find('span').findNext()["href"].split("/")[-2]
self.type = data.find('div').find_all('span')[0].next_sibling.strip()
if self.type == "loved":
self.type = "loveproject"
elif self.type == "favorited":
self.type = "favoriteproject"
elif "curator" in self.type:
self.type = "becomecurator"
elif "shared" in self.type:
self.type = "shareproject"
elif "is now following" in self.type:
if "users" in self.target_link:
self.type = "followuser"
else:
self.type = "followstudio"
return True
[docs]
def actor(self):
"""
Returns the user that performed the activity as User object
"""
return self._make_linked_object("username", self.actor_username, user.User, exceptions.UserNotFound)
[docs]
def target(self):
"""
Returns the activity's target (depending on the activity, this is either a User, Project, Studio or Comment object).
May also return None if the activity type is unknown.
"""
if "project" in self.type: # target is a project
if "target_id" in self.__dict__:
return self._make_linked_object("id", self.target_id, project.Project, exceptions.ProjectNotFound)
if "project_id" in self.__dict__:
return self._make_linked_object("id", self.project_id, project.Project, exceptions.ProjectNotFound)
if self.type == "becomecurator" or self.type == "followstudio": # target is a studio
if "target_id" in self.__dict__:
return self._make_linked_object("id", self.target_id, studio.Studio, exceptions.StudioNotFound)
if "gallery_id" in self.__dict__:
return self._make_linked_object("id", self.gallery_id, studio.Studio, exceptions.StudioNotFound)
# NOTE: the "becomecurator" type is ambigous - if it is inside the studio activity tab, the target is the user who joined
if "username" in self.__dict__:
return self._make_linked_object("username", self.username, user.User, exceptions.UserNotFound)
if self.type == "followuser" or "curator" in self.type: # target is a user
if "target_name" in self.__dict__:
return self._make_linked_object("username", self.target_name, user.User, exceptions.UserNotFound)
if "followed_username" in self.__dict__:
return self._make_linked_object("username", self.followed_username, user.User, exceptions.UserNotFound)
if "recipient_username" in self.__dict__: # the recipient_username field always indicates the target is a user
return self._make_linked_object("username", self.recipient_username, user.User, exceptions.UserNotFound)
if self.type == "addcomment": # target is a comment
if self.comment_type == 0:
_c = project.Project(id=self.comment_obj_id, author_name=self._session.username,
_session=self._session).comment_by_id(self.comment_id)
if self.comment_type == 1:
_c = user.User(username=self.comment_obj_title, _session=self._session).comment_by_id(self.comment_id)
if self.comment_type == 2:
_c = user.User(id=self.comment_obj_id, _session=self._session).comment_by_id(self.comment_id)
else:
raise ValueError(f"{self.comment_type} is an invalid comment type")
return _c