Skip to content

Commit

Permalink
2.10.3
Browse files Browse the repository at this point in the history
  • Loading branch information
TheCommCraft committed Jun 13, 2024
1 parent c638bf9 commit e6e3f39
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 49 deletions.
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,22 +330,33 @@ In order for the cloud socket to work, you'll need to add the sprite from this [

## Using a cloud socket

Once you have created a cloud socket you have to start it using `scratchcommunication.cloud_socket.CloudSocket.listen`
Once you have created a cloud socket you have to start it using `scratchcommunication.cloud_socket.CloudSocket.listen` and you can also put it in a with statement, which makes the cloud socket shut down automatically when the code is done executing.

```python
cloud_socket.listen()

# OR

with cloud_socket.listen():
... # Your code here
```



After you start the cloud socket you can wait for a new user using `scratchcommunication.cloud_socket.CloudSocket.accept`

```python
client, client_username = cloud_socket.accept()
client, client_username = cloud_socket.accept(
timeout = 10 # (Optional)
)
```

When you have a client, you can send messages to them and receive messages.

```python
msg = client.recv()
msg = client.recv(
timeout = 10 # (Optional)
)
client.send("Hello!")
```

Expand Down
2 changes: 1 addition & 1 deletion scratchcommunication/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Module for communicating with scratch projects.
"""

__version_number__ = '2.10.2'
__version_number__ = '2.10.3'

from .session import *
from .cloud import *
Expand Down
93 changes: 50 additions & 43 deletions scratchcommunication/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from .exceptions import QuickAccessDisabledError, NotSupported, ErrorInEventHandler, StopException, EventExpiredError
import scratchcommunication
from func_timeout import StoppableThread
import json, time, requests, warnings, traceback, secrets, sys
import json, time, requests, warnings, traceback, secrets, sys, ssl
from websocket import WebSocket, WebSocketConnectionClosedException, WebSocketTimeoutException

NoneType = type(None)
Expand Down Expand Up @@ -90,6 +90,7 @@ class CloudConnection:
processed_events : list[Event]
keep_all_events : bool
supports_cloud_logs : bool
allow_no_certificate : bool
def __init__(
self,
*,
Expand All @@ -102,8 +103,11 @@ def __init__(
warning_type : type[Warning] = ErrorInEventHandler,
daemon_thread : bool = False,
connect : bool = True,
keep_all_events : bool = False
keep_all_events : bool = False,
allow_no_certificate : bool = False
):
self.websocket = None
self.allow_no_certificate = allow_no_certificate
self.supports_cloud_logs = True
self.keep_all_events = keep_all_events
self.event_order = {}
Expand All @@ -124,14 +128,14 @@ def __init__(
self.data_reception = None
if not connect:
return
self._connect()
self._handle_connect()
if not self.receive_from_websocket:
while True:
try:
self.receive_new_data()
return
except Exception:
self._connect()
self._handle_connect()
self.data_reception = StoppableThread(target=self.receive_data, daemon=daemon_thread)
self.data_reception.start()

Expand All @@ -148,7 +152,7 @@ def stop_thread(self):
self.thread_running = False
self.data_reception.stop(StopException, 0.1)
self.data_reception.join(5)

def enable_quickaccess(self):
"""
Use for enabling the use of the object as a lookup table.
Expand All @@ -161,27 +165,39 @@ def disable_quickaccess(self):
"""
self.quickaccess = False

def _connect(self, *, retry : int = 10):
def _connect(self, no_cert : bool = False):
self.websocket = WebSocket(sslopt=({"cert_reqs": ssl.CERT_NONE} if no_cert else None))
self.websocket.connect(
"wss://clouddata.scratch.mit.edu",
cookie="scratchsessionsid=" + self.session.session_id + ";",
origin="https://scratch.mit.edu",
enable_multithread=True,
timeout=5
)

def _handle_connect(self, *, retry : int = 3) -> Union[Exception, None]:
"""
Don't use this.
"""
try:
self.websocket = WebSocket()
self.websocket.connect(
"wss://clouddata.scratch.mit.edu",
cookie="scratchsessionsid=" + self.session.session_id + ";",
origin="https://scratch.mit.edu",
enable_multithread=True,
timeout=5
)
self._connect()
self.handshake()
self.emit_event("connect", timestamp=time.time())
except ssl.SSLCertVerificationError:
if not self.allow_no_certificate:
return ConnectionError(
"The SSL certificate could not be verified. Add allow_no_certificate=True to allow a connection without a certificate."
)
warnings.warn("Connecting without a certificate.", RuntimeWarning)
self._connect(no_cert=True)
except Exception as e:
if retry == 1:
raise ConnectionError(
f"There was an error while connecting to the cloud server."
) from e
self._connect(retry=retry - 1)
return ConnectionError(
"There was an error while connecting to the cloud server."
)
exc = self._handle_connect(retry=retry - 1)
if exc:
raise exc from e

def handshake(self):
self.send_packet(
Expand Down Expand Up @@ -270,7 +286,7 @@ def _set_variable(
raise ConnectionError(
"There was an error while setting the cloud variable."
) from e
self._connect()
self._handle_connect()
self._set_variable(name=name, value=value, retry=retry - 1)

def set_variable(
Expand Down Expand Up @@ -360,29 +376,29 @@ def receive_new_data(self, first : bool = False) -> dict:
self.values[i["var"]] = i["value"]
return self.values

def _prepare_connection(self):
def _prepare_handle_connection(self):
while self.thread_running:
try:
self.receive_new_data(first=True)
break
except WebSocketTimeoutException:
pass
except WebSocketConnectionClosedException:
self._connect()
self._handle_connect()

def receive_data(self):
"""
Use for receiving cloud data.
"""
self._prepare_connection()
self._prepare_handle_connection()
while self.thread_running:
try:
self.receive_new_data()
except WebSocketTimeoutException:
pass
except WebSocketConnectionClosedException:
self._connect()
self._prepare_connection()
self._handle_connect()
self._prepare_handle_connection()

def get_age_of_event(self, event : Event) -> int:
"""
Expand Down Expand Up @@ -478,7 +494,8 @@ def __init__(
cloud_host : str = "wss://clouddata.turbowarp.org/",
accept_strs : bool = False,
keep_all_events : bool = False,
contact_info : str
contact_info : str,
allow_no_certificate : bool = False
):
super().__init__(
project_id=project_id,
Expand All @@ -490,39 +507,29 @@ def __init__(
warning_type=warning_type,
daemon_thread=daemon_thread,
connect=False,
keep_all_events=keep_all_events
keep_all_events=keep_all_events,
allow_no_certificate=allow_no_certificate
)
self.supports_cloud_logs = False
self.contact_info = contact_info or ((f"@{session.username} on scratch" if session else "Anonymous") if username == "player1000" else f"@{username} on scratch")
assert self.contact_info != "Anonymous", "You need to specify your contact_info for the turbowarp cloud."
self.user_agent = f"scratchcommunication/{scratchcommunication.__version_number__} - {self.contact_info}"
self.cloud_host = cloud_host
self.accept_strs = accept_strs
self._connect()
self._handle_connect()
if not self.receive_from_websocket:
while True:
try:
self.receive_new_data()
return
except Exception:
self._connect()
self._handle_connect()
self.data_reception = StoppableThread(target=self.receive_data, daemon=daemon_thread)
self.data_reception.start()

def _connect(self, *, cloud_host = None, retry : int = 10):
try:
if cloud_host is not None:
self.cloud_host = cloud_host
self.websocket = WebSocket()
self.websocket.connect(self.cloud_host, enable_multithread=True, timeout=5, header={"User-Agent": self.user_agent})
self.handshake()
self.emit_event("connect")
except Exception as e:
if retry == 1:
raise ConnectionError(
f"There was an error while connecting to the cloud server."
) from e
self._connect(cloud_host=cloud_host, retry=retry - 1)

def _connect(self, no_cert : bool = False):
self.websocket = WebSocket(sslopt=({"cert_reqs": ssl.CERT_NONE} if no_cert else None))
self.websocket.connect(self.cloud_host, enable_multithread=True, timeout=5, header={"User-Agent": self.user_agent})

@staticmethod
def get_cloud_logs(*args, **kwargs):
Expand Down
3 changes: 2 additions & 1 deletion scratchcommunication/cloud_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ def on_packet(event):
return
except AssertionError:
pass
return self

def _decrypt_key(self, key : str) -> int:
if isinstance(self.security, sec.ECSecurity):
Expand Down Expand Up @@ -326,7 +327,7 @@ def accept(self, timeout : Union[float, None] = 10) -> tuple[BaseCloudSocketConn
new_client = self.new_clients.pop(0)
return new_client
except IndexError:
raise TimeoutError("The timeout expired (consider setting timeout=None)")
raise TimeoutError("The timeout expired (consider setting timeout=None)") from None
finally:
self.accepting.release()

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
with open("README.md", encoding="utf-8") as f:
long_description = f.read()

VERSION = '2.10.2'
VERSION = '2.10.3'

setup(
name='scratchcommunication',
Expand Down
29 changes: 29 additions & 0 deletions tests/bugtest1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import sys, os
sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
import scratchcommunication#
from dotenv import load_dotenv

load_dotenv()

USERNAME, PASSWORD = os.getenv("SCRATCH_USERNAME"), os.getenv("SCRATCH_PASSWORD")
PROJECT_ID = os.getenv("PROJECT_ID")


session = scratchcommunication.Session.login(USERNAME, PASSWORD)

security = scratchcommunication.security.Security.from_string(os.getenv("SCRATCH_SECURITY"))

cloud_socket = session.create_cloud_socket(
project_id = "884190099",
security = security
)

with cloud_socket.listen():
while True:
try:
client, client_username = cloud_socket.accept()
except TimeoutError:
continue
print(client_username + " connected!")

client.send("Hello client!")

0 comments on commit e6e3f39

Please sign in to comment.