Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ docs/_build
pyjd.egg-info/
venv/
.coverage
*.env
14 changes: 14 additions & 0 deletions .zed/debug.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"label": "Connect MYJD",
"adapter": "Debugpy",
"request": "launch",
"program": "pyjd/__main__.py",
"justMyCode": false,
"purpose": ["debug-test"],
"args": ["connect"],
"env": {
"PYTHONASYNCIODEBUG": "1",
},
},
]
8 changes: 8 additions & 0 deletions pyjd/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
def main() -> None:
from pyjd.cli import app

app()


if __name__ == "__main__":
main()
45 changes: 45 additions & 0 deletions pyjd/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import dataclasses
import logging
from typing import Annotated

from cyclopts import App, Parameter
from dotenv import dotenv_values
from rich.logging import RichHandler
from rich.traceback import install

from pyjd.client import JDDeviceClient
from pyjd.jd_types import LinkCollectingJob
from pyjd.queries import AddLinksQuery

app = App()

install()
logger = logging.getLogger("pyjd")
logger.addHandler(RichHandler(level=logging.DEBUG, rich_tracebacks=True))
logger.setLevel(logging.DEBUG)


@app.command()
def connect() -> JDDeviceClient:
creds = dotenv_values()
app.console.log(email := creds["EMAIL"] or "")
device = JDDeviceClient.myjd_connect(
email=email,
password=creds["PASSWORD"] or "",
device_id=creds.get("DEVICE_ID"),
device_name=creds.get("DEVICE_NAME"),
)
app.console.log(device)
return device


@app.command()
def add_links(
link: str, *, query: Annotated[AddLinksQuery | None, Parameter(name="*")] = None
) -> LinkCollectingJob:
device = connect()
query = dataclasses.replace(query, links=link) if query else AddLinksQuery(links=link)

job = device.linkgrabber.add_links(query)
app.console.log(job)
return job
6 changes: 3 additions & 3 deletions pyjd/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,10 @@ def myjd_connect(
cls,
email: str,
password: str,
device_id: str | None,
device_name: str | None,
device_id: str | None = None,
device_name: str | None = None,
) -> Self:
api = MyJDAPI()
api.connect(email, password)
device = api.get_device(device_id, device_name)
device = api.get_device(id=device_id, name=device_name)
return cls(MyJDConnection(api, device))
39 changes: 33 additions & 6 deletions pyjd/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import dataclasses
import json
import logging
import time
import urllib.parse
from contextvars import ContextVar
from typing import TYPE_CHECKING, Any, ClassVar, Self

Expand All @@ -21,6 +23,18 @@
type Params = list[Any]


def next_request_id() -> int:
return time.time_ns()


def update_request_id(url: str | None = None) -> None:
if url and (rid := urllib.parse.parse_qs(url).get("rid")):
new_id = int(rid[0])
else:
new_id = time.time_ns()
REQUEST_ID.set(new_id)


class DictDataClass:
__dataclass_fields__: ClassVar[dict[str, dataclasses.Field[Any]]]

Expand All @@ -31,7 +45,7 @@ def __iter__(self) -> Generator[tuple[str, Any]]:

# recursive dict conversion
def __json__(self) -> dict[str, Any]:
return dataclasses.asdict(self)
return dict(self)

@classmethod
def filter_dict(cls, data: Mapping[str, Any]) -> dict[str, Any]:
Expand All @@ -52,18 +66,31 @@ def make_request(
headers: dict[str, str] | None = None,
data: str | None = None,
timeout: int = 60,
method: str = "POST",
) -> requests.Response:
logger.debug(f"Request to {url}")
headers = headers or {}
headers.setdefault("Content-Type", "application/json; charset=utf-8")
return requests.post(url, headers=headers, timeout=timeout, data=data)
if method == "POST":
headers.setdefault("Content-Type", "application/json; charset=utf-8")
return requests.request(method, url, headers=headers, timeout=timeout, data=data)


def _parse_param(params: Params | None):
if not params:
return
for param in params:
if type(param) is dict:
yield {k: v for k, v in param.items() if v is not None}
else:
yield param


def prepare_api_json(path: str, params: Params | None) -> str:

def prepare_api_json(path: str, params: list[Any] | str | None) -> str:
data = {
"apiVer": 1,
"url": path.partition("?")[0],
"params": params or (),
"rid": 12345,
"params": list(_parse_param(params)),
"rid": REQUEST_ID.get(),
}
return json.dumps(data)
11 changes: 9 additions & 2 deletions pyjd/direct.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import requests

from pyjd.common import Params, make_request, prepare_api_json
from pyjd.common import Params, make_request, prepare_api_json, update_request_id
from pyjd.jd_types import JDDevice

logger = logging.getLogger(__name__)
Expand All @@ -26,9 +26,15 @@ class DirectConnection:
),
)

def jd_version(self) -> int:
url = f"{self.base_url}/jd/version"
update_request_id()
resp = make_request(url, headers=self.headers)
return _parse_resp(resp.content)

def is_connected(self) -> bool:
try:
make_request(self.base_url + "/jd/version", headers=self.headers)
self.jd_version()
except requests.exceptions.RequestException:
return False
else:
Expand All @@ -43,6 +49,7 @@ def request_bytes(self, path: str, params: Params | None = None) -> bytes:

def request(self, path: str, params: Params | None = None) -> requests.Response:
url = f"{self.base_url}{path}"
update_request_id()
return make_request(url, data=prepare_api_json(path, params))


Expand Down
4 changes: 2 additions & 2 deletions pyjd/jd_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@
import dataclasses


@dataclasses.dataclass(slots=True, frozen=True)
@dataclasses.dataclass(slots=True, frozen=True, order=True)
class JDDevice:
name: str
id: str
name: str
type: str


Expand Down
Loading