Skip to content

Classroom abilities + create studio + misc #294

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 45 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
645606a
Added create_studio and did a little with exceptions. - ConnectionErr…
FAReTek1 Nov 9, 2024
07a69c2
typo
FAReTek1 Nov 9, 2024
f9922af
A ton of UNTESTED docstrings
FAReTek1 Nov 9, 2024
253f88a
Merge remote-tracking branch 'origin/main'
FAReTek1 Nov 9, 2024
92344ed
More UNTESTED type hints
FAReTek1 Nov 9, 2024
4e23734
More UNTESTED type hints
FAReTek1 Nov 9, 2024
8a53174
More UNTESTED type hints
FAReTek1 Nov 9, 2024
6cb631f
Added (smart) translation api
FAReTek1 Nov 12, 2024
69e697f
Merge pull request #2 from TimMcCool/main
FAReTek1 Nov 12, 2024
f3ce6a2
Added tts api
FAReTek1 Nov 13, 2024
47b0257
Merge remote-tracking branch 'origin/main'
FAReTek1 Nov 13, 2024
1a9e4d8
added an enum i guess?
FAReTek1 Nov 13, 2024
765b068
using languages enum now + kitten improvement
FAReTek1 Nov 16, 2024
dbe7c32
more classroom stuff. lets you actually edit title, description and s…
FAReTek1 Nov 18, 2024
7434255
classroom alerts, simplified code for wiwo/bio setting
FAReTek1 Nov 18, 2024
1904ee1
added setting pfp! wow that was confusing! bruh
FAReTek1 Nov 18, 2024
680640e
find ended classes, reopen/close classes & set thumbnail
FAReTek1 Nov 18, 2024
0ee943a
activity for classes. private activity is in a weird non-normalised f…
FAReTek1 Nov 18, 2024
c569235
added more parameters for the functions
FAReTek1 Nov 18, 2024
a2b87ef
register by class token
FAReTek1 Nov 18, 2024
51d3422
smarter language evaluation in translate function. some credit to the…
FAReTek1 Nov 20, 2024
cec1a12
dataclasses = conciseness & clean
FAReTek1 Nov 20, 2024
a5c48a0
docstrings and removing use of kwarg for all_of
FAReTek1 Nov 20, 2024
6ce4ff7
merge tts chinese with translate versions, same with portuguese (not …
FAReTek1 Nov 20, 2024
a6c6168
using enums for tts genders asw + enum wrapper with finding by multip…
FAReTek1 Nov 20, 2024
7d71d96
make sure language has code for translation
FAReTek1 Nov 20, 2024
24c7f1f
1 docstring
FAReTek1 Nov 21, 2024
d0f9356
removed json.dumps, using json in requests.post instead
FAReTek1 Nov 21, 2024
7752050
added support for closed classes using bs4
FAReTek1 Nov 21, 2024
9f8f727
studio adding and code simplification
FAReTek1 Nov 21, 2024
fab2cb7
Merge branch 'main' into classes
FAReTek1 Dec 21, 2024
0a03800
class build rate limit
FAReTek1 Dec 21, 2024
8fa2f06
Merge remote-tracking branch 'origin/classes' into classes
FAReTek1 Dec 21, 2024
569d9ec
fix old type hints + add from future import annotations to top of all…
FAReTek1 Dec 21, 2024
0064dba
reset email thing
FAReTek1 Dec 21, 2024
9c2f6c4
various basic scratchtools endpoints
FAReTek1 Dec 21, 2024
9b63c3e
get emoji status
FAReTek1 Dec 21, 2024
decbf69
get pinned comment
FAReTek1 Dec 21, 2024
0daab14
ctrl alt l (reformat)
FAReTek1 Dec 21, 2024
c0f2ee8
actually parse private class activity (analyzed html/js to do so)
FAReTek1 Dec 23, 2024
220c703
moved json activity parser to activity method
FAReTek1 Dec 23, 2024
f349826
using datetime_created because it is more applicable??
FAReTek1 Dec 23, 2024
66f3782
add datetime_created as attribute for hinting
FAReTek1 Dec 23, 2024
ae1002a
Merge branch 'main' into classes
FAReTek1 Dec 24, 2024
4ba9ed0
fix files param
FAReTek1 Dec 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 44 additions & 38 deletions scratchattach/cloud/_base.py
Original file line number Diff line number Diff line change
@@ -1,60 +1,62 @@
from abc import ABC, abstractmethod
from __future__ import annotations

import websocket
import json
import ssl
import time
from ..utils import exceptions
import warnings
from abc import ABC

import websocket

from ..eventhandlers import cloud_recorder
import ssl
from ..utils import exceptions

class BaseCloud(ABC):

class BaseCloud(ABC):
"""
Base class for a project's cloud variables. Represents a cloud.

When inheriting from this class, the __init__ function of the inherited class ...

When inheriting from this class, the __init__ function of the inherited class:
- must first call the constructor of the super class: super().__init__()

- must then set some attributes

Attributes that must be specified in the __init__ function a class inheriting from this one:
project_id: Project id of the cloud variables

:self.project_id: Project id of the cloud variables

:self.cloud_host: URL of the websocket server ("wss://..." or "ws://...")
cloud_host: URL of the websocket server ("wss://..." or "ws://...")

Attributes that can, but don't have to be specified in the __init__ function:

:self._session: Either None or a site.session.Session object. Defaults to None.
_session: Either None or a site.session.Session object. Defaults to None.

:self.ws_shortterm_ratelimit: The wait time between cloud variable sets. Defaults to 0.1
ws_shortterm_ratelimit: The wait time between cloud variable sets. Defaults to 0.1

:self.ws_longterm_ratelimit: The amount of cloud variable set that can be performed long-term without ever getting ratelimited
ws_longterm_ratelimit: The amount of cloud variable set that can be performed long-term without ever getting ratelimited

:self.allow_non_numeric: Whether non-numeric cloud variable values are allowed. Defaults to False
allow_non_numeric: Whether non-numeric cloud variable values are allowed. Defaults to False

:self.length_limit: Length limit for cloud variable values. Defaults to 100000
length_limit: Length limit for cloud variable values. Defaults to 100000

:self.username: The username to send during handshake. Defaults to "scratchattach"
username: The username to send during handshake. Defaults to "scratchattach"

:self.header: The header to send. Defaults to None
header: The header to send. Defaults to None

:self.cookie: The cookie to send. Defaults to None
cookie: The cookie to send. Defaults to None

:self.origin: The origin to send. Defaults to None
origin: The origin to send. Defaults to None

:self.print_connect_messages: Whether to print a message on every connect to the cloud server. Defaults to False.
print_connect_messages: Whether to print a message on every connect to the cloud server. Defaults to False.
"""


def __init__(self, *, _session=None):

# Required internal attributes that every object representing a cloud needs to have (no matter what cloud is represented):
self._session = _session
self.active_connection = False #whether a connection to a cloud variable server is currently established

self.websocket = websocket.WebSocket(sslopt={"cert_reqs": ssl.CERT_NONE})
self.recorder = None # A CloudRecorder object that records cloud activity for the values to be retrieved later will be saved in this attribute as soon as .get_var is called
self.recorder = None # A CloudRecorder object that records cloud activity for the values to be retrieved later,
# which will be saved in this attribute as soon as .get_var is called
self.first_var_set = 0
self.last_var_set = 0
self.var_stets_since_first = 0
Expand All @@ -63,7 +65,8 @@ def __init__(self, *, _session=None):
# (These attributes can be specifically in the constructors of classes inheriting from this base class)
self.ws_shortterm_ratelimit = 0.06667
self.ws_longterm_ratelimit = 0.1
self.ws_timeout = 3 # Timeout for send operations (after the timeout, the connection will be renewed and the operation will be retried 3 times)
self.ws_timeout = 3 # Timeout for send operations (after the timeout,
# the connection will be renewed and the operation will be retried 3 times)
self.allow_non_numeric = False
self.length_limit = 100000
self.username = "scratchattach"
Expand Down Expand Up @@ -100,7 +103,7 @@ def _send_packet(self, packet):
self.websocket.send(json.dumps(packet) + "\n")
except Exception:
self.active_connection = False
raise exceptions.ConnectionError(f"Sending packet failed three times in a row: {packet}")
raise exceptions.CloudConnectionError(f"Sending packet failed three times in a row: {packet}")

def _send_packet_list(self, packet_list):
packet_string = "".join([json.dumps(packet) + "\n" for packet in packet_list])
Expand All @@ -126,7 +129,8 @@ def _send_packet_list(self, packet_list):
self.websocket.send(packet_string)
except Exception:
self.active_connection = False
raise exceptions.ConnectionError(f"Sending packet list failed four times in a row: {packet_list}")
raise exceptions.CloudConnectionError(
f"Sending packet list failed four times in a row: {packet_list}")

def _handshake(self):
packet = {"method": "handshake", "user": self.username, "project_id": self.project_id}
Expand All @@ -139,8 +143,8 @@ def connect(self):
cookie=self.cookie,
origin=self.origin,
enable_multithread=True,
timeout = self.ws_timeout,
header = self.header
timeout=self.ws_timeout,
header=self.header
)
self._handshake()
self.active_connection = True
Expand All @@ -166,29 +170,29 @@ def _assert_valid_value(self, value):
if not (value in [True, False, float('inf'), -float('inf')]):
value = str(value)
if len(value) > self.length_limit:
raise(exceptions.InvalidCloudValue(
raise (exceptions.InvalidCloudValue(
f"Value exceeds length limit: {str(value)}"
))
if not self.allow_non_numeric:
x = value.replace(".", "")
x = x.replace("-", "")
if not (x.isnumeric() or x == ""):
raise(exceptions.InvalidCloudValue(
raise (exceptions.InvalidCloudValue(
"Value not numeric"
))

def _enforce_ratelimit(self, *, n):
# n is the amount of variables being set
if (time.time() - self.first_var_set) / (self.var_stets_since_first+1) > self.ws_longterm_ratelimit: # if the average delay between cloud variable sets has been bigger than the long-term rate-limit, cloud variables can be set fast (wait time smaller than long-term rate limit) again
if (time.time() - self.first_var_set) / (
self.var_stets_since_first + 1) > self.ws_longterm_ratelimit: # if the average delay between cloud variable sets has been bigger than the long-term rate-limit, cloud variables can be set fast (wait time smaller than long-term rate limit) again
self.var_stets_since_first = 0
self.first_var_set = time.time()

wait_time = self.ws_shortterm_ratelimit * n
if time.time() - self.first_var_set > 25: # if cloud variables have been continously set fast (wait time smaller than long-term rate limit) for 25 seconds, they should be set slow now (wait time = long-term rate limit) to avoid getting rate-limited
if time.time() - self.first_var_set > 25: # if cloud variables have been continously set fast (wait time smaller than long-term rate limit) for 25 seconds, they should be set slow now (wait time = long-term rate limit) to avoid getting rate-limited
wait_time = self.ws_longterm_ratelimit * n
while self.last_var_set + wait_time >= time.time():
time.sleep(0.001)


def set_var(self, variable, value):
"""
Expand Down Expand Up @@ -231,7 +235,7 @@ def set_vars(self, var_value_dict, *, intelligent_waits=True):
self.connect()
if intelligent_waits:
self._enforce_ratelimit(n=len(list(var_value_dict.keys())))

self.var_stets_since_first += len(list(var_value_dict.keys()))

packet_list = []
Expand All @@ -256,7 +260,7 @@ def get_var(self, var, *, recorder_initial_values={}):
self.recorder = cloud_recorder.CloudRecorder(self, initial_values=recorder_initial_values)
self.recorder.start()
start_time = time.time()
while not (self.recorder.cloud_values != {} or start_time < time.time() -5):
while not (self.recorder.cloud_values != {} or start_time < time.time() - 5):
time.sleep(0.01)
return self.recorder.get_var(var)

Expand All @@ -265,17 +269,19 @@ def get_all_vars(self, *, recorder_initial_values={}):
self.recorder = cloud_recorder.CloudRecorder(self, initial_values=recorder_initial_values)
self.recorder.start()
start_time = time.time()
while not (self.recorder.cloud_values != {} or start_time < time.time() -5):
while not (self.recorder.cloud_values != {} or start_time < time.time() - 5):
time.sleep(0.01)
return self.recorder.get_all_vars()

def events(self):
from ..eventhandlers.cloud_events import CloudEvents
return CloudEvents(self)

def requests(self, *, no_packet_loss=False, used_cloud_vars=["1", "2", "3", "4", "5", "6", "7", "8", "9"], respond_order="receive"):
def requests(self, *, no_packet_loss=False, used_cloud_vars=["1", "2", "3", "4", "5", "6", "7", "8", "9"],
respond_order="receive"):
from ..eventhandlers.cloud_requests import CloudRequests
return CloudRequests(self, used_cloud_vars=used_cloud_vars, no_packet_loss=no_packet_loss, respond_order=respond_order)
return CloudRequests(self, used_cloud_vars=used_cloud_vars, no_packet_loss=no_packet_loss,
respond_order=respond_order)

def storage(self, *, no_packet_loss=False, used_cloud_vars=["1", "2", "3", "4", "5", "6", "7", "8", "9"]):
from ..eventhandlers.cloud_storage import CloudStorage
Expand Down
9 changes: 6 additions & 3 deletions scratchattach/cloud/cloud.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"""v2 ready: ScratchCloud, TwCloud and CustomCloud classes"""

from __future__ import annotations

from ._base import BaseCloud
from typing import Type
from ..utils.requests import Requests as requests
from ..utils import exceptions, commons
from ..site import cloud_activity

class ScratchCloud(BaseCloud):

class ScratchCloud(BaseCloud):
def __init__(self, *, project_id, _session=None):
super().__init__()

Expand Down Expand Up @@ -91,9 +93,10 @@ def events(self, *, use_logs=False):
else:
return super().events()

class TwCloud(BaseCloud):

def __init__(self, *, project_id, cloud_host="wss://clouddata.turbowarp.org", purpose="", contact=""):
class TwCloud(BaseCloud):
def __init__(self, *, project_id, cloud_host="wss://clouddata.turbowarp.org", purpose="", contact="",
_session=None):
super().__init__()

self.project_id = project_id
Expand Down
2 changes: 2 additions & 0 deletions scratchattach/eventhandlers/_base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from abc import ABC, abstractmethod
from ..utils.requests import Requests as requests
from threading import Thread
Expand Down
1 change: 1 addition & 0 deletions scratchattach/eventhandlers/cloud_events.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""CloudEvents class"""
from __future__ import annotations

from ..cloud import cloud
from ._base import BaseEventHandler
Expand Down
12 changes: 8 additions & 4 deletions scratchattach/eventhandlers/cloud_recorder.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
"""CloudRecorder class (used by ScratchCloud, TwCloud and other classes inheriting from BaseCloud to deliver cloud var values)"""
from __future__ import annotations

from .cloud_events import CloudEvents


class CloudRecorder(CloudEvents):
def __init__(self, cloud, *, initial_values: dict = None):
if initial_values is None:
initial_values = {}

def __init__(self, cloud, *, initial_values={}):
super().__init__(cloud)
self.cloud_values = initial_values
self.event(self.on_set)

def get_var(self, var):
if not var in self.cloud_values:
if var not in self.cloud_values:
return None
return self.cloud_values[var]

def get_all_vars(self):
return self.cloud_values

def on_set(self, activity):
self.cloud_values[activity.var] = activity.value
5 changes: 3 additions & 2 deletions scratchattach/eventhandlers/cloud_requests.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""CloudRequests class (threading.Event version)"""
from __future__ import annotations

from .cloud_events import CloudEvents
from ..site import project
Expand Down Expand Up @@ -167,8 +168,8 @@ def _parse_output(self, request_name, output, request_id):
def _set_FROM_HOST_var(self, value):
try:
self.cloud.set_var(f"FROM_HOST_{self.used_cloud_vars[self.current_var]}", value)
except exceptions.ConnectionError:
self.call_even("on_disconnect")
except exceptions.CloudConnectionError:
self.call_event("on_disconnect")
except Exception as e:
print("scratchattach: internal error while responding (please submit a bug report on GitHub):", e)
self.current_var += 1
Expand Down
2 changes: 2 additions & 0 deletions scratchattach/eventhandlers/cloud_server.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket
from threading import Thread
from ..utils import exceptions
Expand Down
1 change: 1 addition & 0 deletions scratchattach/eventhandlers/cloud_storage.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""CloudStorage class"""
from __future__ import annotations

from .cloud_requests import CloudRequests
import json
Expand Down
2 changes: 2 additions & 0 deletions scratchattach/eventhandlers/combine.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from __future__ import annotations

class MultiEventHandler:

def __init__(self, *handlers):
Expand Down
1 change: 1 addition & 0 deletions scratchattach/eventhandlers/filterbot.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""FilterBot class"""
from __future__ import annotations

from .message_events import MessageEvents
import time
Expand Down
1 change: 1 addition & 0 deletions scratchattach/eventhandlers/message_events.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""MessageEvents class"""
from __future__ import annotations

from ..site import user
from ._base import BaseEventHandler
Expand Down
Loading