scratchattach 2.1.15b0__py3-none-any.whl → 3.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cli/__about__.py +1 -0
- cli/__init__.py +26 -0
- cli/cmd/__init__.py +4 -0
- cli/cmd/group.py +127 -0
- cli/cmd/login.py +60 -0
- cli/cmd/profile.py +7 -0
- cli/cmd/sessions.py +5 -0
- cli/context.py +142 -0
- cli/db.py +66 -0
- cli/namespace.py +14 -0
- {scratchattach/cloud → cloud}/_base.py +112 -87
- {scratchattach/cloud → cloud}/cloud.py +16 -16
- {scratchattach/editor → editor}/__init__.py +2 -1
- {scratchattach/editor → editor}/asset.py +26 -14
- {scratchattach/editor → editor}/backpack_json.py +3 -5
- {scratchattach/editor → editor}/base.py +2 -4
- {scratchattach/editor → editor}/block.py +27 -22
- {scratchattach/editor → editor}/blockshape.py +1 -1
- {scratchattach/editor → editor}/build_defaulting.py +2 -2
- editor/commons.py +145 -0
- {scratchattach/editor → editor}/field.py +1 -1
- {scratchattach/editor → editor}/inputs.py +6 -3
- {scratchattach/editor → editor}/meta.py +10 -7
- {scratchattach/editor → editor}/monitor.py +10 -8
- {scratchattach/editor → editor}/mutation.py +68 -11
- {scratchattach/editor → editor}/pallete.py +1 -3
- {scratchattach/editor → editor}/prim.py +4 -0
- {scratchattach/editor → editor}/project.py +118 -16
- {scratchattach/editor → editor}/sprite.py +25 -15
- {scratchattach/editor → editor}/vlb.py +2 -2
- {scratchattach/eventhandlers → eventhandlers}/_base.py +1 -0
- {scratchattach/eventhandlers → eventhandlers}/cloud_events.py +26 -6
- {scratchattach/eventhandlers → eventhandlers}/cloud_recorder.py +4 -4
- {scratchattach/eventhandlers → eventhandlers}/cloud_requests.py +139 -54
- {scratchattach/eventhandlers → eventhandlers}/cloud_server.py +6 -3
- {scratchattach/eventhandlers → eventhandlers}/cloud_storage.py +1 -2
- eventhandlers/filterbot.py +163 -0
- other/other_apis.py +598 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/METADATA +7 -11
- scratchattach-3.0.0b1.dist-info/RECORD +79 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/WHEEL +1 -1
- scratchattach-3.0.0b1.dist-info/entry_points.txt +2 -0
- scratchattach-3.0.0b1.dist-info/top_level.txt +7 -0
- {scratchattach/site → site}/_base.py +32 -5
- site/activity.py +426 -0
- {scratchattach/site → site}/alert.py +4 -5
- {scratchattach/site → site}/backpack_asset.py +2 -1
- {scratchattach/site → site}/classroom.py +80 -73
- {scratchattach/site → site}/cloud_activity.py +43 -29
- {scratchattach/site → site}/comment.py +86 -100
- {scratchattach/site → site}/forum.py +8 -4
- site/placeholder.py +132 -0
- {scratchattach/site → site}/project.py +228 -122
- {scratchattach/site → site}/session.py +156 -71
- {scratchattach/site → site}/studio.py +139 -46
- site/typed_dicts.py +151 -0
- {scratchattach/site → site}/user.py +511 -215
- {scratchattach/utils → utils}/commons.py +12 -4
- {scratchattach/utils → utils}/encoder.py +7 -4
- {scratchattach/utils → utils}/enums.py +1 -0
- {scratchattach/utils → utils}/exceptions.py +36 -2
- utils/optional_async.py +154 -0
- utils/requests.py +306 -0
- scratchattach/__init__.py +0 -29
- scratchattach/editor/commons.py +0 -273
- scratchattach/eventhandlers/filterbot.py +0 -161
- scratchattach/other/other_apis.py +0 -284
- scratchattach/site/activity.py +0 -382
- scratchattach/utils/requests.py +0 -93
- scratchattach-2.1.15b0.dist-info/RECORD +0 -66
- scratchattach-2.1.15b0.dist-info/top_level.txt +0 -1
- {scratchattach/cloud → cloud}/__init__.py +0 -0
- {scratchattach/editor → editor}/code_translation/__init__.py +0 -0
- {scratchattach/editor → editor}/code_translation/parse.py +0 -0
- {scratchattach/editor → editor}/comment.py +0 -0
- {scratchattach/editor → editor}/extension.py +0 -0
- {scratchattach/editor → editor}/twconfig.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/__init__.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/combine.py +0 -0
- {scratchattach/eventhandlers → eventhandlers}/message_events.py +0 -0
- {scratchattach/other → other}/__init__.py +0 -0
- {scratchattach/other → other}/project_json_capabilities.py +0 -0
- {scratchattach-2.1.15b0.dist-info → scratchattach-3.0.0b1.dist-info}/licenses/LICENSE +0 -0
- {scratchattach/site → site}/__init__.py +0 -0
- {scratchattach/site → site}/browser_cookie3_stub.py +0 -0
- {scratchattach/site → site}/browser_cookies.py +0 -0
- {scratchattach/utils → utils}/__init__.py +0 -0
cli/__about__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.0.0"
|
cli/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Only for use within __main__.py
|
|
3
|
+
"""
|
|
4
|
+
import io
|
|
5
|
+
|
|
6
|
+
from rich.console import RenderableType
|
|
7
|
+
from typing_extensions import Optional
|
|
8
|
+
from scratchattach.cli.__about__ import VERSION
|
|
9
|
+
from scratchattach.cli.namespace import ArgSpace
|
|
10
|
+
from scratchattach.cli.context import ctx
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# noinspection PyPackageRequirements
|
|
14
|
+
def try_get_img(image: bytes, size: tuple[int, int] | None = None) -> Optional[RenderableType]:
|
|
15
|
+
try:
|
|
16
|
+
from PIL import Image
|
|
17
|
+
from rich_pixels import Pixels
|
|
18
|
+
|
|
19
|
+
with Image.open(io.BytesIO(image)) as image:
|
|
20
|
+
if size is not None:
|
|
21
|
+
image = image.resize(size)
|
|
22
|
+
|
|
23
|
+
return Pixels.from_image(image)
|
|
24
|
+
|
|
25
|
+
except ImportError:
|
|
26
|
+
return ""
|
cli/cmd/__init__.py
ADDED
cli/cmd/group.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
from scratchattach.cli import db
|
|
2
|
+
from scratchattach.cli.context import console, ctx
|
|
3
|
+
|
|
4
|
+
from rich.markup import escape
|
|
5
|
+
from rich.table import Table
|
|
6
|
+
|
|
7
|
+
def _list():
|
|
8
|
+
table = Table(title="All groups")
|
|
9
|
+
table.add_column("Name")
|
|
10
|
+
table.add_column("Description")
|
|
11
|
+
table.add_column("Usernames")
|
|
12
|
+
|
|
13
|
+
db.cursor.execute("SELECT NAME, DESCRIPTION FROM GROUPS")
|
|
14
|
+
for name, description in db.cursor.fetchall():
|
|
15
|
+
db.cursor.execute("SELECT USERNAME FROM GROUP_USERS WHERE GROUP_NAME=?", (name,))
|
|
16
|
+
usernames = db.cursor.fetchall()
|
|
17
|
+
|
|
18
|
+
table.add_row(escape(name), escape(description),
|
|
19
|
+
'\n'.join(f"{i}. {u}" for i, (u,) in enumerate(usernames)))
|
|
20
|
+
|
|
21
|
+
console.print(table)
|
|
22
|
+
|
|
23
|
+
def add(group_name: str):
|
|
24
|
+
accounts = input("Add accounts (split by space): ").split()
|
|
25
|
+
for account in accounts:
|
|
26
|
+
ctx.db_add_to_group(group_name, account)
|
|
27
|
+
|
|
28
|
+
def remove(group_name: str):
|
|
29
|
+
accounts = input("Remove accounts (split by space): ").split()
|
|
30
|
+
for account in accounts:
|
|
31
|
+
ctx.db_remove_from_group(group_name, account)
|
|
32
|
+
|
|
33
|
+
def new():
|
|
34
|
+
console.rule(f"New group {escape(ctx.args.group_name)}")
|
|
35
|
+
if ctx.db_group_exists(ctx.args.group_name):
|
|
36
|
+
raise ValueError(f"Group {escape(ctx.args.group_name)} already exists")
|
|
37
|
+
|
|
38
|
+
db.conn.execute("BEGIN")
|
|
39
|
+
db.cursor.execute("INSERT INTO GROUPS (NAME, DESCRIPTION) "
|
|
40
|
+
"VALUES (?, ?)", (ctx.args.group_name, input("Description: ")))
|
|
41
|
+
db.conn.commit()
|
|
42
|
+
add(ctx.args.group_name)
|
|
43
|
+
|
|
44
|
+
_group(ctx.args.group_name)
|
|
45
|
+
|
|
46
|
+
def _group(group_name: str):
|
|
47
|
+
"""
|
|
48
|
+
Display information about a group
|
|
49
|
+
"""
|
|
50
|
+
db.cursor.execute(
|
|
51
|
+
"SELECT NAME, DESCRIPTION FROM GROUPS WHERE NAME = ?", (group_name,))
|
|
52
|
+
result = db.cursor.fetchone()
|
|
53
|
+
if result is None:
|
|
54
|
+
print("No group selected!!")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
name, description = result
|
|
58
|
+
|
|
59
|
+
db.cursor.execute("SELECT USERNAME FROM GROUP_USERS WHERE GROUP_NAME = ?", (name,))
|
|
60
|
+
usernames = [name for (name,) in db.cursor.fetchall()]
|
|
61
|
+
|
|
62
|
+
table = Table(title=escape(group_name))
|
|
63
|
+
table.add_column(escape(name))
|
|
64
|
+
table.add_column('Usernames')
|
|
65
|
+
|
|
66
|
+
table.add_row(escape(description),
|
|
67
|
+
'\n'.join(f"{i}. {u}" for i, u in enumerate(usernames)))
|
|
68
|
+
|
|
69
|
+
console.print(table)
|
|
70
|
+
|
|
71
|
+
def switch():
|
|
72
|
+
console.rule(f"Switching to {escape(ctx.args.group_name)}")
|
|
73
|
+
if not ctx.db_group_exists(ctx.args.group_name):
|
|
74
|
+
raise ValueError(f"Group {escape(ctx.args.group_name)} does not exist")
|
|
75
|
+
|
|
76
|
+
ctx.current_group_name = ctx.args.group_name
|
|
77
|
+
_group(ctx.current_group_name)
|
|
78
|
+
|
|
79
|
+
def delete(group_name: str):
|
|
80
|
+
print(f"Deleting {group_name}")
|
|
81
|
+
if not ctx.db_group_exists(group_name):
|
|
82
|
+
raise ValueError(f"Group {group_name} does not exist")
|
|
83
|
+
if ctx.db_group_count == 1:
|
|
84
|
+
raise ValueError(f"Make another group first")
|
|
85
|
+
|
|
86
|
+
ctx.db_group_delete(group_name)
|
|
87
|
+
|
|
88
|
+
def copy(group_name: str, new_name: str):
|
|
89
|
+
print(f"Copying {group_name} as {new_name}")
|
|
90
|
+
if not ctx.db_group_exists(group_name):
|
|
91
|
+
raise ValueError(f"Group {group_name} does not exist")
|
|
92
|
+
|
|
93
|
+
ctx.db_group_copy(group_name, new_name)
|
|
94
|
+
|
|
95
|
+
def rename(group_name: str, new_name: str):
|
|
96
|
+
copy(group_name, new_name)
|
|
97
|
+
delete(group_name)
|
|
98
|
+
|
|
99
|
+
def group():
|
|
100
|
+
match ctx.args.group_command:
|
|
101
|
+
case "list":
|
|
102
|
+
_list()
|
|
103
|
+
case "new":
|
|
104
|
+
new()
|
|
105
|
+
case "switch":
|
|
106
|
+
switch()
|
|
107
|
+
case "add":
|
|
108
|
+
add(ctx.current_group_name)
|
|
109
|
+
case "remove":
|
|
110
|
+
remove(ctx.current_group_name)
|
|
111
|
+
case "delete":
|
|
112
|
+
if input("Are you sure? (y/N): ").lower() != "y":
|
|
113
|
+
return
|
|
114
|
+
delete(ctx.current_group_name)
|
|
115
|
+
new_current = ctx.db_first_group_name
|
|
116
|
+
print(f"Switching to {new_current}")
|
|
117
|
+
ctx.current_group_name = new_current
|
|
118
|
+
_group(new_current)
|
|
119
|
+
|
|
120
|
+
case "copy":
|
|
121
|
+
copy(ctx.current_group_name, ctx.args.group_name)
|
|
122
|
+
case "rename":
|
|
123
|
+
rename(ctx.current_group_name, ctx.args.group_name)
|
|
124
|
+
ctx.current_group_name = ctx.args.group_name
|
|
125
|
+
_group(ctx.args.group_name)
|
|
126
|
+
case None:
|
|
127
|
+
_group(ctx.current_group_name)
|
cli/cmd/login.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from scratchattach.cli.context import ctx, console
|
|
2
|
+
from scratchattach.cli import db
|
|
3
|
+
from rich.markup import escape
|
|
4
|
+
|
|
5
|
+
from getpass import getpass
|
|
6
|
+
|
|
7
|
+
import scratchattach as sa
|
|
8
|
+
import warnings
|
|
9
|
+
|
|
10
|
+
warnings.filterwarnings("ignore", category=sa.LoginDataWarning)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def login():
|
|
14
|
+
if ctx.args.sessid:
|
|
15
|
+
if isinstance(ctx.args.sessid, bool):
|
|
16
|
+
ctx.args.sessid = getpass("Session ID: ")
|
|
17
|
+
|
|
18
|
+
session = sa.login_by_id(ctx.args.sessid)
|
|
19
|
+
password = None
|
|
20
|
+
else:
|
|
21
|
+
username = input("Username: ")
|
|
22
|
+
password = getpass()
|
|
23
|
+
|
|
24
|
+
session = sa.login(username, password)
|
|
25
|
+
|
|
26
|
+
console.rule()
|
|
27
|
+
console.print(f"Logged in as [b]{session.username}[/]")
|
|
28
|
+
|
|
29
|
+
# register session
|
|
30
|
+
db.conn.execute("BEGIN")
|
|
31
|
+
db.cursor.execute(
|
|
32
|
+
"INSERT OR REPLACE INTO SESSIONS (ID, USERNAME, PASSWORD) "
|
|
33
|
+
"VALUES (?, ?, ?)", (session.id, session.username, password)
|
|
34
|
+
)
|
|
35
|
+
db.conn.commit()
|
|
36
|
+
|
|
37
|
+
# make new group
|
|
38
|
+
db.cursor.execute("SELECT NAME FROM GROUPS WHERE NAME = ?", (session.username,))
|
|
39
|
+
if not db.cursor.fetchone():
|
|
40
|
+
console.rule(f"Registering [b]{escape(session.username)}[/] as group")
|
|
41
|
+
db.conn.execute("BEGIN")
|
|
42
|
+
db.cursor.execute(
|
|
43
|
+
"INSERT INTO GROUPS (NAME, DESCRIPTION) "
|
|
44
|
+
"VALUES (?, ?)", (session.username, input(f"Description for {session.username}: "))
|
|
45
|
+
).execute(
|
|
46
|
+
"INSERT INTO GROUP_USERS (GROUP_NAME, USERNAME) "
|
|
47
|
+
"VALUES (?, ?)", (session.username, session.username)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
db.conn.commit()
|
|
51
|
+
|
|
52
|
+
console.rule()
|
|
53
|
+
if input("Add to current session group? (Y/n)").lower() not in ("y", ''):
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
db.conn.execute("BEGIN")
|
|
57
|
+
db.cursor.execute("INSERT INTO GROUP_USERS (GROUP_NAME, USERNAME) "
|
|
58
|
+
"VALUES (?, ?)", (ctx.current_group_name, session.username))
|
|
59
|
+
db.conn.commit()
|
|
60
|
+
console.print(f"Added to [b]{escape(ctx.current_group_name)}[/]")
|
cli/cmd/profile.py
ADDED
cli/cmd/sessions.py
ADDED
cli/context.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Handles data like current session for 'sessionable' commands.
|
|
3
|
+
Holds objects that should be available for the whole CLI system
|
|
4
|
+
Also provides wrappers for some SQL info.
|
|
5
|
+
"""
|
|
6
|
+
import argparse
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
|
|
9
|
+
import rich.console
|
|
10
|
+
from typing_extensions import Optional
|
|
11
|
+
|
|
12
|
+
from scratchattach.cli.namespace import ArgSpace
|
|
13
|
+
from scratchattach.cli import db
|
|
14
|
+
import scratchattach as sa
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class _Ctx:
|
|
19
|
+
args: ArgSpace = field(default_factory=ArgSpace)
|
|
20
|
+
parser: argparse.ArgumentParser = field(default_factory=argparse.ArgumentParser)
|
|
21
|
+
_username: Optional[str] = None
|
|
22
|
+
_session: Optional[sa.Session] = None
|
|
23
|
+
|
|
24
|
+
# TODO: implement this
|
|
25
|
+
def sessionable(self, func):
|
|
26
|
+
"""
|
|
27
|
+
Decorate a command that will be run for every session in the group.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def wrapper(*args, **kwargs):
|
|
31
|
+
for username in self.db_users_in_group(self.current_group_name):
|
|
32
|
+
self._username = username
|
|
33
|
+
self._session = None
|
|
34
|
+
func(*args, **kwargs)
|
|
35
|
+
|
|
36
|
+
return wrapper
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def username(self):
|
|
40
|
+
if not self._username:
|
|
41
|
+
self._username = self.db_users_in_group(self.current_group_name)[0]
|
|
42
|
+
|
|
43
|
+
return self._username
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def session(self):
|
|
47
|
+
if not self._session:
|
|
48
|
+
self._session = sa.login_by_id(self.db_get_sessid(self.username))
|
|
49
|
+
|
|
50
|
+
return self._session
|
|
51
|
+
|
|
52
|
+
# helper functions with DB
|
|
53
|
+
# if possible, put all db funcs here
|
|
54
|
+
|
|
55
|
+
@property
|
|
56
|
+
def current_group_name(self):
|
|
57
|
+
return db.cursor \
|
|
58
|
+
.execute("SELECT * FROM CURRENT WHERE GROUP_NAME IS NOT NULL") \
|
|
59
|
+
.fetchone()[0]
|
|
60
|
+
|
|
61
|
+
@current_group_name.setter
|
|
62
|
+
def current_group_name(self, value: str):
|
|
63
|
+
db.conn.execute("BEGIN")
|
|
64
|
+
db.cursor.execute("DELETE FROM CURRENT WHERE GROUP_NAME IS NOT NULL")
|
|
65
|
+
db.cursor.execute("INSERT INTO CURRENT (GROUP_NAME) VALUES (?)", (value,))
|
|
66
|
+
db.conn.commit()
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def db_group_exists(name: str) -> bool:
|
|
70
|
+
return db.cursor.execute("SELECT NAME FROM GROUPS WHERE NAME = ?", (name,)).fetchone() is not None
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def db_session_exists(name: str) -> bool:
|
|
74
|
+
return db.cursor.execute("SELECT USERNAME FROM SESSIONS WHERE USERNAME = ?", (name,)).fetchone() is not None
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def db_users_in_group(name: str) -> list[str]:
|
|
78
|
+
return [i for (i,) in db.cursor.execute(
|
|
79
|
+
"SELECT USERNAME FROM GROUP_USERS WHERE GROUP_NAME = ?", (name,)).fetchall()]
|
|
80
|
+
|
|
81
|
+
def db_remove_from_group(self, group_name: str, username: str):
|
|
82
|
+
if username in self.db_users_in_group(group_name):
|
|
83
|
+
db.conn.execute("BEGIN")
|
|
84
|
+
db.cursor.execute("DELETE FROM GROUP_USERS "
|
|
85
|
+
"WHERE USERNAME = ? AND GROUP_NAME = ?", (username, group_name))
|
|
86
|
+
db.conn.commit()
|
|
87
|
+
|
|
88
|
+
@staticmethod
|
|
89
|
+
def db_get_sessid(username: str) -> Optional[str]:
|
|
90
|
+
ret = db.cursor.execute("SELECT ID FROM SESSIONS WHERE USERNAME = ?", (username,)).fetchone()
|
|
91
|
+
if ret:
|
|
92
|
+
ret = ret[0]
|
|
93
|
+
return ret
|
|
94
|
+
|
|
95
|
+
def db_add_to_group(self, group_name: str, username: str):
|
|
96
|
+
if username in self.db_users_in_group(group_name) or not self.db_session_exists(username):
|
|
97
|
+
return
|
|
98
|
+
db.conn.execute("BEGIN")
|
|
99
|
+
db.cursor.execute("INSERT INTO GROUP_USERS (GROUP_NAME, USERNAME) "
|
|
100
|
+
"VALUES (?, ?)", (group_name, username))
|
|
101
|
+
db.conn.commit()
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def db_group_delete(group_name: str):
|
|
105
|
+
db.conn.execute("BEGIN")
|
|
106
|
+
# delete links to sessions first
|
|
107
|
+
db.cursor.execute("DELETE FROM GROUP_USERS WHERE GROUP_NAME = ?", (group_name,))
|
|
108
|
+
# delete group itself
|
|
109
|
+
db.cursor.execute("DELETE FROM GROUPS WHERE NAME = ?", (group_name,))
|
|
110
|
+
db.conn.commit()
|
|
111
|
+
|
|
112
|
+
@staticmethod
|
|
113
|
+
def db_group_copy(group_name: str, copy_name: str):
|
|
114
|
+
db.conn.execute("BEGIN")
|
|
115
|
+
# copy group metadata
|
|
116
|
+
db.cursor.execute("INSERT INTO GROUPS (NAME, DESCRIPTION) "
|
|
117
|
+
"SELECT ?, DESCRIPTION FROM GROUPS WHERE NAME = ?", (copy_name, group_name,))
|
|
118
|
+
# copy sessions
|
|
119
|
+
db.cursor.execute("INSERT INTO GROUP_USERS (GROUP_NAME, USERNAME) "
|
|
120
|
+
"SELECT ?, USERNAME FROM GROUP_USERS WHERE GROUP_NAME = ?", (copy_name, group_name,))
|
|
121
|
+
|
|
122
|
+
db.conn.commit()
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def db_first_group_name(self) -> str:
|
|
126
|
+
"""Just get a group, I don't care which"""
|
|
127
|
+
db.cursor.execute("SELECT NAME FROM GROUPS")
|
|
128
|
+
return db.cursor.fetchone()[0]
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def db_group_count(self):
|
|
132
|
+
db.cursor.execute("SELECT COUNT(*) FROM GROUPS")
|
|
133
|
+
return db.cursor.fetchone()[0]
|
|
134
|
+
|
|
135
|
+
def db_get_sess(self, sess_name: str):
|
|
136
|
+
if sess_id := self.db_get_sessid(sess_name):
|
|
137
|
+
return sa.login_by_id(sess_id)
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
ctx = _Ctx()
|
|
142
|
+
console = rich.console.Console()
|
cli/db.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Basic connections to the scratch.sqlite file
|
|
3
|
+
"""
|
|
4
|
+
import sqlite3
|
|
5
|
+
import sys
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from typing_extensions import LiteralString
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _gen_appdata_folder() -> Path:
|
|
14
|
+
name = "scratchattach"
|
|
15
|
+
match sys.platform:
|
|
16
|
+
case "win32":
|
|
17
|
+
return Path(os.getenv('APPDATA')) / name
|
|
18
|
+
case "linux":
|
|
19
|
+
return Path.home() / f".{name}"
|
|
20
|
+
case plat:
|
|
21
|
+
raise NotImplementedError(f"No 'appdata' folder implemented for {plat}")
|
|
22
|
+
|
|
23
|
+
_path = _gen_appdata_folder()
|
|
24
|
+
_path.mkdir(parents=True, exist_ok=True)
|
|
25
|
+
|
|
26
|
+
conn = sqlite3.connect(_path / "cli.sqlite")
|
|
27
|
+
cursor = conn.cursor()
|
|
28
|
+
|
|
29
|
+
# Init any tables
|
|
30
|
+
def add_col(table: LiteralString, column: LiteralString, _type: LiteralString):
|
|
31
|
+
try:
|
|
32
|
+
# strangely, using `?` here doesn't seem to work. Make sure to use LiteralStrings, not str
|
|
33
|
+
return cursor.execute(f"ALTER TABLE {table} ADD COLUMN {column} {_type}")
|
|
34
|
+
except sqlite3.OperationalError as e:
|
|
35
|
+
if "duplicate column name" not in str(e).lower():
|
|
36
|
+
raise
|
|
37
|
+
|
|
38
|
+
# NOTE: IF YOU WANT TO ADD EXTRA KEYS TO A TABLE RETROACTIVELY, USE add_col
|
|
39
|
+
# note: avoide using select * with tuple unpacking/indexing, because if fields are added, things will break.
|
|
40
|
+
conn.execute("BEGIN")
|
|
41
|
+
cursor.executescript("""
|
|
42
|
+
CREATE TABLE IF NOT EXISTS SESSIONS (
|
|
43
|
+
ID TEXT NOT NULL,
|
|
44
|
+
USERNAME TEXT NOT NULL PRIMARY KEY
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
CREATE TABLE IF NOT EXISTS GROUPS (
|
|
48
|
+
NAME TEXT NOT NULL PRIMARY KEY,
|
|
49
|
+
DESCRIPTION TEXT
|
|
50
|
+
-- If you want to add users to a group, you add to the next table
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE TABLE IF NOT EXISTS GROUP_USERS (
|
|
54
|
+
GROUP_NAME TEXT NOT NULL,
|
|
55
|
+
USERNAME TEXT NOT NULL
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
-- stores info like current group, last project/studio, etc
|
|
59
|
+
CREATE TABLE IF NOT EXISTS CURRENT (
|
|
60
|
+
GROUP_NAME TEXT NOT NULL
|
|
61
|
+
);
|
|
62
|
+
""")
|
|
63
|
+
add_col("SESSIONS", "PASSWORD", "TEXT")
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
conn.commit()
|
cli/namespace.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
from typing_extensions import Optional, Literal
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ArgSpace(argparse.Namespace):
|
|
6
|
+
command: Optional[Literal['login', 'group', 'profile', 'sessions']]
|
|
7
|
+
sessid: bool | str
|
|
8
|
+
username: Optional[str]
|
|
9
|
+
studio_id: Optional[str]
|
|
10
|
+
project_id: Optional[str]
|
|
11
|
+
session_name: Optional[str]
|
|
12
|
+
|
|
13
|
+
group_command: Optional[Literal['list', 'new', 'switch', 'add', 'remove', 'delete', 'copy', 'rename']]
|
|
14
|
+
group_name: str
|