Added mypy and pydantic
This commit is contained in:
parent
afa09a2698
commit
986c3dee45
92 changed files with 1986 additions and 1755 deletions
3
.flake8
3
.flake8
|
@ -1,3 +1,4 @@
|
|||
[flake8]
|
||||
max-line-length = 120
|
||||
ignore = D,E800,F401,F403,F405,I,N818,Q000,RST210,RST213,RST304,WPS,W503
|
||||
ignore = C812,C813,C815,C816,D,E800,F401,F403,F405,I,N818,Q000,RST210,RST213,RST304,WPS,W503
|
||||
classmethod-decorators=classmethod,validator
|
||||
|
|
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
|
@ -33,6 +33,30 @@ jobs:
|
|||
- name: Check code formatting with black
|
||||
run: black -l 120 . --diff --check
|
||||
|
||||
mypy:
|
||||
if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Set up Python ${{ env.PYTHON_VERSION }}
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: ${{ env.PYTHON_VERSION }}
|
||||
|
||||
- name: Install mypy
|
||||
run: |
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
pip install mypy types-python-dateutil types-requests
|
||||
|
||||
- name: Check typing with mypy
|
||||
run: mypy PyCrypCli
|
||||
|
||||
linter:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
|
@ -219,7 +243,7 @@ jobs:
|
|||
|
||||
pypi:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ codestyle, linter, build, docker_build ]
|
||||
needs: [ codestyle, mypy, linter, build, docker_build ]
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
environment: pypi
|
||||
|
||||
|
@ -240,7 +264,7 @@ jobs:
|
|||
|
||||
docker_push_ghcr:
|
||||
if: ${{ github.event_name != 'pull_request' && github.actor != 'dependabot[bot]' }}
|
||||
needs: [ codestyle, linter, build, docker_build ]
|
||||
needs: [ codestyle, mypy, linter, build, docker_build ]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
|
|
@ -8,7 +8,6 @@ COPY requirements.txt /app/
|
|||
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
COPY pycrypcli.py /app/
|
||||
COPY PyCrypCli /app/PyCrypCli
|
||||
|
||||
CMD ["python", "pycrypcli.py"]
|
||||
CMD ["python", "-m", "PyCrypCli"]
|
||||
|
|
|
@ -2,5 +2,4 @@
|
|||
|
||||
from PyCrypCli.pycrypcli import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
main()
|
|
@ -2,24 +2,27 @@ import json
|
|||
import re
|
||||
import ssl
|
||||
import time
|
||||
from typing import List, Optional, Type
|
||||
from typing import Type, Any, cast
|
||||
from uuid import uuid4
|
||||
|
||||
import sentry_sdk
|
||||
from pydantic import ValidationError
|
||||
from websocket import WebSocket, create_connection
|
||||
|
||||
from PyCrypCli.exceptions import (
|
||||
UnknownMicroserviceException,
|
||||
InvalidServerResponseException,
|
||||
from .exceptions import (
|
||||
UnknownMicroserviceError,
|
||||
InvalidServerResponseError,
|
||||
MicroserviceException,
|
||||
WeakPasswordException,
|
||||
UsernameAlreadyExistsException,
|
||||
InvalidLoginException,
|
||||
InvalidSessionTokenException,
|
||||
PermissionsDeniedException,
|
||||
LoggedInException,
|
||||
LoggedOutException,
|
||||
WeakPasswordError,
|
||||
UsernameAlreadyExistsError,
|
||||
InvalidLoginError,
|
||||
InvalidSessionTokenError,
|
||||
PermissionDeniedError,
|
||||
LoggedInError,
|
||||
LoggedOutError,
|
||||
ClientNotReadyError,
|
||||
)
|
||||
from .models import HardwareConfig, StatusResponse, InfoResponse, TokenResponse
|
||||
from PyCrypCli.timer import Timer
|
||||
|
||||
|
||||
|
@ -30,184 +33,213 @@ def uuid() -> str:
|
|||
class Client:
|
||||
def __init__(self, server: str):
|
||||
self.server: str = server
|
||||
self.websocket: Optional[WebSocket] = None
|
||||
self.timer: Optional[Timer] = None
|
||||
self.websocket: WebSocket | None = None
|
||||
self.timer: Timer | None = None
|
||||
self.waiting_for_response: bool = False
|
||||
self.notifications: List[dict] = []
|
||||
self.notifications: list[dict[str, Any]] = []
|
||||
self.logged_in: bool = False
|
||||
|
||||
def init(self):
|
||||
def init(self) -> None:
|
||||
try:
|
||||
self.websocket: WebSocket = create_connection(self.server)
|
||||
self.websocket = create_connection(self.server)
|
||||
except ssl.SSLCertVerificationError:
|
||||
self.websocket: WebSocket = create_connection(self.server, sslopt={"cert_reqs": ssl.CERT_NONE})
|
||||
self.timer: Timer = Timer(10, self.info)
|
||||
self.websocket = create_connection(self.server, sslopt={"cert_reqs": ssl.CERT_NONE})
|
||||
self.timer = Timer(10, self.info)
|
||||
|
||||
def close(self):
|
||||
if self.timer is not None:
|
||||
def close(self) -> None:
|
||||
if self.timer:
|
||||
self.timer.stop()
|
||||
self.timer = None
|
||||
|
||||
self.websocket.close()
|
||||
self.websocket = None
|
||||
self.logged_in: bool = False
|
||||
if self.websocket:
|
||||
self.websocket.close()
|
||||
self.websocket = None
|
||||
|
||||
def request(self, data: dict, no_response: bool = False) -> dict:
|
||||
self.logged_in = False
|
||||
|
||||
def _send(self, obj: dict[str, Any]) -> None:
|
||||
if not self.websocket:
|
||||
raise ClientNotReadyError
|
||||
|
||||
data = json.dumps(obj)
|
||||
sentry_sdk.add_breadcrumb(category="ws", message=f"send: {data}", level="debug")
|
||||
self.websocket.send(data)
|
||||
|
||||
def _recv(self) -> dict[str, Any]:
|
||||
if not self.websocket:
|
||||
raise ClientNotReadyError
|
||||
|
||||
data = self.websocket.recv()
|
||||
sentry_sdk.add_breadcrumb(category="ws", message=f"recv: {data}", level="debug")
|
||||
return cast(dict[str, Any], json.loads(data))
|
||||
|
||||
def request(self, data: dict[str, Any], no_response: bool = False) -> dict[str, Any]:
|
||||
if self.websocket is None:
|
||||
raise ConnectionError
|
||||
|
||||
while self.waiting_for_response:
|
||||
time.sleep(0.01)
|
||||
self.waiting_for_response: bool = True
|
||||
data = json.dumps(data)
|
||||
sentry_sdk.add_breadcrumb(category="ws", message=f"send: {data}", level="debug")
|
||||
self.websocket.send(data)
|
||||
self.waiting_for_response = True
|
||||
|
||||
self._send(data)
|
||||
|
||||
if no_response:
|
||||
self.waiting_for_response: bool = False
|
||||
self.waiting_for_response = False
|
||||
return {}
|
||||
|
||||
while True:
|
||||
data = self.websocket.recv()
|
||||
sentry_sdk.add_breadcrumb(category="ws", message=f"recv: {data}", level="debug")
|
||||
response: dict = json.loads(data)
|
||||
response = self._recv()
|
||||
if "notify-id" in response:
|
||||
self.notifications.append(response)
|
||||
else:
|
||||
break
|
||||
self.waiting_for_response: bool = False
|
||||
|
||||
self.waiting_for_response = False
|
||||
return response
|
||||
|
||||
def ms(self, ms: str, endpoint: List[str], **data) -> dict:
|
||||
def ms(self, ms: str, endpoint: list[str], **data: Any) -> dict[str, Any]:
|
||||
if not self.logged_in:
|
||||
raise LoggedOutException
|
||||
raise LoggedOutError
|
||||
|
||||
response: dict = self.request({"ms": ms, "endpoint": endpoint, "data": data, "tag": uuid()})
|
||||
response: dict[str, Any] = self.request({"ms": ms, "endpoint": endpoint, "data": data, "tag": uuid()})
|
||||
|
||||
if "error" in response:
|
||||
error: str = response["error"]
|
||||
if error == "unknown microservice":
|
||||
raise UnknownMicroserviceException(ms)
|
||||
raise InvalidServerResponseException(response)
|
||||
raise UnknownMicroserviceError(ms)
|
||||
raise InvalidServerResponseError(response)
|
||||
|
||||
if "data" not in response:
|
||||
raise InvalidServerResponseException(response)
|
||||
raise InvalidServerResponseError(response)
|
||||
|
||||
data: dict = response["data"]
|
||||
if "error" in data:
|
||||
error: str = data["error"]
|
||||
for exception in MicroserviceException.__subclasses__(): # type: Type[MicroserviceException]
|
||||
match = re.fullmatch(exception.error, error)
|
||||
if match:
|
||||
response_data: dict[str, Any] = response["data"]
|
||||
if "error" in response_data:
|
||||
error = response_data["error"]
|
||||
exception: Type[MicroserviceException]
|
||||
for exception in MicroserviceException.__subclasses__():
|
||||
if exception.error and (match := re.fullmatch(exception.error, error)):
|
||||
raise exception(error, list(match.groups()))
|
||||
raise InvalidServerResponseException(response)
|
||||
return data
|
||||
raise InvalidServerResponseError(response)
|
||||
|
||||
def register(self, username: str, password: str) -> str:
|
||||
return response_data
|
||||
|
||||
def register(self, username: str, password: str) -> TokenResponse:
|
||||
if self.logged_in:
|
||||
raise LoggedInException
|
||||
raise LoggedInError
|
||||
|
||||
self.init()
|
||||
response: dict = self.request({"action": "register", "name": username, "password": password})
|
||||
response: dict[str, Any] = self.request({"action": "register", "name": username, "password": password})
|
||||
if "error" in response:
|
||||
self.close()
|
||||
error: str = response["error"]
|
||||
if error == "invalid password":
|
||||
raise WeakPasswordException()
|
||||
raise WeakPasswordError()
|
||||
if error == "username already exists":
|
||||
raise UsernameAlreadyExistsException()
|
||||
raise InvalidServerResponseException(response)
|
||||
if "token" not in response:
|
||||
self.close()
|
||||
raise InvalidServerResponseException(response)
|
||||
self.logged_in: bool = True
|
||||
self.timer.start()
|
||||
return response["token"]
|
||||
raise UsernameAlreadyExistsError()
|
||||
raise InvalidServerResponseError(response)
|
||||
|
||||
def login(self, username: str, password: str) -> str:
|
||||
try:
|
||||
token_response = TokenResponse.parse(self, response)
|
||||
except ValidationError:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
self.logged_in = True
|
||||
cast(Timer, self.timer).start()
|
||||
return token_response
|
||||
|
||||
def login(self, username: str, password: str) -> TokenResponse:
|
||||
if self.logged_in:
|
||||
raise LoggedInException
|
||||
raise LoggedInError
|
||||
|
||||
self.init()
|
||||
response: dict = self.request({"action": "login", "name": username, "password": password})
|
||||
response: dict[str, Any] = self.request({"action": "login", "name": username, "password": password})
|
||||
if "error" in response:
|
||||
self.close()
|
||||
error: str = response["error"]
|
||||
if error == "permissions denied":
|
||||
raise InvalidLoginException()
|
||||
raise InvalidServerResponseException(response)
|
||||
if "token" not in response:
|
||||
self.close()
|
||||
raise InvalidServerResponseException(response)
|
||||
self.logged_in: bool = True
|
||||
self.timer.start()
|
||||
return response["token"]
|
||||
raise InvalidLoginError()
|
||||
raise InvalidServerResponseError(response)
|
||||
|
||||
def session(self, token: str):
|
||||
try:
|
||||
token_response = TokenResponse.parse(self, response)
|
||||
except ValidationError:
|
||||
self.close()
|
||||
raise
|
||||
|
||||
self.logged_in = True
|
||||
cast(Timer, self.timer).start()
|
||||
return token_response
|
||||
|
||||
def session(self, token: str) -> TokenResponse:
|
||||
if self.logged_in:
|
||||
raise LoggedInException
|
||||
raise LoggedInError
|
||||
|
||||
self.init()
|
||||
response: dict = self.request({"action": "session", "token": token})
|
||||
response: dict[str, Any] = self.request({"action": "session", "token": token})
|
||||
if "error" in response:
|
||||
self.close()
|
||||
error: str = response["error"]
|
||||
if error == "invalid token":
|
||||
raise InvalidSessionTokenException()
|
||||
raise InvalidServerResponseException(response)
|
||||
if "token" not in response:
|
||||
raise InvalidSessionTokenError()
|
||||
raise InvalidServerResponseError(response)
|
||||
|
||||
try:
|
||||
token_response = TokenResponse.parse(self, response)
|
||||
except ValidationError:
|
||||
self.close()
|
||||
raise InvalidServerResponseException(response)
|
||||
self.logged_in: bool = True
|
||||
self.timer.start()
|
||||
raise
|
||||
|
||||
def change_password(self, old_password: str, new_password: str) -> str:
|
||||
self.logged_in = True
|
||||
cast(Timer, self.timer).start()
|
||||
return token_response
|
||||
|
||||
def change_password(self, old_password: str, new_password: str) -> TokenResponse:
|
||||
if not self.logged_in:
|
||||
raise LoggedOutException
|
||||
raise LoggedOutError
|
||||
|
||||
response: dict = self.request(
|
||||
{"action": "password", "password": old_password, "new": new_password},
|
||||
)
|
||||
response: dict[str, Any] = self.request({"action": "password", "password": old_password, "new": new_password})
|
||||
if "error" in response:
|
||||
error: str = response["error"]
|
||||
if error == "permissions denied":
|
||||
raise PermissionsDeniedException()
|
||||
raise InvalidServerResponseException(response)
|
||||
if "token" not in response:
|
||||
raise InvalidServerResponseException(response)
|
||||
return response["token"]
|
||||
raise PermissionDeniedError()
|
||||
raise InvalidServerResponseError(response)
|
||||
|
||||
def logout(self):
|
||||
return TokenResponse.parse(self, response)
|
||||
|
||||
def logout(self) -> None:
|
||||
if not self.logged_in:
|
||||
raise LoggedOutException
|
||||
raise LoggedOutError
|
||||
|
||||
self.request({"action": "logout"})
|
||||
self.close()
|
||||
|
||||
def status(self) -> dict:
|
||||
def status(self) -> StatusResponse:
|
||||
if self.logged_in:
|
||||
raise LoggedInException
|
||||
raise LoggedInError
|
||||
|
||||
self.init()
|
||||
response: dict = self.request({"action": "status"})
|
||||
response: dict[str, Any] = self.request({"action": "status"})
|
||||
self.close()
|
||||
if "error" in response:
|
||||
raise InvalidServerResponseException(response)
|
||||
return response
|
||||
raise InvalidServerResponseError(response)
|
||||
return StatusResponse.parse(self, response)
|
||||
|
||||
def info(self) -> dict:
|
||||
def info(self) -> InfoResponse:
|
||||
if not self.logged_in:
|
||||
raise LoggedOutException
|
||||
raise LoggedOutError
|
||||
|
||||
response: dict = self.request({"action": "info"})
|
||||
response: dict[str, Any] = self.request({"action": "info"})
|
||||
if "error" in response:
|
||||
raise InvalidServerResponseException(response)
|
||||
return response
|
||||
raise InvalidServerResponseError(response)
|
||||
return InfoResponse.parse(self, response)
|
||||
|
||||
def delete_user(self):
|
||||
def delete_user(self) -> None:
|
||||
if not self.logged_in:
|
||||
raise LoggedOutException
|
||||
raise LoggedOutError
|
||||
|
||||
self.request({"action": "delete"}, no_response=True)
|
||||
self.close()
|
||||
|
||||
def get_hardware_config(self) -> dict:
|
||||
return self.ms("device", ["hardware", "list"])
|
||||
def get_hardware_config(self) -> HardwareConfig:
|
||||
return HardwareConfig.parse(self, self.ms("device", ["hardware", "list"]))
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from PyCrypCli.commands.command import command, make_commands, commands, Command, CommandError
|
||||
from .command import command, make_commands, commands, Command, CommandError
|
||||
|
||||
|
||||
__all__ = ["command", "make_commands", "commands", "Command", "CommandError"]
|
||||
|
|
|
@ -1,18 +1,14 @@
|
|||
import getpass
|
||||
import sys
|
||||
from typing import Any
|
||||
|
||||
from PyCrypCli.commands import command, CommandError
|
||||
from PyCrypCli.context import LoginContext, MainContext, DeviceContext
|
||||
from PyCrypCli.exceptions import (
|
||||
WeakPasswordException,
|
||||
UsernameAlreadyExistsException,
|
||||
InvalidLoginException,
|
||||
PermissionsDeniedException,
|
||||
)
|
||||
from .command import command, CommandError
|
||||
from ..context import LoginContext, MainContext, DeviceContext
|
||||
from ..exceptions import WeakPasswordError, UsernameAlreadyExistsError, InvalidLoginError, PermissionDeniedError
|
||||
|
||||
|
||||
@command("register", [LoginContext], aliases=["signup"])
|
||||
def register(context: LoginContext, *_):
|
||||
def register(context: LoginContext, _: Any) -> None:
|
||||
"""
|
||||
Create a new account
|
||||
"""
|
||||
|
@ -27,16 +23,16 @@ def register(context: LoginContext, *_):
|
|||
if password != confirm_password:
|
||||
raise CommandError("Passwords don't match.")
|
||||
try:
|
||||
session_token: str = context.client.register(username, password)
|
||||
session_token: str = context.client.register(username, password).token
|
||||
context.open(MainContext(context.root_context, session_token))
|
||||
except WeakPasswordException:
|
||||
except WeakPasswordError:
|
||||
raise CommandError("Password is too weak.")
|
||||
except UsernameAlreadyExistsException:
|
||||
except UsernameAlreadyExistsError:
|
||||
raise CommandError("Username already exists.")
|
||||
|
||||
|
||||
@command("login", [LoginContext])
|
||||
def login(context: LoginContext, *_):
|
||||
def login(context: LoginContext, _: Any) -> None:
|
||||
"""
|
||||
Login with an existing account
|
||||
"""
|
||||
|
@ -48,14 +44,14 @@ def login(context: LoginContext, *_):
|
|||
raise CommandError("\nAborted.")
|
||||
|
||||
try:
|
||||
session_token: str = context.client.login(username, password)
|
||||
session_token: str = context.client.login(username, password).token
|
||||
context.open(MainContext(context.root_context, session_token))
|
||||
except InvalidLoginException:
|
||||
except InvalidLoginError:
|
||||
raise CommandError("Invalid Login Credentials.")
|
||||
|
||||
|
||||
@command("exit", [LoginContext], aliases=["quit"])
|
||||
def handle_login_exit(*_):
|
||||
def handle_login_exit(*_: Any) -> None:
|
||||
"""
|
||||
Exit PyCrypCli
|
||||
"""
|
||||
|
@ -64,7 +60,7 @@ def handle_login_exit(*_):
|
|||
|
||||
|
||||
@command("exit", [MainContext], aliases=["quit"])
|
||||
def handle_main_exit(context: MainContext, *_):
|
||||
def handle_main_exit(context: MainContext, _: Any) -> None:
|
||||
"""
|
||||
Exit PyCrypCli (session will be saved)
|
||||
"""
|
||||
|
@ -74,7 +70,7 @@ def handle_main_exit(context: MainContext, *_):
|
|||
|
||||
|
||||
@command("exit", [DeviceContext], aliases=["quit", "logout"])
|
||||
def handle_device_exit(context: DeviceContext, *_):
|
||||
def handle_device_exit(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
Disconnect from this device
|
||||
"""
|
||||
|
@ -83,7 +79,7 @@ def handle_device_exit(context: DeviceContext, *_):
|
|||
|
||||
|
||||
@command("logout", [MainContext])
|
||||
def handle_main_logout(context: MainContext, *_):
|
||||
def handle_main_logout(context: MainContext, _: Any) -> None:
|
||||
"""
|
||||
Delete the current session and exit PyCrypCli
|
||||
"""
|
||||
|
@ -92,7 +88,7 @@ def handle_main_logout(context: MainContext, *_):
|
|||
|
||||
|
||||
@command("passwd", [MainContext])
|
||||
def handle_passwd(context: MainContext, *_):
|
||||
def handle_passwd(context: MainContext, _: Any) -> None:
|
||||
"""
|
||||
Change your password
|
||||
"""
|
||||
|
@ -105,15 +101,15 @@ def handle_passwd(context: MainContext, *_):
|
|||
raise CommandError("Passwords don't match.")
|
||||
|
||||
try:
|
||||
context.session_token = context.client.change_password(old_password, new_password)
|
||||
context.session_token = context.client.change_password(old_password, new_password).token
|
||||
context.save_session()
|
||||
print("Password updated successfully.")
|
||||
except PermissionsDeniedException:
|
||||
except PermissionDeniedError:
|
||||
raise CommandError("Incorrect password or the new password does not meet the requirements.")
|
||||
|
||||
|
||||
@command("_delete_user", [MainContext])
|
||||
def handle_delete_user(context: MainContext, *_):
|
||||
def handle_delete_user(context: MainContext, _: Any) -> None:
|
||||
"""
|
||||
Delete this account
|
||||
"""
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
from importlib import import_module
|
||||
from typing import Callable, List, Dict, Type, Optional, Tuple
|
||||
from __future__ import annotations
|
||||
|
||||
from PyCrypCli.context import Context, COMMAND_FUNCTION, COMPLETER_FUNCTION
|
||||
from PyCrypCli.exceptions import CommandRegistrationException, NoDocStringException
|
||||
from importlib import import_module
|
||||
from typing import Callable, Type
|
||||
|
||||
from ..context import Context, COMMAND_FUNCTION, COMPLETER_FUNCTION, ContextType
|
||||
from ..exceptions import CommandRegistrationError, NoDocStringError
|
||||
|
||||
|
||||
class CommandError(Exception):
|
||||
|
@ -15,40 +17,40 @@ class Command:
|
|||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
func: COMMAND_FUNCTION,
|
||||
func: COMMAND_FUNCTION[ContextType],
|
||||
description: str,
|
||||
contexts: List[Type[Context]],
|
||||
aliases: List[str],
|
||||
contexts: list[Type[Context]],
|
||||
aliases: list[str],
|
||||
):
|
||||
self.name: str = name
|
||||
self.func: COMMAND_FUNCTION = func
|
||||
self.func: COMMAND_FUNCTION[ContextType] = func
|
||||
self.description: str = description
|
||||
self.contexts: List[Type[Context]] = contexts
|
||||
self.aliases: List[str] = aliases
|
||||
self.completer_func: Optional[COMPLETER_FUNCTION] = None
|
||||
self.subcommands: List[Command] = []
|
||||
self.prepared_subcommands: Dict[Type[Context], Dict[str, Command]] = {}
|
||||
self.contexts: list[Type[Context]] = contexts
|
||||
self.aliases: list[str] = aliases
|
||||
self.completer_func: COMPLETER_FUNCTION[ContextType] | None = None
|
||||
self.subcommands: list[Command] = []
|
||||
self.prepared_subcommands: dict[Type[Context], dict[str, Command]] = {}
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.func(*args, **kwargs)
|
||||
def __call__(self, context: ContextType, args: list[str]) -> None:
|
||||
self.func(context, args)
|
||||
|
||||
def parse_command(self, context: Context, args: List[str]) -> Tuple["Command", List[str]]:
|
||||
def parse_command(self, context: Context, args: list[str]) -> tuple[Command, list[str]]:
|
||||
prepared_subcommands = self.prepared_subcommands.get(type(context), {})
|
||||
if args:
|
||||
cmd: Optional[Command] = prepared_subcommands.get(args[0])
|
||||
cmd: Command | None = prepared_subcommands.get(args[0])
|
||||
if cmd is not None:
|
||||
return cmd.parse_command(context, args[1:])
|
||||
|
||||
return self, args
|
||||
|
||||
def handle(self, context: Context, args: List[str]):
|
||||
def handle(self, context: ContextType, args: list[str]) -> None:
|
||||
cmd, args = self.parse_command(context, args)
|
||||
try:
|
||||
cmd.func(context, args)
|
||||
except CommandError as error:
|
||||
print(error.msg)
|
||||
|
||||
def handle_completer(self, context: Context, args: List[str]) -> List[str]:
|
||||
def handle_completer(self, context: ContextType, args: list[str]) -> list[str]:
|
||||
cmd, args = self.parse_command(context, args)
|
||||
|
||||
out = list(cmd.prepared_subcommands.get(type(context), {}))
|
||||
|
@ -57,55 +59,46 @@ class Command:
|
|||
|
||||
return out
|
||||
|
||||
def completer(self):
|
||||
def decorator(func: COMPLETER_FUNCTION) -> COMPLETER_FUNCTION:
|
||||
def completer(self) -> Callable[[COMPLETER_FUNCTION[ContextType]], COMPLETER_FUNCTION[ContextType]]:
|
||||
def decorator(func: COMPLETER_FUNCTION[ContextType]) -> COMPLETER_FUNCTION[ContextType]:
|
||||
self.completer_func = func
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
def subcommand(
|
||||
self,
|
||||
name: str,
|
||||
*,
|
||||
contexts: List[Type[Context]] = None,
|
||||
aliases: List[str] = None,
|
||||
) -> Callable[[COMMAND_FUNCTION], "Command"]:
|
||||
if contexts is None:
|
||||
contexts = self.contexts
|
||||
|
||||
def decorator(func: COMMAND_FUNCTION) -> Command:
|
||||
self, name: str, *, contexts: list[Type[Context]] | None = None, aliases: list[str] | None = None
|
||||
) -> Callable[[COMMAND_FUNCTION[ContextType]], "Command"]:
|
||||
def decorator(func: COMMAND_FUNCTION[ContextType]) -> Command:
|
||||
if func.__doc__ is None:
|
||||
raise NoDocStringException(name, subcommand=True)
|
||||
raise NoDocStringError(name, subcommand=True)
|
||||
desc: str = func.__doc__
|
||||
desc = "\n".join(map(str.strip, desc.splitlines())).strip()
|
||||
cmd = Command(name, func, desc, contexts, aliases or [])
|
||||
cmd = Command(name, func, desc, contexts or self.contexts, aliases or [])
|
||||
self.subcommands.append(cmd)
|
||||
return cmd
|
||||
|
||||
return decorator
|
||||
|
||||
def make_subcommands(self):
|
||||
def make_subcommands(self) -> None:
|
||||
for cmd in self.subcommands:
|
||||
for context in cmd.contexts:
|
||||
for name in [cmd.name] + cmd.aliases:
|
||||
if name in self.prepared_subcommands.setdefault(context, {}):
|
||||
raise CommandRegistrationException(name, subcommand=True)
|
||||
raise CommandRegistrationError(name, subcommand=True)
|
||||
self.prepared_subcommands[context][name] = cmd
|
||||
cmd.make_subcommands()
|
||||
|
||||
|
||||
commands: List[Command] = []
|
||||
commands: list[Command] = []
|
||||
|
||||
|
||||
def command(
|
||||
name: str,
|
||||
contexts: List[Type[Context]],
|
||||
aliases: List[str] = None,
|
||||
) -> Callable[[COMMAND_FUNCTION], Command]:
|
||||
def decorator(func: COMMAND_FUNCTION) -> Command:
|
||||
name: str, contexts: list[Type[Context]], aliases: list[str] | None = None
|
||||
) -> Callable[[COMMAND_FUNCTION[ContextType]], Command]:
|
||||
def decorator(func: COMMAND_FUNCTION[ContextType]) -> Command:
|
||||
if func.__doc__ is None:
|
||||
raise NoDocStringException(name)
|
||||
raise NoDocStringError(name)
|
||||
desc: str = func.__doc__
|
||||
desc = "\n".join(map(str.strip, desc.splitlines())).strip()
|
||||
cmd = Command(name, func, desc, contexts, aliases or [])
|
||||
|
@ -115,7 +108,7 @@ def command(
|
|||
return decorator
|
||||
|
||||
|
||||
def make_commands() -> Dict[Type[Context], Dict[str, Command]]:
|
||||
def make_commands() -> dict[Type[Context], dict[str, Command]]:
|
||||
for module in [
|
||||
"account",
|
||||
"help",
|
||||
|
@ -132,12 +125,12 @@ def make_commands() -> Dict[Type[Context], Dict[str, Command]]:
|
|||
]:
|
||||
import_module(f"PyCrypCli.commands.{module}")
|
||||
|
||||
result: Dict[Type[Context], Dict[str, Command]] = {}
|
||||
result: dict[Type[Context], dict[str, Command]] = {}
|
||||
for cmd in commands:
|
||||
for context in cmd.contexts:
|
||||
for name in [cmd.name] + cmd.aliases:
|
||||
if name in result.setdefault(context, {}):
|
||||
raise CommandRegistrationException(name)
|
||||
raise CommandRegistrationError(name)
|
||||
result[context][name] = cmd
|
||||
cmd.make_subcommands()
|
||||
return result
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
from typing import List, Dict, Optional
|
||||
from typing import Any, cast
|
||||
|
||||
from PyCrypCli.commands import command, CommandError
|
||||
from PyCrypCli.commands.help import print_help
|
||||
from PyCrypCli.context import MainContext, DeviceContext
|
||||
from PyCrypCli.exceptions import (
|
||||
AlreadyOwnADeviceException,
|
||||
DeviceNotFoundException,
|
||||
IncompatibleCPUSocket,
|
||||
NotEnoughRAMSlots,
|
||||
IncompatibleRAMTypes,
|
||||
IncompatibleDriverInterface,
|
||||
from .command import command, CommandError
|
||||
from .help import print_help
|
||||
from ..context import MainContext, DeviceContext
|
||||
from ..exceptions import (
|
||||
AlreadyOwnADeviceError,
|
||||
DeviceNotFoundError,
|
||||
IncompatibleCPUSocketError,
|
||||
NotEnoughRAMSlotsError,
|
||||
IncompatibleRAMTypesError,
|
||||
IncompatibleDriverInterfaceError,
|
||||
)
|
||||
from PyCrypCli.game_objects import Device, ResourceUsage, DeviceHardware, InventoryElement
|
||||
from PyCrypCli.util import is_uuid
|
||||
from ..models import Device, ResourceUsage, DeviceHardware, InventoryElement, HardwareConfig
|
||||
from ..util import is_uuid
|
||||
|
||||
|
||||
def get_device(context: MainContext, name_or_uuid: str, devices: Optional[List[Device]] = None) -> Device:
|
||||
def get_device(context: MainContext, name_or_uuid: str, devices: list[Device] | None = None) -> Device:
|
||||
if is_uuid(name_or_uuid):
|
||||
try:
|
||||
return Device.get_device(context.client, name_or_uuid)
|
||||
except DeviceNotFoundException:
|
||||
except DeviceNotFoundError:
|
||||
raise CommandError(f"There is no device with the uuid '{name_or_uuid}'.")
|
||||
else:
|
||||
found_devices: List[Device] = []
|
||||
found_devices: list[Device] = []
|
||||
for device in devices or Device.list_devices(context.client):
|
||||
if device.name == name_or_uuid:
|
||||
found_devices.append(device)
|
||||
|
@ -30,13 +30,13 @@ def get_device(context: MainContext, name_or_uuid: str, devices: Optional[List[D
|
|||
raise CommandError(f"There is no device with the name '{name_or_uuid}'.")
|
||||
if len(found_devices) > 1:
|
||||
raise CommandError(
|
||||
f"There is more than one device with the name '{name_or_uuid}'. You need to specify its UUID.",
|
||||
f"There is more than one device with the name '{name_or_uuid}'. You need to specify its UUID."
|
||||
)
|
||||
return found_devices[0]
|
||||
|
||||
|
||||
@command("device", [MainContext, DeviceContext])
|
||||
def handle_device(context: MainContext, args: List[str]):
|
||||
def handle_device(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
Manage your devices
|
||||
"""
|
||||
|
@ -47,7 +47,7 @@ def handle_device(context: MainContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_device.subcommand("list")
|
||||
def handle_device_list(context: MainContext, args: List[str]):
|
||||
def handle_device_list(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
List your devices
|
||||
"""
|
||||
|
@ -55,7 +55,7 @@ def handle_device_list(context: MainContext, args: List[str]):
|
|||
if len(args) != 0:
|
||||
raise CommandError("usage: device list")
|
||||
|
||||
devices: List[Device] = Device.list_devices(context.client)
|
||||
devices: list[Device] = Device.list_devices(context.client)
|
||||
if not devices:
|
||||
print("You don't have any devices.")
|
||||
else:
|
||||
|
@ -65,7 +65,7 @@ def handle_device_list(context: MainContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_device.subcommand("create")
|
||||
def handle_device_create(context: MainContext, args: List[str]):
|
||||
def handle_device_create(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
Create your starter device
|
||||
"""
|
||||
|
@ -75,7 +75,7 @@ def handle_device_create(context: MainContext, args: List[str]):
|
|||
|
||||
try:
|
||||
device: Device = Device.starter_device(context.client)
|
||||
except AlreadyOwnADeviceException:
|
||||
except AlreadyOwnADeviceError:
|
||||
raise CommandError("You already own a device.")
|
||||
|
||||
print("Your device has been created!")
|
||||
|
@ -83,7 +83,7 @@ def handle_device_create(context: MainContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_device.subcommand("build")
|
||||
def handle_device_build(context: MainContext, args: List[str]):
|
||||
def handle_device_build(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
Build a new device
|
||||
"""
|
||||
|
@ -91,42 +91,42 @@ def handle_device_build(context: MainContext, args: List[str]):
|
|||
if len(args) < 5:
|
||||
raise CommandError("usage: device build <mainboard> <cpu> <gpu> <ram> [<ram>...] <disk> [<disk>...]")
|
||||
|
||||
hardware: dict = context.client.get_hardware_config()
|
||||
hardware: HardwareConfig = context.client.get_hardware_config()
|
||||
mainboard, cpu, gpu, *ram_and_disk = args
|
||||
ram: List[str] = []
|
||||
disk: List[str] = []
|
||||
ram: list[str] = []
|
||||
disk: list[str] = []
|
||||
|
||||
for e in hardware["mainboards"]:
|
||||
for e in hardware.mainboard:
|
||||
if e.replace(" ", "") == mainboard:
|
||||
mainboard: str = e
|
||||
mainboard = e
|
||||
break
|
||||
else:
|
||||
print(f"'{mainboard}' is no mainboard.")
|
||||
return
|
||||
|
||||
for e in hardware["cpu"]:
|
||||
for e in hardware.cpu:
|
||||
if e.replace(" ", "") == cpu:
|
||||
cpu: str = e
|
||||
cpu = e
|
||||
break
|
||||
else:
|
||||
print(f"'{cpu}' is no cpu.")
|
||||
return
|
||||
|
||||
for e in hardware["gpu"]:
|
||||
for e in hardware.gpu:
|
||||
if e.replace(" ", "") == gpu:
|
||||
gpu: str = e
|
||||
gpu = e
|
||||
break
|
||||
else:
|
||||
print(f"'{gpu}' is no gpu.")
|
||||
return
|
||||
|
||||
for element in ram_and_disk:
|
||||
for e in hardware["ram"]:
|
||||
for e in hardware.ram:
|
||||
if e.replace(" ", "") == element:
|
||||
ram.append(e)
|
||||
break
|
||||
else:
|
||||
for e in hardware["disk"]:
|
||||
for e in hardware.disk:
|
||||
if e.replace(" ", "") == element:
|
||||
disk.append(e)
|
||||
break
|
||||
|
@ -139,26 +139,26 @@ def handle_device_build(context: MainContext, args: List[str]):
|
|||
if not disk:
|
||||
raise CommandError("You have to chose at least one hard drive.")
|
||||
|
||||
inventory: List[str] = [e.name for e in InventoryElement.list_inventory(context.client)]
|
||||
inventory_complete: bool = True
|
||||
inventory: list[str] = [e.name for e in InventoryElement.list_inventory(context.client)]
|
||||
inventory_complete = True
|
||||
for element in [mainboard, cpu, gpu] + ram + disk:
|
||||
if element in inventory:
|
||||
inventory.remove(element)
|
||||
else:
|
||||
print(f"'{element}' could not be found in your inventory.")
|
||||
inventory_complete: bool = False
|
||||
inventory_complete = False
|
||||
if not inventory_complete:
|
||||
return
|
||||
|
||||
try:
|
||||
device: Device = Device.build(context.client, mainboard, cpu, gpu, ram, disk)
|
||||
except IncompatibleCPUSocket:
|
||||
except IncompatibleCPUSocketError:
|
||||
raise CommandError("The mainboard socket is not compatible with the cpu.")
|
||||
except NotEnoughRAMSlots:
|
||||
except NotEnoughRAMSlotsError:
|
||||
raise CommandError("The mainboard has not enough ram slots.")
|
||||
except IncompatibleRAMTypes:
|
||||
except IncompatibleRAMTypesError:
|
||||
raise CommandError("A ram type is incompatible with the mainboard.")
|
||||
except IncompatibleDriverInterface:
|
||||
except IncompatibleDriverInterfaceError:
|
||||
raise CommandError("The drive interface is not compatible with the mainboard.")
|
||||
else:
|
||||
print("Your device has been created!")
|
||||
|
@ -166,7 +166,7 @@ def handle_device_build(context: MainContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_device.subcommand("boot", aliases=["start"])
|
||||
def handle_device_boot(context: MainContext, args: List[str]):
|
||||
def handle_device_boot(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
Boot a device
|
||||
"""
|
||||
|
@ -182,7 +182,7 @@ def handle_device_boot(context: MainContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_device.subcommand("shutdown", aliases=["poweroff", "halt"])
|
||||
def handle_device_shutdown(context: MainContext, args: List[str]):
|
||||
def handle_device_shutdown(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
Shut down a device
|
||||
"""
|
||||
|
@ -200,7 +200,7 @@ def handle_device_shutdown(context: MainContext, args: List[str]):
|
|||
|
||||
|
||||
@command("shutdown", [DeviceContext], ["poweroff", "halt"])
|
||||
def handle_shutdown(context: DeviceContext, _):
|
||||
def handle_shutdown(context: DeviceContext, _: Any) -> None:
|
||||
"""Shutdown this device"""
|
||||
|
||||
device: Device = context.host
|
||||
|
@ -212,7 +212,7 @@ def handle_shutdown(context: DeviceContext, _):
|
|||
|
||||
|
||||
@handle_device.subcommand("connect")
|
||||
def handle_device_connect(context: MainContext, args: List[str]):
|
||||
def handle_device_connect(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
Connect to one of your devices
|
||||
"""
|
||||
|
@ -226,11 +226,11 @@ def handle_device_connect(context: MainContext, args: List[str]):
|
|||
return
|
||||
device.power()
|
||||
|
||||
context.open(DeviceContext(context.root_context, context.session_token, device))
|
||||
context.open(DeviceContext(context.root_context, cast(str, context.session_token), device))
|
||||
|
||||
|
||||
@handle_device.subcommand("delete")
|
||||
def handle_device_delete(context: MainContext, args: List[str]):
|
||||
def handle_device_delete(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
Delete a device
|
||||
"""
|
||||
|
@ -248,31 +248,31 @@ def handle_device_delete(context: MainContext, args: List[str]):
|
|||
@handle_device_shutdown.completer()
|
||||
@handle_device_connect.completer()
|
||||
@handle_device_delete.completer()
|
||||
def complete_device(context: MainContext, args: List[str]) -> List[str]:
|
||||
def complete_device(context: MainContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
device_names: List[str] = [device.name for device in Device.list_devices(context.client)]
|
||||
device_names: list[str] = [device.name for device in Device.list_devices(context.client)]
|
||||
return [name for name in device_names if device_names.count(name) == 1]
|
||||
return []
|
||||
|
||||
|
||||
@handle_device_build.completer()
|
||||
def complete_build(context: MainContext, args: List[str]) -> List[str]:
|
||||
def complete_build(context: MainContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return [name.replace(" ", "") for name in list(context.client.get_hardware_config()["mainboards"])]
|
||||
return [name.replace(" ", "") for name in list(context.client.get_hardware_config().mainboard)]
|
||||
if len(args) == 2:
|
||||
return [name.replace(" ", "") for name in list(context.client.get_hardware_config()["cpu"])]
|
||||
return [name.replace(" ", "") for name in list(context.client.get_hardware_config().cpu)]
|
||||
if len(args) == 3:
|
||||
return [name.replace(" ", "") for name in list(context.client.get_hardware_config()["gpu"])]
|
||||
return [name.replace(" ", "") for name in list(context.client.get_hardware_config().gpu)]
|
||||
if len(args) == 4:
|
||||
return [name.replace(" ", "") for name in list(context.client.get_hardware_config()["ram"])]
|
||||
return [name.replace(" ", "") for name in list(context.client.get_hardware_config().ram)]
|
||||
if len(args) >= 5:
|
||||
hardware: dict = context.client.get_hardware_config()
|
||||
return [name.replace(" ", "") for name in list(hardware["ram"]) + list(hardware["disk"])]
|
||||
hardware: HardwareConfig = context.client.get_hardware_config()
|
||||
return [name.replace(" ", "") for name in list(hardware.ram) + list(hardware.disk)]
|
||||
return []
|
||||
|
||||
|
||||
@command("hostname", [DeviceContext])
|
||||
def handle_hostname(context: DeviceContext, args: List[str]):
|
||||
def handle_hostname(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Show or modify the name of the device
|
||||
"""
|
||||
|
@ -289,7 +289,7 @@ def handle_hostname(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@command("top", [DeviceContext])
|
||||
def handle_top(context: DeviceContext, *_):
|
||||
def handle_top(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
Display the current resource usage of this device
|
||||
"""
|
||||
|
@ -297,7 +297,7 @@ def handle_top(context: DeviceContext, *_):
|
|||
print(f"Resource usage of '{context.host.name}':")
|
||||
print()
|
||||
resource_usage: ResourceUsage = context.host.get_resource_usage()
|
||||
hardware: Dict[str, DeviceHardware] = {dh.hardware_type: dh for dh in context.host.get_hardware()}
|
||||
hardware: dict[str, DeviceHardware] = {dh.hardware_type: dh for dh in context.host.get_hardware()}
|
||||
|
||||
print(f" Mainboard: {hardware['mainboard'].hardware_element}")
|
||||
print()
|
||||
|
|
|
@ -1,43 +1,39 @@
|
|||
from typing import List, Optional, Tuple
|
||||
from typing import Any, cast
|
||||
|
||||
from PyCrypCli.exceptions import (
|
||||
FileAlreadyExistsException,
|
||||
InvalidWalletFile,
|
||||
UnknownSourceOrDestinationException,
|
||||
PermissionDeniedException,
|
||||
FileNotChangeableException,
|
||||
from .command import command, CommandError
|
||||
from ..context import DeviceContext
|
||||
from ..exceptions import (
|
||||
FileAlreadyExistsError,
|
||||
InvalidWalletFileError,
|
||||
UnknownSourceOrDestinationError,
|
||||
PermissionDeniedError,
|
||||
FileNotChangeableError,
|
||||
)
|
||||
|
||||
from PyCrypCli.commands import command, CommandError
|
||||
from PyCrypCli.context import DeviceContext
|
||||
from PyCrypCli.game_objects import File, Wallet
|
||||
from ..models import File, Wallet
|
||||
|
||||
|
||||
@command("ls", [DeviceContext], aliases=["l", "dir"])
|
||||
def handle_ls(context: DeviceContext, args: List[str]):
|
||||
def handle_ls(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
List all files
|
||||
"""
|
||||
|
||||
if not args:
|
||||
directory: File = context.pwd
|
||||
else:
|
||||
directory: File = context.path_to_file(args[0])
|
||||
if directory is None:
|
||||
raise CommandError("No such file or directory.")
|
||||
directory = context.pwd if not args else context.path_to_file(args[0])
|
||||
if directory is None:
|
||||
raise CommandError("No such file or directory.")
|
||||
|
||||
if directory.is_directory:
|
||||
files: List[File] = context.get_files(directory.uuid)
|
||||
files.sort(key=lambda f: [1 - f.is_directory, f.filename])
|
||||
files: list[File] = context.get_files(directory.uuid)
|
||||
files.sort(key=lambda f: [1 - f.is_directory, f.name])
|
||||
else:
|
||||
files: List[File] = [directory]
|
||||
files = [directory]
|
||||
|
||||
for file in files:
|
||||
print(["[FILE] ", "[DIR] "][file.is_directory] + file.filename)
|
||||
print(["[FILE] ", "[DIR] "][file.is_directory] + file.name)
|
||||
|
||||
|
||||
@command("pwd", [DeviceContext])
|
||||
def handle_pwd(context: DeviceContext, *_):
|
||||
def handle_pwd(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
Print the current working directory
|
||||
"""
|
||||
|
@ -46,7 +42,7 @@ def handle_pwd(context: DeviceContext, *_):
|
|||
|
||||
|
||||
@command("mkdir", [DeviceContext])
|
||||
def handle_mkdir(context: DeviceContext, args: List[str]):
|
||||
def handle_mkdir(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Create a new directory
|
||||
"""
|
||||
|
@ -55,7 +51,7 @@ def handle_mkdir(context: DeviceContext, args: List[str]):
|
|||
raise CommandError("usage: mkdir <dirname>")
|
||||
|
||||
*path, dirname = args[0].split("/")
|
||||
parent: Optional[File] = context.path_to_file("/".join(path))
|
||||
parent: File | None = context.path_to_file("/".join(path))
|
||||
if parent is None:
|
||||
raise CommandError("No such file or directory.")
|
||||
if not parent.is_directory:
|
||||
|
@ -63,12 +59,12 @@ def handle_mkdir(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
context.host.create_file(dirname, "", True, parent.uuid)
|
||||
except FileAlreadyExistsException:
|
||||
except FileAlreadyExistsError:
|
||||
raise CommandError("There already exists a file with this name.")
|
||||
|
||||
|
||||
@command("cd", [DeviceContext])
|
||||
def handle_cd(context: DeviceContext, args: List[str]):
|
||||
def handle_cd(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Change the current working directory
|
||||
"""
|
||||
|
@ -76,7 +72,7 @@ def handle_cd(context: DeviceContext, args: List[str]):
|
|||
if not args:
|
||||
context.pwd = context.get_root_dir()
|
||||
else:
|
||||
directory: Optional[File] = context.path_to_file(args[0])
|
||||
directory: File | None = context.path_to_file(args[0])
|
||||
if directory is None:
|
||||
raise CommandError("The specified directory does not exist")
|
||||
if not directory.is_directory:
|
||||
|
@ -86,7 +82,7 @@ def handle_cd(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@command("..", [DeviceContext])
|
||||
def handle_dot_dot(context: DeviceContext, _):
|
||||
def handle_dot_dot(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
Go to parent directory
|
||||
"""
|
||||
|
@ -94,16 +90,18 @@ def handle_dot_dot(context: DeviceContext, _):
|
|||
handle_cd(context, [".."])
|
||||
|
||||
|
||||
def create_file(context: DeviceContext, filepath: str, content: str):
|
||||
def create_file(context: DeviceContext, filepath: str, content: str) -> None:
|
||||
*path, filename = filepath.split("/")
|
||||
parent: Optional[File] = context.path_to_file("/".join(path))
|
||||
parent: File | None = context.path_to_file("/".join(path))
|
||||
if not parent:
|
||||
raise CommandError("Parent directory does not exist.")
|
||||
|
||||
if not filename:
|
||||
raise CommandError("Filename cannot be empty.")
|
||||
if len(filename) > 64:
|
||||
raise CommandError("Filename cannot be longer than 64 characters.")
|
||||
|
||||
file: File = context.get_file(filename, parent.uuid)
|
||||
file: File | None = context.get_file(filename, parent.uuid)
|
||||
if file is not None:
|
||||
if file.is_directory:
|
||||
raise CommandError("A directory with this name already exists.")
|
||||
|
@ -113,7 +111,7 @@ def create_file(context: DeviceContext, filepath: str, content: str):
|
|||
|
||||
|
||||
@command("touch", [DeviceContext])
|
||||
def handle_touch(context: DeviceContext, args: List[str]):
|
||||
def handle_touch(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Create a new file with given content
|
||||
"""
|
||||
|
@ -126,7 +124,7 @@ def handle_touch(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@command("cat", [DeviceContext])
|
||||
def handle_cat(context: DeviceContext, args: List[str]):
|
||||
def handle_cat(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Print the content of a file
|
||||
"""
|
||||
|
@ -135,7 +133,7 @@ def handle_cat(context: DeviceContext, args: List[str]):
|
|||
raise CommandError("usage: cat <filepath>")
|
||||
|
||||
path: str = args[0]
|
||||
file: File = context.path_to_file(path)
|
||||
file: File | None = context.path_to_file(path)
|
||||
if file is None:
|
||||
raise CommandError("File does not exist.")
|
||||
if file.is_directory:
|
||||
|
@ -145,7 +143,7 @@ def handle_cat(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@command("rm", [DeviceContext])
|
||||
def handle_rm(context: DeviceContext, args: List[str]):
|
||||
def handle_rm(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Remove a file
|
||||
"""
|
||||
|
@ -154,7 +152,7 @@ def handle_rm(context: DeviceContext, args: List[str]):
|
|||
raise CommandError("usage: rm <filepath>")
|
||||
|
||||
filepath: str = args[0]
|
||||
file: File = context.path_to_file(filepath)
|
||||
file: File | None = context.path_to_file(filepath)
|
||||
if file is None:
|
||||
raise CommandError("File does not exist.")
|
||||
|
||||
|
@ -169,7 +167,7 @@ def handle_rm(context: DeviceContext, args: List[str]):
|
|||
|
||||
question: str = f"Are you sure you want to delete the directory `{filepath}` including all contained files?"
|
||||
else:
|
||||
question: str = f"Are you sure you want to delete this file `{filepath}`?"
|
||||
question = f"Are you sure you want to delete this file `{filepath}`?"
|
||||
if context.ask(question + " [yes|no] ", ["yes", "no"]) == "no":
|
||||
raise CommandError("File has not been deleted.")
|
||||
|
||||
|
@ -187,29 +185,27 @@ def handle_rm(context: DeviceContext, args: List[str]):
|
|||
else:
|
||||
print("The following key might now be the only way to access your wallet.")
|
||||
print(content)
|
||||
except (InvalidWalletFile, UnknownSourceOrDestinationException, PermissionDeniedException):
|
||||
except (InvalidWalletFileError, UnknownSourceOrDestinationError, PermissionDeniedError):
|
||||
pass
|
||||
|
||||
try:
|
||||
file.delete()
|
||||
except FileNotChangeableException:
|
||||
except FileNotChangeableError:
|
||||
raise CommandError("Some files could not be deleted.")
|
||||
|
||||
|
||||
def check_file_movable(
|
||||
context: DeviceContext,
|
||||
source: str,
|
||||
destination: str,
|
||||
move: bool,
|
||||
) -> Optional[Tuple[File, str, str]]:
|
||||
file: Optional[File] = context.path_to_file(source)
|
||||
context: DeviceContext, source: str, destination: str, move: bool
|
||||
) -> tuple[File, str, str | None] | None:
|
||||
file: File | None = context.path_to_file(source)
|
||||
if file is None:
|
||||
raise CommandError("File does not exist.")
|
||||
|
||||
dest_file: Optional[File] = context.path_to_file(destination)
|
||||
dest_file: File | None = context.path_to_file(destination)
|
||||
absolute = destination[0] == "/"
|
||||
dest_parent_path, _, dest_name = destination[absolute:].rpartition("/")
|
||||
dest_parent: Optional[File] = context.path_to_file("/" * absolute + dest_parent_path)
|
||||
dest_parent: File | None = context.path_to_file("/" * absolute + dest_parent_path)
|
||||
dest_dir: str | None
|
||||
|
||||
if file.is_directory:
|
||||
if dest_file is None:
|
||||
|
@ -217,9 +213,9 @@ def check_file_movable(
|
|||
raise CommandError("No such file or directory.")
|
||||
if not dest_parent.is_directory:
|
||||
raise CommandError("Not a directory.")
|
||||
dest_dir: str = dest_parent.uuid
|
||||
dest_dir = dest_parent.uuid
|
||||
elif dest_file.is_directory:
|
||||
sub_file: Optional[File] = context.get_file(dest_name, dest_file.uuid)
|
||||
sub_file: File | None = context.get_file(dest_name, dest_file.uuid)
|
||||
if sub_file is not None:
|
||||
if sub_file.is_directory:
|
||||
if context.get_files(sub_file.uuid):
|
||||
|
@ -227,8 +223,8 @@ def check_file_movable(
|
|||
sub_file.delete()
|
||||
else:
|
||||
raise CommandError("Directory cannot replace a file.")
|
||||
dest_name: str = file.filename
|
||||
dest_dir: str = dest_file.uuid
|
||||
dest_name = file.name
|
||||
dest_dir = dest_file.uuid
|
||||
else:
|
||||
raise CommandError("Directory cannot replace a file.")
|
||||
else:
|
||||
|
@ -237,29 +233,29 @@ def check_file_movable(
|
|||
raise CommandError("No such file or directory.")
|
||||
if not dest_parent.is_directory:
|
||||
raise CommandError("Not a directory.")
|
||||
dest_dir: str = dest_parent.uuid
|
||||
dest_dir = dest_parent.uuid
|
||||
elif dest_file.is_directory:
|
||||
sub_file: Optional[File] = context.get_file(dest_name, dest_file.uuid)
|
||||
sub_file = context.get_file(dest_name, dest_file.uuid)
|
||||
if sub_file is not None:
|
||||
if sub_file.is_directory:
|
||||
raise CommandError("File cannot replace a directory.")
|
||||
sub_file.delete()
|
||||
dest_name: str = file.filename
|
||||
dest_dir: str = dest_file.uuid
|
||||
dest_name = file.name
|
||||
dest_dir = dest_file.uuid
|
||||
else:
|
||||
dest_file.delete()
|
||||
dest_dir: str = dest_parent.uuid
|
||||
dest_dir = cast(File, dest_parent).uuid
|
||||
|
||||
if dest_dir == file.parent_dir_uuid and dest_name == file.filename:
|
||||
if dest_dir == file.parent_dir_uuid and dest_name == file.name:
|
||||
return None
|
||||
|
||||
if dest_dir is not None:
|
||||
dir_to_check: File = File.get_file(context.client, file.device, dest_dir)
|
||||
dir_to_check: File | None = File.get_file(context.client, file.device_uuid, dest_dir)
|
||||
while True:
|
||||
if not dir_to_check:
|
||||
break
|
||||
if dir_to_check.uuid == file.uuid:
|
||||
raise CommandError(f"You cannot {['copy', 'move'][move]} a directory into itself.")
|
||||
if dir_to_check.uuid is None:
|
||||
break
|
||||
dir_to_check = context.get_parent_dir(dir_to_check)
|
||||
|
||||
if not dest_name:
|
||||
|
@ -271,7 +267,7 @@ def check_file_movable(
|
|||
|
||||
|
||||
@command("cp", [DeviceContext])
|
||||
def handle_cp(context: DeviceContext, args: List[str]):
|
||||
def handle_cp(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Create a copy of a file
|
||||
"""
|
||||
|
@ -279,22 +275,22 @@ def handle_cp(context: DeviceContext, args: List[str]):
|
|||
if len(args) != 2:
|
||||
raise CommandError("usage: cp <source> <destination>")
|
||||
|
||||
result: Optional[Tuple[File, str, str]] = check_file_movable(context, args[0], args[1], move=False)
|
||||
result: tuple[File, str, str | None] | None = check_file_movable(context, args[0], args[1], move=False)
|
||||
if result is None:
|
||||
return
|
||||
|
||||
file, dest_name, dest_dir = result
|
||||
queue: List[Tuple[File, str, str]] = [(file, dest_name, dest_dir)]
|
||||
queue: list[tuple[File, str, str | None]] = [(file, dest_name, dest_dir)]
|
||||
while queue:
|
||||
file, dest_name, dest_dir = queue.pop(0)
|
||||
new_file: File = context.host.create_file(dest_name, file.content, file.is_directory, dest_dir)
|
||||
if file.is_directory:
|
||||
for child in context.get_files(file.uuid):
|
||||
queue.append((child, child.filename, new_file.uuid))
|
||||
queue.append((child, child.name, cast(str, new_file.uuid)))
|
||||
|
||||
|
||||
@command("mv", [DeviceContext])
|
||||
def handle_mv(context: DeviceContext, args: List[str]):
|
||||
def handle_mv(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Rename a file
|
||||
"""
|
||||
|
@ -302,7 +298,7 @@ def handle_mv(context: DeviceContext, args: List[str]):
|
|||
if len(args) != 2:
|
||||
raise CommandError("usage: mv <source> <destination>")
|
||||
|
||||
result: Optional[Tuple[File, str, str]] = check_file_movable(context, args[0], args[1], move=True)
|
||||
result: tuple[File, str, str | None] | None = check_file_movable(context, args[0], args[1], move=True)
|
||||
if result is None:
|
||||
return
|
||||
|
||||
|
@ -314,7 +310,7 @@ def handle_mv(context: DeviceContext, args: List[str]):
|
|||
@handle_cat.completer()
|
||||
@handle_touch.completer()
|
||||
@handle_rm.completer()
|
||||
def simple_file_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def simple_file_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return context.file_path_completer(args[0])
|
||||
return []
|
||||
|
@ -322,7 +318,7 @@ def simple_file_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
|||
|
||||
@handle_cd.completer()
|
||||
@handle_mkdir.completer()
|
||||
def simple_directory_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def simple_directory_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return context.file_path_completer(args[0], dirs_only=True)
|
||||
return []
|
||||
|
@ -330,7 +326,7 @@ def simple_directory_completer(context: DeviceContext, args: List[str]) -> List[
|
|||
|
||||
@handle_mv.completer()
|
||||
@handle_cp.completer()
|
||||
def copy_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def copy_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if 1 <= len(args) <= 2:
|
||||
return context.file_path_completer(args[-1])
|
||||
return []
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
from typing import List, Optional, Dict, Tuple
|
||||
|
||||
from PyCrypCli.commands import command, Command, CommandError
|
||||
from PyCrypCli.context import LoginContext, MainContext, DeviceContext, Context
|
||||
from .command import command, Command, CommandError
|
||||
from ..context import LoginContext, MainContext, DeviceContext, Context
|
||||
|
||||
|
||||
def print_help(context: Context, cmd: Optional[Command]):
|
||||
def print_help(context: Context, cmd: Command | None) -> None:
|
||||
if cmd is None:
|
||||
cmds: Dict[str, Command] = {c.name: c for c in context.get_commands().values()}
|
||||
cmds: dict[str, Command] = {c.name: c for c in context.get_commands().values()}
|
||||
else:
|
||||
print(cmd.description)
|
||||
cmds: Dict[str, Command] = {c.name: c for c in cmd.prepared_subcommands.get(type(context), {}).values()}
|
||||
command_list: List[Tuple[str, str]] = [
|
||||
cmds = {c.name: c for c in cmd.prepared_subcommands.get(type(context), {}).values()}
|
||||
command_list: list[tuple[str, str]] = [
|
||||
("|".join([name] + cmd.aliases), cmd.description) for name, cmd in cmds.items()
|
||||
]
|
||||
if not command_list:
|
||||
|
@ -23,12 +21,12 @@ def print_help(context: Context, cmd: Optional[Command]):
|
|||
print(f"Available {'sub' * (cmd is not None)}commands:")
|
||||
max_length: int = max([len(cmd[0]) for cmd in command_list])
|
||||
for com, desc in command_list:
|
||||
com: str = com.ljust(max_length)
|
||||
com = com.ljust(max_length)
|
||||
print(f" - {com} {desc}")
|
||||
|
||||
|
||||
@command("help", [LoginContext, MainContext, DeviceContext])
|
||||
def handle_main_help(context: Context, args: List[str]):
|
||||
def handle_main_help(context: Context, args: list[str]) -> None:
|
||||
"""
|
||||
Show a list of available commands
|
||||
"""
|
||||
|
@ -46,7 +44,7 @@ def handle_main_help(context: Context, args: List[str]):
|
|||
|
||||
|
||||
@handle_main_help.completer()
|
||||
def complete_help(context: Context, args: List[str]) -> List[str]:
|
||||
def complete_help(context: Context, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return list(context.get_commands())
|
||||
|
||||
|
@ -54,3 +52,5 @@ def complete_help(context: Context, args: List[str]) -> List[str]:
|
|||
cmd, args = context.get_commands()[args[0]].parse_command(context, args[1:-1])
|
||||
if not args:
|
||||
return list(cmd.prepared_subcommands.get(type(context), {}))
|
||||
|
||||
return []
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
from collections import Counter
|
||||
from typing import List, Dict
|
||||
from typing import List, Dict, Any
|
||||
|
||||
from PyCrypCli.commands import CommandError, command
|
||||
from PyCrypCli.commands.help import print_help
|
||||
from PyCrypCli.context import MainContext, DeviceContext
|
||||
from PyCrypCli.exceptions import CannotTradeWithYourselfException, UserUUIDDoesNotExistException
|
||||
from PyCrypCli.game_objects import InventoryElement, ShopCategory
|
||||
from PyCrypCli.util import print_tree
|
||||
from .command import CommandError, command
|
||||
from .help import print_help
|
||||
from ..context import MainContext, DeviceContext
|
||||
from ..exceptions import CannotTradeWithYourselfError, UserUUIDDoesNotExistError
|
||||
from ..models import InventoryElement, ShopCategory
|
||||
from ..util import print_tree
|
||||
|
||||
|
||||
@command("inventory", [MainContext, DeviceContext])
|
||||
def handle_inventory(context: MainContext, args: List[str]):
|
||||
def handle_inventory(context: MainContext, args: List[str]) -> None:
|
||||
"""
|
||||
Manage your inventory and trade with other players
|
||||
"""
|
||||
|
@ -21,7 +21,7 @@ def handle_inventory(context: MainContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_inventory.subcommand("list")
|
||||
def handle_inventory_list(context: MainContext, _):
|
||||
def handle_inventory_list(context: MainContext, _: Any) -> None:
|
||||
"""
|
||||
List your inventory
|
||||
"""
|
||||
|
@ -35,7 +35,7 @@ def handle_inventory_list(context: MainContext, _):
|
|||
for category in categories:
|
||||
category_tree = []
|
||||
for subcategory in category.subcategories:
|
||||
subcategory_tree = [
|
||||
subcategory_tree: list[tuple[str, list[Any]]] = [
|
||||
(f"{inventory[item.name]}x {item.name}", []) for item in subcategory.items if inventory[item.name]
|
||||
]
|
||||
if subcategory_tree:
|
||||
|
@ -53,7 +53,7 @@ def handle_inventory_list(context: MainContext, _):
|
|||
|
||||
|
||||
@handle_inventory.subcommand("trade")
|
||||
def handle_inventory_trade(context: MainContext, args: List[str]):
|
||||
def handle_inventory_trade(context: MainContext, args: List[str]) -> None:
|
||||
"""
|
||||
Trade with other players
|
||||
"""
|
||||
|
@ -71,9 +71,9 @@ def handle_inventory_trade(context: MainContext, args: List[str]):
|
|||
|
||||
try:
|
||||
item.trade(target_user)
|
||||
except CannotTradeWithYourselfException:
|
||||
except CannotTradeWithYourselfError:
|
||||
raise CommandError("You cannot trade with yourself.")
|
||||
except UserUUIDDoesNotExistException:
|
||||
except UserUUIDDoesNotExistError:
|
||||
raise CommandError("This user does not exist.")
|
||||
|
||||
|
||||
|
|
|
@ -1,23 +1,22 @@
|
|||
from typing import List
|
||||
from typing import Any
|
||||
|
||||
from PyCrypCli.exceptions import ServiceNotFoundException, WalletNotFoundException
|
||||
|
||||
from PyCrypCli.commands import CommandError, command
|
||||
from PyCrypCli.commands.help import print_help
|
||||
from PyCrypCli.context import DeviceContext
|
||||
from PyCrypCli.game_objects import Miner
|
||||
from PyCrypCli.util import is_uuid
|
||||
from .command import CommandError, command
|
||||
from .help import print_help
|
||||
from ..context import DeviceContext
|
||||
from ..exceptions import ServiceNotFoundError, WalletNotFoundError
|
||||
from ..models import Miner
|
||||
from ..util import is_uuid
|
||||
|
||||
|
||||
def get_miner(context: DeviceContext) -> Miner:
|
||||
try:
|
||||
return context.host.get_miner()
|
||||
except ServiceNotFoundException:
|
||||
except ServiceNotFoundError:
|
||||
raise CommandError("You have to create the miner service before you can use it.")
|
||||
|
||||
|
||||
@command("miner", [DeviceContext])
|
||||
def handle_miner(context: DeviceContext, args: List[str]):
|
||||
def handle_miner(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Manager your Morphcoin miners
|
||||
"""
|
||||
|
@ -28,13 +27,13 @@ def handle_miner(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_miner.subcommand("look")
|
||||
def handle_miner_look(context: DeviceContext, _):
|
||||
def handle_miner_look(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
View miner configuration
|
||||
"""
|
||||
|
||||
miner: Miner = get_miner(context)
|
||||
print("Destination wallet: " + miner.wallet)
|
||||
print("Destination wallet: " + miner.wallet_uuid)
|
||||
print("Running: " + ["no", "yes"][miner.running])
|
||||
print(f"Power: {miner.power * 100}%")
|
||||
if miner.running:
|
||||
|
@ -42,7 +41,7 @@ def handle_miner_look(context: DeviceContext, _):
|
|||
|
||||
|
||||
@handle_miner.subcommand("power")
|
||||
def handle_miner_power(context: DeviceContext, args: List[str]):
|
||||
def handle_miner_power(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Change miner power
|
||||
"""
|
||||
|
@ -61,12 +60,12 @@ def handle_miner_power(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
miner.set_power(power)
|
||||
except WalletNotFoundException:
|
||||
except WalletNotFoundError:
|
||||
raise CommandError("Wallet does not exist.")
|
||||
|
||||
|
||||
@handle_miner.subcommand("wallet")
|
||||
def handle_miner_wallet(context: DeviceContext, args: List[str]):
|
||||
def handle_miner_wallet(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Connect the miner to a different wallet
|
||||
"""
|
||||
|
@ -81,5 +80,5 @@ def handle_miner_wallet(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
miner.set_wallet(args[0])
|
||||
except WalletNotFoundException:
|
||||
except WalletNotFoundError:
|
||||
raise CommandError("Wallet does not exist.")
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
import re
|
||||
import time
|
||||
from typing import List
|
||||
from typing import Any
|
||||
|
||||
from PyCrypCli.commands import command, CommandError
|
||||
from PyCrypCli.commands.files import create_file
|
||||
from PyCrypCli.commands.help import print_help
|
||||
from PyCrypCli.context import DeviceContext
|
||||
from PyCrypCli.exceptions import (
|
||||
FileNotFoundException,
|
||||
InvalidWalletFile,
|
||||
UnknownSourceOrDestinationException,
|
||||
PermissionDeniedException,
|
||||
AlreadyOwnAWalletException,
|
||||
from .command import command, CommandError
|
||||
from .files import create_file
|
||||
from .help import print_help
|
||||
from ..context import DeviceContext
|
||||
from ..exceptions import (
|
||||
FileNotFoundError,
|
||||
InvalidWalletFileError,
|
||||
UnknownSourceOrDestinationError,
|
||||
PermissionDeniedError,
|
||||
AlreadyOwnAWalletError,
|
||||
)
|
||||
from PyCrypCli.game_objects import Wallet, Transaction, PublicWallet
|
||||
from PyCrypCli.util import is_uuid, extract_wallet, strip_float
|
||||
from ..models import Wallet, Transaction, PublicWallet
|
||||
from ..util import is_uuid, extract_wallet, strip_float
|
||||
|
||||
|
||||
def get_wallet_from_file(context: DeviceContext, path: str) -> Wallet:
|
||||
try:
|
||||
return get_wallet(context, *context.get_wallet_credentials_from_file(path))
|
||||
except FileNotFoundException:
|
||||
except FileNotFoundError:
|
||||
raise CommandError("File does not exist.")
|
||||
except InvalidWalletFile:
|
||||
except InvalidWalletFileError:
|
||||
raise CommandError("File is no wallet file.")
|
||||
|
||||
|
||||
def get_wallet(context: DeviceContext, uuid: str, key: str) -> Wallet:
|
||||
try:
|
||||
return Wallet.get_wallet(context.client, uuid, key)
|
||||
except UnknownSourceOrDestinationException:
|
||||
except UnknownSourceOrDestinationError:
|
||||
raise CommandError("Invalid wallet file. Wallet does not exist.")
|
||||
except PermissionDeniedException:
|
||||
except PermissionDeniedError:
|
||||
raise CommandError("Invalid wallet file. Key is incorrect.")
|
||||
|
||||
|
||||
@command("morphcoin", [DeviceContext])
|
||||
def handle_morphcoin(context: DeviceContext, args: List[str]):
|
||||
def handle_morphcoin(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Manage your Morphcoin wallet
|
||||
"""
|
||||
|
@ -47,7 +47,7 @@ def handle_morphcoin(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_morphcoin.subcommand("create")
|
||||
def handle_morphcoin_create(context: DeviceContext, args: List[str]):
|
||||
def handle_morphcoin_create(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Create a new MorphCoin wallet
|
||||
"""
|
||||
|
@ -66,17 +66,17 @@ def handle_morphcoin_create(context: DeviceContext, args: List[str]):
|
|||
except CommandError:
|
||||
wallet.delete()
|
||||
raise
|
||||
except AlreadyOwnAWalletException:
|
||||
except AlreadyOwnAWalletError:
|
||||
raise CommandError("You already own a wallet")
|
||||
|
||||
|
||||
@handle_morphcoin.subcommand("list")
|
||||
def handle_morphcoin_list(context: DeviceContext, _):
|
||||
def handle_morphcoin_list(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
List your MorphCoin wallets
|
||||
"""
|
||||
|
||||
wallets: List[PublicWallet] = PublicWallet.list_wallets(context.client)
|
||||
wallets: list[PublicWallet] = PublicWallet.list_wallets(context.client)
|
||||
if not wallets:
|
||||
print("You don't own any wallet.")
|
||||
else:
|
||||
|
@ -86,7 +86,7 @@ def handle_morphcoin_list(context: DeviceContext, _):
|
|||
|
||||
|
||||
@handle_morphcoin.subcommand("look")
|
||||
def handle_morphcoin_look(context: DeviceContext, args: List[str]):
|
||||
def handle_morphcoin_look(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
View the balance of your wallet
|
||||
"""
|
||||
|
@ -105,7 +105,7 @@ def handle_morphcoin_look(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_morphcoin.subcommand("transactions")
|
||||
def handle_morphcoin_transactions(context: DeviceContext, args: List[str]):
|
||||
def handle_morphcoin_transactions(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
View the transaction history of your wallet
|
||||
"""
|
||||
|
@ -115,19 +115,19 @@ def handle_morphcoin_transactions(context: DeviceContext, args: List[str]):
|
|||
|
||||
wallet: Wallet = get_wallet_from_file(context, args[0])
|
||||
|
||||
if not wallet.transactions:
|
||||
if not wallet.transaction_count:
|
||||
print("No transactions found for this wallet.")
|
||||
return
|
||||
|
||||
transactions: List[Transaction] = wallet.get_transactions(wallet.transactions, 0)
|
||||
transactions: list[Transaction] = wallet.get_transactions(wallet.transaction_count, 0)
|
||||
print("Transactions for this wallet:")
|
||||
for transaction in transactions:
|
||||
source: str = transaction.source_uuid
|
||||
if source == wallet.uuid:
|
||||
source: str = "self"
|
||||
source = "self"
|
||||
destination: str = transaction.destination_uuid
|
||||
if destination == wallet.uuid:
|
||||
destination: str = "self"
|
||||
destination = "self"
|
||||
amount: int = transaction.amount
|
||||
usage: str = transaction.usage
|
||||
text = f"{transaction.timestamp.ctime()}| {strip_float(amount / 1000, 3)} MC: {source} -> {destination}"
|
||||
|
@ -137,7 +137,7 @@ def handle_morphcoin_transactions(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_morphcoin.subcommand("reset")
|
||||
def handle_morphcoin_reset(context: DeviceContext, args: List[str]):
|
||||
def handle_morphcoin_reset(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Reset your wallet in case you lost the secret key
|
||||
"""
|
||||
|
@ -151,14 +151,14 @@ def handle_morphcoin_reset(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
PublicWallet.get_public_wallet(context.client, wallet_uuid).reset_wallet()
|
||||
except UnknownSourceOrDestinationException:
|
||||
except UnknownSourceOrDestinationError:
|
||||
raise CommandError("Wallet does not exist.")
|
||||
except PermissionDeniedException:
|
||||
except PermissionDeniedError:
|
||||
raise CommandError("Permission denied.")
|
||||
|
||||
|
||||
@handle_morphcoin.subcommand("watch")
|
||||
def handle_morphcoin_watch(context: DeviceContext, args: List[str]):
|
||||
def handle_morphcoin_watch(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Live view of the wallet balance
|
||||
"""
|
||||
|
@ -176,9 +176,9 @@ def handle_morphcoin_watch(context: DeviceContext, args: List[str]):
|
|||
now = time.time()
|
||||
|
||||
if now - last_update > 20:
|
||||
wallet: Wallet = get_wallet(context, wallet.uuid, wallet.key)
|
||||
current_mining_rate: float = wallet.get_mining_rate()
|
||||
last_update: float = now
|
||||
wallet = get_wallet(context, wallet.uuid, wallet.key)
|
||||
current_mining_rate = wallet.get_mining_rate()
|
||||
last_update = now
|
||||
|
||||
current_balance: int = wallet.amount + int(current_mining_rate * 1000 * (now - last_update))
|
||||
print(
|
||||
|
@ -193,35 +193,35 @@ def handle_morphcoin_watch(context: DeviceContext, args: List[str]):
|
|||
@handle_morphcoin_look.completer()
|
||||
@handle_morphcoin_transactions.completer()
|
||||
@handle_morphcoin_watch.completer()
|
||||
def morphcoin_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def morphcoin_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return context.file_path_completer(args[0])
|
||||
return []
|
||||
|
||||
|
||||
@handle_morphcoin_create.completer()
|
||||
def morphcoin_create_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def morphcoin_create_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return context.file_path_completer(args[0], dirs_only=True)
|
||||
return []
|
||||
|
||||
|
||||
@command("pay", [DeviceContext])
|
||||
def handle_pay(context: DeviceContext, args: List[str]):
|
||||
def handle_pay(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Send Morphcoins to another wallet
|
||||
"""
|
||||
|
||||
if len(args) < 3:
|
||||
raise CommandError(
|
||||
"usage: pay <filename> <receiver> <amount> [usage]\n" " or: pay <uuid> <key> <receiver> <amount> [usage]",
|
||||
"usage: pay <filename> <receiver> <amount> [usage]\n" " or: pay <uuid> <key> <receiver> <amount> [usage]"
|
||||
)
|
||||
|
||||
if extract_wallet(f"{args[0]} {args[1]}") is not None:
|
||||
wallet: Wallet = get_wallet(context, args[0], args[1])
|
||||
args.pop(0)
|
||||
else:
|
||||
wallet: Wallet = get_wallet_from_file(context, args[0])
|
||||
wallet = get_wallet_from_file(context, args[0])
|
||||
|
||||
receiver: str = args[1]
|
||||
if not is_uuid(receiver):
|
||||
|
@ -239,12 +239,12 @@ def handle_pay(context: DeviceContext, args: List[str]):
|
|||
try:
|
||||
wallet.send(PublicWallet.get_public_wallet(context.client, receiver), amount, " ".join(args[3:]))
|
||||
print(f"Sent {strip_float(amount / 1000, 3)} morphcoin to {receiver}.")
|
||||
except UnknownSourceOrDestinationException:
|
||||
except UnknownSourceOrDestinationError:
|
||||
raise CommandError("Destination wallet does not exist.")
|
||||
|
||||
|
||||
@handle_pay.completer()
|
||||
def pay_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def pay_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return context.file_path_completer(args[0])
|
||||
return []
|
||||
|
|
|
@ -1,39 +1,39 @@
|
|||
from typing import List
|
||||
from typing import Any
|
||||
|
||||
from PyCrypCli.commands import command, CommandError
|
||||
from PyCrypCli.commands.device import get_device
|
||||
from PyCrypCli.commands.help import print_help
|
||||
from PyCrypCli.context import DeviceContext
|
||||
from PyCrypCli.exceptions import (
|
||||
MaximumNetworksReachedException,
|
||||
InvalidNameException,
|
||||
NameAlreadyInUseException,
|
||||
NetworkNotFoundException,
|
||||
AlreadyMemberOfNetworkException,
|
||||
InvitationAlreadyExistsException,
|
||||
NoPermissionsException,
|
||||
CannotLeaveOwnNetworkException,
|
||||
CannotKickOwnerException,
|
||||
DeviceNotFoundException,
|
||||
from .command import command, CommandError
|
||||
from .device import get_device
|
||||
from .help import print_help
|
||||
from ..context import DeviceContext
|
||||
from ..exceptions import (
|
||||
MaximumNetworksReachedError,
|
||||
InvalidNameError,
|
||||
NameAlreadyInUseError,
|
||||
NetworkNotFoundError,
|
||||
AlreadyMemberOfNetworkError,
|
||||
InvitationAlreadyExistsError,
|
||||
NoPermissionsError,
|
||||
CannotLeaveOwnNetworkError,
|
||||
CannotKickOwnerError,
|
||||
DeviceNotFoundError,
|
||||
)
|
||||
from PyCrypCli.game_objects import Network, NetworkMembership, Device, NetworkInvitation
|
||||
from PyCrypCli.util import is_uuid
|
||||
from ..models import Network, NetworkMembership, Device, NetworkInvitation
|
||||
from ..util import is_uuid
|
||||
|
||||
|
||||
def get_network(context: DeviceContext, name_or_uuid: str) -> Network:
|
||||
if is_uuid(name_or_uuid):
|
||||
try:
|
||||
return Network.get_by_uuid(context.client, name_or_uuid)
|
||||
except NetworkNotFoundException:
|
||||
except NetworkNotFoundError:
|
||||
pass
|
||||
|
||||
try:
|
||||
return Network.get_network_by_name(context.client, name_or_uuid)
|
||||
except NetworkNotFoundException:
|
||||
except NetworkNotFoundError:
|
||||
raise CommandError("This network does not exist.")
|
||||
|
||||
|
||||
def handle_membership_request(context: DeviceContext, args: List[str], accept: bool):
|
||||
def handle_membership_request(context: DeviceContext, args: list[str], accept: bool) -> None:
|
||||
if len(args) not in (1, 2):
|
||||
raise CommandError(f"usage: network {['deny', 'accept'][accept]} <network> [<device>]")
|
||||
|
||||
|
@ -41,26 +41,26 @@ def handle_membership_request(context: DeviceContext, args: List[str], accept: b
|
|||
|
||||
if len(args) == 1:
|
||||
for invitation in context.host.get_network_invitations():
|
||||
if invitation.network == network.uuid:
|
||||
if invitation.network_uuid == network.uuid:
|
||||
break
|
||||
else:
|
||||
raise CommandError("Invitation not found.")
|
||||
else:
|
||||
devices: List[Device] = []
|
||||
devices: list[Device] = []
|
||||
for request in network.get_membership_requests():
|
||||
try:
|
||||
devices.append(Device.get_device(context.client, request.device))
|
||||
except DeviceNotFoundException:
|
||||
devices.append(Device.get_device(context.client, request.device_uuid))
|
||||
except DeviceNotFoundError:
|
||||
pass
|
||||
|
||||
device: Device = get_device(context, args[1], devices)
|
||||
try:
|
||||
for invitation in network.get_membership_requests():
|
||||
if invitation.network == network.uuid and invitation.device == device.uuid:
|
||||
if invitation.network_uuid == network.uuid and invitation.device_uuid == device.uuid:
|
||||
break
|
||||
else:
|
||||
raise CommandError("Join request not found.")
|
||||
except NoPermissionsException:
|
||||
except NoPermissionsError:
|
||||
raise CommandError("Permission denied.")
|
||||
|
||||
if accept:
|
||||
|
@ -70,7 +70,7 @@ def handle_membership_request(context: DeviceContext, args: List[str], accept: b
|
|||
|
||||
|
||||
@command("network", [DeviceContext])
|
||||
def handle_network(context: DeviceContext, args: List[str]):
|
||||
def handle_network(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Manage your networks
|
||||
"""
|
||||
|
@ -81,41 +81,41 @@ def handle_network(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_network.subcommand("list")
|
||||
def handle_network_list(context: DeviceContext, _):
|
||||
def handle_network_list(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
View the networks this device is a member of
|
||||
"""
|
||||
|
||||
networks: List[Network] = context.host.get_networks()
|
||||
networks: list[Network] = context.host.get_networks()
|
||||
|
||||
if not networks:
|
||||
print("This device is not a member of any network.")
|
||||
else:
|
||||
print("Networks this device is a member of:")
|
||||
for network in networks:
|
||||
owner: str = " (owner)" * (network.owner == context.host.uuid)
|
||||
owner: str = " (owner)" * (network.owner_uuid == context.host.uuid)
|
||||
print(f" - [{['public', 'private'][network.hidden]}] {network.name}{owner} (UUID: {network.uuid})")
|
||||
|
||||
|
||||
@handle_network.subcommand("public")
|
||||
def handle_network_public(context: DeviceContext, _):
|
||||
def handle_network_public(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
View public networks
|
||||
"""
|
||||
|
||||
networks: List[Network] = Network.get_public_networks(context.client)
|
||||
networks: list[Network] = Network.get_public_networks(context.client)
|
||||
|
||||
if not networks:
|
||||
print("There is no public network.")
|
||||
else:
|
||||
print("Public networks:")
|
||||
for network in networks:
|
||||
owner: str = " (owner)" * (network.owner == context.host.uuid)
|
||||
owner: str = " (owner)" * (network.owner_uuid == context.host.uuid)
|
||||
print(f" - {network.name}{owner} (UUID: {network.uuid})")
|
||||
|
||||
|
||||
@handle_network.subcommand("create")
|
||||
def handle_network_create(context: DeviceContext, args: List[str]):
|
||||
def handle_network_create(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Create a new network
|
||||
"""
|
||||
|
@ -125,16 +125,16 @@ def handle_network_create(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
context.host.create_network(args[0], args[1] == "private")
|
||||
except MaximumNetworksReachedException:
|
||||
except MaximumNetworksReachedError:
|
||||
raise CommandError("You already own two networks.")
|
||||
except InvalidNameException:
|
||||
except InvalidNameError:
|
||||
raise CommandError("Invalid name.")
|
||||
except NameAlreadyInUseException:
|
||||
except NameAlreadyInUseError:
|
||||
raise CommandError("This name is already in use.")
|
||||
|
||||
|
||||
@handle_network.subcommand("members")
|
||||
def handle_network_members(context: DeviceContext, args: List[str]):
|
||||
def handle_network_members(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
View the members of one of your networks
|
||||
"""
|
||||
|
@ -145,8 +145,8 @@ def handle_network_members(context: DeviceContext, args: List[str]):
|
|||
network: Network = get_network(context, args[0])
|
||||
|
||||
try:
|
||||
members: List[NetworkMembership] = network.get_members()
|
||||
except NetworkNotFoundException:
|
||||
members: list[NetworkMembership] = network.get_members()
|
||||
except NetworkNotFoundError:
|
||||
raise CommandError("Permission denied.")
|
||||
|
||||
if not members:
|
||||
|
@ -154,12 +154,12 @@ def handle_network_members(context: DeviceContext, args: List[str]):
|
|||
else:
|
||||
print(f"Members of '{network.name}':")
|
||||
for member in members:
|
||||
device: Device = Device.get_device(context.client, member.device)
|
||||
device: Device = Device.get_device(context.client, member.device_uuid)
|
||||
print(f" - [{['off', 'on'][device.powered_on]}] {device.name} (UUID: {device.uuid})")
|
||||
|
||||
|
||||
@handle_network.subcommand("request")
|
||||
def handle_network_request(context: DeviceContext, args: List[str]):
|
||||
def handle_network_request(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Request membership of a network
|
||||
"""
|
||||
|
@ -171,14 +171,14 @@ def handle_network_request(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
network.request_membership(context.host)
|
||||
except AlreadyMemberOfNetworkException:
|
||||
except AlreadyMemberOfNetworkError:
|
||||
raise CommandError("This device is already a member of the network.")
|
||||
except InvitationAlreadyExistsException:
|
||||
except InvitationAlreadyExistsError:
|
||||
raise CommandError("You already requested to join this network.")
|
||||
|
||||
|
||||
@handle_network.subcommand("requests")
|
||||
def handle_network_requests(context: DeviceContext, args: List[str]):
|
||||
def handle_network_requests(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
View open membership requests of one of your networks
|
||||
"""
|
||||
|
@ -189,8 +189,8 @@ def handle_network_requests(context: DeviceContext, args: List[str]):
|
|||
network: Network = get_network(context, args[0])
|
||||
|
||||
try:
|
||||
requests: List[NetworkInvitation] = network.get_membership_requests()
|
||||
except NoPermissionsException:
|
||||
requests: list[NetworkInvitation] = network.get_membership_requests()
|
||||
except NoPermissionsError:
|
||||
raise CommandError("Permission denied.")
|
||||
|
||||
if not requests:
|
||||
|
@ -198,12 +198,12 @@ def handle_network_requests(context: DeviceContext, args: List[str]):
|
|||
else:
|
||||
print("Pending requests:")
|
||||
for request in requests:
|
||||
device: Device = Device.get_device(context.client, request.device)
|
||||
device: Device = Device.get_device(context.client, request.device_uuid)
|
||||
print(f" - {device.name} (UUID: {device.uuid})")
|
||||
|
||||
|
||||
@handle_network.subcommand("accept")
|
||||
def handle_network_accept(context: DeviceContext, args: List[str]):
|
||||
def handle_network_accept(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Accept a membership request or an invitation
|
||||
"""
|
||||
|
@ -212,7 +212,7 @@ def handle_network_accept(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_network.subcommand("deny")
|
||||
def handle_network_deny(context: DeviceContext, args: List[str]):
|
||||
def handle_network_deny(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Deny a membership request or an invitation
|
||||
"""
|
||||
|
@ -221,7 +221,7 @@ def handle_network_deny(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_network.subcommand("invite")
|
||||
def handle_network_invite(context: DeviceContext, args: List[str]):
|
||||
def handle_network_invite(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Invite a device to one of your networks
|
||||
"""
|
||||
|
@ -234,33 +234,33 @@ def handle_network_invite(context: DeviceContext, args: List[str]):
|
|||
device: Device = get_device(context, args[1])
|
||||
try:
|
||||
network.invite_device(device)
|
||||
except NetworkNotFoundException:
|
||||
except NetworkNotFoundError:
|
||||
raise CommandError("Permission denied.")
|
||||
except AlreadyMemberOfNetworkException:
|
||||
except AlreadyMemberOfNetworkError:
|
||||
raise CommandError("Device is already a member of this network.")
|
||||
except InvitationAlreadyExistsException:
|
||||
except InvitationAlreadyExistsError:
|
||||
raise CommandError("An invitation for this device already exists.")
|
||||
|
||||
|
||||
@handle_network.subcommand("invitations")
|
||||
def handle_network_invitations(context: DeviceContext, _):
|
||||
def handle_network_invitations(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
View invitations for this device to other networks
|
||||
"""
|
||||
|
||||
invitations: List[NetworkInvitation] = context.host.get_network_invitations()
|
||||
invitations: list[NetworkInvitation] = context.host.get_network_invitations()
|
||||
if not invitations:
|
||||
print("There are no pending network invitations for this device.")
|
||||
else:
|
||||
print("Pending network invitations:")
|
||||
for invitation in invitations:
|
||||
network: Network = Network.get_by_uuid(context.client, invitation.network)
|
||||
owner: str = " (owner)" * (network.owner == context.host.uuid)
|
||||
network: Network = Network.get_by_uuid(context.client, invitation.network_uuid)
|
||||
owner: str = " (owner)" * (network.owner_uuid == context.host.uuid)
|
||||
print(f" - [{['public', 'private'][network.hidden]}] {network.name}{owner} (UUID: {network.uuid})")
|
||||
|
||||
|
||||
@handle_network.subcommand("leave")
|
||||
def handle_network_leave(context: DeviceContext, args: List[str]):
|
||||
def handle_network_leave(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Leave a network
|
||||
"""
|
||||
|
@ -272,12 +272,12 @@ def handle_network_leave(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
network.leave(context.host)
|
||||
except CannotLeaveOwnNetworkException:
|
||||
except CannotLeaveOwnNetworkError:
|
||||
raise CommandError("You cannot leave your own network.")
|
||||
|
||||
|
||||
@handle_network.subcommand("kick")
|
||||
def handle_network_kick(context: DeviceContext, args: List[str]):
|
||||
def handle_network_kick(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Kick a device from one of your networks
|
||||
"""
|
||||
|
@ -287,23 +287,23 @@ def handle_network_kick(context: DeviceContext, args: List[str]):
|
|||
|
||||
network: Network = get_network(context, args[0])
|
||||
|
||||
devices: List[Device] = []
|
||||
devices: list[Device] = []
|
||||
for member in network.get_members():
|
||||
devices.append(Device.get_device(context.client, member.device))
|
||||
devices.append(Device.get_device(context.client, member.device_uuid))
|
||||
|
||||
device: Device = get_device(context, args[1], devices)
|
||||
try:
|
||||
network.kick(device)
|
||||
except NetworkNotFoundException:
|
||||
except NetworkNotFoundError:
|
||||
raise CommandError("Permission denied.")
|
||||
except NoPermissionsException:
|
||||
except NoPermissionsError:
|
||||
raise CommandError("Permission denied.")
|
||||
except CannotKickOwnerException:
|
||||
except CannotKickOwnerError:
|
||||
raise CommandError("You cannot kick the owner of the network.")
|
||||
|
||||
|
||||
@handle_network.subcommand("delete")
|
||||
def handle_network_delete(context: DeviceContext, args: List[str]):
|
||||
def handle_network_delete(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Delete one of your networks
|
||||
"""
|
||||
|
@ -315,21 +315,21 @@ def handle_network_delete(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
network.delete()
|
||||
except NetworkNotFoundException:
|
||||
except NetworkNotFoundError:
|
||||
raise CommandError("Permission denied.")
|
||||
|
||||
|
||||
def device_network_names(context: DeviceContext) -> List[str]:
|
||||
def device_network_names(context: DeviceContext) -> list[str]:
|
||||
return [network.name for network in context.host.get_networks()]
|
||||
|
||||
|
||||
def public_network_names(context: DeviceContext) -> List[str]:
|
||||
def public_network_names(context: DeviceContext) -> list[str]:
|
||||
return [network.name for network in Network.get_public_networks(context.client)]
|
||||
|
||||
|
||||
def invitation_network_names(context: DeviceContext) -> List[str]:
|
||||
def invitation_network_names(context: DeviceContext) -> list[str]:
|
||||
return [
|
||||
Network.get_by_uuid(context.client, invitation.network).name
|
||||
Network.get_by_uuid(context.client, invitation.network_uuid).name
|
||||
for invitation in context.host.get_network_invitations()
|
||||
]
|
||||
|
||||
|
@ -339,14 +339,14 @@ def invitation_network_names(context: DeviceContext) -> List[str]:
|
|||
@handle_network_requests.completer()
|
||||
@handle_network_leave.completer()
|
||||
@handle_network_delete.completer()
|
||||
def network_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def network_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return [*{*device_network_names(context), *public_network_names(context), *invitation_network_names(context)}]
|
||||
return []
|
||||
|
||||
|
||||
@handle_network_create.completer()
|
||||
def network_create_completer(_, args: List[str]) -> List[str]:
|
||||
def network_create_completer(_: Any, args: list[str]) -> list[str]:
|
||||
if len(args) == 2:
|
||||
return ["public", "private"]
|
||||
return []
|
||||
|
@ -354,7 +354,7 @@ def network_create_completer(_, args: List[str]) -> List[str]:
|
|||
|
||||
@handle_network_accept.completer()
|
||||
@handle_network_deny.completer()
|
||||
def network_accept_deny_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def network_accept_deny_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return [*{*device_network_names(context), *invitation_network_names(context)}]
|
||||
if len(args) == 2:
|
||||
|
@ -362,28 +362,28 @@ def network_accept_deny_completer(context: DeviceContext, args: List[str]) -> Li
|
|||
network: Network = get_network(context, args[0])
|
||||
except CommandError:
|
||||
return []
|
||||
device_names: List[str] = []
|
||||
device_names: list[str] = []
|
||||
for request in network.get_membership_requests():
|
||||
try:
|
||||
device_names.append(Device.get_device(context.client, request.device).name)
|
||||
except DeviceNotFoundException:
|
||||
device_names.append(Device.get_device(context.client, request.device_uuid).name)
|
||||
except DeviceNotFoundError:
|
||||
pass
|
||||
return [name for name in device_names if device_names.count(name) == 1]
|
||||
return []
|
||||
|
||||
|
||||
@handle_network_invite.completer()
|
||||
def network_invite_completer(context: DeviceContext, args: List[str]):
|
||||
def network_invite_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return [*{*device_network_names(context)}]
|
||||
if len(args) == 2:
|
||||
device_names: List[str] = [device.name for device in Device.list_devices(context.client)]
|
||||
device_names: list[str] = [device.name for device in Device.list_devices(context.client)]
|
||||
return [name for name in device_names if device_names.count(name) == 1]
|
||||
return []
|
||||
|
||||
|
||||
@handle_network_kick.completer()
|
||||
def network_kick_completer(context: DeviceContext, args: List[str]):
|
||||
def network_kick_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return [*{*device_network_names(context)}]
|
||||
if len(args) == 2:
|
||||
|
@ -391,8 +391,8 @@ def network_kick_completer(context: DeviceContext, args: List[str]):
|
|||
network: Network = get_network(context, args[0])
|
||||
except CommandError:
|
||||
return []
|
||||
device_names: List[str] = []
|
||||
device_names: list[str] = []
|
||||
for member in network.get_members():
|
||||
device_names.append(Device.get_device(context.client, member.device).name)
|
||||
device_names.append(Device.get_device(context.client, member.device_uuid).name)
|
||||
return [name for name in device_names if device_names.count(name) == 1]
|
||||
return []
|
||||
|
|
|
@ -1,36 +1,36 @@
|
|||
import os
|
||||
import time
|
||||
from typing import List, Tuple
|
||||
from typing import Any, cast
|
||||
|
||||
from PyCrypCli.commands import command, CommandError
|
||||
from PyCrypCli.commands.help import print_help
|
||||
from PyCrypCli.commands.morphcoin import get_wallet_from_file
|
||||
from PyCrypCli.context import DeviceContext, MainContext
|
||||
from PyCrypCli.exceptions import (
|
||||
ServiceNotFoundException,
|
||||
AttackNotRunningException,
|
||||
AlreadyOwnThisServiceException,
|
||||
WalletNotFoundException,
|
||||
CannotDeleteEnforcedServiceException,
|
||||
CannotToggleDirectlyException,
|
||||
CouldNotStartService,
|
||||
ServiceNotRunningException,
|
||||
from .command import command, CommandError
|
||||
from .help import print_help
|
||||
from .morphcoin import get_wallet_from_file
|
||||
from ..context import DeviceContext, MainContext
|
||||
from ..exceptions import (
|
||||
ServiceNotFoundError,
|
||||
AttackNotRunningError,
|
||||
AlreadyOwnThisServiceError,
|
||||
WalletNotFoundError,
|
||||
CannotDeleteEnforcedServiceError,
|
||||
CannotToggleDirectlyError,
|
||||
CouldNotStartServiceError,
|
||||
ServiceNotRunningError,
|
||||
)
|
||||
from PyCrypCli.game_objects import Device, Service, PortscanService, BruteforceService, PublicService
|
||||
from PyCrypCli.util import is_uuid
|
||||
from ..models import Device, Service, PortscanService, BruteforceService, PublicService
|
||||
from ..util import is_uuid
|
||||
|
||||
|
||||
def get_service(context: DeviceContext, name: str) -> Service:
|
||||
try:
|
||||
return context.host.get_service_by_name(name)
|
||||
except ServiceNotFoundException:
|
||||
except ServiceNotFoundError:
|
||||
raise CommandError(f"The service '{name}' could not be found on this device")
|
||||
|
||||
|
||||
def stop_bruteforce(context: DeviceContext, service: BruteforceService):
|
||||
def stop_bruteforce(context: DeviceContext, service: BruteforceService) -> None:
|
||||
try:
|
||||
access, _, target_device = service.stop()
|
||||
except AttackNotRunningException:
|
||||
except AttackNotRunningError:
|
||||
raise CommandError("Bruteforce attack is not running.")
|
||||
if access:
|
||||
if context.ask("Access granted. Do you want to connect to the device? [yes|no] ", ["yes", "no"]) == "yes":
|
||||
|
@ -42,7 +42,7 @@ def stop_bruteforce(context: DeviceContext, service: BruteforceService):
|
|||
|
||||
|
||||
@command("service", [DeviceContext])
|
||||
def handle_service(context: DeviceContext, args: List[str]):
|
||||
def handle_service(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Create or use a service
|
||||
"""
|
||||
|
@ -53,7 +53,7 @@ def handle_service(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_service.subcommand("create")
|
||||
def handle_service_create(context: DeviceContext, args: List[str]):
|
||||
def handle_service_create(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Create a new service
|
||||
"""
|
||||
|
@ -61,7 +61,7 @@ def handle_service_create(context: DeviceContext, args: List[str]):
|
|||
if len(args) not in (1, 2) or args[0] not in ("bruteforce", "portscan", "telnet", "ssh", "miner"):
|
||||
raise CommandError("usage: service create bruteforce|portscan|telnet|ssh|miner")
|
||||
|
||||
extra: dict = {}
|
||||
extra: dict[str, Any] = {}
|
||||
if args[0] == "miner":
|
||||
if len(args) != 2:
|
||||
raise CommandError("usage: service create miner <wallet>")
|
||||
|
@ -70,7 +70,7 @@ def handle_service_create(context: DeviceContext, args: List[str]):
|
|||
wallet_uuid: str = get_wallet_from_file(context, args[1]).uuid
|
||||
except CommandError:
|
||||
if is_uuid(args[1]):
|
||||
wallet_uuid: str = args[1]
|
||||
wallet_uuid = args[1]
|
||||
else:
|
||||
raise CommandError("Invalid wallet uuid")
|
||||
|
||||
|
@ -79,19 +79,19 @@ def handle_service_create(context: DeviceContext, args: List[str]):
|
|||
try:
|
||||
context.host.create_service(args[0], **extra)
|
||||
print("Service has been created")
|
||||
except AlreadyOwnThisServiceException:
|
||||
except AlreadyOwnThisServiceError:
|
||||
raise CommandError("You already created this service")
|
||||
except WalletNotFoundException:
|
||||
except WalletNotFoundError:
|
||||
raise CommandError("Wallet does not exist.")
|
||||
|
||||
|
||||
@handle_service.subcommand("list")
|
||||
def handle_service_list(context: DeviceContext, _):
|
||||
def handle_service_list(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
List all services installed on this device
|
||||
"""
|
||||
|
||||
services: List[Service] = context.host.get_services()
|
||||
services: list[Service] = context.host.get_services()
|
||||
if not services:
|
||||
print("There are no services on this device.")
|
||||
else:
|
||||
|
@ -104,7 +104,7 @@ def handle_service_list(context: DeviceContext, _):
|
|||
|
||||
|
||||
@handle_service.subcommand("delete")
|
||||
def handle_service_delete(context: DeviceContext, args: List[str]):
|
||||
def handle_service_delete(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Delete a service
|
||||
"""
|
||||
|
@ -116,12 +116,12 @@ def handle_service_delete(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
service.delete()
|
||||
except CannotDeleteEnforcedServiceException:
|
||||
except CannotDeleteEnforcedServiceError:
|
||||
raise CommandError("The service could not be deleted.")
|
||||
|
||||
|
||||
@handle_service.subcommand("start")
|
||||
def handle_service_start(context: DeviceContext, args: List[str]):
|
||||
def handle_service_start(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Start a service
|
||||
"""
|
||||
|
@ -135,12 +135,12 @@ def handle_service_start(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
service.toggle()
|
||||
except (CannotToggleDirectlyException, CouldNotStartService):
|
||||
except (CannotToggleDirectlyError, CouldNotStartServiceError):
|
||||
raise CommandError("The service could not be started.")
|
||||
|
||||
|
||||
@handle_service.subcommand("stop")
|
||||
def handle_service_stop(context: DeviceContext, args: List[str]):
|
||||
def handle_service_stop(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Stop a service
|
||||
"""
|
||||
|
@ -154,12 +154,12 @@ def handle_service_stop(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
service.toggle()
|
||||
except CannotToggleDirectlyException:
|
||||
except CannotToggleDirectlyError:
|
||||
raise CommandError("The service could not be stopped.")
|
||||
|
||||
|
||||
@handle_service.subcommand("portscan")
|
||||
def handle_portscan(context: DeviceContext, args: List[str]):
|
||||
def handle_portscan(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Perform a portscan
|
||||
"""
|
||||
|
@ -173,30 +173,31 @@ def handle_portscan(context: DeviceContext, args: List[str]):
|
|||
|
||||
try:
|
||||
service: PortscanService = PortscanService.get_portscan_service(context.client, context.host.uuid)
|
||||
except ServiceNotFoundException:
|
||||
except ServiceNotFoundError:
|
||||
raise CommandError("You have to create a portscan service before you can use it.")
|
||||
|
||||
services: List[PublicService] = service.scan(target)
|
||||
context.update_last_portscan((target, services))
|
||||
services: list[PublicService] = service.scan(target)
|
||||
context.last_portscan = target, services
|
||||
if not services:
|
||||
print("That device doesn't have any running services")
|
||||
for service in services:
|
||||
print(f" - {service.name} on port {service.running_port} (UUID: {service.uuid})")
|
||||
for s in services:
|
||||
print(f" - {s.name} on port {s.running_port} (UUID: {s.uuid})")
|
||||
|
||||
|
||||
@handle_service.subcommand("bruteforce")
|
||||
def handle_bruteforce(context: DeviceContext, args: List[str]):
|
||||
def handle_bruteforce(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Start a bruteforce attack
|
||||
"""
|
||||
|
||||
duration: str = "100%"
|
||||
duration_arg: str = "100%"
|
||||
duration: int = 0
|
||||
chance: float | None = None
|
||||
if len(args) in (1, 2) and args[0] in ("ssh", "telnet"):
|
||||
last_portscan: Tuple[str, List[PublicService]] = context.get_last_portscan()
|
||||
if last_portscan is None:
|
||||
if context.last_portscan is None:
|
||||
raise CommandError("You have to portscan your target first to find open ports.")
|
||||
target_device, services = last_portscan
|
||||
|
||||
target_device, services = context.last_portscan
|
||||
for service in services:
|
||||
if service.name == args[0]:
|
||||
target_service: str = service.uuid
|
||||
|
@ -204,64 +205,66 @@ def handle_bruteforce(context: DeviceContext, args: List[str]):
|
|||
else:
|
||||
raise CommandError(f"Service '{args[0]}' is not running on target device.")
|
||||
if len(args) == 2:
|
||||
duration: str = args[1]
|
||||
duration_arg = args[1]
|
||||
elif len(args) in (2, 3):
|
||||
target_device: str = args[0]
|
||||
target_service: str = args[1]
|
||||
target_device = args[0]
|
||||
target_service = args[1]
|
||||
if not is_uuid(target_device):
|
||||
raise CommandError("Invalid target device")
|
||||
if not is_uuid(target_service):
|
||||
raise CommandError("Invalid target service")
|
||||
|
||||
if len(args) == 3:
|
||||
duration: str = args[2]
|
||||
duration_arg = args[2]
|
||||
else:
|
||||
raise CommandError(
|
||||
"usage: service bruteforce <target-device> <target-service> [duration|success_chance]\n"
|
||||
" service bruteforce ssh|telnet [duration|success_chance]",
|
||||
" service bruteforce ssh|telnet [duration|success_chance]"
|
||||
)
|
||||
|
||||
if duration.endswith("%"):
|
||||
if duration_arg.endswith("%"):
|
||||
error = "Success chance has to be a positive number between 0 and 100"
|
||||
try:
|
||||
chance = float(duration[:-1]) / 100
|
||||
chance = float(duration_arg[:-1]) / 100
|
||||
except ValueError:
|
||||
raise CommandError(error)
|
||||
if chance < 0 or chance > 1:
|
||||
raise CommandError(error)
|
||||
else:
|
||||
if not duration.isnumeric():
|
||||
if not duration_arg.isnumeric():
|
||||
raise CommandError("Duration has to be a positive integer")
|
||||
duration: int = int(duration)
|
||||
duration = int(duration_arg)
|
||||
|
||||
try:
|
||||
service: BruteforceService = BruteforceService.get_bruteforce_service(context.client, context.host.uuid)
|
||||
except ServiceNotFoundException:
|
||||
bruteforce_service: BruteforceService = BruteforceService.get_bruteforce_service(
|
||||
context.client, context.host.uuid
|
||||
)
|
||||
except ServiceNotFoundError:
|
||||
raise CommandError("You have to create a bruteforce service before you can use it.")
|
||||
|
||||
if service.running:
|
||||
if bruteforce_service.running:
|
||||
print("You are already attacking a device.")
|
||||
print(f"Target device: {service.target_device}")
|
||||
print(f"Target device: {bruteforce_service.target_device_uuid}")
|
||||
if context.ask("Do you want to stop this attack? [yes|no] ", ["yes", "no"]) == "yes":
|
||||
stop_bruteforce(context, service)
|
||||
stop_bruteforce(context, bruteforce_service)
|
||||
return
|
||||
|
||||
try:
|
||||
service.attack(target_device, target_service)
|
||||
bruteforce_service.attack(target_device, target_service)
|
||||
if chance is not None:
|
||||
service.update()
|
||||
duration: int = round((chance + 0.1) * 20 / service.speed)
|
||||
except ServiceNotFoundException:
|
||||
bruteforce_service.update()
|
||||
duration = round((chance + 0.1) * 20 / bruteforce_service.speed)
|
||||
except ServiceNotFoundError:
|
||||
raise CommandError("The target service does not exist.")
|
||||
except ServiceNotRunningException:
|
||||
except ServiceNotRunningError:
|
||||
raise CommandError("The target service is not running and cannot be exploited.")
|
||||
|
||||
print("You started a bruteforce attack")
|
||||
width: int = os.get_terminal_size().columns - 31
|
||||
steps: int = 17
|
||||
d: int = duration * steps
|
||||
i: int = 0
|
||||
last_check = 0
|
||||
width = os.get_terminal_size().columns - 31
|
||||
steps = 17
|
||||
d = duration * steps
|
||||
i = 0
|
||||
last_check: float = 0
|
||||
try:
|
||||
context.update_presence(
|
||||
state=f"Logged in: {context.username}@{context.root_context.host}",
|
||||
|
@ -273,27 +276,27 @@ def handle_bruteforce(context: DeviceContext, args: List[str]):
|
|||
for i in range(d):
|
||||
if time.time() - last_check > 1:
|
||||
last_check = time.time()
|
||||
service.update()
|
||||
if not service.running:
|
||||
bruteforce_service.update()
|
||||
if not bruteforce_service.running:
|
||||
print("\rBruteforce attack has been aborted.")
|
||||
return
|
||||
|
||||
progress: int = int(i / d * width)
|
||||
j = i // steps
|
||||
progress_bar = "[" + "=" * progress + ">" + " " * (width - progress) + "]"
|
||||
text: str = f"\rBruteforcing {j // 60:02d}:{j % 60:02d} {progress_bar} ({i / d * 100:.1f}%) "
|
||||
text = f"\rBruteforcing {j // 60:02d}:{j % 60:02d} {progress_bar} ({i / d * 100:.1f}%) "
|
||||
print(end=text, flush=True)
|
||||
time.sleep(1 / steps)
|
||||
i: int = (i + 1) // steps
|
||||
i = (i + 1) // steps
|
||||
print(f"\rBruteforcing {i // 60:02d}:{i % 60:02d} [" + "=" * width + ">] (100%) ")
|
||||
except KeyboardInterrupt:
|
||||
print()
|
||||
context.main_loop_presence()
|
||||
stop_bruteforce(context, service)
|
||||
stop_bruteforce(context, bruteforce_service)
|
||||
|
||||
|
||||
@handle_service_create.completer()
|
||||
def service_create_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def service_create_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return ["bruteforce", "portscan", "ssh", "telnet", "miner"]
|
||||
if len(args) == 2 and args[0] == "miner":
|
||||
|
@ -302,7 +305,7 @@ def service_create_completer(context: DeviceContext, args: List[str]) -> List[st
|
|||
|
||||
|
||||
@handle_service_delete.completer()
|
||||
def service_delete_completer(_, args: List[str]) -> List[str]:
|
||||
def service_delete_completer(_: Any, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return ["bruteforce", "portscan", "telnet", "miner"]
|
||||
return []
|
||||
|
@ -311,14 +314,14 @@ def service_delete_completer(_, args: List[str]) -> List[str]:
|
|||
@handle_service_start.completer()
|
||||
@handle_service_stop.completer()
|
||||
@handle_bruteforce.completer()
|
||||
def service_completer(_, args: List[str]) -> List[str]:
|
||||
def service_completer(_: Any, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return ["ssh", "telnet"]
|
||||
return []
|
||||
|
||||
|
||||
@command("spot", [DeviceContext])
|
||||
def handle_spot(context: DeviceContext, _):
|
||||
def handle_spot(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
Find a random device in the network
|
||||
"""
|
||||
|
@ -330,7 +333,7 @@ def handle_spot(context: DeviceContext, _):
|
|||
|
||||
|
||||
@command("remote", [MainContext, DeviceContext])
|
||||
def handle_remote(context: MainContext, args: List[str]):
|
||||
def handle_remote(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
Manage and connect to the devices you hacked before
|
||||
"""
|
||||
|
@ -341,12 +344,12 @@ def handle_remote(context: MainContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_remote.subcommand("list")
|
||||
def handle_remote_list(context: MainContext, _):
|
||||
def handle_remote_list(context: MainContext, _: Any) -> None:
|
||||
"""
|
||||
List remote devices
|
||||
"""
|
||||
|
||||
devices: List[Device] = context.get_hacked_devices()
|
||||
devices: list[Device] = context.get_hacked_devices()
|
||||
|
||||
if not devices:
|
||||
print("You don't have access to any remote device.")
|
||||
|
@ -357,7 +360,7 @@ def handle_remote_list(context: MainContext, _):
|
|||
|
||||
|
||||
@handle_remote.subcommand("connect")
|
||||
def handle_remote_connect(context: MainContext, args: List[str]):
|
||||
def handle_remote_connect(context: MainContext, args: list[str]) -> None:
|
||||
"""
|
||||
Connect to a remote device
|
||||
"""
|
||||
|
@ -372,7 +375,7 @@ def handle_remote_connect(context: MainContext, args: List[str]):
|
|||
if device is None:
|
||||
raise CommandError("This device does not exist or you have no permission to access it.")
|
||||
else:
|
||||
found_devices: List[Device] = []
|
||||
found_devices: list[Device] = []
|
||||
for device in context.get_hacked_devices():
|
||||
if device.name == name:
|
||||
found_devices.append(device)
|
||||
|
@ -382,18 +385,18 @@ def handle_remote_connect(context: MainContext, args: List[str]):
|
|||
if len(found_devices) > 1:
|
||||
raise CommandError(f"There is more than one device with the name '{name}'. You need to specify its UUID.")
|
||||
|
||||
device: Device = found_devices[0]
|
||||
device = found_devices[0]
|
||||
|
||||
print(f"Connecting to {device.name} (UUID: {device.uuid})")
|
||||
if device.part_owner():
|
||||
context.open(DeviceContext(context.root_context, context.session_token, device))
|
||||
context.open(DeviceContext(context.root_context, cast(str, context.session_token), device))
|
||||
else:
|
||||
raise CommandError("This device does not exist or you have no permission to access it.")
|
||||
|
||||
|
||||
@handle_remote_connect.completer()
|
||||
def remote_completer(context: MainContext, args: List[str]) -> List[str]:
|
||||
def remote_completer(context: MainContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
device_names: List[str] = [device.name for device in context.get_hacked_devices()]
|
||||
device_names: list[str] = [device.name for device in context.get_hacked_devices()]
|
||||
return [name for name in device_names if device_names.count(name) == 1]
|
||||
return []
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
from typing import Any
|
||||
|
||||
import sentry_sdk
|
||||
|
||||
from PyCrypCli.commands import command
|
||||
from PyCrypCli.context import LoginContext, MainContext, DeviceContext, Context
|
||||
from .command import command
|
||||
from ..context import LoginContext, MainContext, DeviceContext
|
||||
|
||||
|
||||
@command("clear", [LoginContext, MainContext, DeviceContext])
|
||||
def handle_main_clear(*_):
|
||||
def handle_main_clear(*_: Any) -> None:
|
||||
"""
|
||||
Clear the console
|
||||
"""
|
||||
|
@ -14,7 +16,7 @@ def handle_main_clear(*_):
|
|||
|
||||
|
||||
@command("history", [MainContext, DeviceContext])
|
||||
def handle_main_history(context: MainContext, *_):
|
||||
def handle_main_history(context: MainContext, _: Any) -> None:
|
||||
"""
|
||||
Show the history of commands entered in this session
|
||||
"""
|
||||
|
@ -24,14 +26,14 @@ def handle_main_history(context: MainContext, *_):
|
|||
|
||||
|
||||
@command("feedback", [LoginContext, MainContext, DeviceContext])
|
||||
def handle_feedback(context: Context, *_):
|
||||
def handle_feedback(context: LoginContext, _: Any) -> None:
|
||||
"""
|
||||
Send feedback to the developer
|
||||
"""
|
||||
|
||||
print("Please type your feedback about PyCrypCli below. When you are done press Ctrl+C")
|
||||
feedback = ["User Feedback"]
|
||||
if hasattr(context, "username"):
|
||||
if isinstance(context, MainContext) and context.username:
|
||||
feedback[0] += " from " + context.username
|
||||
while True:
|
||||
try:
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
from typing import List, Dict
|
||||
from typing import Any
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.commands import command, CommandError
|
||||
from PyCrypCli.commands.help import print_help
|
||||
from PyCrypCli.commands.morphcoin import get_wallet_from_file
|
||||
from PyCrypCli.context import DeviceContext
|
||||
from PyCrypCli.exceptions import ItemNotFoundException, NotEnoughCoinsException
|
||||
from PyCrypCli.game_objects import Wallet, ShopCategory, ShopProduct
|
||||
from PyCrypCli.util import strip_float, print_tree
|
||||
from .command import command, CommandError
|
||||
from .help import print_help
|
||||
from .morphcoin import get_wallet_from_file
|
||||
from ..client import Client
|
||||
from ..context import DeviceContext
|
||||
from ..exceptions import ItemNotFoundError, NotEnoughCoinsError
|
||||
from ..models import Wallet, ShopCategory, ShopProduct
|
||||
from ..util import strip_float, print_tree
|
||||
|
||||
|
||||
def list_shop_products(client: Client) -> Dict[str, ShopProduct]:
|
||||
def list_shop_products(client: Client) -> dict[str, ShopProduct]:
|
||||
out = {}
|
||||
for category in ShopCategory.shop_list(client):
|
||||
for subcategory in category.subcategories:
|
||||
|
@ -22,7 +22,7 @@ def list_shop_products(client: Client) -> Dict[str, ShopProduct]:
|
|||
|
||||
|
||||
@command("shop", [DeviceContext])
|
||||
def handle_shop(context: DeviceContext, args: List[str]):
|
||||
def handle_shop(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Buy new hardware and more in the shop
|
||||
"""
|
||||
|
@ -33,12 +33,12 @@ def handle_shop(context: DeviceContext, args: List[str]):
|
|||
|
||||
|
||||
@handle_shop.subcommand("list")
|
||||
def handle_shop_list(context: DeviceContext, _):
|
||||
def handle_shop_list(context: DeviceContext, _: Any) -> None:
|
||||
"""
|
||||
List shop prodcuts
|
||||
"""
|
||||
|
||||
categories: List[ShopCategory] = ShopCategory.shop_list(context.client)
|
||||
categories: list[ShopCategory] = ShopCategory.shop_list(context.client)
|
||||
maxlength = max(
|
||||
*[len(item.name) + 4 for category in categories for item in category.items],
|
||||
*[
|
||||
|
@ -52,7 +52,7 @@ def handle_shop_list(context: DeviceContext, _):
|
|||
for category in categories:
|
||||
category_tree = []
|
||||
for subcategory in category.subcategories:
|
||||
subcategory_tree = [
|
||||
subcategory_tree: list[tuple[str, list[Any]]] = [
|
||||
(item.name.ljust(maxlength) + strip_float(item.price / 1000, 3) + " MC", [])
|
||||
for item in subcategory.items
|
||||
]
|
||||
|
@ -68,7 +68,7 @@ def handle_shop_list(context: DeviceContext, _):
|
|||
|
||||
|
||||
@handle_shop.subcommand("buy")
|
||||
def handle_shop_buy(context: DeviceContext, args: List[str]):
|
||||
def handle_shop_buy(context: DeviceContext, args: list[str]) -> None:
|
||||
"""
|
||||
Buy something in the shop
|
||||
"""
|
||||
|
@ -80,21 +80,21 @@ def handle_shop_buy(context: DeviceContext, args: List[str]):
|
|||
|
||||
wallet: Wallet = get_wallet_from_file(context, wallet_filepath)
|
||||
|
||||
shop_products: Dict[str, ShopProduct] = list_shop_products(context.client)
|
||||
shop_products: dict[str, ShopProduct] = list_shop_products(context.client)
|
||||
if product_name not in shop_products:
|
||||
raise CommandError("This product does not exist in the shop.")
|
||||
product: ShopProduct = shop_products[product_name]
|
||||
|
||||
try:
|
||||
product.buy(wallet)
|
||||
except ItemNotFoundException:
|
||||
except ItemNotFoundError:
|
||||
raise CommandError("This product does not exist in the shop.")
|
||||
except NotEnoughCoinsException:
|
||||
except NotEnoughCoinsError:
|
||||
raise CommandError("You don't have enough coins on your wallet to buy this product.")
|
||||
|
||||
|
||||
@handle_shop_buy.completer()
|
||||
def shop_completer(context: DeviceContext, args: List[str]) -> List[str]:
|
||||
def shop_completer(context: DeviceContext, args: list[str]) -> list[str]:
|
||||
if len(args) == 1:
|
||||
return list(list_shop_products(context.client))
|
||||
if len(args) == 2:
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
from PyCrypCli.commands import command
|
||||
from PyCrypCli.context import MainContext, DeviceContext, LoginContext, Context
|
||||
from typing import Any
|
||||
|
||||
from .command import command
|
||||
from ..context import MainContext, DeviceContext, LoginContext, Context
|
||||
|
||||
|
||||
@command("whoami", [MainContext, DeviceContext])
|
||||
def handle_whoami(context: MainContext, *_):
|
||||
def handle_whoami(context: MainContext, _: Any) -> None:
|
||||
"""
|
||||
Print the name of the current user
|
||||
"""
|
||||
|
@ -12,13 +14,13 @@ def handle_whoami(context: MainContext, *_):
|
|||
|
||||
|
||||
@command("status", [LoginContext, MainContext, DeviceContext])
|
||||
def handle_status(context: Context, _):
|
||||
def handle_status(context: Context, _: Any) -> None:
|
||||
"""
|
||||
Indicate how many players are online
|
||||
"""
|
||||
|
||||
if type(context) is LoginContext:
|
||||
online: int = context.client.status()["online"]
|
||||
online: int = context.client.status().online
|
||||
else:
|
||||
online: int = context.client.info()["online"]
|
||||
online = context.client.info().online
|
||||
print(f"Online players: {online}")
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
from PyCrypCli.context.context import Context, COMMAND_FUNCTION, COMPLETER_FUNCTION
|
||||
from PyCrypCli.context.root_context import RootContext
|
||||
from PyCrypCli.context.login_context import LoginContext
|
||||
from PyCrypCli.context.main_context import MainContext
|
||||
from PyCrypCli.context.device_context import DeviceContext
|
||||
from .context import Context, COMMAND_FUNCTION, COMPLETER_FUNCTION, ContextType
|
||||
from .device_context import DeviceContext
|
||||
from .login_context import LoginContext
|
||||
from .main_context import MainContext
|
||||
from .root_context import RootContext
|
||||
|
||||
__all__ = [
|
||||
"Context",
|
||||
"COMMAND_FUNCTION",
|
||||
"COMPLETER_FUNCTION",
|
||||
"RootContext",
|
||||
"ContextType",
|
||||
"DeviceContext",
|
||||
"LoginContext",
|
||||
"MainContext",
|
||||
"DeviceContext",
|
||||
"RootContext",
|
||||
]
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
from typing import Optional, List, Callable, Dict, TYPE_CHECKING
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Callable, TYPE_CHECKING, TypeVar
|
||||
|
||||
import readline
|
||||
from pypresence import PyPresenceException
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from ..client import Client
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyCrypCli.commands import Command
|
||||
from PyCrypCli.context import RootContext
|
||||
from .root_context import RootContext
|
||||
from ..commands import Command
|
||||
|
||||
|
||||
class Context:
|
||||
def __init__(self, root_context: "RootContext"):
|
||||
self.root_context: "RootContext" = root_context
|
||||
def __init__(self, root_context: RootContext):
|
||||
self.root_context: RootContext = root_context
|
||||
|
||||
self.override_completions: Optional[List[str]] = None
|
||||
self.history: List[str] = []
|
||||
self.override_completions: list[str] | None = None
|
||||
self.history: list[str] = []
|
||||
|
||||
def add_to_history(self, command: str):
|
||||
def add_to_history(self, command: str) -> None:
|
||||
if self.history[-1:] != [command]:
|
||||
self.history.append(command)
|
||||
|
||||
|
@ -29,18 +31,20 @@ class Context:
|
|||
def client(self) -> Client:
|
||||
return self.root_context.client
|
||||
|
||||
def get_commands(self) -> Dict[str, "Command"]:
|
||||
def get_commands(self) -> dict[str, Command]:
|
||||
return self.root_context.get_commands()
|
||||
|
||||
def ask(self, prompt: str, options: List[str]) -> str:
|
||||
def ask(self, prompt: str, options: list[str]) -> str:
|
||||
while True:
|
||||
try:
|
||||
choice: str = self.input_no_history(prompt, options).strip()
|
||||
except (KeyboardInterrupt, EOFError):
|
||||
print("\nEnter one of the following:", ", ".join(options))
|
||||
continue
|
||||
|
||||
if choice in options:
|
||||
return choice
|
||||
|
||||
print(f"'{choice}' is not one of the following:", ", ".join(options))
|
||||
|
||||
def confirm(self, question: str) -> bool:
|
||||
|
@ -48,63 +52,60 @@ class Context:
|
|||
|
||||
def update_presence(
|
||||
self,
|
||||
state: str = None,
|
||||
details: str = None,
|
||||
start: int = None,
|
||||
end: int = None,
|
||||
large_image: str = None,
|
||||
large_text: str = None,
|
||||
):
|
||||
state: str | None = None,
|
||||
details: str | None = None,
|
||||
start: int | None = None,
|
||||
end: int | None = None,
|
||||
large_image: str | None = None,
|
||||
large_text: str | None = None,
|
||||
) -> None:
|
||||
if self.root_context.presence is None:
|
||||
return
|
||||
|
||||
try:
|
||||
self.root_context.presence.update(
|
||||
state=state,
|
||||
details=details,
|
||||
start=start,
|
||||
end=end,
|
||||
large_image=large_image,
|
||||
large_text=large_text,
|
||||
state=state, details=details, start=start, end=end, large_image=large_image, large_text=large_text
|
||||
)
|
||||
except PyPresenceException:
|
||||
pass
|
||||
|
||||
def enter_context(self):
|
||||
def enter_context(self) -> None:
|
||||
readline.clear_history()
|
||||
|
||||
def leave_context(self):
|
||||
def leave_context(self) -> None:
|
||||
pass
|
||||
|
||||
def reenter_context(self):
|
||||
def reenter_context(self) -> None:
|
||||
self.restore_history()
|
||||
|
||||
def input_no_history(self, prompt: str, override_completions: Optional[List[str]] = None) -> Optional[str]:
|
||||
def input_no_history(self, prompt: str, override_completions: list[str] | None = None) -> str:
|
||||
readline.clear_history()
|
||||
old_override: List[str] = self.override_completions
|
||||
self.override_completions: List[str] = override_completions or []
|
||||
old_override = self.override_completions
|
||||
self.override_completions = override_completions or []
|
||||
try:
|
||||
return input(prompt)
|
||||
finally:
|
||||
self.restore_history()
|
||||
self.override_completions = old_override
|
||||
|
||||
def restore_history(self):
|
||||
def restore_history(self) -> None:
|
||||
readline.clear_history()
|
||||
for command in self.history:
|
||||
readline.add_history(command)
|
||||
|
||||
def open(self, context: "Context"):
|
||||
def open(self, context: Context) -> None:
|
||||
self.root_context.open(context)
|
||||
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
self.root_context.close()
|
||||
|
||||
def loop_tick(self):
|
||||
def loop_tick(self) -> None:
|
||||
pass
|
||||
|
||||
def before_command(self) -> bool:
|
||||
return True
|
||||
|
||||
|
||||
COMMAND_FUNCTION = Callable[[Context, List[str]], None]
|
||||
COMPLETER_FUNCTION = Callable[[Context, List[str]], List[str]]
|
||||
ContextType = TypeVar("ContextType", bound=Context)
|
||||
COMMAND_FUNCTION = Callable[[ContextType, list[str]], None]
|
||||
COMPLETER_FUNCTION = Callable[[ContextType, list[str]], list[str]]
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
from typing import Optional, Tuple, List
|
||||
|
||||
import readline
|
||||
|
||||
from PyCrypCli.context.context import Context
|
||||
from PyCrypCli.context.main_context import MainContext
|
||||
from PyCrypCli.context.root_context import RootContext
|
||||
from PyCrypCli.exceptions import FileNotFoundException, InvalidWalletFile
|
||||
from PyCrypCli.game_objects import Device, File, Service, PublicService
|
||||
from PyCrypCli.util import extract_wallet
|
||||
from .context import Context
|
||||
from .main_context import MainContext
|
||||
from .root_context import RootContext
|
||||
from ..exceptions import InvalidWalletFileError
|
||||
from ..models import Device, File, Service, PublicService
|
||||
from ..util import extract_wallet
|
||||
|
||||
|
||||
class DeviceContext(MainContext):
|
||||
|
@ -16,37 +14,38 @@ class DeviceContext(MainContext):
|
|||
|
||||
self.host: Device = device
|
||||
self.pwd: File = self.get_root_dir()
|
||||
self.last_portscan: Optional[Tuple[str, List[Service]]] = None
|
||||
self.last_portscan: tuple[str, list[PublicService]] | None = None
|
||||
|
||||
def update_pwd(self):
|
||||
if self.pwd.uuid is not None:
|
||||
def update_pwd(self) -> None:
|
||||
if self.pwd.uuid:
|
||||
self.pwd = self.host.get_file(self.pwd.uuid)
|
||||
|
||||
def is_localhost(self):
|
||||
return self.user_uuid == self.host.owner
|
||||
def is_localhost(self) -> bool:
|
||||
return self.user_uuid == self.host.owner_uuid
|
||||
|
||||
@property
|
||||
def prompt(self) -> str:
|
||||
self.update_pwd()
|
||||
if self.is_localhost():
|
||||
color: str = "\033[38;2;100;221;23m"
|
||||
color = "\033[38;2;100;221;23m"
|
||||
else:
|
||||
color: str = "\033[38;2;255;64;23m"
|
||||
color = "\033[38;2;255;64;23m"
|
||||
return f"{color}[{self.username}@{self.host.name}:{self.file_to_path(self.pwd)}]$\033[0m "
|
||||
|
||||
def loop_tick(self):
|
||||
def loop_tick(self) -> None:
|
||||
super().loop_tick()
|
||||
|
||||
self.host.update()
|
||||
self.check_device_permission()
|
||||
|
||||
def check_device_permission(self) -> bool:
|
||||
if self.host.owner != self.user_uuid and all(
|
||||
service.device != self.host.uuid for service in Service.list_part_owner(self.client)
|
||||
if self.host.owner_uuid != self.user_uuid and all(
|
||||
service.device_uuid != self.host.uuid for service in Service.list_part_owner(self.client)
|
||||
):
|
||||
print("You don't have access to this device anymore.")
|
||||
self.close()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def check_powered_on(self) -> bool:
|
||||
|
@ -54,22 +53,23 @@ class DeviceContext(MainContext):
|
|||
print("This device is not powered on.")
|
||||
self.close()
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def before_command(self) -> bool:
|
||||
return self.check_device_permission() and self.check_powered_on()
|
||||
|
||||
def enter_context(self):
|
||||
def enter_context(self) -> None:
|
||||
Context.enter_context(self)
|
||||
|
||||
def leave_context(self):
|
||||
def leave_context(self) -> None:
|
||||
pass
|
||||
|
||||
def reenter_context(self):
|
||||
def reenter_context(self) -> None:
|
||||
Context.reenter_context(self)
|
||||
|
||||
def get_files(self, directory: str) -> List[File]:
|
||||
return self.host.get_files(directory)
|
||||
def get_files(self, parent_dir_uuid: str | None) -> list[File]:
|
||||
return self.host.get_files(parent_dir_uuid)
|
||||
|
||||
def get_parent_dir(self, file: File) -> File:
|
||||
if file.parent_dir_uuid is None:
|
||||
|
@ -77,51 +77,45 @@ class DeviceContext(MainContext):
|
|||
return self.host.get_file(file.parent_dir_uuid)
|
||||
|
||||
def get_root_dir(self) -> File:
|
||||
return File(self.client, {"device": self.host.uuid, "is_directory": True})
|
||||
return File.get_root_directory(self.client, self.host.uuid)
|
||||
|
||||
def get_file(self, filename: str, directory: str) -> Optional[File]:
|
||||
files: List[File] = self.get_files(directory)
|
||||
def get_file(self, filename: str, directory_uuid: str | None) -> File | None:
|
||||
files: list[File] = self.get_files(directory_uuid)
|
||||
for file in files:
|
||||
if file.filename == filename:
|
||||
if file.name == filename:
|
||||
return file
|
||||
return None
|
||||
|
||||
def get_filenames(self, directory: str) -> List[str]:
|
||||
return [file.filename for file in self.get_files(directory)]
|
||||
def get_filenames(self, directory: str) -> list[str]:
|
||||
return [file.name for file in self.get_files(directory)]
|
||||
|
||||
def get_wallet_credentials_from_file(self, filepath: str) -> Tuple[str, str]:
|
||||
file: File = self.path_to_file(filepath)
|
||||
def get_wallet_credentials_from_file(self, filepath: str) -> tuple[str, str]:
|
||||
file: File | None = self.path_to_file(filepath)
|
||||
if file is None:
|
||||
raise FileNotFoundException
|
||||
raise FileNotFoundError
|
||||
|
||||
wallet: Optional[Tuple[str, str]] = extract_wallet(file.content)
|
||||
wallet: tuple[str, str] | None = extract_wallet(file.content)
|
||||
if wallet is None:
|
||||
raise InvalidWalletFile
|
||||
raise InvalidWalletFileError
|
||||
|
||||
return wallet
|
||||
|
||||
def get_last_portscan(self) -> Tuple[str, List[PublicService]]:
|
||||
return self.last_portscan
|
||||
|
||||
def update_last_portscan(self, scan: Tuple[str, List[PublicService]]):
|
||||
self.last_portscan: Tuple[str, List[PublicService]] = scan
|
||||
|
||||
def file_path_completer(self, path: str, dirs_only: bool = False) -> List[str]:
|
||||
def file_path_completer(self, path: str, dirs_only: bool = False) -> list[str]:
|
||||
base_path: str = "/".join(path.split("/")[:-1])
|
||||
if path.startswith("/"):
|
||||
base_path: str = "/" + base_path
|
||||
base_dir: Optional[File] = self.path_to_file(base_path)
|
||||
base_path = "/" + base_path
|
||||
base_dir: File | None = self.path_to_file(base_path)
|
||||
if base_dir is None or not base_dir.is_directory:
|
||||
return []
|
||||
if base_path:
|
||||
readline.set_completer_delims("/")
|
||||
return [
|
||||
file.filename + "/\0" * file.is_directory
|
||||
file.name + "/\0" * file.is_directory
|
||||
for file in self.get_files(base_dir.uuid)
|
||||
if file.is_directory or not dirs_only
|
||||
] + ["./\0", "../\0"] * path.split("/")[-1].startswith(".")
|
||||
|
||||
def path_to_file(self, path: str) -> Optional[File]:
|
||||
def path_to_file(self, path: str) -> File | None:
|
||||
pwd: File = self.get_root_dir() if path.startswith("/") else self.pwd
|
||||
for file_name in path.split("/"):
|
||||
if not file_name or file_name == ".":
|
||||
|
@ -129,15 +123,16 @@ class DeviceContext(MainContext):
|
|||
if file_name == "..":
|
||||
pwd = self.get_parent_dir(pwd)
|
||||
else:
|
||||
pwd: Optional[File] = self.get_file(file_name, pwd.uuid)
|
||||
if pwd is None:
|
||||
if not (file := self.get_file(file_name, pwd.uuid)):
|
||||
return None
|
||||
pwd = file
|
||||
return pwd
|
||||
|
||||
def file_to_path(self, file: Optional[File]) -> str:
|
||||
if file.uuid is None:
|
||||
def file_to_path(self, file: File) -> str:
|
||||
if file.is_root_directory:
|
||||
return "/"
|
||||
path: List[File] = [file]
|
||||
|
||||
path: list[File] = [file]
|
||||
while path[-1].parent_dir_uuid is not None:
|
||||
path.append(self.host.get_file(path[-1].parent_dir_uuid))
|
||||
return "/" + "/".join(f.filename for f in path[::-1])
|
||||
return "/" + "/".join(f.name for f in path[::-1])
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import time
|
||||
|
||||
from PyCrypCli.context.context import Context
|
||||
from PyCrypCli.context.root_context import RootContext
|
||||
from PyCrypCli.exceptions import InvalidSessionTokenException
|
||||
from .context import Context
|
||||
from .root_context import RootContext
|
||||
from ..exceptions import InvalidSessionTokenError
|
||||
from ..models import Config
|
||||
|
||||
|
||||
class LoginContext(Context):
|
||||
|
@ -13,37 +14,36 @@ class LoginContext(Context):
|
|||
def prompt(self) -> str:
|
||||
return "$ "
|
||||
|
||||
def enter_context(self):
|
||||
def enter_context(self) -> None:
|
||||
self.login_loop_presence()
|
||||
|
||||
try:
|
||||
self.load_session()
|
||||
except InvalidSessionTokenException:
|
||||
except InvalidSessionTokenError:
|
||||
self.delete_session()
|
||||
print("You are not logged in.")
|
||||
print("Type `register` to create a new account or `login` if you already have one.")
|
||||
|
||||
def reenter_context(self):
|
||||
def reenter_context(self) -> None:
|
||||
super().reenter_context()
|
||||
self.login_loop_presence()
|
||||
|
||||
def load_session(self):
|
||||
def load_session(self) -> None:
|
||||
from PyCrypCli.context.main_context import MainContext
|
||||
|
||||
config: dict = self.root_context.read_config_file()
|
||||
config: Config = self.root_context.read_config_file()
|
||||
|
||||
if "token" in config.setdefault("servers", {}).setdefault(self.root_context.host, {}):
|
||||
session_token: str = config["servers"][self.root_context.host]["token"]
|
||||
self.client.session(session_token)
|
||||
self.open(MainContext(self.root_context, session_token))
|
||||
if (server_config := config.servers.get(self.root_context.host)) and server_config.token:
|
||||
self.client.session(server_config.token)
|
||||
self.open(MainContext(self.root_context, server_config.token))
|
||||
|
||||
def delete_session(self):
|
||||
config: dict = self.root_context.read_config_file()
|
||||
if "token" in config.setdefault("servers", {}).setdefault(self.root_context.host, {}):
|
||||
del config["servers"][self.root_context.host]["token"]
|
||||
def delete_session(self) -> None:
|
||||
config: Config = self.root_context.read_config_file()
|
||||
if server_config := config.servers.get(self.root_context.host):
|
||||
server_config.token = None
|
||||
self.root_context.write_config_file(config)
|
||||
|
||||
def login_loop_presence(self):
|
||||
def login_loop_presence(self) -> None:
|
||||
self.update_presence(
|
||||
state=f"Server: {self.root_context.host}",
|
||||
details="Logging in",
|
||||
|
|
|
@ -1,35 +1,34 @@
|
|||
import time
|
||||
from typing import Optional, Tuple, List
|
||||
|
||||
from PyCrypCli.context.context import Context
|
||||
from PyCrypCli.context.login_context import LoginContext
|
||||
from PyCrypCli.context.root_context import RootContext
|
||||
from PyCrypCli.exceptions import InvalidWalletFile
|
||||
from PyCrypCli.game_objects import Wallet, Device, Service
|
||||
from PyCrypCli.util import extract_wallet
|
||||
from .context import Context
|
||||
from .login_context import LoginContext
|
||||
from .root_context import RootContext
|
||||
from ..exceptions import InvalidWalletFileError, LoggedOutError
|
||||
from ..models import Wallet, Device, Service, Config, ServerConfig, InfoResponse
|
||||
from ..util import extract_wallet
|
||||
|
||||
|
||||
class MainContext(LoginContext):
|
||||
def __init__(self, root_context: RootContext, session_token: str):
|
||||
super().__init__(root_context)
|
||||
|
||||
self.username: Optional[str] = None
|
||||
self.user_uuid: Optional[str] = None
|
||||
self.session_token: Optional[str] = session_token
|
||||
self.username: str | None = None
|
||||
self.user_uuid: str | None = None
|
||||
self.session_token: str | None = session_token
|
||||
|
||||
@property
|
||||
def prompt(self) -> str:
|
||||
return f"\033[38;2;53;160;171m[{self.username}]$\033[0m "
|
||||
|
||||
def update_user_info(self):
|
||||
info: dict = self.root_context.client.info()
|
||||
self.username: str = info["name"]
|
||||
self.user_uuid: str = info["uuid"]
|
||||
def update_user_info(self) -> None:
|
||||
info: InfoResponse = self.root_context.client.info()
|
||||
self.username = info.name
|
||||
self.user_uuid = info.uuid
|
||||
|
||||
def loop_tick(self):
|
||||
def loop_tick(self) -> None:
|
||||
self.update_user_info()
|
||||
|
||||
def enter_context(self):
|
||||
def enter_context(self) -> None:
|
||||
Context.enter_context(self)
|
||||
|
||||
self.update_user_info()
|
||||
|
@ -39,27 +38,30 @@ class MainContext(LoginContext):
|
|||
|
||||
print(f"Logged in as {self.username}.")
|
||||
|
||||
def leave_context(self):
|
||||
def leave_context(self) -> None:
|
||||
if self.client.logged_in:
|
||||
self.client.logout()
|
||||
self.delete_session()
|
||||
print("Logged out.")
|
||||
|
||||
def reenter_context(self):
|
||||
def reenter_context(self) -> None:
|
||||
Context.reenter_context(self)
|
||||
|
||||
self.main_loop_presence()
|
||||
|
||||
def save_session(self):
|
||||
config: dict = self.root_context.read_config_file()
|
||||
config.setdefault("servers", {}).setdefault(self.root_context.host, {})["token"] = self.session_token
|
||||
def save_session(self) -> None:
|
||||
if not self.session_token:
|
||||
raise LoggedOutError
|
||||
|
||||
config: Config = self.root_context.read_config_file()
|
||||
config.servers[self.root_context.host] = ServerConfig(token=self.session_token)
|
||||
self.root_context.write_config_file(config)
|
||||
|
||||
def delete_session(self):
|
||||
def delete_session(self) -> None:
|
||||
super().delete_session()
|
||||
self.session_token = None
|
||||
|
||||
def main_loop_presence(self):
|
||||
def main_loop_presence(self) -> None:
|
||||
self.update_presence(
|
||||
state=f"Logged in: {self.username}@{self.root_context.host}",
|
||||
details="in Cryptic Terminal",
|
||||
|
@ -69,12 +71,12 @@ class MainContext(LoginContext):
|
|||
)
|
||||
|
||||
def extract_wallet(self, content: str) -> Wallet:
|
||||
wallet: Optional[Tuple[str, str]] = extract_wallet(content)
|
||||
wallet: tuple[str, str] | None = extract_wallet(content)
|
||||
if wallet is None:
|
||||
raise InvalidWalletFile
|
||||
raise InvalidWalletFileError
|
||||
return Wallet.get_wallet(self.client, *wallet)
|
||||
|
||||
def get_hacked_devices(self) -> List[Device]:
|
||||
def get_hacked_devices(self) -> list[Device]:
|
||||
return list(
|
||||
{Device.get_device(self.client, service.device) for service in Service.list_part_owner(self.client)},
|
||||
{Device.get_device(self.client, service.device_uuid) for service in Service.list_part_owner(self.client)}
|
||||
)
|
||||
|
|
|
@ -1,32 +1,34 @@
|
|||
import json
|
||||
import os
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import List, Dict, Type, Optional, TYPE_CHECKING
|
||||
from pathlib import Path
|
||||
from typing import Type, TYPE_CHECKING
|
||||
|
||||
from pypresence import Presence, PyPresenceException
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.context.context import Context
|
||||
from .context import Context
|
||||
from ..client import Client
|
||||
from ..exceptions import InvalidServerURLError
|
||||
from ..models import Config
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyCrypCli.commands import Command
|
||||
from ..commands import Command
|
||||
|
||||
|
||||
class RootContext:
|
||||
def __init__(
|
||||
self,
|
||||
server: str,
|
||||
config_file: List[str],
|
||||
commands: Dict[Type[Context], Dict[str, "Command"]],
|
||||
):
|
||||
def __init__(self, server: str, config_file: Path, commands: dict[Type[Context], dict[str, Command]]):
|
||||
self.client: Client = Client(server)
|
||||
self.host: str = re.match(r"^wss?://(.+)$", server).group(1).split("/")[0]
|
||||
|
||||
self.config_file: List[str] = config_file
|
||||
if not (match := re.match(r"^wss?://(.+)$", server)):
|
||||
raise InvalidServerURLError
|
||||
|
||||
self.context_stack: List[Context] = []
|
||||
self.host: str = match.group(1).split("/")[0]
|
||||
|
||||
self.commands: Dict[Type[Context], Dict[str, "Command"]] = commands
|
||||
self.config_file: Path = config_file
|
||||
|
||||
self.context_stack: list[Context] = []
|
||||
|
||||
self.commands: dict[Type[Context], dict[str, Command]] = commands
|
||||
|
||||
try:
|
||||
self.presence: Presence = Presence(client_id="596676243144048640")
|
||||
|
@ -34,35 +36,29 @@ class RootContext:
|
|||
except (PyPresenceException, FileNotFoundError, ConnectionRefusedError):
|
||||
self.presence = None
|
||||
|
||||
def open(self, context: "Context"):
|
||||
def open(self, context: Context) -> None:
|
||||
self.context_stack.append(context)
|
||||
context.enter_context()
|
||||
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
self.context_stack.pop().leave_context()
|
||||
self.get_context().reenter_context()
|
||||
|
||||
def get_context(self) -> "Context":
|
||||
return self.context_stack[-1]
|
||||
|
||||
def get_override_completions(self) -> Optional[List[str]]:
|
||||
def get_override_completions(self) -> list[str] | None:
|
||||
return self.get_context().override_completions
|
||||
|
||||
def get_commands(self) -> Dict[str, "Command"]:
|
||||
def get_commands(self) -> dict[str, Command]:
|
||||
return self.commands[type(self.get_context())]
|
||||
|
||||
def read_config_file(self) -> dict:
|
||||
for i in range(1, len(self.config_file)):
|
||||
path: str = os.path.join(*self.config_file[:i])
|
||||
if not os.path.exists(path):
|
||||
os.mkdir(path)
|
||||
path: str = os.path.join(*self.config_file)
|
||||
if not os.path.exists(path):
|
||||
self.write_config_file({"servers": {}})
|
||||
return json.load(open(path))
|
||||
def read_config_file(self) -> Config:
|
||||
if not self.config_file.exists():
|
||||
self.write_config_file(Config.get_default_config())
|
||||
|
||||
def write_config_file(self, config: dict):
|
||||
path: str = os.path.join(*self.config_file)
|
||||
with open(path, "w") as file:
|
||||
json.dump(config, file)
|
||||
file.flush()
|
||||
return Config.parse_raw(self.config_file.read_text())
|
||||
|
||||
def write_config_file(self, config: Config) -> None:
|
||||
self.config_file.parent.mkdir(parents=True, exist_ok=True)
|
||||
self.config_file.write_text(config.json())
|
||||
|
|
|
@ -1,275 +1,278 @@
|
|||
import json
|
||||
from typing import Optional
|
||||
from typing import Any
|
||||
|
||||
|
||||
class CommandRegistrationException(Exception):
|
||||
class ClientNotReadyError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class InvalidServerURLError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CommandRegistrationError(Exception):
|
||||
def __init__(self, name: str, subcommand: bool = False):
|
||||
super().__init__(f"The {'sub' * subcommand}command {name} has already been registered.")
|
||||
|
||||
|
||||
class NoDocStringException(Exception):
|
||||
class NoDocStringError(Exception):
|
||||
def __init__(self, name: str, subcommand: bool = False):
|
||||
super().__init__(f"The {'sub' * subcommand}command {name} is missing a docstring.")
|
||||
|
||||
|
||||
class LoggedInException(Exception):
|
||||
def __init__(self):
|
||||
class LoggedInError(Exception):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Endpoint cannot be used while client is logged in.")
|
||||
|
||||
|
||||
class LoggedOutException(Exception):
|
||||
def __init__(self):
|
||||
class LoggedOutError(Exception):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Endpoint can only be used while client is logged in.")
|
||||
|
||||
|
||||
class InvalidServerResponseException(Exception):
|
||||
def __init__(self, response: dict):
|
||||
class InvalidServerResponseError(Exception):
|
||||
def __init__(self, response: dict[Any, Any]):
|
||||
super().__init__("Invalid Server Response: " + json.dumps(response))
|
||||
|
||||
|
||||
class InvalidSessionTokenException(Exception):
|
||||
def __init__(self):
|
||||
class InvalidSessionTokenError(Exception):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Invalid session token")
|
||||
|
||||
|
||||
class WeakPasswordException(Exception):
|
||||
def __init__(self):
|
||||
class WeakPasswordError(Exception):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Password is too weak")
|
||||
|
||||
|
||||
class UsernameAlreadyExistsException(Exception):
|
||||
def __init__(self):
|
||||
class UsernameAlreadyExistsError(Exception):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Username already exists")
|
||||
|
||||
|
||||
class InvalidLoginException(Exception):
|
||||
def __init__(self):
|
||||
class InvalidLoginError(Exception):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Invalid Login Credentials")
|
||||
|
||||
|
||||
class PermissionsDeniedException(Exception):
|
||||
def __init__(self):
|
||||
super().__init__("Permissions Denied")
|
||||
|
||||
|
||||
class InvalidWalletFile(Exception):
|
||||
def __init__(self):
|
||||
class InvalidWalletFileError(Exception):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("Invalid wallet file")
|
||||
|
||||
|
||||
class UnknownMicroserviceException(Exception):
|
||||
class UnknownMicroserviceError(Exception):
|
||||
def __init__(self, ms: str):
|
||||
super().__init__("Unknown Microservice: " + ms)
|
||||
|
||||
|
||||
class MicroserviceException(Exception):
|
||||
error: str = None
|
||||
error: str | None = None
|
||||
|
||||
def __init__(self, error: Optional[str] = None, args: Optional[list] = None):
|
||||
def __init__(self, error: str | None = None, args: list[Any] | None = None):
|
||||
super().__init__(error or "")
|
||||
self.error = error
|
||||
self.params = args
|
||||
|
||||
|
||||
class InternalErrorException(MicroserviceException):
|
||||
class InternalError(MicroserviceException):
|
||||
error: str = "internal error"
|
||||
|
||||
|
||||
class NoResponseTimeoutException(MicroserviceException):
|
||||
class NoResponseTimeoutError(MicroserviceException):
|
||||
error: str = "no response - timeout"
|
||||
|
||||
|
||||
class InvalidRequestException(MicroserviceException):
|
||||
class InvalidRequestError(MicroserviceException):
|
||||
error: str = "invalid_request"
|
||||
|
||||
|
||||
class AlreadyOwnADeviceException(MicroserviceException):
|
||||
class AlreadyOwnADeviceError(MicroserviceException):
|
||||
error: str = "already_own_a_device"
|
||||
|
||||
|
||||
class PermissionDeniedException(MicroserviceException):
|
||||
class PermissionDeniedError(MicroserviceException):
|
||||
error: str = "permission_denied"
|
||||
|
||||
|
||||
class DeviceNotFoundException(MicroserviceException):
|
||||
class DeviceNotFoundError(MicroserviceException):
|
||||
error: str = "device_not_found"
|
||||
|
||||
|
||||
class DevicePoweredOffException(MicroserviceException):
|
||||
class DevicePoweredOffError(MicroserviceException):
|
||||
error: str = "device_powered_off"
|
||||
|
||||
|
||||
class DeviceNotOnlineException(MicroserviceException):
|
||||
class DeviceNotOnlineError(MicroserviceException):
|
||||
error: str = "device_not_online"
|
||||
|
||||
|
||||
class DeviceIsStarterDeviceException(MicroserviceException):
|
||||
class DeviceIsStarterDeviceError(MicroserviceException):
|
||||
error: str = "device_is_starter_device"
|
||||
|
||||
|
||||
class MaximumDevicesReachedException(MicroserviceException):
|
||||
class MaximumDevicesReachedError(MicroserviceException):
|
||||
error: str = "maximum_devices_reached"
|
||||
|
||||
|
||||
class ElementPartNotFoundException(MicroserviceException):
|
||||
class ElementPartNotFoundError(MicroserviceException):
|
||||
error: str = "element_(.+)_not_found"
|
||||
|
||||
|
||||
class PartNotInInventoryException(MicroserviceException):
|
||||
class PartNotInInventoryError(MicroserviceException):
|
||||
error: str = "(.+)_not_in_inventory"
|
||||
|
||||
|
||||
class MissingPartException(MicroserviceException):
|
||||
class MissingPartError(MicroserviceException):
|
||||
error: str = "missing_(.+)"
|
||||
|
||||
|
||||
class IncompatibleCPUSocket(MicroserviceException):
|
||||
class IncompatibleCPUSocketError(MicroserviceException):
|
||||
error: str = "incompatible_cpu_socket"
|
||||
|
||||
|
||||
class NotEnoughRAMSlots(MicroserviceException):
|
||||
class NotEnoughRAMSlotsError(MicroserviceException):
|
||||
error: str = "not_enough_ram_slots"
|
||||
|
||||
|
||||
class IncompatibleRAMTypes(MicroserviceException):
|
||||
class IncompatibleRAMTypesError(MicroserviceException):
|
||||
error: str = "incompatible_ram_types"
|
||||
|
||||
|
||||
class IncompatibleDriverInterface(MicroserviceException):
|
||||
class IncompatibleDriverInterfaceError(MicroserviceException):
|
||||
error: str = "incompatible_drive_interface"
|
||||
|
||||
|
||||
class FileNotFoundException(MicroserviceException):
|
||||
class FileNotFoundError(MicroserviceException):
|
||||
error: str = "file_not_found"
|
||||
|
||||
|
||||
class FileNotChangeableException(MicroserviceException):
|
||||
class FileNotChangeableError(MicroserviceException):
|
||||
error: str = "file_not_changeable"
|
||||
|
||||
|
||||
class FileAlreadyExistsException(MicroserviceException):
|
||||
class FileAlreadyExistsError(MicroserviceException):
|
||||
error: str = "file_already_exists"
|
||||
|
||||
|
||||
class ParentDirectoryNotFound(MicroserviceException):
|
||||
class ParentDirectoryNotFoundError(MicroserviceException):
|
||||
error: str = "parent_directory_not_found"
|
||||
|
||||
|
||||
class CanNotMoveDirIntoItselfException(MicroserviceException):
|
||||
class CanNotMoveDirIntoItselfError(MicroserviceException):
|
||||
error: str = "can_not_move_dir_into_itself"
|
||||
|
||||
|
||||
class DirectoriesCanNotBeUpdatedException(MicroserviceException):
|
||||
class DirectoriesCanNotBeUpdatedError(MicroserviceException):
|
||||
error: str = "directories_can_not_be_updated"
|
||||
|
||||
|
||||
class DirectoryCanNotHaveTextContentException(MicroserviceException):
|
||||
class DirectoryCanNotHaveTextContentError(MicroserviceException):
|
||||
error: str = "directory_can_not_have_textcontent"
|
||||
|
||||
|
||||
class AlreadyOwnAWalletException(MicroserviceException):
|
||||
class AlreadyOwnAWalletError(MicroserviceException):
|
||||
error: str = "already_own_a_wallet"
|
||||
|
||||
|
||||
class UnknownSourceOrDestinationException(MicroserviceException):
|
||||
class UnknownSourceOrDestinationError(MicroserviceException):
|
||||
error: str = "unknown_source_or_destination"
|
||||
|
||||
|
||||
class NotEnoughCoinsException(MicroserviceException):
|
||||
class NotEnoughCoinsError(MicroserviceException):
|
||||
error: str = "not_enough_coins"
|
||||
|
||||
|
||||
class AlreadyOwnThisServiceException(MicroserviceException):
|
||||
class AlreadyOwnThisServiceError(MicroserviceException):
|
||||
error: str = "already_own_this_service"
|
||||
|
||||
|
||||
class ServiceNotSupportedException(MicroserviceException):
|
||||
class ServiceNotSupportedError(MicroserviceException):
|
||||
error: str = "service_not_supported"
|
||||
|
||||
|
||||
class ServiceNotRunningException(MicroserviceException):
|
||||
class ServiceNotRunningError(MicroserviceException):
|
||||
error: str = "service_not_running"
|
||||
|
||||
|
||||
class CannotToggleDirectlyException(MicroserviceException):
|
||||
class CannotToggleDirectlyError(MicroserviceException):
|
||||
error: str = "cannot_toggle_directly"
|
||||
|
||||
|
||||
class CouldNotStartService(MicroserviceException):
|
||||
class CouldNotStartServiceError(MicroserviceException):
|
||||
error: str = "could_not_start_service"
|
||||
|
||||
|
||||
class WalletNotFoundException(MicroserviceException):
|
||||
class WalletNotFoundError(MicroserviceException):
|
||||
error: str = "wallet_not_found"
|
||||
|
||||
|
||||
class MinerNotFoundException(MicroserviceException):
|
||||
class MinerNotFoundError(MicroserviceException):
|
||||
error: str = "miner_not_found"
|
||||
|
||||
|
||||
class ServiceNotFoundException(MicroserviceException):
|
||||
class ServiceNotFoundError(MicroserviceException):
|
||||
error: str = "service_not_found"
|
||||
|
||||
|
||||
class UnknownServiceException(MicroserviceException):
|
||||
class UnknownServiceError(MicroserviceException):
|
||||
error: str = "unknown_service"
|
||||
|
||||
|
||||
class ServiceCannotBeUsedException(MicroserviceException):
|
||||
class ServiceCannotBeUsedError(MicroserviceException):
|
||||
error: str = "service_cannot_be_used"
|
||||
|
||||
|
||||
class CannotDeleteEnforcedServiceException(MicroserviceException):
|
||||
class CannotDeleteEnforcedServiceError(MicroserviceException):
|
||||
error: str = "cannot_delete_enforced_service"
|
||||
|
||||
|
||||
class AttackNotRunningException(MicroserviceException):
|
||||
class AttackNotRunningError(MicroserviceException):
|
||||
error: str = "attack_not_running"
|
||||
|
||||
|
||||
class ItemNotFoundException(MicroserviceException):
|
||||
class ItemNotFoundError(MicroserviceException):
|
||||
error: str = "item_not_found"
|
||||
|
||||
|
||||
class CannotTradeWithYourselfException(MicroserviceException):
|
||||
class CannotTradeWithYourselfError(MicroserviceException):
|
||||
error: str = "cannot_trade_with_yourself"
|
||||
|
||||
|
||||
class UserUUIDDoesNotExistException(MicroserviceException):
|
||||
class UserUUIDDoesNotExistError(MicroserviceException):
|
||||
error: str = "user_uuid_does_not_exist"
|
||||
|
||||
|
||||
class NetworkNotFoundException(MicroserviceException):
|
||||
class NetworkNotFoundError(MicroserviceException):
|
||||
error: str = "network_not_found"
|
||||
|
||||
|
||||
class AlreadyMemberOfNetworkException(MicroserviceException):
|
||||
class AlreadyMemberOfNetworkError(MicroserviceException):
|
||||
error: str = "already_member_of_network"
|
||||
|
||||
|
||||
class InvitationAlreadyExistsException(MicroserviceException):
|
||||
class InvitationAlreadyExistsError(MicroserviceException):
|
||||
error: str = "invitation_already_exists"
|
||||
|
||||
|
||||
class CannotLeaveOwnNetworkException(MicroserviceException):
|
||||
class CannotLeaveOwnNetworkError(MicroserviceException):
|
||||
error: str = "cannot_leave_own_network"
|
||||
|
||||
|
||||
class CannotKickOwnerException(MicroserviceException):
|
||||
class CannotKickOwnerError(MicroserviceException):
|
||||
error: str = "cannot_kick_owner"
|
||||
|
||||
|
||||
class MaximumNetworksReachedException(MicroserviceException):
|
||||
class MaximumNetworksReachedError(MicroserviceException):
|
||||
error: str = "maximum_networks_reached"
|
||||
|
||||
|
||||
class InvalidNameException(MicroserviceException):
|
||||
class InvalidNameError(MicroserviceException):
|
||||
error: str = "invalid_name"
|
||||
|
||||
|
||||
class NameAlreadyInUseException(MicroserviceException):
|
||||
class NameAlreadyInUseError(MicroserviceException):
|
||||
error: str = "name_already_in_use"
|
||||
|
||||
|
||||
class NoPermissionsException(MicroserviceException):
|
||||
class NoPermissionsError(MicroserviceException):
|
||||
error: str = "no_permissions"
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
from PyCrypCli.game_objects.device import Device
|
||||
from PyCrypCli.game_objects.device_hardware import DeviceHardware
|
||||
from PyCrypCli.game_objects.file import File
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
from PyCrypCli.game_objects.inventory_element import InventoryElement
|
||||
from PyCrypCli.game_objects.network import Network, NetworkInvitation, NetworkMembership
|
||||
from PyCrypCli.game_objects.resource_usage import ResourceUsage
|
||||
from PyCrypCli.game_objects.service import Service, PublicService, Miner, BruteforceService, PortscanService
|
||||
from PyCrypCli.game_objects.shop import ShopProduct, ShopCategory
|
||||
from PyCrypCli.game_objects.transaction import Transaction
|
||||
from PyCrypCli.game_objects.wallet import Wallet, PublicWallet
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Device",
|
||||
"DeviceHardware",
|
||||
"File",
|
||||
"GameObject",
|
||||
"InventoryElement",
|
||||
"Network",
|
||||
"NetworkMembership",
|
||||
"NetworkInvitation",
|
||||
"ResourceUsage",
|
||||
"Service",
|
||||
"PublicService",
|
||||
"Miner",
|
||||
"BruteforceService",
|
||||
"PortscanService",
|
||||
"ShopProduct",
|
||||
"ShopCategory",
|
||||
"Transaction",
|
||||
"Wallet",
|
||||
"PublicWallet",
|
||||
]
|
|
@ -1,129 +0,0 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.device_hardware import DeviceHardware
|
||||
from PyCrypCli.game_objects.file import File
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
from PyCrypCli.game_objects.network import Network, NetworkInvitation
|
||||
from PyCrypCli.game_objects.resource_usage import ResourceUsage
|
||||
from PyCrypCli.game_objects.service import PublicService, Service, Miner
|
||||
|
||||
|
||||
class Device(GameObject):
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
return self._data.get("uuid")
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._data.get("name")
|
||||
|
||||
@property
|
||||
def owner(self) -> str:
|
||||
return self._data.get("owner")
|
||||
|
||||
@property
|
||||
def powered_on(self) -> bool:
|
||||
return self._data.get("powered_on")
|
||||
|
||||
@staticmethod
|
||||
def get_device(client: Client, device_uuid: str) -> "Device":
|
||||
return Device(client, client.ms("device", ["device", "info"], device_uuid=device_uuid))
|
||||
|
||||
@staticmethod
|
||||
def list_devices(client: Client) -> List["Device"]:
|
||||
return [Device(client, device) for device in client.ms("device", ["device", "all"])["devices"]]
|
||||
|
||||
def update(self):
|
||||
self._update(Device.get_device(self._client, self.uuid))
|
||||
|
||||
@staticmethod
|
||||
def build(client: Client, mainboard: str, cpu: str, gpu: str, ram: List[str], disk: List[str]) -> "Device":
|
||||
return Device(
|
||||
client,
|
||||
client.ms("device", ["device", "create"], motherboard=mainboard, cpu=cpu, gpu=gpu, ram=ram, disk=disk),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def starter_device(client: Client) -> "Device":
|
||||
return Device(client, client.ms("device", ["device", "starter_device"]))
|
||||
|
||||
@staticmethod
|
||||
def spot(client: Client) -> "Device":
|
||||
return Device(client, client.ms("device", ["device", "spot"]))
|
||||
|
||||
def power(self):
|
||||
self._update(self._ms("device", ["device", "power"], device_uuid=self.uuid))
|
||||
|
||||
def change_name(self, name: str):
|
||||
self._update(self._ms("device", ["device", "change_name"], device_uuid=self.uuid, name=name))
|
||||
|
||||
def delete(self):
|
||||
self._ms("device", ["device", "delete"], device_uuid=self.uuid)
|
||||
|
||||
def get_files(self, parent_dir_uuid: Optional[str]) -> List[File]:
|
||||
return [
|
||||
File(self._client, file)
|
||||
for file in self._ms("device", ["file", "all"], device_uuid=self.uuid, parent_dir_uuid=parent_dir_uuid)[
|
||||
"files"
|
||||
]
|
||||
]
|
||||
|
||||
def get_file(self, file_uuid: str) -> File:
|
||||
return File.get_file(self._client, self.uuid, file_uuid)
|
||||
|
||||
def create_file(self, filename: str, content: str, is_directory: bool, parent_dir_uuid: Optional[str]) -> File:
|
||||
return File(
|
||||
self._client,
|
||||
self._ms(
|
||||
"device",
|
||||
["file", "create"],
|
||||
device_uuid=self.uuid,
|
||||
filename=filename,
|
||||
content=content,
|
||||
is_directory=is_directory,
|
||||
parent_dir_uuid=parent_dir_uuid,
|
||||
),
|
||||
)
|
||||
|
||||
def get_public_service(self, service_uuid: str) -> PublicService:
|
||||
return PublicService.get_public_service(self._client, self.uuid, service_uuid)
|
||||
|
||||
def get_services(self) -> List[Service]:
|
||||
return Service.get_services(self._client, self.uuid)
|
||||
|
||||
def get_service(self, service_uuid: str) -> Service:
|
||||
return Service.get_service(self._client, self.uuid, service_uuid)
|
||||
|
||||
def get_service_by_name(self, service: str) -> Service:
|
||||
return Service.get_service_by_name(self._client, self.uuid, service)
|
||||
|
||||
def get_miner(self) -> Miner:
|
||||
return Miner.get_miner(self._client, self.uuid)
|
||||
|
||||
def create_service(self, name: str, **extra) -> Service:
|
||||
return Service(self._client, self._ms("service", ["create"], name=name, device_uuid=self.uuid, **extra))
|
||||
|
||||
def part_owner(self) -> bool:
|
||||
return self._ms("service", ["part_owner"], device_uuid=self.uuid)["ok"]
|
||||
|
||||
def get_hardware(self) -> List[DeviceHardware]:
|
||||
return [
|
||||
DeviceHardware(self._client, dh)
|
||||
for dh in self._ms("device", ["device", "info"], device_uuid=self.uuid)["hardware"]
|
||||
]
|
||||
|
||||
def get_resource_usage(self) -> ResourceUsage:
|
||||
return ResourceUsage(self._client, self._ms("device", ["hardware", "resources"], device_uuid=self.uuid))
|
||||
|
||||
def get_networks(self) -> List[Network]:
|
||||
return [Network(self._client, net) for net in self._ms("network", ["member"], device=self.uuid)["networks"]]
|
||||
|
||||
def create_network(self, name: str, hidden: bool) -> Network:
|
||||
return Network(self._client, self._ms("network", ["create"], device=self.uuid, name=name, hidden=hidden))
|
||||
|
||||
def get_network_invitations(self) -> List[NetworkInvitation]:
|
||||
return [
|
||||
NetworkInvitation(self._client, invitation)
|
||||
for invitation in self._ms("network", ["invitations"], device=self.uuid)["invitations"]
|
||||
]
|
|
@ -1,19 +0,0 @@
|
|||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
|
||||
|
||||
class DeviceHardware(GameObject):
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
return self._data.get("uuid")
|
||||
|
||||
@property
|
||||
def device(self) -> str:
|
||||
return self._data.get("device_uuid")
|
||||
|
||||
@property
|
||||
def hardware_element(self) -> str:
|
||||
return self._data.get("hardware_element")
|
||||
|
||||
@property
|
||||
def hardware_type(self) -> str:
|
||||
return self._data.get("hardware_type")
|
|
@ -1,61 +0,0 @@
|
|||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
|
||||
|
||||
class File(GameObject):
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
return self._data.get("uuid")
|
||||
|
||||
@property
|
||||
def device(self) -> str:
|
||||
return self._data.get("device")
|
||||
|
||||
@property
|
||||
def filename(self) -> str:
|
||||
return self._data.get("filename")
|
||||
|
||||
@property
|
||||
def content(self) -> str:
|
||||
return self._data.get("content")
|
||||
|
||||
@property
|
||||
def is_directory(self) -> bool:
|
||||
return self._data.get("is_directory")
|
||||
|
||||
@property
|
||||
def parent_dir_uuid(self) -> str:
|
||||
return self._data.get("parent_dir_uuid")
|
||||
|
||||
@staticmethod
|
||||
def get_file(client: Client, device_uuid: str, file_uuid: str) -> "File":
|
||||
return File(client, client.ms("device", ["file", "info"], device_uuid=device_uuid, file_uuid=file_uuid))
|
||||
|
||||
def update(self):
|
||||
self._update(File.get_file(self._client, self.device, self.uuid))
|
||||
|
||||
def move(self, new_filename: str, new_parent_dir_uuid: str):
|
||||
self._update(
|
||||
self._ms(
|
||||
"device",
|
||||
["file", "move"],
|
||||
device_uuid=self.device,
|
||||
file_uuid=self.uuid,
|
||||
new_filename=new_filename,
|
||||
new_parent_dir_uuid=new_parent_dir_uuid,
|
||||
),
|
||||
)
|
||||
|
||||
def edit(self, new_content: str):
|
||||
self._update(
|
||||
self._ms(
|
||||
"device",
|
||||
["file", "update"],
|
||||
device_uuid=self.device,
|
||||
file_uuid=self.uuid,
|
||||
content=new_content,
|
||||
),
|
||||
)
|
||||
|
||||
def delete(self):
|
||||
self._ms("device", ["file", "delete"], device_uuid=self.device, file_uuid=self.uuid)
|
|
@ -1,36 +0,0 @@
|
|||
from typing import List, Union, Type, TypeVar
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class GameObject:
|
||||
def __init__(self, client: Client, data: dict):
|
||||
self._client: Client = client
|
||||
self._data: dict = data
|
||||
|
||||
def __repr__(self):
|
||||
out: str = self.__class__.__name__ + "("
|
||||
members = []
|
||||
for key in dir(self):
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
value = getattr(self, key)
|
||||
if not callable(value):
|
||||
members.append(f"{key}={repr(value)}")
|
||||
out += ", ".join(members)
|
||||
out += ")"
|
||||
return out
|
||||
|
||||
def _ms(self, microservice: str, endpoint: List[str], **data) -> dict:
|
||||
return self._client.ms(microservice, endpoint, **data)
|
||||
|
||||
def _update(self, data: Union["GameObject", dict]):
|
||||
self._data.update(data if isinstance(data, dict) else data._data)
|
||||
|
||||
def update(self):
|
||||
pass
|
||||
|
||||
def clone(self, cls: Type[T]) -> T:
|
||||
return cls(self._client, self._data)
|
|
@ -1,31 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
|
||||
|
||||
class InventoryElement(GameObject):
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
return self._data.get("element_uuid")
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._data.get("element_name")
|
||||
|
||||
@property
|
||||
def related_ms(self) -> str:
|
||||
return self._data.get("related_ms")
|
||||
|
||||
@property
|
||||
def owner(self) -> str:
|
||||
return self._data.get("owner")
|
||||
|
||||
@staticmethod
|
||||
def list_inventory(client: Client) -> List["InventoryElement"]:
|
||||
return [
|
||||
InventoryElement(client, element) for element in client.ms("inventory", ["inventory", "list"])["elements"]
|
||||
]
|
||||
|
||||
def trade(self, target: str):
|
||||
self._ms("inventory", ["inventory", "trade"], element_uuid=self.uuid, target=target)
|
|
@ -1,5 +0,0 @@
|
|||
from PyCrypCli.game_objects.network.network import Network
|
||||
from PyCrypCli.game_objects.network.network_invitation import NetworkInvitation
|
||||
from PyCrypCli.game_objects.network.network_membership import NetworkMembership
|
||||
|
||||
__all__ = ["Network", "NetworkInvitation", "NetworkMembership"]
|
|
@ -1,66 +0,0 @@
|
|||
from typing import List, TYPE_CHECKING
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
from PyCrypCli.game_objects.network.network_invitation import NetworkInvitation
|
||||
from PyCrypCli.game_objects.network.network_membership import NetworkMembership
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from PyCrypCli.game_objects.device import Device
|
||||
|
||||
|
||||
class Network(GameObject):
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
return self._data.get("uuid")
|
||||
|
||||
@property
|
||||
def hidden(self) -> bool:
|
||||
return self._data.get("hidden")
|
||||
|
||||
@property
|
||||
def owner(self) -> str:
|
||||
return self._data.get("owner")
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._data.get("name")
|
||||
|
||||
@staticmethod
|
||||
def get_public_networks(client: Client) -> List["Network"]:
|
||||
return [Network(client, net) for net in client.ms("network", ["public"])["networks"]]
|
||||
|
||||
@staticmethod
|
||||
def get_by_uuid(client: Client, uuid: str) -> "Network":
|
||||
return Network(client, client.ms("network", ["get"], uuid=uuid))
|
||||
|
||||
@staticmethod
|
||||
def get_network_by_name(client: Client, name: str) -> "Network":
|
||||
return Network(client, client.ms("network", ["name"], name=name))
|
||||
|
||||
def get_members(self) -> List[NetworkMembership]:
|
||||
return [
|
||||
NetworkMembership(self._client, member)
|
||||
for member in self._ms("network", ["members"], uuid=self.uuid)["members"]
|
||||
]
|
||||
|
||||
def request_membership(self, device: "Device") -> NetworkInvitation:
|
||||
return NetworkInvitation(self._client, self._ms("network", ["request"], uuid=self.uuid, device=device.uuid))
|
||||
|
||||
def get_membership_requests(self) -> List[NetworkInvitation]:
|
||||
return [
|
||||
NetworkInvitation(self._client, invitation)
|
||||
for invitation in self._ms("network", ["requests"], uuid=self.uuid)["requests"]
|
||||
]
|
||||
|
||||
def invite_device(self, device: "Device") -> NetworkInvitation:
|
||||
return NetworkInvitation(self._client, self._ms("network", ["invite"], uuid=self.uuid, device=device.uuid))
|
||||
|
||||
def leave(self, device: "Device"):
|
||||
self._ms("network", ["leave"], uuid=self.uuid, device=device.uuid)
|
||||
|
||||
def kick(self, device: "Device"):
|
||||
self._ms("network", ["kick"], uuid=self.uuid, device=device.uuid)
|
||||
|
||||
def delete(self):
|
||||
self._ms("network", ["delete"], uuid=self.uuid)
|
|
@ -1,25 +0,0 @@
|
|||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
|
||||
|
||||
class NetworkInvitation(GameObject):
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
return self._data.get("uuid")
|
||||
|
||||
@property
|
||||
def network(self) -> str:
|
||||
return self._data.get("network")
|
||||
|
||||
@property
|
||||
def device(self) -> str:
|
||||
return self._data.get("device")
|
||||
|
||||
@property
|
||||
def request(self) -> bool:
|
||||
return self._data.get("request")
|
||||
|
||||
def accept(self):
|
||||
self._ms("network", ["accept"], uuid=self.uuid)
|
||||
|
||||
def deny(self):
|
||||
self._ms("network", ["deny"], uuid=self.uuid)
|
|
@ -1,15 +0,0 @@
|
|||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
|
||||
|
||||
class NetworkMembership(GameObject):
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
return self._data.get("uuid")
|
||||
|
||||
@property
|
||||
def network(self) -> str:
|
||||
return self._data.get("network")
|
||||
|
||||
@property
|
||||
def device(self) -> str:
|
||||
return self._data.get("device")
|
|
@ -1,23 +0,0 @@
|
|||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
|
||||
|
||||
class ResourceUsage(GameObject):
|
||||
@property
|
||||
def cpu(self) -> float:
|
||||
return min(1, self._data["usage_cpu"] / self._data["performance_cpu"])
|
||||
|
||||
@property
|
||||
def ram(self) -> float:
|
||||
return min(1, self._data["usage_ram"] / self._data["performance_ram"])
|
||||
|
||||
@property
|
||||
def gpu(self) -> float:
|
||||
return min(1, self._data["usage_gpu"] / self._data["performance_gpu"])
|
||||
|
||||
@property
|
||||
def disk(self) -> float:
|
||||
return min(1, self._data["usage_disk"] / self._data["performance_disk"])
|
||||
|
||||
@property
|
||||
def network(self) -> float:
|
||||
return min(1, self._data["usage_network"] / self._data["performance_network"])
|
|
@ -1,7 +0,0 @@
|
|||
from PyCrypCli.game_objects.service.public_service import PublicService
|
||||
from PyCrypCli.game_objects.service.service import Service
|
||||
from PyCrypCli.game_objects.service.bruteforce_service import BruteforceService
|
||||
from PyCrypCli.game_objects.service.miner import Miner
|
||||
from PyCrypCli.game_objects.service.portscan_service import PortscanService
|
||||
|
||||
__all__ = ["PublicService", "Service", "BruteforceService", "Miner", "PortscanService"]
|
|
@ -1,60 +0,0 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.exceptions import AttackNotRunningException
|
||||
from PyCrypCli.game_objects.service.service import Service
|
||||
|
||||
|
||||
class BruteforceService(Service):
|
||||
@property
|
||||
def target_device(self) -> Optional[str]:
|
||||
return self._data.get("target_device")
|
||||
|
||||
@property
|
||||
def target_service(self) -> Optional[str]:
|
||||
return self._data.get("target_service")
|
||||
|
||||
@property
|
||||
def started(self) -> Optional[datetime]:
|
||||
timestamp: Optional[int] = self._data.get("started")
|
||||
if timestamp is None:
|
||||
return None
|
||||
|
||||
return datetime.fromtimestamp(timestamp)
|
||||
|
||||
@property
|
||||
def progress(self) -> Optional[float]:
|
||||
return self._data.get("progress")
|
||||
|
||||
@staticmethod
|
||||
def get_bruteforce_service(client: Client, device_uuid: str) -> "BruteforceService":
|
||||
service: BruteforceService = Service.get_service_by_name(client, device_uuid, "bruteforce").clone(
|
||||
BruteforceService,
|
||||
)
|
||||
service._update(service.get_bruteforce_details())
|
||||
return service
|
||||
|
||||
def get_bruteforce_details(self) -> dict:
|
||||
try:
|
||||
return self._ms("service", ["bruteforce", "status"], device_uuid=self.device, service_uuid=self.uuid)
|
||||
except AttackNotRunningException:
|
||||
return {}
|
||||
|
||||
def update(self):
|
||||
self._update(BruteforceService.get_bruteforce_service(self._client, self.device))
|
||||
|
||||
def attack(self, target_device: str, target_service: str):
|
||||
self._ms(
|
||||
"service",
|
||||
["bruteforce", "attack"],
|
||||
device_uuid=self.device,
|
||||
service_uuid=self.uuid,
|
||||
target_device=target_device,
|
||||
target_service=target_service,
|
||||
)
|
||||
|
||||
def stop(self) -> Tuple[bool, float, str]:
|
||||
result = self._ms("service", ["bruteforce", "stop"], device_uuid=self.device, service_uuid=self.uuid)
|
||||
self.update()
|
||||
return result["access"], result["progress"], result["target_device"]
|
|
@ -1,50 +0,0 @@
|
|||
from datetime import datetime
|
||||
from typing import Optional, List
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.service.service import Service
|
||||
|
||||
|
||||
class Miner(Service):
|
||||
@property
|
||||
def wallet(self) -> Optional[str]:
|
||||
return self._data.get("wallet")
|
||||
|
||||
@property
|
||||
def started(self) -> Optional[datetime]:
|
||||
timestamp: Optional[int] = self._data.get("started")
|
||||
if timestamp is None:
|
||||
return None
|
||||
|
||||
return datetime.fromtimestamp(timestamp / 1000)
|
||||
|
||||
@property
|
||||
def power(self) -> Optional[float]:
|
||||
return self._data.get("power")
|
||||
|
||||
@staticmethod
|
||||
def get_miner(client: Client, device_uuid: str) -> "Miner":
|
||||
miner: Miner = Service.get_service_by_name(client, device_uuid, "miner").clone(Miner)
|
||||
miner._update(miner.get_miner_details())
|
||||
return miner
|
||||
|
||||
@staticmethod
|
||||
def get_miners(client: Client, wallet_uuid: str) -> List["Miner"]:
|
||||
return [
|
||||
Miner(client, {**miner["service"], **miner["miner"]})
|
||||
for miner in client.ms("service", ["miner", "list"], wallet_uuid=wallet_uuid)["miners"]
|
||||
]
|
||||
|
||||
def get_miner_details(self) -> dict:
|
||||
return self._ms("service", ["miner", "get"], service_uuid=self.uuid)
|
||||
|
||||
def update(self):
|
||||
self._update(Miner.get_miner(self._client, self.device))
|
||||
|
||||
def set_power(self, power: float):
|
||||
self._ms("service", ["miner", "power"], service_uuid=self.uuid, power=power)
|
||||
self.update()
|
||||
|
||||
def set_wallet(self, wallet_uuid: str):
|
||||
self._ms("service", ["miner", "wallet"], service_uuid=self.uuid, wallet_uuid=wallet_uuid)
|
||||
self.update()
|
|
@ -1,17 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.service.public_service import PublicService
|
||||
from PyCrypCli.game_objects.service.service import Service
|
||||
|
||||
|
||||
class PortscanService(Service):
|
||||
@staticmethod
|
||||
def get_portscan_service(client: Client, device_uuid: str) -> "PortscanService":
|
||||
return Service.get_service_by_name(client, device_uuid, "portscan").clone(PortscanService)
|
||||
|
||||
def update(self):
|
||||
self._update(PortscanService.get_portscan_service(self._client, self.device))
|
||||
|
||||
def scan(self, target: str) -> List[PublicService]:
|
||||
return [PublicService(self._client, service) for service in self.use(target_device=target)["services"]]
|
|
@ -1,30 +0,0 @@
|
|||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
|
||||
|
||||
class PublicService(GameObject):
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
return self._data.get("uuid")
|
||||
|
||||
@property
|
||||
def device(self) -> str:
|
||||
return self._data.get("device")
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._data.get("name")
|
||||
|
||||
@property
|
||||
def running_port(self) -> int:
|
||||
return self._data.get("running_port")
|
||||
|
||||
@staticmethod
|
||||
def get_public_service(client: Client, device_uuid: str, service_uuid: str) -> "PublicService":
|
||||
return PublicService(
|
||||
client,
|
||||
client.ms("service", ["public_info"], device_uuid=device_uuid, service_uuid=service_uuid),
|
||||
)
|
||||
|
||||
def update(self):
|
||||
self._update(PublicService.get_public_service(self._client, self.device, self.uuid))
|
|
@ -1,60 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from PyCrypCli.exceptions import ServiceNotFoundException
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.service.public_service import PublicService
|
||||
|
||||
|
||||
class Service(PublicService):
|
||||
@property
|
||||
def owner(self) -> str:
|
||||
return self._data.get("owner")
|
||||
|
||||
@property
|
||||
def running(self) -> bool:
|
||||
return self._data.get("running")
|
||||
|
||||
@property
|
||||
def part_owner(self) -> str:
|
||||
return self._data.get("part_owner")
|
||||
|
||||
@property
|
||||
def speed(self) -> float:
|
||||
return self._data.get("speed")
|
||||
|
||||
@staticmethod
|
||||
def get_services(client: Client, device_uuid: str) -> List["Service"]:
|
||||
return [
|
||||
Service(client, service) for service in client.ms("service", ["list"], device_uuid=device_uuid)["services"]
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_service(client: Client, device_uuid: str, service_uuid: str) -> "Service":
|
||||
return Service(
|
||||
client,
|
||||
client.ms("service", ["private_info"], device_uuid=device_uuid, service_uuid=service_uuid),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_service_by_name(client: Client, device_uuid: str, name: str) -> "Service":
|
||||
for service in Service.get_services(client, device_uuid):
|
||||
if service.name == name:
|
||||
return service
|
||||
raise ServiceNotFoundException
|
||||
|
||||
@staticmethod
|
||||
def list_part_owner(client: Client) -> List["Service"]:
|
||||
return [Service(client, service) for service in client.ms("service", ["list_part_owner"])["services"]]
|
||||
|
||||
def update(self):
|
||||
self._update(Service.get_service(self._client, self.device, self.uuid))
|
||||
|
||||
def use(self, **data) -> dict:
|
||||
return self._ms("service", ["use"], device_uuid=self.device, service_uuid=self.uuid, **data)
|
||||
|
||||
def toggle(self):
|
||||
self._update(self._ms("service", ["toggle"], device_uuid=self.device, service_uuid=self.uuid))
|
||||
|
||||
def delete(self):
|
||||
self._ms("service", ["delete"], device_uuid=self.device, service_uuid=self.uuid)
|
|
@ -1,5 +0,0 @@
|
|||
from PyCrypCli.game_objects.shop.shop_category import ShopCategory
|
||||
from PyCrypCli.game_objects.shop.shop_product import ShopProduct
|
||||
|
||||
|
||||
__all__ = ["ShopProduct", "ShopCategory"]
|
|
@ -1,34 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
from PyCrypCli.game_objects.shop.shop_product import ShopProduct
|
||||
|
||||
|
||||
class ShopCategory(GameObject):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._data.get("name")
|
||||
|
||||
@property
|
||||
def index(self) -> int:
|
||||
return self._data.get("index")
|
||||
|
||||
@property
|
||||
def items(self) -> List[ShopProduct]:
|
||||
return [ShopProduct(self._client, {"name": k, **v}) for k, v in self._data.get("items").items()]
|
||||
|
||||
@property
|
||||
def subcategories(self) -> List["ShopCategory"]:
|
||||
out = [ShopCategory(self._client, {"name": k, **v}) for k, v in self._data.get("categories").items()]
|
||||
out.sort(key=lambda c: c.index)
|
||||
return out
|
||||
|
||||
@staticmethod
|
||||
def shop_list(client: Client) -> List["ShopCategory"]:
|
||||
out = [
|
||||
ShopCategory(client, {"name": k, **v})
|
||||
for k, v in client.ms("inventory", ["shop", "list"])["categories"].items()
|
||||
]
|
||||
out.sort(key=lambda c: c.index)
|
||||
return out
|
|
@ -1,40 +0,0 @@
|
|||
from typing import Dict, List
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
from PyCrypCli.game_objects.inventory_element import InventoryElement
|
||||
from PyCrypCli.game_objects.wallet import Wallet
|
||||
|
||||
|
||||
class ShopProduct(GameObject):
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._data.get("name")
|
||||
|
||||
@property
|
||||
def price(self) -> int:
|
||||
return self._data.get("price")
|
||||
|
||||
@property
|
||||
def related_ms(self) -> str:
|
||||
return self._data.get("related_ms")
|
||||
|
||||
@staticmethod
|
||||
def shop_info(client: Client, product: str) -> "ShopProduct":
|
||||
return ShopProduct(client, client.ms("inventory", ["shop", "info"], product=product))
|
||||
|
||||
@staticmethod
|
||||
def bulk_buy(client: Client, products: Dict["ShopProduct", int], wallet: Wallet) -> List[InventoryElement]:
|
||||
return [
|
||||
InventoryElement(client, element)
|
||||
for element in client.ms(
|
||||
"inventory",
|
||||
["shop", "buy"],
|
||||
products={k.name: v for k, v in products.items()},
|
||||
wallet_uuid=wallet.uuid,
|
||||
key=wallet.key,
|
||||
)["bought_products"]
|
||||
]
|
||||
|
||||
def buy(self, wallet: Wallet) -> InventoryElement:
|
||||
return ShopProduct.bulk_buy(self._client, {self: 1}, wallet)[0]
|
|
@ -1,30 +0,0 @@
|
|||
from datetime import datetime
|
||||
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
from PyCrypCli.util import convert_timestamp
|
||||
|
||||
|
||||
class Transaction(GameObject):
|
||||
@property
|
||||
def timestamp(self) -> datetime:
|
||||
return convert_timestamp(datetime.fromisoformat(self._data.get("time_stamp")))
|
||||
|
||||
@property
|
||||
def source_uuid(self) -> str:
|
||||
return self._data.get("source_uuid")
|
||||
|
||||
@property
|
||||
def destination_uuid(self) -> str:
|
||||
return self._data.get("destination_uuid")
|
||||
|
||||
@property
|
||||
def amount(self) -> int:
|
||||
return self._data.get("send_amount")
|
||||
|
||||
@property
|
||||
def usage(self) -> str:
|
||||
return self._data.get("usage")
|
||||
|
||||
@property
|
||||
def origin(self) -> int:
|
||||
return self._data.get("origin")
|
|
@ -1,5 +0,0 @@
|
|||
from PyCrypCli.game_objects.wallet.public_wallet import PublicWallet
|
||||
from PyCrypCli.game_objects.wallet.wallet import Wallet
|
||||
|
||||
|
||||
__all__ = ["Wallet", "PublicWallet"]
|
|
@ -1,21 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.game_object import GameObject
|
||||
|
||||
|
||||
class PublicWallet(GameObject):
|
||||
@property
|
||||
def uuid(self) -> str:
|
||||
return self._data.get("source_uuid")
|
||||
|
||||
@staticmethod
|
||||
def get_public_wallet(client: Client, uuid: str) -> "PublicWallet":
|
||||
return PublicWallet(client, {"source_uuid": uuid})
|
||||
|
||||
@staticmethod
|
||||
def list_wallets(client: Client) -> List["PublicWallet"]:
|
||||
return [PublicWallet.get_public_wallet(client, uuid) for uuid in client.ms("currency", ["list"])["wallets"]]
|
||||
|
||||
def reset_wallet(self):
|
||||
self._ms("currency", ["reset"], source_uuid=self.uuid)
|
|
@ -1,69 +0,0 @@
|
|||
from typing import List
|
||||
|
||||
from PyCrypCli.client import Client
|
||||
from PyCrypCli.game_objects.transaction import Transaction
|
||||
from PyCrypCli.game_objects.wallet.public_wallet import PublicWallet
|
||||
from PyCrypCli.game_objects.service import Miner
|
||||
|
||||
|
||||
class Wallet(PublicWallet):
|
||||
@property
|
||||
def key(self) -> str:
|
||||
return self._data.get("key")
|
||||
|
||||
@property
|
||||
def user(self) -> str:
|
||||
return self._data.get("user_uuid")
|
||||
|
||||
@property
|
||||
def amount(self) -> int:
|
||||
return self._data.get("amount")
|
||||
|
||||
@property
|
||||
def transactions(self) -> int:
|
||||
return self._data.get("transactions")
|
||||
|
||||
@staticmethod
|
||||
def create_wallet(client: Client) -> "Wallet":
|
||||
return Wallet(client, client.ms("currency", ["create"]))
|
||||
|
||||
@staticmethod
|
||||
def get_wallet(client: Client, uuid: str, key: str) -> "Wallet":
|
||||
return Wallet(client, client.ms("currency", ["get"], source_uuid=uuid, key=key))
|
||||
|
||||
def update(self):
|
||||
self._update(Wallet.get_wallet(self._client, self.uuid, self.key))
|
||||
|
||||
def get_transactions(self, count: int, offset: int) -> List[Transaction]:
|
||||
return [
|
||||
Transaction(self._client, t)
|
||||
for t in self._ms(
|
||||
"currency",
|
||||
["transactions"],
|
||||
source_uuid=self.uuid,
|
||||
key=self.key,
|
||||
count=count,
|
||||
offset=offset,
|
||||
)["transactions"]
|
||||
]
|
||||
|
||||
def get_miners(self) -> List[Miner]:
|
||||
return Miner.get_miners(self._client, self.uuid)
|
||||
|
||||
def get_mining_rate(self) -> float:
|
||||
return sum(miner.speed for miner in self.get_miners() if miner.running)
|
||||
|
||||
def send(self, destination: PublicWallet, amount: int, usage: str):
|
||||
self._ms(
|
||||
"currency",
|
||||
["send"],
|
||||
source_uuid=self.uuid,
|
||||
key=self.key,
|
||||
send_amount=amount,
|
||||
destination_uuid=destination.uuid,
|
||||
usage=usage,
|
||||
)
|
||||
self.update()
|
||||
|
||||
def delete(self):
|
||||
self._ms("currency", ["delete"], source_uuid=self.uuid, key=self.key)
|
41
PyCrypCli/models/__init__.py
Normal file
41
PyCrypCli/models/__init__.py
Normal file
|
@ -0,0 +1,41 @@
|
|||
from .config import Config, ServerConfig
|
||||
from .device import Device
|
||||
from .device_hardware import DeviceHardware
|
||||
from .file import File
|
||||
from .hardware_config import HardwareConfig
|
||||
from .inventory_element import InventoryElement
|
||||
from .model import Model
|
||||
from .network import Network, NetworkInvitation, NetworkMembership
|
||||
from .resource_usage import ResourceUsage
|
||||
from .server_responses import StatusResponse, InfoResponse, TokenResponse
|
||||
from .service import Service, PublicService, Miner, BruteforceService, PortscanService
|
||||
from .shop import ShopProduct, ShopCategory
|
||||
from .wallet import Wallet, PublicWallet, Transaction
|
||||
|
||||
__all__ = [
|
||||
"Config",
|
||||
"ServerConfig",
|
||||
"Device",
|
||||
"DeviceHardware",
|
||||
"File",
|
||||
"HardwareConfig",
|
||||
"InventoryElement",
|
||||
"Model",
|
||||
"Network",
|
||||
"NetworkInvitation",
|
||||
"NetworkMembership",
|
||||
"ResourceUsage",
|
||||
"StatusResponse",
|
||||
"InfoResponse",
|
||||
"TokenResponse",
|
||||
"Service",
|
||||
"PublicService",
|
||||
"Miner",
|
||||
"BruteforceService",
|
||||
"PortscanService",
|
||||
"ShopProduct",
|
||||
"ShopCategory",
|
||||
"Wallet",
|
||||
"PublicWallet",
|
||||
"Transaction",
|
||||
]
|
15
PyCrypCli/models/config.py
Normal file
15
PyCrypCli/models/config.py
Normal file
|
@ -0,0 +1,15 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class ServerConfig(BaseModel):
|
||||
token: str | None
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
servers: dict[str, ServerConfig]
|
||||
|
||||
@staticmethod
|
||||
def get_default_config() -> Config:
|
||||
return Config(servers={})
|
129
PyCrypCli/models/device.py
Normal file
129
PyCrypCli/models/device.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any, cast, TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .device_hardware import DeviceHardware
|
||||
from .file import File
|
||||
from .model import Model
|
||||
from .network import Network, NetworkInvitation
|
||||
from .resource_usage import ResourceUsage
|
||||
from .service import PublicService, Service, Miner
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..client import Client
|
||||
|
||||
|
||||
class Device(Model):
|
||||
uuid: str
|
||||
name: str
|
||||
owner_uuid: str = Field(alias="owner")
|
||||
powered_on: bool
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.uuid)
|
||||
|
||||
@staticmethod
|
||||
def get_device(client: Client, device_uuid: str) -> Device:
|
||||
return Device.parse(client, client.ms("device", ["device", "info"], device_uuid=device_uuid))
|
||||
|
||||
@staticmethod
|
||||
def list_devices(client: Client) -> list[Device]:
|
||||
return [Device.parse(client, device) for device in client.ms("device", ["device", "all"])["devices"]]
|
||||
|
||||
def update(self) -> Device:
|
||||
return self._update(Device.get_device(self._client, self.uuid))
|
||||
|
||||
@staticmethod
|
||||
def build(client: Client, mainboard: str, cpu: str, gpu: str, ram: list[str], disk: list[str]) -> Device:
|
||||
return Device.parse(
|
||||
client,
|
||||
client.ms("device", ["device", "create"], motherboard=mainboard, cpu=cpu, gpu=gpu, ram=ram, disk=disk),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def starter_device(client: Client) -> Device:
|
||||
return Device.parse(client, client.ms("device", ["device", "starter_device"]))
|
||||
|
||||
@staticmethod
|
||||
def spot(client: Client) -> Device:
|
||||
return Device.parse(client, client.ms("device", ["device", "spot"]))
|
||||
|
||||
def power(self) -> Device:
|
||||
return self._update(self._ms("device", ["device", "power"], device_uuid=self.uuid))
|
||||
|
||||
def change_name(self, name: str) -> Device:
|
||||
return self._update(self._ms("device", ["device", "change_name"], device_uuid=self.uuid, name=name))
|
||||
|
||||
def delete(self) -> None:
|
||||
self._ms("device", ["device", "delete"], device_uuid=self.uuid)
|
||||
|
||||
def get_files(self, parent_dir_uuid: str | None) -> list[File]:
|
||||
return [
|
||||
File.parse(self._client, file)
|
||||
for file in self._ms("device", ["file", "all"], device_uuid=self.uuid, parent_dir_uuid=parent_dir_uuid)[
|
||||
"files"
|
||||
]
|
||||
]
|
||||
|
||||
def get_file(self, file_uuid: str) -> File:
|
||||
return File.get_file(self._client, self.uuid, file_uuid)
|
||||
|
||||
def create_file(self, filename: str, content: str, is_directory: bool, parent_dir_uuid: str | None) -> File:
|
||||
return File.parse(
|
||||
self._client,
|
||||
self._ms(
|
||||
"device",
|
||||
["file", "create"],
|
||||
device_uuid=self.uuid,
|
||||
filename=filename,
|
||||
content=content,
|
||||
is_directory=is_directory,
|
||||
parent_dir_uuid=parent_dir_uuid,
|
||||
),
|
||||
)
|
||||
|
||||
def get_public_service(self, service_uuid: str) -> PublicService:
|
||||
return PublicService.get_public_service(self._client, self.uuid, service_uuid)
|
||||
|
||||
def get_services(self) -> list[Service]:
|
||||
return Service.get_services(self._client, self.uuid)
|
||||
|
||||
def get_service(self, service_uuid: str) -> Service:
|
||||
return Service.get_service(self._client, self.uuid, service_uuid)
|
||||
|
||||
def get_service_by_name(self, service: str) -> Service:
|
||||
return Service.get_service_by_name(self._client, self.uuid, service)
|
||||
|
||||
def get_miner(self) -> Miner:
|
||||
return Miner.get_miner(self._client, self.uuid)
|
||||
|
||||
def create_service(self, name: str, **extra: Any) -> Service:
|
||||
return Service.parse(self._client, self._ms("service", ["create"], name=name, device_uuid=self.uuid, **extra))
|
||||
|
||||
def part_owner(self) -> bool:
|
||||
return cast(bool, self._ms("service", ["part_owner"], device_uuid=self.uuid)["ok"])
|
||||
|
||||
def get_hardware(self) -> list[DeviceHardware]:
|
||||
return [
|
||||
DeviceHardware.parse(self._client, dh)
|
||||
for dh in self._ms("device", ["device", "info"], device_uuid=self.uuid)["hardware"]
|
||||
]
|
||||
|
||||
def get_resource_usage(self) -> ResourceUsage:
|
||||
return ResourceUsage.parse(self._client, self._ms("device", ["hardware", "resources"], device_uuid=self.uuid))
|
||||
|
||||
def get_networks(self) -> list[Network]:
|
||||
return [
|
||||
Network.parse(self._client, net) for net in self._ms("network", ["member"], device=self.uuid)["networks"]
|
||||
]
|
||||
|
||||
def create_network(self, name: str, hidden: bool) -> Network:
|
||||
return Network.parse(self._client, self._ms("network", ["create"], device=self.uuid, name=name, hidden=hidden))
|
||||
|
||||
def get_network_invitations(self) -> list[NetworkInvitation]:
|
||||
return [
|
||||
NetworkInvitation.parse(self._client, invitation)
|
||||
for invitation in self._ms("network", ["invitations"], device=self.uuid)["invitations"]
|
||||
]
|
8
PyCrypCli/models/device_hardware.py
Normal file
8
PyCrypCli/models/device_hardware.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from .model import Model
|
||||
|
||||
|
||||
class DeviceHardware(Model):
|
||||
uuid: str
|
||||
device_uuid: str
|
||||
hardware_element: str
|
||||
hardware_type: str
|
69
PyCrypCli/models/file.py
Normal file
69
PyCrypCli/models/file.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .model import Model
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..client import Client
|
||||
|
||||
|
||||
class File(Model):
|
||||
uuid: str | None
|
||||
device_uuid: str = Field(alias="device")
|
||||
name: str = Field(alias="filename")
|
||||
content: str
|
||||
is_directory: bool
|
||||
parent_dir_uuid: str | None
|
||||
|
||||
@property
|
||||
def is_root_directory(self) -> bool:
|
||||
return self.uuid is None
|
||||
|
||||
@staticmethod
|
||||
def get_root_directory(client: Client, device_uuid: str) -> File:
|
||||
return File.parse(
|
||||
client,
|
||||
{
|
||||
"uuid": None,
|
||||
"device": device_uuid,
|
||||
"filename": "",
|
||||
"content": "",
|
||||
"is_directory": True,
|
||||
"parent_dir_uuid": None,
|
||||
},
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_file(client: Client, device_uuid: str, file_uuid: str) -> File:
|
||||
return File.parse(client, client.ms("device", ["file", "info"], device_uuid=device_uuid, file_uuid=file_uuid))
|
||||
|
||||
def update(self) -> File:
|
||||
if not self.uuid:
|
||||
return self
|
||||
|
||||
return self._update(File.get_file(self._client, self.device_uuid, self.uuid))
|
||||
|
||||
def move(self, new_filename: str, new_parent_dir_uuid: str | None) -> File:
|
||||
return self._update(
|
||||
self._ms(
|
||||
"device",
|
||||
["file", "move"],
|
||||
device_uuid=self.device_uuid,
|
||||
file_uuid=self.uuid,
|
||||
new_filename=new_filename,
|
||||
new_parent_dir_uuid=new_parent_dir_uuid,
|
||||
)
|
||||
)
|
||||
|
||||
def edit(self, new_content: str) -> File:
|
||||
return self._update(
|
||||
self._ms(
|
||||
"device", ["file", "update"], device_uuid=self.device_uuid, file_uuid=self.uuid, content=new_content
|
||||
)
|
||||
)
|
||||
|
||||
def delete(self) -> None:
|
||||
self._ms("device", ["file", "delete"], device_uuid=self.device_uuid, file_uuid=self.uuid)
|
22
PyCrypCli/models/hardware_config/__init__.py
Normal file
22
PyCrypCli/models/hardware_config/__init__.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from .case import Case
|
||||
from .cpu import CPU
|
||||
from .disk import Disk
|
||||
from .gpu import GPU
|
||||
from .hardware_config import HardwareConfig, StartPC
|
||||
from .mainboard import Mainboard
|
||||
from .power_pack import PowerPack
|
||||
from .processor_cooler import ProcessorCooler
|
||||
from .ram import RAM
|
||||
|
||||
__all__ = [
|
||||
"Case",
|
||||
"CPU",
|
||||
"Disk",
|
||||
"GPU",
|
||||
"HardwareConfig",
|
||||
"Mainboard",
|
||||
"PowerPack",
|
||||
"ProcessorCooler",
|
||||
"RAM",
|
||||
"StartPC",
|
||||
]
|
8
PyCrypCli/models/hardware_config/case.py
Normal file
8
PyCrypCli/models/hardware_config/case.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from typing import Literal
|
||||
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class Case(Model):
|
||||
id: int
|
||||
size: Literal["small", "middle", "big"]
|
17
PyCrypCli/models/hardware_config/cpu.py
Normal file
17
PyCrypCli/models/hardware_config/cpu.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from pydantic import Field
|
||||
|
||||
from .mainboard import GraphicUnit
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class CPU(Model):
|
||||
id: int
|
||||
frequency_min: int = Field(alias="frequencyMin")
|
||||
frequency_max: int = Field(alias="frequencyMax")
|
||||
socket: str
|
||||
cores: int
|
||||
turbo_speed: bool = Field(alias="turboSpeed")
|
||||
overclock: bool = Field(alias="overClock")
|
||||
max_temperature: int = Field(alias="maxTemperature")
|
||||
graphic_unit: GraphicUnit | None = Field(alias="graphicUnit")
|
||||
power: int
|
14
PyCrypCli/models/hardware_config/disk.py
Normal file
14
PyCrypCli/models/hardware_config/disk.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
from pydantic import Field
|
||||
|
||||
from .mainboard import Interface
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class Disk(Model):
|
||||
id: int
|
||||
type: str = Field(alias="diskTyp")
|
||||
capacity: int
|
||||
writing_speed: int = Field(alias="writingSpeed")
|
||||
reading_speed: int = Field(alias="readingSpeed")
|
||||
interface: Interface
|
||||
power: int
|
13
PyCrypCli/models/hardware_config/gpu.py
Normal file
13
PyCrypCli/models/hardware_config/gpu.py
Normal file
|
@ -0,0 +1,13 @@
|
|||
from pydantic import Field
|
||||
|
||||
from .mainboard import Interface
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class GPU(Model):
|
||||
id: int
|
||||
ram_size: int = Field(alias="ramSize")
|
||||
ram_type: Interface = Field(alias="ramTyp")
|
||||
frequency: int
|
||||
interface: Interface
|
||||
power: int
|
34
PyCrypCli/models/hardware_config/hardware_config.py
Normal file
34
PyCrypCli/models/hardware_config/hardware_config.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from pydantic import Field
|
||||
|
||||
from .case import Case
|
||||
from .cpu import CPU
|
||||
from .disk import Disk
|
||||
from .gpu import GPU
|
||||
from .mainboard import Mainboard
|
||||
from .power_pack import PowerPack
|
||||
from .processor_cooler import ProcessorCooler
|
||||
from .ram import RAM
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class StartPC(Model):
|
||||
mainboard: str
|
||||
cpu: list[str]
|
||||
processor_cooler: list[str] = Field(alias="processorCooler")
|
||||
ram: list[str]
|
||||
gpu: list[str]
|
||||
disk: list[str]
|
||||
power_pack: str = Field(alias="powerPack")
|
||||
case: str
|
||||
|
||||
|
||||
class HardwareConfig(Model):
|
||||
start_pc: StartPC
|
||||
mainboard: dict[str, Mainboard]
|
||||
cpu: dict[str, CPU]
|
||||
processor_cooler: dict[str, ProcessorCooler] = Field(alias="processorCooler")
|
||||
ram: dict[str, RAM]
|
||||
gpu: dict[str, GPU]
|
||||
disk: dict[str, Disk]
|
||||
power_pack: dict[str, PowerPack] = Field(alias="powerPack")
|
||||
case: dict[str, Case]
|
50
PyCrypCli/models/hardware_config/mainboard.py
Normal file
50
PyCrypCli/models/hardware_config/mainboard.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
from typing import NamedTuple
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from ..model import Model
|
||||
|
||||
Interface = NamedTuple("Interface", [("name", str), ("version", int)])
|
||||
|
||||
|
||||
class RAM(Model):
|
||||
ram_slots: int = Field(alias="ramSlots")
|
||||
max_ram_size: int = Field(alias="maxRamSize")
|
||||
ram_type: list[Interface] = Field(alias="ramTyp")
|
||||
frequency: list[int]
|
||||
|
||||
|
||||
class GraphicUnit(Model):
|
||||
name: str
|
||||
ram_size: int = Field(alias="ramSize")
|
||||
frequency: int
|
||||
|
||||
|
||||
class ExpansionSlot(Model):
|
||||
interface: Interface
|
||||
interface_slots: int = Field(alias="interfaceSlots")
|
||||
|
||||
|
||||
class DiskStorage(Model):
|
||||
disk_slots: int = Field(alias="diskSlots")
|
||||
interface: list[Interface]
|
||||
|
||||
|
||||
class NetworkPort(Model):
|
||||
name: str
|
||||
interface: str
|
||||
speed: int
|
||||
|
||||
|
||||
class Mainboard(Model):
|
||||
id: int
|
||||
case: str
|
||||
cpu_socket: str = Field(alias="cpuSocket")
|
||||
cpu_slots: int = Field(alias="cpuSlots")
|
||||
core_temperature_control: bool = Field(alias="coreTemperatureControl")
|
||||
usb_ports: int = Field(alias="usbPorts")
|
||||
graphic_unit_on_board: GraphicUnit = Field(alias="graphicUnitOnBoard")
|
||||
expansion_slots: list[ExpansionSlot] = Field(alias="expansionSlots")
|
||||
disk_storage: DiskStorage = Field(alias="diskStorage")
|
||||
network_port: NetworkPort = Field(alias="NetworkPort")
|
||||
power: int
|
8
PyCrypCli/models/hardware_config/power_pack.py
Normal file
8
PyCrypCli/models/hardware_config/power_pack.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from pydantic import Field
|
||||
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class PowerPack(Model):
|
||||
id: int
|
||||
total_power: int = Field(alias="totalPower")
|
10
PyCrypCli/models/hardware_config/processor_cooler.py
Normal file
10
PyCrypCli/models/hardware_config/processor_cooler.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from pydantic import Field
|
||||
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class ProcessorCooler(Model):
|
||||
id: int
|
||||
speed: int = Field(alias="coolerSpeed")
|
||||
socket: str
|
||||
power: int
|
12
PyCrypCli/models/hardware_config/ram.py
Normal file
12
PyCrypCli/models/hardware_config/ram.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
from pydantic import Field
|
||||
|
||||
from .mainboard import Interface
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class RAM(Model):
|
||||
id: int
|
||||
size: int = Field(alias="ramSize")
|
||||
type: Interface = Field(alias="ramTyp")
|
||||
frequency: int
|
||||
power: int
|
27
PyCrypCli/models/inventory_element.py
Normal file
27
PyCrypCli/models/inventory_element.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .model import Model
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..client import Client
|
||||
|
||||
|
||||
class InventoryElement(Model):
|
||||
uuid: str = Field(alias="element_uuid")
|
||||
name: str = Field(alias="element_name")
|
||||
related_ms: str
|
||||
owner_uuid: str = Field(alias="owner")
|
||||
|
||||
@staticmethod
|
||||
def list_inventory(client: Client) -> list[InventoryElement]:
|
||||
return [
|
||||
InventoryElement.parse(client, element)
|
||||
for element in client.ms("inventory", ["inventory", "list"])["elements"]
|
||||
]
|
||||
|
||||
def trade(self, target: str) -> None:
|
||||
self._ms("inventory", ["inventory", "trade"], element_uuid=self.uuid, target=target)
|
30
PyCrypCli/models/model.py
Normal file
30
PyCrypCli/models/model.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Type, TypeVar, Any, TYPE_CHECKING
|
||||
|
||||
from pydantic import BaseModel, PrivateAttr, ValidationError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..client import Client
|
||||
|
||||
ModelType = TypeVar("ModelType", bound="Model")
|
||||
|
||||
|
||||
class Model(BaseModel):
|
||||
_client: Client = PrivateAttr()
|
||||
|
||||
@classmethod
|
||||
def parse(cls: Type[ModelType], client: Client, obj: dict[Any, Any]) -> ModelType:
|
||||
out = cls.parse_obj(obj)
|
||||
out._client = client
|
||||
return out
|
||||
|
||||
def _ms(self, microservice: str, endpoint: list[str], **data: Any) -> dict[Any, Any]:
|
||||
return self._client.ms(microservice, endpoint, **data)
|
||||
|
||||
def _update(self: ModelType, obj: ModelType | dict[Any, Any]) -> ModelType:
|
||||
if isinstance(obj, dict):
|
||||
obj = self.validate(obj)
|
||||
for k, v in obj.dict().items():
|
||||
setattr(self, k, v)
|
||||
return self
|
5
PyCrypCli/models/network/__init__.py
Normal file
5
PyCrypCli/models/network/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from .network import Network
|
||||
from .network_invitation import NetworkInvitation
|
||||
from .network_membership import NetworkMembership
|
||||
|
||||
__all__ = ["Network", "NetworkInvitation", "NetworkMembership"]
|
63
PyCrypCli/models/network/network.py
Normal file
63
PyCrypCli/models/network/network.py
Normal file
|
@ -0,0 +1,63 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .network_invitation import NetworkInvitation
|
||||
from .network_membership import NetworkMembership
|
||||
from ..model import Model
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..device import Device
|
||||
from ...client import Client
|
||||
|
||||
|
||||
class Network(Model):
|
||||
uuid: str
|
||||
hidden: bool
|
||||
owner_uuid: str = Field(alias="owner")
|
||||
name: str
|
||||
|
||||
@staticmethod
|
||||
def get_public_networks(client: Client) -> list[Network]:
|
||||
return [Network.parse(client, net) for net in client.ms("network", ["public"])["networks"]]
|
||||
|
||||
@staticmethod
|
||||
def get_by_uuid(client: Client, uuid: str) -> Network:
|
||||
return Network.parse(client, client.ms("network", ["get"], uuid=uuid))
|
||||
|
||||
@staticmethod
|
||||
def get_network_by_name(client: Client, name: str) -> Network:
|
||||
return Network.parse(client, client.ms("network", ["name"], name=name))
|
||||
|
||||
def get_members(self) -> list[NetworkMembership]:
|
||||
return [
|
||||
NetworkMembership.parse(self._client, member)
|
||||
for member in self._ms("network", ["members"], uuid=self.uuid)["members"]
|
||||
]
|
||||
|
||||
def request_membership(self, device: Device) -> NetworkInvitation:
|
||||
return NetworkInvitation.parse(
|
||||
self._client, self._ms("network", ["request"], uuid=self.uuid, device=device.uuid)
|
||||
)
|
||||
|
||||
def get_membership_requests(self) -> list[NetworkInvitation]:
|
||||
return [
|
||||
NetworkInvitation.parse(self._client, invitation)
|
||||
for invitation in self._ms("network", ["requests"], uuid=self.uuid)["requests"]
|
||||
]
|
||||
|
||||
def invite_device(self, device: Device) -> NetworkInvitation:
|
||||
return NetworkInvitation.parse(
|
||||
self._client, self._ms("network", ["invite"], uuid=self.uuid, device=device.uuid)
|
||||
)
|
||||
|
||||
def leave(self, device: Device) -> None:
|
||||
self._ms("network", ["leave"], uuid=self.uuid, device=device.uuid)
|
||||
|
||||
def kick(self, device: Device) -> None:
|
||||
self._ms("network", ["kick"], uuid=self.uuid, device=device.uuid)
|
||||
|
||||
def delete(self) -> None:
|
||||
self._ms("network", ["delete"], uuid=self.uuid)
|
16
PyCrypCli/models/network/network_invitation.py
Normal file
16
PyCrypCli/models/network/network_invitation.py
Normal file
|
@ -0,0 +1,16 @@
|
|||
from pydantic import Field
|
||||
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class NetworkInvitation(Model):
|
||||
uuid: str
|
||||
network_uuid: str = Field(alias="network")
|
||||
device_uuid: str = Field(alias="device")
|
||||
request: bool
|
||||
|
||||
def accept(self) -> None:
|
||||
self._ms("network", ["accept"], uuid=self.uuid)
|
||||
|
||||
def deny(self) -> None:
|
||||
self._ms("network", ["deny"], uuid=self.uuid)
|
9
PyCrypCli/models/network/network_membership.py
Normal file
9
PyCrypCli/models/network/network_membership.py
Normal file
|
@ -0,0 +1,9 @@
|
|||
from pydantic import Field
|
||||
|
||||
from ..model import Model
|
||||
|
||||
|
||||
class NetworkMembership(Model):
|
||||
uuid: str
|
||||
network_uuid: str = Field(alias="network")
|
||||
device_uuid: str = Field(alias="device")
|
34
PyCrypCli/models/resource_usage.py
Normal file
34
PyCrypCli/models/resource_usage.py
Normal file
|
@ -0,0 +1,34 @@
|
|||
from .model import Model
|
||||
|
||||
|
||||
class ResourceUsage(Model):
|
||||
usage_cpu: float
|
||||
usage_ram: float
|
||||
usage_gpu: float
|
||||
usage_disk: float
|
||||
usage_network: float
|
||||
performance_cpu: float
|
||||
performance_ram: float
|
||||
performance_gpu: float
|
||||
performance_disk: float
|
||||
performance_network: float
|
||||
|
||||
@property
|
||||
def cpu(self) -> float:
|
||||
return min(self.usage_cpu / self.performance_cpu, 1)
|
||||
|
||||
@property
|
||||
def ram(self) -> float:
|
||||
return min(self.usage_ram / self.performance_ram, 1)
|
||||
|
||||
@property
|
||||
def gpu(self) -> float:
|
||||
return min(self.usage_gpu / self.performance_gpu, 1)
|
||||
|
||||
@property
|
||||
def disk(self) -> float:
|
||||
return min(self.usage_disk / self.performance_disk, 1)
|
||||
|
||||
@property
|
||||
def network(self) -> float:
|
||||
return min(self.usage_network / self.performance_network, 1)
|
21
PyCrypCli/models/server_responses.py
Normal file
21
PyCrypCli/models/server_responses.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from datetime import datetime
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .model import Model
|
||||
|
||||
|
||||
class StatusResponse(Model):
|
||||
online: int
|
||||
|
||||
|
||||
class InfoResponse(Model):
|
||||
name: str
|
||||
uuid: str
|
||||
created: datetime
|
||||
last_login: datetime = Field(alias="last")
|
||||
online: int
|
||||
|
||||
|
||||
class TokenResponse(Model):
|
||||
token: str
|
8
PyCrypCli/models/service/__init__.py
Normal file
8
PyCrypCli/models/service/__init__.py
Normal file
|
@ -0,0 +1,8 @@
|
|||
from .bruteforce_service import BruteforceService
|
||||
from .miner import Miner
|
||||
from .portscan_service import PortscanService
|
||||
from .public_service import PublicService
|
||||
from .service import Service
|
||||
|
||||
|
||||
__all__ = ["BruteforceService", "Miner", "PortscanService", "PublicService", "Service"]
|
52
PyCrypCli/models/service/bruteforce_service.py
Normal file
52
PyCrypCli/models/service/bruteforce_service.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .service import Service
|
||||
from ...exceptions import AttackNotRunningError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client import Client
|
||||
|
||||
|
||||
class BruteforceService(Service):
|
||||
target_device_uuid: str | None = Field(alias="target_device")
|
||||
target_service_uuid: str | None = Field(alias="target_service")
|
||||
started: datetime | None
|
||||
progress: float | None
|
||||
|
||||
@staticmethod
|
||||
def get_bruteforce_service(client: Client, device_uuid: str) -> BruteforceService:
|
||||
service: Service = Service.get_service_by_name(client, device_uuid, "bruteforce")
|
||||
return BruteforceService.parse(
|
||||
client,
|
||||
service.dict(by_alias=True) | BruteforceService.get_bruteforce_details(client, device_uuid, service.uuid),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_bruteforce_details(client: Client, device_uuid: str, service_uuid: str) -> dict[Any, Any]:
|
||||
try:
|
||||
return client.ms("service", ["bruteforce", "status"], device_uuid=device_uuid, service_uuid=service_uuid)
|
||||
except AttackNotRunningError:
|
||||
return {"target_device_uuid": None, "target_service_uuid": None, "started": None, "progress": None}
|
||||
|
||||
def update(self) -> BruteforceService:
|
||||
return self._update(BruteforceService.get_bruteforce_service(self._client, self.device_uuid))
|
||||
|
||||
def attack(self, target_device: str, target_service: str) -> None:
|
||||
self._ms(
|
||||
"service",
|
||||
["bruteforce", "attack"],
|
||||
device_uuid=self.device_uuid,
|
||||
service_uuid=self.uuid,
|
||||
target_device=target_device,
|
||||
target_service=target_service,
|
||||
)
|
||||
|
||||
def stop(self) -> tuple[bool, float, str]:
|
||||
result = self._ms("service", ["bruteforce", "stop"], device_uuid=self.device_uuid, service_uuid=self.uuid)
|
||||
self.update()
|
||||
return result["access"], result["progress"], result["target_device"]
|
44
PyCrypCli/models/service/miner.py
Normal file
44
PyCrypCli/models/service/miner.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Any, TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .service import Service
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client import Client
|
||||
|
||||
|
||||
class Miner(Service):
|
||||
wallet_uuid: str = Field(alias="wallet")
|
||||
started: datetime | None
|
||||
power: float
|
||||
|
||||
@staticmethod
|
||||
def get_miner(client: Client, device_uuid: str) -> Miner:
|
||||
service: Service = Service.get_service_by_name(client, device_uuid, "miner")
|
||||
return Miner.parse(client, service.dict(by_alias=True) | Miner.get_miner_details(client, service.uuid))
|
||||
|
||||
@staticmethod
|
||||
def get_miners(client: Client, wallet_uuid: str) -> list[Miner]:
|
||||
return [
|
||||
Miner.parse(client, {**miner["service"], **miner["miner"]})
|
||||
for miner in client.ms("service", ["miner", "list"], wallet_uuid=wallet_uuid)["miners"]
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_miner_details(client: Client, service_uuid: str) -> dict[Any, Any]:
|
||||
return client.ms("service", ["miner", "get"], service_uuid=service_uuid)
|
||||
|
||||
def update(self) -> Miner:
|
||||
return self._update(Miner.get_miner(self._client, self.device_uuid))
|
||||
|
||||
def set_power(self, power: float) -> Miner:
|
||||
self._ms("service", ["miner", "power"], service_uuid=self.uuid, power=power)
|
||||
return self.update()
|
||||
|
||||
def set_wallet(self, wallet_uuid: str) -> Miner:
|
||||
self._ms("service", ["miner", "wallet"], service_uuid=self.uuid, wallet_uuid=wallet_uuid)
|
||||
return self.update()
|
22
PyCrypCli/models/service/portscan_service.py
Normal file
22
PyCrypCli/models/service/portscan_service.py
Normal file
|
@ -0,0 +1,22 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from .public_service import PublicService
|
||||
from .service import Service
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client import Client
|
||||
|
||||
|
||||
class PortscanService(Service):
|
||||
@staticmethod
|
||||
def get_portscan_service(client: Client, device_uuid: str) -> PortscanService:
|
||||
service: Service = Service.get_service_by_name(client, device_uuid, "portscan")
|
||||
return PortscanService.parse(client, service.dict(by_alias=True))
|
||||
|
||||
def update(self) -> PortscanService:
|
||||
return self._update(PortscanService.get_portscan_service(self._client, self.device_uuid))
|
||||
|
||||
def scan(self, target: str) -> list[PublicService]:
|
||||
return [PublicService.parse(self._client, service) for service in self.use(target_device=target)["services"]]
|
26
PyCrypCli/models/service/public_service.py
Normal file
26
PyCrypCli/models/service/public_service.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from ..model import Model
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client import Client
|
||||
|
||||
|
||||
class PublicService(Model):
|
||||
uuid: str
|
||||
device_uuid: str = Field(alias="device")
|
||||
name: str
|
||||
running_port: int | None
|
||||
|
||||
@staticmethod
|
||||
def get_public_service(client: Client, device_uuid: str, service_uuid: str) -> PublicService:
|
||||
return PublicService.parse(
|
||||
client, client.ms("service", ["public_info"], device_uuid=device_uuid, service_uuid=service_uuid)
|
||||
)
|
||||
|
||||
def update(self) -> PublicService:
|
||||
return self._update(PublicService.get_public_service(self._client, self.device_uuid, self.uuid))
|
56
PyCrypCli/models/service/service.py
Normal file
56
PyCrypCli/models/service/service.py
Normal file
|
@ -0,0 +1,56 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import Any, TypeVar, TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .public_service import PublicService
|
||||
from ...exceptions import ServiceNotFoundError
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client import Client
|
||||
|
||||
ServiceType = TypeVar("ServiceType", bound="Service")
|
||||
|
||||
|
||||
class Service(PublicService):
|
||||
owner_uuid: str = Field(alias="owner")
|
||||
running: bool
|
||||
part_owner_uuid: str | None = Field(alias="part_owner")
|
||||
speed: float
|
||||
|
||||
@staticmethod
|
||||
def get_services(client: Client, device_uuid: str) -> list[Service]:
|
||||
return [
|
||||
Service.parse(client, service)
|
||||
for service in client.ms("service", ["list"], device_uuid=device_uuid)["services"]
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
def get_service(client: Client, device_uuid: str, service_uuid: str) -> Service:
|
||||
return Service.parse(
|
||||
client, client.ms("service", ["private_info"], device_uuid=device_uuid, service_uuid=service_uuid)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def get_service_by_name(client: Client, device_uuid: str, name: str) -> Service:
|
||||
for service in Service.get_services(client, device_uuid):
|
||||
if service.name == name:
|
||||
return service
|
||||
raise ServiceNotFoundError
|
||||
|
||||
@staticmethod
|
||||
def list_part_owner(client: Client) -> list[Service]:
|
||||
return [Service.parse(client, service) for service in client.ms("service", ["list_part_owner"])["services"]]
|
||||
|
||||
def update(self) -> Service:
|
||||
return self._update(Service.get_service(self._client, self.device_uuid, self.uuid))
|
||||
|
||||
def use(self, **data: Any) -> dict[Any, Any]:
|
||||
return self._ms("service", ["use"], device_uuid=self.device_uuid, service_uuid=self.uuid, **data)
|
||||
|
||||
def toggle(self) -> Service:
|
||||
return self._update(self._ms("service", ["toggle"], device_uuid=self.device_uuid, service_uuid=self.uuid))
|
||||
|
||||
def delete(self) -> None:
|
||||
self._ms("service", ["delete"], device_uuid=self.device_uuid, service_uuid=self.uuid)
|
5
PyCrypCli/models/shop/__init__.py
Normal file
5
PyCrypCli/models/shop/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from .shop_category import ShopCategory
|
||||
from .shop_product import ShopProduct
|
||||
|
||||
|
||||
__all__ = ["ShopCategory", "ShopProduct"]
|
39
PyCrypCli/models/shop/shop_category.py
Normal file
39
PyCrypCli/models/shop/shop_category.py
Normal file
|
@ -0,0 +1,39 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from pydantic import validator, Field
|
||||
|
||||
from .shop_product import ShopProduct
|
||||
from ..model import Model
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client import Client
|
||||
|
||||
|
||||
class ShopCategory(Model):
|
||||
name: str
|
||||
index: int
|
||||
items: list[ShopProduct]
|
||||
subcategories: list[ShopCategory] = Field(alias="categories")
|
||||
|
||||
@staticmethod
|
||||
def shop_list(client: Client) -> list[ShopCategory]:
|
||||
out = [
|
||||
ShopCategory.parse(client, {"name": k, **v})
|
||||
for k, v in client.ms("inventory", ["shop", "list"])["categories"].items()
|
||||
]
|
||||
out.sort(key=lambda c: c.index)
|
||||
return out
|
||||
|
||||
@validator("items", pre=True)
|
||||
def _parse_items(cls, value: dict[str, dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
out = [v | {"name": k} for k, v in value.items()]
|
||||
out.sort(key=lambda e: e["id"])
|
||||
return out
|
||||
|
||||
@validator("subcategories", pre=True)
|
||||
def _parse_subcategories(cls, value: dict[str, dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
out = [v | {"name": k} for k, v in value.items()]
|
||||
out.sort(key=lambda e: e["index"])
|
||||
return out
|
37
PyCrypCli/models/shop/shop_product.py
Normal file
37
PyCrypCli/models/shop/shop_product.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..inventory_element import InventoryElement
|
||||
from ..model import Model
|
||||
from ..wallet import Wallet
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client import Client
|
||||
|
||||
|
||||
class ShopProduct(Model):
|
||||
id: int
|
||||
name: str
|
||||
price: int
|
||||
related_ms: str
|
||||
|
||||
@staticmethod
|
||||
def shop_info(client: Client, product: str) -> ShopProduct:
|
||||
return ShopProduct.parse(client, client.ms("inventory", ["shop", "info"], product=product))
|
||||
|
||||
@staticmethod
|
||||
def bulk_buy(client: Client, products: dict[ShopProduct, int], wallet: Wallet) -> list[InventoryElement]:
|
||||
return [
|
||||
InventoryElement.parse(client, element)
|
||||
for element in client.ms(
|
||||
"inventory",
|
||||
["shop", "buy"],
|
||||
products={k.name: v for k, v in products.items()},
|
||||
wallet_uuid=wallet.uuid,
|
||||
key=wallet.key,
|
||||
)["bought_products"]
|
||||
]
|
||||
|
||||
def buy(self, wallet: Wallet) -> InventoryElement:
|
||||
return ShopProduct.bulk_buy(self._client, {self: 1}, wallet)[0]
|
6
PyCrypCli/models/wallet/__init__.py
Normal file
6
PyCrypCli/models/wallet/__init__.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from .public_wallet import PublicWallet
|
||||
from .transaction import Transaction
|
||||
from .wallet import Wallet
|
||||
|
||||
|
||||
__all__ = ["PublicWallet", "Transaction", "Wallet"]
|
25
PyCrypCli/models/wallet/public_wallet.py
Normal file
25
PyCrypCli/models/wallet/public_wallet.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from ..model import Model
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client import Client
|
||||
|
||||
|
||||
class PublicWallet(Model):
|
||||
uuid: str = Field(alias="source_uuid")
|
||||
|
||||
@staticmethod
|
||||
def get_public_wallet(client: Client, uuid: str) -> PublicWallet:
|
||||
return PublicWallet.parse(client, {"source_uuid": uuid})
|
||||
|
||||
@staticmethod
|
||||
def list_wallets(client: Client) -> list[PublicWallet]:
|
||||
return [PublicWallet.get_public_wallet(client, uuid) for uuid in client.ms("currency", ["list"])["wallets"]]
|
||||
|
||||
def reset_wallet(self) -> None:
|
||||
self._ms("currency", ["reset"], source_uuid=self.uuid)
|
19
PyCrypCli/models/wallet/transaction.py
Normal file
19
PyCrypCli/models/wallet/transaction.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from datetime import datetime
|
||||
|
||||
from pydantic import Field, validator
|
||||
|
||||
from ..model import Model
|
||||
from ...util import utc_to_local
|
||||
|
||||
|
||||
class Transaction(Model):
|
||||
timestamp: datetime = Field(alias="time_stamp")
|
||||
source_uuid: str
|
||||
destination_uuid: str
|
||||
amount: int = Field(alias="send_amount")
|
||||
usage: str
|
||||
origin: str
|
||||
|
||||
@validator("timestamp")
|
||||
def _convert_timestamp(cls, value: datetime) -> datetime:
|
||||
return utc_to_local(value)
|
59
PyCrypCli/models/wallet/wallet.py
Normal file
59
PyCrypCli/models/wallet/wallet.py
Normal file
|
@ -0,0 +1,59 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .public_wallet import PublicWallet
|
||||
from .transaction import Transaction
|
||||
from ..service import Miner
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...client import Client
|
||||
|
||||
|
||||
class Wallet(PublicWallet):
|
||||
key: str
|
||||
owner_uuid: str = Field(alias="user_uuid")
|
||||
amount: int
|
||||
transaction_count: int = Field(alias="transactions")
|
||||
|
||||
@staticmethod
|
||||
def create_wallet(client: Client) -> Wallet:
|
||||
return Wallet.parse(client, client.ms("currency", ["create"]))
|
||||
|
||||
@staticmethod
|
||||
def get_wallet(client: Client, uuid: str, key: str) -> Wallet:
|
||||
return Wallet.parse(client, client.ms("currency", ["get"], source_uuid=uuid, key=key))
|
||||
|
||||
def update(self) -> Wallet:
|
||||
return self._update(Wallet.get_wallet(self._client, self.uuid, self.key))
|
||||
|
||||
def get_transactions(self, count: int, offset: int) -> list[Transaction]:
|
||||
return [
|
||||
Transaction.parse(self._client, t)
|
||||
for t in self._ms(
|
||||
"currency", ["transactions"], source_uuid=self.uuid, key=self.key, count=count, offset=offset
|
||||
)["transactions"]
|
||||
]
|
||||
|
||||
def get_miners(self) -> list[Miner]:
|
||||
return Miner.get_miners(self._client, self.uuid)
|
||||
|
||||
def get_mining_rate(self) -> float:
|
||||
return sum(miner.speed for miner in self.get_miners() if miner.running)
|
||||
|
||||
def send(self, destination: PublicWallet, amount: int, usage: str) -> Wallet:
|
||||
self._ms(
|
||||
"currency",
|
||||
["send"],
|
||||
source_uuid=self.uuid,
|
||||
key=self.key,
|
||||
send_amount=amount,
|
||||
destination_uuid=destination.uuid,
|
||||
usage=usage,
|
||||
)
|
||||
return self.update()
|
||||
|
||||
def delete(self) -> None:
|
||||
self._ms("currency", ["delete"], source_uuid=self.uuid, key=self.key)
|
|
@ -1,18 +1,18 @@
|
|||
import os
|
||||
import sys
|
||||
from os import getenv
|
||||
from typing import List, Optional
|
||||
from pathlib import Path
|
||||
from typing import NoReturn
|
||||
|
||||
import requests
|
||||
import sentry_sdk
|
||||
|
||||
from PyCrypCli.commands import make_commands, Command
|
||||
from PyCrypCli.context import Context, LoginContext, RootContext
|
||||
from .commands import make_commands, Command
|
||||
from .context import Context, LoginContext, RootContext
|
||||
|
||||
try:
|
||||
import readline
|
||||
except ImportError:
|
||||
import pyreadline as readline
|
||||
import pyreadline as readline # type: ignore
|
||||
|
||||
if not getenv("DEBUG"):
|
||||
response = requests.get("https://sentrydsn.defelo.de/pycrypcli")
|
||||
|
@ -21,10 +21,10 @@ if not getenv("DEBUG"):
|
|||
|
||||
|
||||
class Frontend:
|
||||
def __init__(self, server: str, config_file: List[str]):
|
||||
self.config_file: List[str] = config_file
|
||||
def __init__(self, server: str, config_file: Path):
|
||||
self.config_file: Path = config_file
|
||||
|
||||
self.history: List[str] = []
|
||||
self.history: list[str] = []
|
||||
|
||||
readline.parse_and_bind("tab: complete")
|
||||
readline.set_completer(self.completer)
|
||||
|
@ -33,8 +33,8 @@ class Frontend:
|
|||
self.root_context: RootContext = RootContext(server, config_file, make_commands())
|
||||
self.root_context.open(LoginContext(self.root_context))
|
||||
|
||||
def complete_command(self, text: str) -> List[str]:
|
||||
override_completions: Optional[List[str]] = self.root_context.get_override_completions()
|
||||
def complete_command(self, text: str) -> list[str]:
|
||||
override_completions: list[str] | None = self.root_context.get_override_completions()
|
||||
if override_completions is not None:
|
||||
return override_completions
|
||||
|
||||
|
@ -42,16 +42,16 @@ class Frontend:
|
|||
if not args:
|
||||
return list(self.root_context.get_commands())
|
||||
|
||||
comp: Optional[Command] = self.root_context.get_commands().get(cmd, None)
|
||||
comp: Command | None = self.root_context.get_commands().get(cmd, None)
|
||||
if comp is None:
|
||||
return []
|
||||
|
||||
return comp.handle_completer(self.get_context(), args) or []
|
||||
|
||||
def completer(self, text: str, state: int) -> Optional[str]:
|
||||
def completer(self, text: str, state: int) -> str | None:
|
||||
readline.set_completer_delims(" ")
|
||||
options: List[str] = self.complete_command(readline.get_line_buffer())
|
||||
options: List[str] = [o + " " if o[-1:] != "\0" else o[:-1] for o in sorted(options) if o.startswith(text)]
|
||||
options: list[str] = self.complete_command(readline.get_line_buffer())
|
||||
options = [o + " " if o[-1:] != "\0" else o[:-1] for o in sorted(options) if o.startswith(text)]
|
||||
|
||||
if state < len(options):
|
||||
return options[state]
|
||||
|
@ -60,7 +60,7 @@ class Frontend:
|
|||
def get_context(self) -> Context:
|
||||
return self.root_context.get_context()
|
||||
|
||||
def mainloop(self):
|
||||
def mainloop(self) -> NoReturn:
|
||||
while True:
|
||||
self.get_context().loop_tick()
|
||||
context: Context = self.get_context()
|
||||
|
@ -88,7 +88,7 @@ class Frontend:
|
|||
print("Type `help` for a list of commands.")
|
||||
|
||||
|
||||
def main():
|
||||
def main() -> NoReturn:
|
||||
print(
|
||||
"\033[32m\033[1m"
|
||||
r"""
|
||||
|
@ -99,20 +99,20 @@ def main():
|
|||
\____/_/ \__, / .___/\__/_/\___/
|
||||
/____/_/
|
||||
"""
|
||||
"\033[0m",
|
||||
"\033[0m"
|
||||
)
|
||||
print("Python Cryptic Game Client (https://github.com/Defelo/PyCrypCli)")
|
||||
print("You can always type `help` for a list of available commands.")
|
||||
|
||||
server: str = "wss://ws.cryptic-game.net/"
|
||||
server = "wss://ws.cryptic-game.net/"
|
||||
if len(sys.argv) > 1:
|
||||
server: str = sys.argv[1]
|
||||
server = sys.argv[1]
|
||||
if server.lower() == "test":
|
||||
server: str = "wss://ws.test.cryptic-game.net/"
|
||||
server = "wss://ws.test.cryptic-game.net/"
|
||||
elif not server.startswith("wss://") and not server.startswith("ws://"):
|
||||
server: str = "ws://" + server
|
||||
server = "ws://" + server
|
||||
|
||||
frontend: Frontend = Frontend(server, [os.path.expanduser("~"), ".config", "PyCrypCli", "config.json"])
|
||||
frontend: Frontend = Frontend(server, Path("~/.config/PyCrypCli/config.json").expanduser())
|
||||
frontend.mainloop()
|
||||
|
||||
|
||||
|
|
|
@ -1,18 +1,18 @@
|
|||
import time
|
||||
from threading import Thread
|
||||
from typing import Callable
|
||||
from typing import Callable, Any
|
||||
|
||||
|
||||
class Timer(Thread):
|
||||
def __init__(self, interval: float, func: Callable):
|
||||
def __init__(self, interval: float, func: Callable[[], Any]):
|
||||
super().__init__(daemon=True)
|
||||
|
||||
self.interval: float = interval
|
||||
self.func: Callable = func
|
||||
self.func: Callable[[], Any] = func
|
||||
self.running: bool = False
|
||||
|
||||
def run(self):
|
||||
self.running: bool = True
|
||||
def run(self) -> None:
|
||||
self.running = True
|
||||
while self.running:
|
||||
self.func()
|
||||
|
||||
|
@ -20,7 +20,7 @@ class Timer(Thread):
|
|||
sleep_until: float = t + self.interval
|
||||
while t < sleep_until and self.running:
|
||||
time.sleep(min(0.1, sleep_until - t))
|
||||
t: float = time.time()
|
||||
t = time.time()
|
||||
|
||||
def stop(self):
|
||||
self.running: bool = False
|
||||
def stop(self) -> None:
|
||||
self.running = False
|
||||
|
|
|
@ -1,30 +1,28 @@
|
|||
import re
|
||||
from datetime import datetime
|
||||
from typing import Optional, Tuple, List
|
||||
|
||||
from dateutil.tz import tz
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any, Sequence
|
||||
|
||||
|
||||
def is_uuid(x: str) -> bool:
|
||||
return bool(re.match(r"^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$", x))
|
||||
|
||||
|
||||
def extract_wallet(content: str) -> Optional[Tuple[str, str]]:
|
||||
def extract_wallet(content: str) -> tuple[str, str] | None:
|
||||
if re.match(r"^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12} [0-9a-f]{10}$", content):
|
||||
uuid, key = content.split()
|
||||
return uuid, key
|
||||
return None
|
||||
|
||||
|
||||
def convert_timestamp(timestamp: datetime) -> datetime:
|
||||
return timestamp.replace(tzinfo=tz.tzutc()).astimezone(tz.tzlocal()).replace(tzinfo=None)
|
||||
def utc_to_local(timestamp: datetime) -> datetime:
|
||||
return timestamp.replace(tzinfo=timezone.utc).astimezone().replace(tzinfo=None)
|
||||
|
||||
|
||||
def strip_float(num: float, precision):
|
||||
def strip_float(num: float, precision: int) -> str:
|
||||
return f"{num:.{precision}f}".rstrip("0").rstrip(".")
|
||||
|
||||
|
||||
def print_tree(items: List[Tuple[str, Optional[list]]], indent: Optional[List[bool]] = None):
|
||||
def print_tree(items: Sequence[tuple[str, Sequence[Any] | None]], indent: list[bool] | None = None) -> None:
|
||||
if not indent:
|
||||
indent = []
|
||||
for i, (item, children) in enumerate(items):
|
||||
|
|
4
mypy.ini
Normal file
4
mypy.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
[mypy]
|
||||
strict = True
|
||||
ignore_missing_imports = True
|
||||
plugins=pydantic.mypy
|
|
@ -1,2 +1,4 @@
|
|||
[tool.black]
|
||||
target-version = ["py310"]
|
||||
line-length = 120
|
||||
skip-magic-trailing-comma = true
|
||||
|
|
|
@ -3,4 +3,5 @@ pyreadline~=2.1
|
|||
pypresence~=4.2
|
||||
python-dateutil~=2.8
|
||||
sentry-sdk~=1.5
|
||||
requests~=2.27
|
||||
requests~=2.27
|
||||
pydantic~=1.9
|
||||
|
|
Reference in a new issue