main
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3import base64
4import io
5import shutil
6from pathlib import Path
7
8import anyio
9from glom import glom
10from loguru import logger
11
12from config import DB, DOWNLOAD_DIR
13from networking import hx_req
14
15
16async def list_alist() -> list[dict]:
17 """List from Alist."""
18 if not DB.ALIST_ENABLED:
19 return [{}]
20 api = DB.ALIST_SERVER.removesuffix("/") + "/api/fs/list"
21 logger.info(f"List Alist from: {api}")
22 res = await hx_req(api, method="POST", json_data={"path": "/"}, check_kv={"code": 200})
23 return glom(res, "data.content", default=[]) or []
24
25
26async def download_alist(fname: str, save_path: str | Path | None = None) -> str:
27 """Download file from Alist."""
28 if not DB.ALIST_ENABLED:
29 return ""
30 save_path = Path(DOWNLOAD_DIR) / fname if save_path is None else Path(save_path)
31 ext = Path(fname).suffix
32 # Headers DO NOT support Unicode characters
33 if any(ord(c) > 127 for c in fname): # has Non-ASCII
34 b64str = base64.urlsafe_b64encode(fname.encode("utf-8")).decode("ascii").rstrip("=")
35 fname = b64str + ext
36 url = DB.ALIST_SERVER.removesuffix("/") + "/d/" + DB.ALIST_BASR_PATH.strip("/") + f"/{fname.lstrip('/')}"
37 res = await hx_req(url, rformat="content", check_keys=["content"])
38 logger.info(f"Download {fname} to {save_path.name} from: {url}")
39 async with await anyio.open_file(save_path, "wb") as f:
40 await f.write(res["content"])
41 return save_path.as_posix()
42
43
44async def upload_alist(path: str | Path) -> str:
45 """Upload to Alist."""
46 if not DB.ALIST_ENABLED:
47 return ""
48 path = Path(path).expanduser().resolve()
49 if not path.is_file():
50 return ""
51 api = DB.ALIST_SERVER.removesuffix("/") + "/api/fs/form"
52 # Headers DO NOT support Unicode characters
53 new_path = Path("/non-exist")
54 if any(ord(c) > 127 for c in path.name): # has Non-ASCII
55 new_name = base64.urlsafe_b64encode(path.name.encode("utf-8")).decode("ascii").rstrip("=")
56 new_path = path.with_stem(new_name)
57 shutil.copy(path, new_path)
58 path = new_path
59 headers = {"File-Path": f"/{path.name}"}
60 logger.info(f"Upload {path.name} to: {api}")
61 async with await anyio.open_file(path, "rb") as f:
62 content = await f.read()
63 await hx_req(api, method="PUT", headers=headers, files={"file": (path.name, io.BytesIO(content))}, check_kv={"code": 200})
64 if "new_path" in locals():
65 new_path.unlink(missing_ok=True)
66 return DB.ALIST_SERVER.removesuffix("/") + "/d/" + DB.ALIST_BASR_PATH.strip("/") + f"/{path.name}"
67
68
69async def delete_alist(fname: str, *, ensure_ascii: bool = True) -> None:
70 """Delete from Alist."""
71 if not DB.ALIST_ENABLED:
72 return
73 # Get JWT Token
74 payload = {"username": DB.ALIST_USERNAME, "password": DB.ALIST_PASSWORD}
75 auth_api = DB.ALIST_SERVER.removesuffix("/") + "/api/auth/login"
76 res = await hx_req(auth_api, method="POST", json_data=payload, check_keys=["data.token"])
77 token = res["data"]["token"]
78
79 # Delete
80 api = DB.ALIST_SERVER.removesuffix("/") + "/api/fs/remove"
81 headers = {"Content-Type": "application/json", "Authorization": token}
82 if ensure_ascii and any(ord(c) > 127 for c in fname): # has Non-ASCII
83 b64str = base64.urlsafe_b64encode(fname.encode("utf-8")).decode("ascii").rstrip("=")
84 payload = {"names": [b64str + Path(fname).suffix]}
85 else:
86 payload = {"names": [fname]}
87 logger.info(f"Delete {fname} from: {api}")
88 res = await hx_req(api, method="POST", headers=headers, json_data=payload, check_kv={"code": 200})