diff --git a/README.md b/README.md index f1076c7..1d4b972 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,26 @@ session = scratchcommunication.Session.login("YOUR_USERNAME", "YOUR_PASSWORD") I recommend using your session id instead of your password. +## Login from browser + +This will login using cookies from your browser. It only works if you have that browser installed and if you are logged in. + +```python +import scratchcommunication +session = scratchcommunication.Session.from_browser(scratchcommunication.ANY) +``` + +You can choose from these browsers: + +FIREFOX +CHROME +EDGE +SAFARI +CHROMIUM +EDGE_DEV +VIVALDI +ANY + ## Access account data Once you have logged into your account, you can access your account data. diff --git a/scratchcommunication/__init__.py b/scratchcommunication/__init__.py index 8f86456..72aba16 100644 --- a/scratchcommunication/__init__.py +++ b/scratchcommunication/__init__.py @@ -2,7 +2,7 @@ Module for communicating with scratch projects. """ -__version_number__ = '2.6.1' +__version_number__ = '2.7.1' from .session import * from .cloud import * diff --git a/scratchcommunication/session.py b/scratchcommunication/session.py index 11ba30e..0231f25 100644 --- a/scratchcommunication/session.py +++ b/scratchcommunication/session.py @@ -1,12 +1,30 @@ import warnings +import requests, json, re +from typing import Literal, Self, assert_never from .headers import headers -from .exceptions import InvalidValueError, ErrorInCloudSocket +from .exceptions import InvalidValueError, ErrorInCloudSocket, NotSupported from . import cloud, cloud_socket -import requests, json, re +try: + import browsercookie + browsercookie_err = None +except Exception as e: + browsercookie = None + browsercookie_err = e + +FIREFOX = 0 +CHROME = 1 +EDGE = 2 +SAFARI = 3 +CHROMIUM = 4 +EDGE_DEV = 5 +VIVALDI = 6 +ANY = 7 class Session: __slots__ = ("session_id", "username", "headers", "cookies", "xtoken", "email", "id", "permissions", "flags", "banned", "session_data", "mute_status", "new_scratcher") - def __init__(self, username : str = None, *, session_id : str): + def __init__(self, username : str = None, *, session_id : str = None, _login : bool = False): + if not _login: + return self.session_id = session_id self.username = username self.headers = headers @@ -30,12 +48,12 @@ def logout(self): for attr in self.__slots__: delattr(self, attr) - def _login(self): + def _login(self, *, _session : requests.Session = None): ''' Don't use this ''' try: - account = requests.post("https://scratch.mit.edu/session", headers=self.headers, cookies={ + account = (_session or requests).post("https://scratch.mit.edu/session", headers=self.headers, cookies={ "scratchsessionsid": self.session_id, "scratchcsrftoken": "a", "scratchlanguage": "en", @@ -53,11 +71,55 @@ def _login(self): self.mute_status = account["permissions"]["mute_status"] except Exception: if self.username is None: - raise ValueError("No username supplied and there was no found. The username is needed.") + raise ValueError("No username supplied and there was none found. The username is needed.") warnings.warn("Couldn't find token. Most features will probably still work.") @classmethod - def login(cls, username : str, password : str): + def from_browser(cls, browser : Literal[0,1,2,3,4,5,6,7]) -> Self: + """ + Import cookies from browser to login + """ + if not browsercookie: + raise NotSupported("You cannot use browsercookie") from browsercookie_err + match browser: + case 0: + cookies = browsercookie.firefox() + case 1: + cookies = browsercookie.chrome() + case 2: + cookies = browsercookie.edge() + case 3: + cookies = browsercookie.safari() + case 4: + cookies = browsercookie.chromium() + case 5: + cookies = browsercookie.edge_dev() + case 6: + cookies = browsercookie.vivaldi() + case 7: + cookies = browsercookie.load() + case _: + assert_never(browser) + + with requests.Session() as session: + session.cookies.update(cookies) + session.headers.update(headers) + obj = cls(_login=False) + obj.cookies = { + "scratchcsrftoken" : "a", + "scratchlanguage" : "en", + "scratchpolicyseen": "true", + "accept": "application/json", + "Content-Type": "application/json", + } + obj.cookies.update(session.cookies.get_dict(".scratch.mit.edu")) + obj.session_id = session.cookies.get_dict(".scratch.mit.edu").get("scratchsessionsid") + obj.headers = session.headers + obj._login(_session=session) + return obj + + @classmethod + def login(cls, username : str, password : str) -> Self: ''' Login from your username and password. ''' diff --git a/setup.py b/setup.py index 0109185..0d67281 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ with open("README.md", encoding="utf-8") as f: long_description = f.read() -VERSION = '2.6.1' +VERSION = '2.7.1' setup( name='scratchcommunication', @@ -30,7 +30,8 @@ 'websocket-client', 'func-timeout', 'pycryptodome', - 'attrs' + 'attrs', + 'browsercookie' ], - python_requires='>=3.6', + python_requires='>=3.11', )