From 892a1648cf75fc42e10ce4595340858f5bc86f98 Mon Sep 17 00:00:00 2001 From: yupix Date: Thu, 30 Nov 2023 18:14:14 +0900 Subject: [PATCH 01/16] =?UTF-8?q?feat:=20DriveStatus=E3=83=A2=E3=83=87?= =?UTF-8?q?=E3=83=AB=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/models/drive.py | 29 +++++++++++++++++++++++++++++ mipac/types/drive.py | 4 ++++ 2 files changed, 33 insertions(+) diff --git a/mipac/models/drive.py b/mipac/models/drive.py index 5aea0c64..d41983b9 100644 --- a/mipac/models/drive.py +++ b/mipac/models/drive.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any from mipac.abstract.model import AbstractModel +from mipac.types.drive import IDriveStatus if TYPE_CHECKING: from mipac.manager.client import ClientManager @@ -12,6 +13,34 @@ __all__ = ["FileProperties", "File", "Folder"] +class DriveStatus: + def __init__(self, raw_drive_status: IDriveStatus, *, client: ClientManager) -> None: + self.__raw_drive_status: IDriveStatus = raw_drive_status + self.__client: ClientManager = client + + @property + def capacity(self) -> int: + """Total capacity of the drive in bytes + + Returns + ------- + int + Total capacity of the drive in bytes + """ + return self.__raw_drive_status["capacity"] + + @property + def usage(self) -> int: + """Total usage of the drive in bytes + + Returns + ------- + int + Total usage of the drive in bytes + """ + return self.__raw_drive_status["usage"] + + class FileProperties(AbstractModel): def __init__(self, properties: IFileProperties) -> None: self.__properties: IFileProperties = properties diff --git a/mipac/types/drive.py b/mipac/types/drive.py index 71b836d3..4f2d3e51 100644 --- a/mipac/types/drive.py +++ b/mipac/types/drive.py @@ -4,6 +4,10 @@ __all__ = ("IFileProperties", "FolderPayload", "IDriveFile") +class IDriveStatus(TypedDict): + capacity: int + usage: int + class IFileProperties(TypedDict): """ From 1b9dc39edf474a70a780a50f19854d2b30a9dcd9 Mon Sep 17 00:00:00 2001 From: yupix Date: Thu, 30 Nov 2023 18:15:48 +0900 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20Drive=E5=91=A8=E3=82=8A=E3=81=AE?= =?UTF-8?q?=E3=83=A2=E3=83=87=E3=83=AB=E3=81=A8=E5=9E=8B=E3=82=92=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E3=81=AE=E7=89=A9=E3=81=AB=E8=BF=BD=E5=BE=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/models/drive.py | 98 ++++++++++++++++++++++++++++--------------- mipac/types/drive.py | 39 +++++++++++------ 2 files changed, 91 insertions(+), 46 deletions(-) diff --git a/mipac/models/drive.py b/mipac/models/drive.py index d41983b9..029635c5 100644 --- a/mipac/models/drive.py +++ b/mipac/models/drive.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, Any from mipac.abstract.model import AbstractModel +from mipac.models.lite.user import PartialUser from mipac.types.drive import IDriveStatus if TYPE_CHECKING: @@ -42,20 +43,24 @@ def usage(self) -> int: class FileProperties(AbstractModel): - def __init__(self, properties: IFileProperties) -> None: - self.__properties: IFileProperties = properties + def __init__(self, raw_properties: IFileProperties) -> None: + self.__raw_properties: IFileProperties = raw_properties @property def width(self) -> int | None: - return self.__properties["width"] + return self.__raw_properties.get("width") @property - def height(self) -> int: - return self.__properties["height"] + def height(self) -> int | None: + return self.__raw_properties.get("height") + + @property + def orientation(self) -> int | None: + return self.__raw_properties.get("orientation") @property def avg_color(self) -> str | None: - return self.__properties["avg_color"] + return self.__raw_properties.get("avg_color") class Folder(AbstractModel): @@ -65,36 +70,35 @@ def __init__(self, folder: FolderPayload, client: ClientManager): @property def id(self) -> str: - """フォルダのID""" return self.__folder["id"] @property def created_at(self) -> str: # TODO: 型 - """フォルダの作成日時""" return self.__folder["created_at"] @property def name(self) -> str: - """フォルダ名""" return self.__folder["name"] @property - def folders_count(self) -> int: - """フォルダ内のフォルダ数""" - return self.__folder["folders_count"] + def parent_id(self) -> str | None: + return self.__folder["parent_id"] @property - def files_count(self) -> int: - """フォルダ内のファイル数""" - return self.__folder["files_count"] + def folders_count(self) -> int | None: + return self.__folder.get("folders_count") @property - def parent_id(self) -> str: - return self.__folder["parent_id"] + def files_count(self) -> int | None: + return self.__folder.get("files_count") @property - def parent(self) -> dict[str, Any]: - return self.__folder["parent"] + def parent(self) -> Folder | None: + return ( + Folder(self.__folder["parent"], client=self.__client) + if "parent" in self.__folder and self.__folder["parent"] + else None + ) @property def api(self) -> ClientFolderManager: @@ -120,41 +124,69 @@ def id(self) -> str: def created_at(self): return self.__file["created_at"] + @property + def name(self) -> str: + return self.__file["name"] + + @property + def type(self) -> str: + return self.__file["type"] + + @property + def md5(self) -> str: + return self.__file["md5"] + + @property + def size(self) -> int: + return self.__file["size"] + @property def is_sensitive(self) -> bool: return self.__file["is_sensitive"] @property - def name(self) -> str: - return self.__file["name"] + def blurhash(self) -> str | None: + return self.__file["blurhash"] @property - def thumbnail_url(self) -> str: - return self.__file["thumbnail_url"] + def properties(self) -> FileProperties: + return FileProperties(self.__file["properties"]) @property def url(self) -> str: return self.__file["url"] @property - def type(self) -> str: - return self.__file["type"] + def thumbnail_url(self) -> str | None: + return self.__file["thumbnail_url"] @property - def size(self) -> int: - return self.__file["size"] + def comment(self) -> str | None: + return self.__file["comment"] @property - def md5(self) -> str: - return self.__file["md5"] + def folder_id(self) -> str | None: + return self.__file["folder_id"] @property - def blurhash(self) -> str: - return self.__file["blurhash"] + def folder(self) -> Folder | None: + return ( + Folder(self.__file["folder"], client=self.__client) + if "folder" in self.__file and self.__file["folder"] + else None + ) @property - def properties(self) -> FileProperties: - return FileProperties(self.__file["properties"]) + def user_id(self) -> str | None: + return self.__file["user_id"] + + @property + def user(self) -> PartialUser | None: + return ( + PartialUser(self.__file["user"], client=self.__client) + if "user" in self.__file and self.__file["user"] + else None + ) @property def api(self) -> ClientFileManager: diff --git a/mipac/types/drive.py b/mipac/types/drive.py index 4f2d3e51..212ffc31 100644 --- a/mipac/types/drive.py +++ b/mipac/types/drive.py @@ -1,9 +1,16 @@ from __future__ import annotations -from typing import Any, TypedDict +from typing import TYPE_CHECKING, Any, Literal, NotRequired, TypedDict + +if TYPE_CHECKING: + from mipac.models.lite.user import PartialUser + __all__ = ("IFileProperties", "FolderPayload", "IDriveFile") +IDriveSort = Literal["+createdAt", "-createdAt", "+name", "-name", "+size", "-size"] + + class IDriveStatus(TypedDict): capacity: int usage: int @@ -14,9 +21,10 @@ class IFileProperties(TypedDict): プロパティー情報 """ - width: int - height: int - avg_color: str | None + width: NotRequired[int] + height: NotRequired[int] + orientation: NotRequired[int] + avg_color: NotRequired[str] class FolderPayload(TypedDict): @@ -27,10 +35,10 @@ class FolderPayload(TypedDict): id: str created_at: str name: str - folders_count: int - files_count: int - parent_id: str - parent: dict[str, Any] + parent_id: str | None + folders_count: NotRequired[int] + files_count: NotRequired[int] + parent: NotRequired[FolderPayload | None] class IDriveFile(TypedDict): @@ -40,12 +48,17 @@ class IDriveFile(TypedDict): id: str created_at: str - is_sensitive: bool name: str - thumbnail_url: str - url: str type: str - size: int md5: str - blurhash: str + size: int + is_sensitive: bool + blurhash: str | None properties: IFileProperties + url: str + thumbnail_url: str | None + comment: str | None + folder_id: str | None + folder: NotRequired[FolderPayload | None] + user_id: str | None + user: NotRequired[PartialUser | None] From 10c8c8d6982b89ddb7e284339ea396cf64ed14ec Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 12:02:24 +0900 Subject: [PATCH 03/16] =?UTF-8?q?feat:=20Missing=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/utils/util.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mipac/utils/util.py b/mipac/utils/util.py index 12310e16..4e244025 100644 --- a/mipac/utils/util.py +++ b/mipac/utils/util.py @@ -19,6 +19,20 @@ else: _from_json = json.loads +class Missing: + def __repr__(self) -> str: + return "MISSING" + + def __bool__(self) -> bool: + return False + + def __eq__(self, other: Any) -> bool: + return isinstance(other, Missing) + + def __ne__(self, other: Any) -> bool: + return not isinstance(other, Missing) + +MISSING: Any = Missing() def credentials_required(func): @functools.wraps(func) From c2cac8f6d51f0dec76d11462a02a3e50da0cce78 Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 12:19:49 +0900 Subject: [PATCH 04/16] =?UTF-8?q?chore:=20remove=5Fdict=5Fempty=E3=81=A7?= =?UTF-8?q?=E7=89=B9=E5=AE=9A=E3=81=AEkey=E3=82=92ignore=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/utils/format.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/mipac/utils/format.py b/mipac/utils/format.py index 6ef14521..b9352b79 100644 --- a/mipac/utils/format.py +++ b/mipac/utils/format.py @@ -55,12 +55,16 @@ def remove_list_empty(data: list[Any]) -> list[Any]: return [k for k in data if k] -def remove_dict_empty(data: dict[str, Any]) -> dict[str, Any]: +def remove_dict_empty( + data: dict[str, Any], ignore_keys: list[str] | None = None +) -> dict[str, Any]: """ Parameters ---------- data: dict 空のkeyを削除したいdict + ignore_keys: list + 削除したくないkeyのリスト Returns ------- @@ -68,7 +72,10 @@ def remove_dict_empty(data: dict[str, Any]) -> dict[str, Any]: 空のkeyがなくなったdict """ _data = {} - _data = {k: v for k, v in data.items() if v is not None} + if ignore_keys is None: + ignore_keys = [] + _data = {k: v for k, v in data.items() if v is not None or k in ignore_keys} + return _data return _data From 5e36908b17884c2044e0dcbee1f4a01b151f956c Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 12:20:16 +0900 Subject: [PATCH 05/16] =?UTF-8?q?feat:=20remove=5Fdict=5Fmissing=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/utils/format.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mipac/utils/format.py b/mipac/utils/format.py index b9352b79..8892a882 100644 --- a/mipac/utils/format.py +++ b/mipac/utils/format.py @@ -76,6 +76,22 @@ def remove_dict_empty( ignore_keys = [] _data = {k: v for k, v in data.items() if v is not None or k in ignore_keys} return _data + + +def remove_dict_missing(data: dict[str, Any]) -> dict[str, Any]: + """ + Parameters + ---------- + data: dict + 空のkeyを削除したいdict + + Returns + ------- + _data: dict + MISSINGのkeyがなくなったdict + """ + _data = {} + _data = {k: v for k, v in data.items() if isinstance(v, Missing) is False} return _data From 300c2e435f65df582243f20259fba5083018754e Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 12:20:40 +0900 Subject: [PATCH 06/16] =?UTF-8?q?chore:=20mipac.utils.util=E3=81=AEMISSING?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E3=81=86=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/http.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/mipac/http.py b/mipac/http.py index a1c734bb..f370184d 100644 --- a/mipac/http.py +++ b/mipac/http.py @@ -14,23 +14,11 @@ from mipac.types.endpoints import ENDPOINTS from mipac.types.user import IMeDetailed from mipac.utils.format import remove_dict_empty, upper_to_lower -from mipac.utils.util import COLORS, _from_json +from mipac.utils.util import COLORS, MISSING, _from_json _log = logging.getLogger(__name__) -class _MissingSentinel: - def __eq__(self, other): - return False - - def __bool__(self): - return False - - def __repr__(self): - return "..." - - -MISSING: Any = _MissingSentinel() R = TypeVar("R") From d55a49c961143c47a7607dc6c68f19b4ff44dca9 Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 12:21:20 +0900 Subject: [PATCH 07/16] =?UTF-8?q?chore:=20=E5=8F=A4=E3=81=84drive=E9=81=94?= =?UTF-8?q?=E3=82=92=E3=81=84=E3=81=A3=E3=81=9F=E3=82=93=E9=81=BF=E9=9B=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/actions/{drive.py => old_drive.py} | 4 ++-- mipac/manager/{drive.py => old_drive.py} | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename mipac/actions/{drive.py => old_drive.py} (100%) rename mipac/manager/{drive.py => old_drive.py} (100%) diff --git a/mipac/actions/drive.py b/mipac/actions/old_drive.py similarity index 100% rename from mipac/actions/drive.py rename to mipac/actions/old_drive.py index 7e6ce9bb..15c55f9c 100644 --- a/mipac/actions/drive.py +++ b/mipac/actions/old_drive.py @@ -244,12 +244,12 @@ async def upload_file( folder_id = self._folder_id or folder_id data = { - "file": file_byte, - "name": file_name, "folderId": folder_id, + "name": file_name, "comment": comment, "isSensitive": bool_to_string(is_sensitive), "force": bool_to_string(force), + "file": file_byte, } res: IDriveFile = await self._session.request( Route("POST", "/api/drive/files/create"), diff --git a/mipac/manager/drive.py b/mipac/manager/old_drive.py similarity index 100% rename from mipac/manager/drive.py rename to mipac/manager/old_drive.py From b91aaa19233ec685530c8f89999f6704ee6e576a Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 12:26:03 +0900 Subject: [PATCH 08/16] =?UTF-8?q?feat:=20drive/files/*=20=E3=82=92?= =?UTF-8?q?=E3=82=B5=E3=83=9D=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore: drive周りを大幅に作り直し --- mipac/actions/drive/drive.py | 29 ++ mipac/actions/drive/files.py | 479 +++++++++++++++++++++++++++++++++ mipac/actions/drive/folders.py | 22 ++ mipac/manager/client.py | 2 +- mipac/manager/drive/drive.py | 31 +++ mipac/manager/drive/files.py | 35 +++ mipac/manager/drive/folders.py | 36 +++ mipac/models/drive.py | 10 +- setup.py | 1 + 9 files changed, 640 insertions(+), 5 deletions(-) create mode 100644 mipac/actions/drive/drive.py create mode 100644 mipac/actions/drive/files.py create mode 100644 mipac/actions/drive/folders.py create mode 100644 mipac/manager/drive/drive.py create mode 100644 mipac/manager/drive/files.py create mode 100644 mipac/manager/drive/folders.py diff --git a/mipac/actions/drive/drive.py b/mipac/actions/drive/drive.py new file mode 100644 index 00000000..a157bc9f --- /dev/null +++ b/mipac/actions/drive/drive.py @@ -0,0 +1,29 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from mipac.abstract.action import AbstractAction +from mipac.http import HTTPClient, Route +from mipac.models.drive import DriveStatus +from mipac.types.drive import IDriveStatus + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class DriveActions(AbstractAction): + def __init__(self, *, session: HTTPClient, client: ClientManager): + self.__session: HTTPClient = session + self.__client: ClientManager = client + + async def get_status(self) -> DriveStatus: + """Get the status of the drive + + Returns + ------- + DriveStatus + The status of the drive + """ + + res: IDriveStatus = await self.__session.request(Route("POST", "/api/drive"), auth=True) + return DriveStatus(raw_drive_status=res, client=self.__client) diff --git a/mipac/actions/drive/files.py b/mipac/actions/drive/files.py new file mode 100644 index 00000000..e0bdce26 --- /dev/null +++ b/mipac/actions/drive/files.py @@ -0,0 +1,479 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from mipac.abstract.action import AbstractAction +from mipac.http import HTTPClient, Route +from mipac.models.drive import File +from mipac.models.note import Note +from mipac.types.drive import IDriveFile, IDriveSort +from mipac.types.note import INote +from mipac.utils.format import bool_to_string, remove_dict_missing +from mipac.utils.util import MISSING, credentials_required + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class ClientFileActions(AbstractAction): + def __init__(self, file_ids: str | None = None, *, session: HTTPClient, client: ClientManager): + self.__file_ids: str | None = file_ids + self._session: HTTPClient = session + self._client: ClientManager = client + + async def get_attached_notes( + self, + since_id: str | None = None, + until_id: str | None = None, + limit: int = 10, + *, + file_id: str | None = None, + ) -> list[Note]: + """Get the attached notes of a file + + Endpoint: `/api/drive/files/attached-notes` + + Parameters + ---------- + since_id: str | None + The id of the note to start from, defaults to None + until_id: str | None + The id of the note to end at, defaults to None + limit: int + The amount of notes to get, defaults to 10 + file_id: str | None + The id of the file to get notes from, defaults to None + + Returns + ------- + list[Note] + The attached notes of the file + """ + + file_id = file_id or self.__file_ids + + data = { + "sinceId": since_id, + "untilId": until_id, + "limit": limit, + "fileId": file_id, + } + + raw_notes: list[INote] = await self._session.request( + Route("POST", "/api/drive/files/attached-notes"), json=data, auth=True + ) + return [Note(raw_note, client=self._client) for raw_note in raw_notes] + + async def delete(self, *, file_id: str | None = None) -> bool: + """Delete a file + + Endpoint: `/api/drive/files/delete` + + Parameters + ---------- + file_id: str | None + The id of the file to delete, defaults to None + + Returns + ------- + bool + Whether the file was deleted or not + """ + + file_id = file_id or self.__file_ids + + data = {"fileId": file_id} + + res: bool = await self._session.request( + Route("POST", "/api/drive/files/delete"), json=data, auth=True + ) + return res + + async def update( + self, + folder_id: str | None = MISSING, + name: str | None = MISSING, + is_sensitive: bool = MISSING, + comment: str | None = MISSING, + *, + file_id: str | None = None, + ): + """Update a file + + Endpoint: `/api/drive/files/update` + + Parameters + ---------- + folder_id: str | None + The id of the folder to update the file to, defaults to MISSING + name: str | None + The name of the file, defaults to MISSING + is_sensitive: bool + Whether the file is sensitive or not, defaults to MISSING + comment: str | None + The comment of the file, defaults to MISSING + file_id: str | None + The id of the file to update, defaults to None + + Returns + ------- + File + The updated file + """ + + file_id = file_id or self.__file_ids + + data = remove_dict_missing( + { + "fileId": file_id, + "folderId": folder_id, + "name": name, + "isSensitive": is_sensitive, + "comment": comment, + } + ) + + res: IDriveFile = await self._session.request( + Route("POST", "/api/drive/files/update"), json=data, auth=True + ) + return File(res, client=self._client) + + +class FileActions(ClientFileActions): + def __init__(self, *, session: HTTPClient, client: ClientManager): + super().__init__(session=session, client=client) + + @credentials_required + async def get_files( + self, + limit: int = 10, + since_id: str | None = None, + until_id: str | None = None, + folder_id: str | None = None, + type: str | None = None, + sort: IDriveSort | None = None, + ) -> list[File]: + """Get the files of the drive + + Endpoint: `/api/drive/files` + + Parameters + ---------- + limit: int + The amount of files to get, defaults to 10 + since_id: str | None + The id of the file to start from, defaults to None + until_id: str | None + The id of the file to end at, defaults to None + folder_id: str | None + The id of the folder to get files from, defaults to None + type: str | None + The type of file to get, defaults to None + sort: IDriveSort | None + The way to sort the files, defaults to None + + Returns + ------- + list[File] + The files of the drive + """ + + data = { + "limit": limit, + "sinceId": since_id, + "untilId": until_id, + "folderId": folder_id, + "type": type, + "sort": sort, + } + + raw_files: list[IDriveFile] = await self._session.request( + Route("POST", "/api/drive/files"), json=data, auth=True + ) + return [File(raw_file, client=self._client) for raw_file in raw_files] + + async def get_attached_notes( + self, + file_id: str, + since_id: str | None = None, + until_id: str | None = None, + limit: int = 10, + ) -> list[Note]: + """Get the attached notes of a file + + Endpoint: `/api/drive/files/attached-notes` + + Parameters + ---------- + file_id: str + The id of the file to get notes from + since_id: str | None + The id of the note to start from, defaults to None + until_id: str | None + The id of the note to end at, defaults to None + limit: int + The amount of notes to get, defaults to 10 + + Returns + ------- + list[Note] + The attached notes of the file + """ + + return await super().get_attached_notes( + since_id=since_id, until_id=until_id, limit=limit, file_id=file_id + ) + + @credentials_required + async def check_existence(self, md5: str) -> bool: + """Check if a file exists in the drive + + Endpoint: `/api/drive/files/check-existence` + + Parameters + ---------- + md5: str + The md5 of the file to check + + Returns + ------- + bool + Whether the file exists or not + """ + + data = {"md5": md5} + + res: bool = await self._session.request( + Route("POST", "/api/drive/files/check-existence"), json=data, auth=True + ) + return res + + async def create( + self, + file, + folder_id: str | None = None, + name: str | None = None, + comment: str | None = None, + is_sensitive: bool = False, + force: bool = False, + ) -> File: + """Upload a file to the drive + + Endpoint: `/api/drive/files/create` + + Parameters + ---------- + file: str + The file to upload + folder_id: str | None + The id of the folder to upload the file to, defaults to None + name: str | None + The name of the file, defaults to None + comment: str | None + The comment of the file, defaults to None + is_sensitive: bool + Whether the file is sensitive or not, defaults to False + force: bool + Whether to force upload the file or not, defaults to False + + Returns + ------- + File + The uploaded file + """ + + file_byte = open(file, "rb") if file else None + + data = { + "folderId": folder_id, + "name": name, + "comment": comment, + "isSensitive": bool_to_string(is_sensitive), + "force": bool_to_string(force), + "file": file_byte, + } + res: IDriveFile = await self._session.request( + Route("POST", "/api/drive/files/create"), + data=data, + auth=True, + lower=True, + ) + return File(res, client=self._client) + + async def delete(self, file_id: str) -> bool: + """Delete a file + + Endpoint: `/api/drive/files/delete` + + Parameters + ---------- + file_id: str + The id of the file to delete + + Returns + ------- + bool + Whether the file was deleted or not + """ + + return await super().delete(file_id=file_id) + + async def find_by_hash(self, md5: str) -> list[File]: + """Find a file by its hash + + Endpoint: `/api/drive/files/find-by-hash` + + Parameters + ---------- + md5: str + The md5 of the file to find + + Returns + ------- + list[File] + The found files + """ + + data = {"md5": md5} + + raw_files: list[IDriveFile] = await self._session.request( + Route("POST", "/api/drive/files/find-by-hash"), json=data, auth=True + ) + return [File(raw_file, client=self._client) for raw_file in raw_files] + + async def find(self, name: str, folder_id:str|None=None) -> list[File]: + """Find a file by its name + + Endpoint: `/api/drive/files/find` + + Parameters + ---------- + name: str + The name of the file to find + folder_id: str | None + The id of the folder to find the file in, defaults to None + + Returns + ------- + list[File] + The found files + """ + + data = {"name": name, "folderId": folder_id} + + res: list[IDriveFile] = await self._session.request( + Route("POST", "/api/drive/files/find"), json=data, auth=True + ) + return [File(raw_file, client=self._client) for raw_file in res] + + async def show(self, file_id: str, url: str | None = None) -> File: + """Show a file + + Endpoint: `/api/drive/files/show` + + Parameters + ---------- + file_id: str + The id of the file to show + url: str | None + The url of the file to show, defaults to None + + Returns + ------- + File + The shown file + """ + + data = {"fileId": file_id, "url": url} + + res: IDriveFile = await self._session.request( + Route("POST", "/api/drive/files/show"), json=data, auth=True + ) + return File(res, client=self._client) + + async def update( + self, + file_id: str, + folder_id: str | None = None, + name: str | None = None, + is_sensitive: bool = False, + comment: str | None = None, + ) -> File: + """Update a file + + Endpoint: `/api/drive/files/update` + + Parameters + ---------- + file_id: str + The id of the file to update + folder_id: str | None + The id of the folder to update the file to, defaults to None + name: str | None + The name of the file, defaults to None + is_sensitive: bool + Whether the file is sensitive or not, defaults to False + comment: str | None + The comment of the file, defaults to None + + Returns + ------- + File + The updated file + """ + + return await super().update( + file_id=file_id, + folder_id=folder_id, + name=name, + is_sensitive=is_sensitive, + comment=comment, + ) + + async def upload_from_url( + self, + url: str, + folder_id: str | None = None, + is_sensitive: bool = False, + comment: str | None = None, + marker: str | None = None, + force: bool = False, + ): + """Upload a file to the drive from a url + + Endpoint: `/api/drive/files/upload-from-url` + + Parameters + ---------- + url: str + The url of the file to upload + folder_id: str | None + The id of the folder to upload the file to, defaults to None + is_sensitive: bool + Whether the file is sensitive or not, defaults to False + comment: str | None + The comment of the file, defaults to None + marker: str | None + The marker of the file, defaults to None + force: bool + Whether to force upload the file or not, defaults to False + + Returns + ------- + bool + Whether the file was uploaded or not + """ + + data = { + "url": url, + "folderId": folder_id, + "isSensitive": is_sensitive, + "comment": comment, + "marker": marker, + "force": force, + } + + res: bool = await self._session.request( + Route("POST", "/api/drive/files/upload-from-url"), json=data, auth=True + ) + return res diff --git a/mipac/actions/drive/folders.py b/mipac/actions/drive/folders.py new file mode 100644 index 00000000..9d501f06 --- /dev/null +++ b/mipac/actions/drive/folders.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from mipac.http import HTTPClient +from mipac.abstract.action import AbstractAction + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class ClientFolderActions(AbstractAction): + def __init__(self, folder_id: str | None = None, *, session: HTTPClient, client: ClientManager): + self.__folder_id: str | None = folder_id + self._session: HTTPClient = session + self._client: ClientManager = client + + +class FolderActions(ClientFolderActions): + def __init__(self, *, session: HTTPClient, client: ClientManager): + super().__init__(session=session, client=client) + diff --git a/mipac/manager/client.py b/mipac/manager/client.py index 8de7f616..924e1791 100644 --- a/mipac/manager/client.py +++ b/mipac/manager/client.py @@ -9,7 +9,7 @@ from mipac.manager.channel import ChannelManager from mipac.manager.chart import ChartManager from mipac.manager.clip import ClipManager -from mipac.manager.drive import DriveManager +from mipac.manager.drive.drive import DriveManager from mipac.manager.emoji import EmojiManager from mipac.manager.follow import FollowManager, FollowRequestManager from mipac.manager.invite import ClientInviteManager, InviteManager diff --git a/mipac/manager/drive/drive.py b/mipac/manager/drive/drive.py new file mode 100644 index 00000000..4a680dbf --- /dev/null +++ b/mipac/manager/drive/drive.py @@ -0,0 +1,31 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from mipac.abstract.manager import AbstractManager +from mipac.actions.drive.drive import DriveActions +from mipac.http import HTTPClient +from mipac.manager.drive.files import ClientFileManager, DriveFileManager +from mipac.manager.drive.folders import ClientFolderManager, FolderManager + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class DriveManager(AbstractManager): + def __init__(self, *, session: HTTPClient, client: ClientManager): + self.__session: HTTPClient = session + self.__client: ClientManager = client + self.__action: DriveActions = DriveActions(session=session, client=client) + self.files: DriveFileManager = DriveFileManager(session=session, client=client) + self.folders: FolderManager = FolderManager(session=session, client=client) + + @property + def action(self) -> DriveActions: + return self.__action + + def _create_client_file_manager(self, *, file_id: str) -> ClientFileManager: + return ClientFileManager(file_id=file_id, session=self.__session, client=self.__client) + + def _create_client_folder_manager(self, *, folder_id: str) -> ClientFolderManager: + return ClientFolderManager(folder_id=folder_id, session=self.__session, client=self.__client) diff --git a/mipac/manager/drive/files.py b/mipac/manager/drive/files.py new file mode 100644 index 00000000..3732656d --- /dev/null +++ b/mipac/manager/drive/files.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from mipac.abstract.manager import AbstractManager +from mipac.actions.drive.files import FileActions, ClientFileActions +from mipac.http import HTTPClient + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class ClientFileManager(AbstractManager): + def __init__(self, file_id: str, *, session: HTTPClient, client: ClientManager): + self.__file_id: str = file_id + self.__session: HTTPClient = session + self.__client: ClientManager = client + self.__action: ClientFileActions = ClientFileActions( + file_ids=file_id, session=session, client=client + ) + + @property + def action(self) -> ClientFileActions: + return self.__action + + +class DriveFileManager(AbstractManager): + def __init__(self, *, session: HTTPClient, client: ClientManager): + self.__session: HTTPClient = session + self.__client: ClientManager = client + self.__action: FileActions = FileActions(session=session, client=client) + + @property + def action(self) -> FileActions: + return self.__action diff --git a/mipac/manager/drive/folders.py b/mipac/manager/drive/folders.py new file mode 100644 index 00000000..730ed602 --- /dev/null +++ b/mipac/manager/drive/folders.py @@ -0,0 +1,36 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from mipac.abstract.manager import AbstractManager +from mipac.actions.drive.folders import FolderActions, ClientFolderActions +from mipac.http import HTTPClient + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class ClientFolderManager(AbstractManager): + + def __init__(self, folder_id: str, *, session: HTTPClient, client: ClientManager): + self.__folder_id: str = folder_id + self.__session: HTTPClient = session + self.__client: ClientManager = client + self.__action: ClientFolderActions = ClientFolderActions( + folder_id=folder_id, session=session, client=client + ) + + @property + def action(self) -> ClientFolderActions: + return self.__action + + +class FolderManager(AbstractManager): + def __init__(self, *, session: HTTPClient, client: ClientManager): + self.__session: HTTPClient = session + self.__client: ClientManager = client + self.__action: FolderActions = FolderActions(session=session, client=client) + + @property + def action(self) -> FolderActions: + return self.__action diff --git a/mipac/models/drive.py b/mipac/models/drive.py index 029635c5..fc3f1a05 100644 --- a/mipac/models/drive.py +++ b/mipac/models/drive.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING from mipac.abstract.model import AbstractModel from mipac.models.lite.user import PartialUser @@ -8,8 +8,10 @@ if TYPE_CHECKING: from mipac.manager.client import ClientManager - from mipac.manager.drive import ClientFileManager, ClientFolderManager + from mipac.manager.drive.files import ClientFileManager from mipac.types import FolderPayload, IDriveFile, IFileProperties + from mipac.manager.drive.folders import ClientFolderManager + __all__ = ["FileProperties", "File", "Folder"] @@ -102,7 +104,7 @@ def parent(self) -> Folder | None: @property def api(self) -> ClientFolderManager: - return self.__client.drive._get_client_folder_instance(folder_id=self.id) + return self.__client.drive._create_client_folder_manager(folder_id=self.id) def __eq__(self, __value: object) -> bool: return isinstance(__value, Folder) and self.id == __value.id @@ -190,7 +192,7 @@ def user(self) -> PartialUser | None: @property def api(self) -> ClientFileManager: - return self.__client.drive._get_client_file_instance(file_id=self.id, url=self.url) + return self.__client.drive._create_client_file_manager(file_id=self.id) def __eq__(self, __value: object) -> bool: return isinstance(__value, File) and self.id == __value.id diff --git a/setup.py b/setup.py index 9b7a4cb0..77675d14 100644 --- a/setup.py +++ b/setup.py @@ -24,6 +24,7 @@ "mipac.abstract", "mipac.actions", "mipac.actions.admins", + "mipac.actions.drive", "mipac.errors", "mipac.manager", "mipac.manager.admins", From 6e2640602ab46978d026b9955ec3a6be3c9b4e66 Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 12:32:26 +0900 Subject: [PATCH 09/16] =?UTF-8?q?chore:=20=E3=82=B5=E3=83=9D=E3=83=BC?= =?UTF-8?q?=E3=83=88=E7=8A=B6=E6=B3=81=E3=82=92=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler/datas/endpoints.json | 22 +++++++++++----------- compiler/datas/support_status.md | 24 ++++++++++++------------ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/compiler/datas/endpoints.json b/compiler/datas/endpoints.json index 3fe3106d..e978f4a6 100644 --- a/compiler/datas/endpoints.json +++ b/compiler/datas/endpoints.json @@ -749,67 +749,67 @@ "path": "/drive", "request_body_hash": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "response_body_hash": "3d1b96d30d248d61c4b1eee203b84f2bad48d6c200851942e368b88104166b1b", - "status": "notSupported" + "status": "supported" }, "/drive/files": { "path": "/drive/files", "request_body_hash": "b07070c472a648963651db6e280e39b490558d9adb92ce7db9d05590178353b9", "response_body_hash": "576b61217cbc7e929e6656690e9898c5b933ac5b2629b0a4f7e2389d65315ab1", - "status": "notSupported" + "status": "supported" }, "/drive/files/attached-notes": { "path": "/drive/files/attached-notes", "request_body_hash": "483cf6378fc3db55c9c3acfb484f77012c0e9ab2abf81bef519089ad0ae27a03", "response_body_hash": "507fab37c26ecb65a55f1f6f11ac18dc875d5594d656588a4c15b396cd35414f", - "status": "notSupported" + "status": "supported" }, "/drive/files/check-existence": { "path": "/drive/files/check-existence", "request_body_hash": "ea75dd1736f53c4238eada5abf871fea88c144b71f03fdaf9f7e56024322a2f3", "response_body_hash": "dd9c6671e5d139f32b17da6b74c2c6498de7ec65cf072b2df951e8c2be5eb477", - "status": "notSupported" + "status": "supported" }, "/drive/files/create": { "path": "/drive/files/create", "request_body_hash": "ee93867da40daf79738829f3bfb7532aad7c60d97f7e54c3f2843ca00fe8cfb9", "response_body_hash": "28d7477d5157e558c4ab3a0c2e2491c17fefc80ad803879a9f38c8fffec27e2c", - "status": "notSupported" + "status": "supported" }, "/drive/files/delete": { "path": "/drive/files/delete", "request_body_hash": "d7c20d1b69b13b8c3bdce034764d6b159e21e14eb20dc6f001c023ed47973e4c", "response_body_hash": "01c3864371cb588f305a706f8083f52d6bb7d249280aa302dfef644c27531a67", - "status": "notSupported" + "status": "supported" }, "/drive/files/find-by-hash": { "path": "/drive/files/find-by-hash", "request_body_hash": "ea75dd1736f53c4238eada5abf871fea88c144b71f03fdaf9f7e56024322a2f3", "response_body_hash": "576b61217cbc7e929e6656690e9898c5b933ac5b2629b0a4f7e2389d65315ab1", - "status": "notSupported" + "status": "supported" }, "/drive/files/find": { "path": "/drive/files/find", "request_body_hash": "be819aa65c197bbe301a63e26be5bc04e6c2a774fce630c23d710a4a2a38e4cf", "response_body_hash": "576b61217cbc7e929e6656690e9898c5b933ac5b2629b0a4f7e2389d65315ab1", - "status": "notSupported" + "status": "supported" }, "/drive/files/show": { "path": "/drive/files/show", "request_body_hash": "469dab9342135333df7936e6dd1a691a975e6b5c205b5ab3040be3bf31a18a17", "response_body_hash": "977e40bbf78c7880fbcfeeaf886ab76a912d931e9f46707cb55f2cac64c912d2", - "status": "notSupported" + "status": "supported" }, "/drive/files/update": { "path": "/drive/files/update", "request_body_hash": "c2f7cb37c89d08770d768c1737d2b1dc5cb1491ca1eacd4729ecd8d9f0701b72", "response_body_hash": "1c935759ba3714e4fc6f6c50ec7505ec744a75714d14ff521ea6a8a2b79f5638", - "status": "notSupported" + "status": "supported" }, "/drive/files/upload-from-url": { "path": "/drive/files/upload-from-url", "request_body_hash": "6a82791ea48bc99f67991548b827191c536ab8f4c08732f95a06f66b910a2167", "response_body_hash": "aead474d2cf0cb02f40e88b806a7f993e6dc1567de6897b7d278c9cc96109291", - "status": "notSupported" + "status": "supported" }, "/drive/folders": { "path": "/drive/folders", diff --git a/compiler/datas/support_status.md b/compiler/datas/support_status.md index 2491918c..fa2c73ee 100644 --- a/compiler/datas/support_status.md +++ b/compiler/datas/support_status.md @@ -1,4 +1,4 @@ -## SUPPORTED ENDPOINTS (43/322) +## SUPPORTED ENDPOINTS (54/322) - [x] /admin/get-index-stats - [x] /admin/get-table-stats - [x] /admin/get-user-ips @@ -8,6 +8,17 @@ - [x] /admin/show-moderation-logs - [x] /admin/unsuspend-user - [x] /admin/update-user-note +- [x] /drive +- [x] /drive/files +- [x] /drive/files/attached-notes +- [x] /drive/files/check-existence +- [x] /drive/files/create +- [x] /drive/files/delete +- [x] /drive/files/find-by-hash +- [x] /drive/files/find +- [x] /drive/files/show +- [x] /drive/files/update +- [x] /drive/files/upload-from-url - [x] /i - [x] /invite/create - [x] /invite/delete @@ -161,17 +172,6 @@ - [ ] /clips/favorite - [ ] /clips/unfavorite - [ ] /clips/my-favorites -- [ ] /drive -- [ ] /drive/files -- [ ] /drive/files/attached-notes -- [ ] /drive/files/check-existence -- [ ] /drive/files/create -- [ ] /drive/files/delete -- [ ] /drive/files/find-by-hash -- [ ] /drive/files/find -- [ ] /drive/files/show -- [ ] /drive/files/update -- [ ] /drive/files/upload-from-url - [ ] /drive/folders - [ ] /drive/folders/create - [ ] /drive/folders/delete From af4bb8207f97f47b295824d6c76643f2573edc8d Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 12:56:58 +0900 Subject: [PATCH 10/16] =?UTF-8?q?chore:=20=E5=9E=8B=E3=81=AE=E5=90=8D?= =?UTF-8?q?=E5=89=8D=E3=82=92=E7=B5=B1=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/actions/drive/files.py | 14 +++++++------- mipac/models/drive.py | 10 +++++----- mipac/models/note.py | 2 +- mipac/types/drive.py | 10 +++++----- mipac/types/note.py | 4 ++-- mipac/types/page.py | 4 ++-- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/mipac/actions/drive/files.py b/mipac/actions/drive/files.py index e0bdce26..3a6d0f9d 100644 --- a/mipac/actions/drive/files.py +++ b/mipac/actions/drive/files.py @@ -6,7 +6,7 @@ from mipac.http import HTTPClient, Route from mipac.models.drive import File from mipac.models.note import Note -from mipac.types.drive import IDriveFile, IDriveSort +from mipac.types.drive import IFile, IDriveSort from mipac.types.note import INote from mipac.utils.format import bool_to_string, remove_dict_missing from mipac.utils.util import MISSING, credentials_required @@ -133,7 +133,7 @@ async def update( } ) - res: IDriveFile = await self._session.request( + res: IFile = await self._session.request( Route("POST", "/api/drive/files/update"), json=data, auth=True ) return File(res, client=self._client) @@ -187,7 +187,7 @@ async def get_files( "sort": sort, } - raw_files: list[IDriveFile] = await self._session.request( + raw_files: list[IFile] = await self._session.request( Route("POST", "/api/drive/files"), json=data, auth=True ) return [File(raw_file, client=self._client) for raw_file in raw_files] @@ -292,7 +292,7 @@ async def create( "force": bool_to_string(force), "file": file_byte, } - res: IDriveFile = await self._session.request( + res: IFile = await self._session.request( Route("POST", "/api/drive/files/create"), data=data, auth=True, @@ -336,7 +336,7 @@ async def find_by_hash(self, md5: str) -> list[File]: data = {"md5": md5} - raw_files: list[IDriveFile] = await self._session.request( + raw_files: list[IFile] = await self._session.request( Route("POST", "/api/drive/files/find-by-hash"), json=data, auth=True ) return [File(raw_file, client=self._client) for raw_file in raw_files] @@ -361,7 +361,7 @@ async def find(self, name: str, folder_id:str|None=None) -> list[File]: data = {"name": name, "folderId": folder_id} - res: list[IDriveFile] = await self._session.request( + res: list[IFile] = await self._session.request( Route("POST", "/api/drive/files/find"), json=data, auth=True ) return [File(raw_file, client=self._client) for raw_file in res] @@ -386,7 +386,7 @@ async def show(self, file_id: str, url: str | None = None) -> File: data = {"fileId": file_id, "url": url} - res: IDriveFile = await self._session.request( + res: IFile = await self._session.request( Route("POST", "/api/drive/files/show"), json=data, auth=True ) return File(res, client=self._client) diff --git a/mipac/models/drive.py b/mipac/models/drive.py index fc3f1a05..30254532 100644 --- a/mipac/models/drive.py +++ b/mipac/models/drive.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from mipac.manager.client import ClientManager from mipac.manager.drive.files import ClientFileManager - from mipac.types import FolderPayload, IDriveFile, IFileProperties + from mipac.types import IFolder, IFile, IFileProperties from mipac.manager.drive.folders import ClientFolderManager @@ -66,8 +66,8 @@ def avg_color(self) -> str | None: class Folder(AbstractModel): - def __init__(self, folder: FolderPayload, client: ClientManager): - self.__folder: FolderPayload = folder + def __init__(self, folder: IFolder, client: ClientManager): + self.__folder: IFolder = folder self.__client: ClientManager = client @property @@ -114,8 +114,8 @@ def __ne__(self, __value: object) -> bool: class File(AbstractModel): - def __init__(self, file: IDriveFile, *, client: ClientManager): - self.__file: IDriveFile = file + def __init__(self, file: IFile, *, client: ClientManager): + self.__file: IFile = file self.__client: ClientManager = client @property diff --git a/mipac/models/note.py b/mipac/models/note.py index 73d528c9..95fde05f 100644 --- a/mipac/models/note.py +++ b/mipac/models/note.py @@ -438,7 +438,7 @@ def files(self) -> list[File]: Returns ------- - list[IDriveFile] + list[IFile] note files """ return [File(raw_file, client=self.__client) for raw_file in self.__raw_note["files"]] diff --git a/mipac/types/drive.py b/mipac/types/drive.py index 212ffc31..ee5280c9 100644 --- a/mipac/types/drive.py +++ b/mipac/types/drive.py @@ -6,7 +6,7 @@ from mipac.models.lite.user import PartialUser -__all__ = ("IFileProperties", "FolderPayload", "IDriveFile") +__all__ = ("IFileProperties", "IFolder", "IFile", "IDriveSort", "IDriveStatus") IDriveSort = Literal["+createdAt", "-createdAt", "+name", "-name", "+size", "-size"] @@ -27,7 +27,7 @@ class IFileProperties(TypedDict): avg_color: NotRequired[str] -class FolderPayload(TypedDict): +class IFolder(TypedDict): """ フォルダーの情報 """ @@ -38,10 +38,10 @@ class FolderPayload(TypedDict): parent_id: str | None folders_count: NotRequired[int] files_count: NotRequired[int] - parent: NotRequired[FolderPayload | None] + parent: NotRequired[IFolder | None] -class IDriveFile(TypedDict): +class IFile(TypedDict): """ ファイル情報 """ @@ -59,6 +59,6 @@ class IDriveFile(TypedDict): thumbnail_url: str | None comment: str | None folder_id: str | None - folder: NotRequired[FolderPayload | None] + folder: NotRequired[IFolder | None] user_id: str | None user: NotRequired[PartialUser | None] diff --git a/mipac/types/note.py b/mipac/types/note.py index 1b26724a..747af6e2 100644 --- a/mipac/types/note.py +++ b/mipac/types/note.py @@ -2,7 +2,7 @@ from typing import Any, Generic, Literal, NotRequired, Optional, TypedDict, TypeVar -from mipac.types.drive import IDriveFile +from mipac.types.drive import IFile from mipac.types.emoji import ICustomEmojiLite from mipac.types.poll import IPoll from mipac.types.reaction import IReactionAcceptance @@ -90,7 +90,7 @@ class INote(TypedDict): mentions: NotRequired[list[str]] visible_user_ids: NotRequired[list[str]] file_ids: list[str] - files: list[IDriveFile] + files: list[IFile] tags: NotRequired[list[str]] poll: NotRequired[IPoll] channel_id: NotRequired[str | None] diff --git a/mipac/types/page.py b/mipac/types/page.py index e42ae6ed..9105c45f 100644 --- a/mipac/types/page.py +++ b/mipac/types/page.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Any, Optional, TypedDict -from mipac.types.drive import IDriveFile +from mipac.types.drive import IFile from mipac.types.user import IPartialUser if TYPE_CHECKING: @@ -40,7 +40,7 @@ class IPageRequired(TypedDict): class IPage(IPageRequired, total=False): is_liked: bool eyeCatchingImageId: str - eyeCatchingImage: IDriveFile + eyeCatchingImage: IFile summary: str From 7075b13f7847a546d4cf43605b2d9684d1f9e436 Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 12:57:30 +0900 Subject: [PATCH 11/16] =?UTF-8?q?chore:=20import=E3=81=AEcommit=E5=BF=98?= =?UTF-8?q?=E3=82=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/utils/format.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mipac/utils/format.py b/mipac/utils/format.py index 8892a882..b675da8b 100644 --- a/mipac/utils/format.py +++ b/mipac/utils/format.py @@ -2,6 +2,8 @@ from datetime import datetime from typing import Any, Mapping +from mipac.utils.util import Missing + def snake_to_camel(snake_str: str, replace_list: dict[str, str]) -> str: components: list[str] = snake_str.split("_") From 641db1d89a6937e85b00f11613cfdcc7f56e2397 Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 13:05:45 +0900 Subject: [PATCH 12/16] =?UTF-8?q?chore:=20=E4=BD=BF=E7=94=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84import=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/http.py | 2 +- mipac/types/drive.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mipac/http.py b/mipac/http.py index f370184d..e302fca4 100644 --- a/mipac/http.py +++ b/mipac/http.py @@ -4,7 +4,7 @@ import logging import re import sys -from typing import Any, Literal, TypeVar +from typing import Literal, TypeVar import aiohttp diff --git a/mipac/types/drive.py b/mipac/types/drive.py index ee5280c9..b1d90c77 100644 --- a/mipac/types/drive.py +++ b/mipac/types/drive.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Literal, NotRequired, TypedDict +from typing import TYPE_CHECKING, Literal, NotRequired, TypedDict if TYPE_CHECKING: from mipac.models.lite.user import PartialUser From 0a247c5b2bd1424663dea2cf74b2d1d00ac50648 Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 13:55:20 +0900 Subject: [PATCH 13/16] =?UTF-8?q?feat:=20drive/stream=E3=82=92=E3=82=B5?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/actions/drive/drive.py | 46 ++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/mipac/actions/drive/drive.py b/mipac/actions/drive/drive.py index a157bc9f..023506ca 100644 --- a/mipac/actions/drive/drive.py +++ b/mipac/actions/drive/drive.py @@ -4,8 +4,8 @@ from mipac.abstract.action import AbstractAction from mipac.http import HTTPClient, Route -from mipac.models.drive import DriveStatus -from mipac.types.drive import IDriveStatus +from mipac.models.drive import DriveStatus, File +from mipac.types.drive import IDriveStatus, IFile if TYPE_CHECKING: from mipac.manager.client import ClientManager @@ -19,6 +19,8 @@ def __init__(self, *, session: HTTPClient, client: ClientManager): async def get_status(self) -> DriveStatus: """Get the status of the drive + Endpoint: `/api/drive` + Returns ------- DriveStatus @@ -27,3 +29,43 @@ async def get_status(self) -> DriveStatus: res: IDriveStatus = await self.__session.request(Route("POST", "/api/drive"), auth=True) return DriveStatus(raw_drive_status=res, client=self.__client) + + async def stream( + self, + limit: int = 10, + since_id: str | None = None, + until_id: str | None = None, + type: str | None = None, + ) -> list[File]: + """Stream files from the drive + + Endpoint: `/api/drive/stream` + + Parameters + ---------- + limit: int + The number of files to get + since_id: str + The id of the file to start from + until_id: str + The id of the file to end at + type: str + The type of file to get + + Returns + ------- + list[File] + A list of files + """ + + params = { + "limit": limit, + "sinceId": since_id, + "untilId": until_id, + "type": type, + } + raw_files: list[IFile] = await self.__session.request( + Route("POST", "/api/drive/stream"), auth=True, json=params + ) + + return [File(raw_file=raw_file, client=self.__client) for raw_file in raw_files] From 1cf757ff1d07c5f7cd659f50a80022277a8aefd8 Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 13:56:58 +0900 Subject: [PATCH 14/16] =?UTF-8?q?chore:=20drive=E3=81=AB=E9=96=A2=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=A2=E3=83=87=E3=83=AB=E3=81=AE=E5=BC=95=E6=95=B0?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/models/drive.py | 60 +++++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/mipac/models/drive.py b/mipac/models/drive.py index 30254532..ca667fc2 100644 --- a/mipac/models/drive.py +++ b/mipac/models/drive.py @@ -66,39 +66,39 @@ def avg_color(self) -> str | None: class Folder(AbstractModel): - def __init__(self, folder: IFolder, client: ClientManager): - self.__folder: IFolder = folder + def __init__(self, raw_folder: IFolder, client: ClientManager): + self.__raw_folder: IFolder = raw_folder self.__client: ClientManager = client @property def id(self) -> str: - return self.__folder["id"] + return self.__raw_folder["id"] @property def created_at(self) -> str: # TODO: 型 - return self.__folder["created_at"] + return self.__raw_folder["created_at"] @property def name(self) -> str: - return self.__folder["name"] + return self.__raw_folder["name"] @property def parent_id(self) -> str | None: - return self.__folder["parent_id"] + return self.__raw_folder["parent_id"] @property def folders_count(self) -> int | None: - return self.__folder.get("folders_count") + return self.__raw_folder.get("folders_count") @property def files_count(self) -> int | None: - return self.__folder.get("files_count") + return self.__raw_folder.get("files_count") @property def parent(self) -> Folder | None: return ( - Folder(self.__folder["parent"], client=self.__client) - if "parent" in self.__folder and self.__folder["parent"] + Folder(self.__raw_folder["parent"], client=self.__client) + if "parent" in self.__raw_folder and self.__raw_folder["parent"] else None ) @@ -114,79 +114,79 @@ def __ne__(self, __value: object) -> bool: class File(AbstractModel): - def __init__(self, file: IFile, *, client: ClientManager): - self.__file: IFile = file + def __init__(self, raw_file: IFile, *, client: ClientManager): + self.__raw_file: IFile = raw_file self.__client: ClientManager = client @property def id(self) -> str: - return self.__file["id"] + return self.__raw_file["id"] @property def created_at(self): - return self.__file["created_at"] + return self.__raw_file["created_at"] @property def name(self) -> str: - return self.__file["name"] + return self.__raw_file["name"] @property def type(self) -> str: - return self.__file["type"] + return self.__raw_file["type"] @property def md5(self) -> str: - return self.__file["md5"] + return self.__raw_file["md5"] @property def size(self) -> int: - return self.__file["size"] + return self.__raw_file["size"] @property def is_sensitive(self) -> bool: - return self.__file["is_sensitive"] + return self.__raw_file["is_sensitive"] @property def blurhash(self) -> str | None: - return self.__file["blurhash"] + return self.__raw_file["blurhash"] @property def properties(self) -> FileProperties: - return FileProperties(self.__file["properties"]) + return FileProperties(self.__raw_file["properties"]) @property def url(self) -> str: - return self.__file["url"] + return self.__raw_file["url"] @property def thumbnail_url(self) -> str | None: - return self.__file["thumbnail_url"] + return self.__raw_file["thumbnail_url"] @property def comment(self) -> str | None: - return self.__file["comment"] + return self.__raw_file["comment"] @property def folder_id(self) -> str | None: - return self.__file["folder_id"] + return self.__raw_file["folder_id"] @property def folder(self) -> Folder | None: return ( - Folder(self.__file["folder"], client=self.__client) - if "folder" in self.__file and self.__file["folder"] + Folder(self.__raw_file["folder"], client=self.__client) + if "folder" in self.__raw_file and self.__raw_file["folder"] else None ) @property def user_id(self) -> str | None: - return self.__file["user_id"] + return self.__raw_file["user_id"] @property def user(self) -> PartialUser | None: return ( - PartialUser(self.__file["user"], client=self.__client) - if "user" in self.__file and self.__file["user"] + PartialUser(self.__raw_file["user"], client=self.__client) + if "user" in self.__raw_file and self.__raw_file["user"] else None ) From 86cc8edb72d17cb90742cef3c2ab47b434d5a00b Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 13:57:20 +0900 Subject: [PATCH 15/16] =?UTF-8?q?feat:=20drive/folders/*=20=E3=82=92?= =?UTF-8?q?=E3=82=B5=E3=83=9D=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mipac/actions/drive/folders.py | 416 ++++++++++++++++++++++++++++++++- mipac/manager/drive/folders.py | 3 +- 2 files changed, 416 insertions(+), 3 deletions(-) diff --git a/mipac/actions/drive/folders.py b/mipac/actions/drive/folders.py index 9d501f06..1aa9a5ea 100644 --- a/mipac/actions/drive/folders.py +++ b/mipac/actions/drive/folders.py @@ -2,21 +2,433 @@ from typing import TYPE_CHECKING -from mipac.http import HTTPClient +from mipac.http import HTTPClient, Route from mipac.abstract.action import AbstractAction +from mipac.models.drive import File, Folder +from mipac.types.drive import IFolder +from mipac.utils.format import remove_dict_missing +from mipac.utils.util import MISSING if TYPE_CHECKING: from mipac.manager.client import ClientManager +class ClientFileActionsInFolder(AbstractAction): + """File actions in a folder""" + + def __init__(self, folder_id: str, *, session: HTTPClient, client: ClientManager): + self._folder_id: str = folder_id + self._session: HTTPClient = session + self._client: ClientManager = client + + async def create( + self, + file: str, + name: str | None = None, + comment: str | None = None, + is_sensitive: bool = False, + force: bool = False, + ) -> File: + """Upload a file to the drive + + Endpoint: `/api/drive/files/create` + + Parameters + ---------- + file: str + The file to upload + folder_id: str | None + The id of the folder to upload the file to, defaults to None + name: str | None + The name of the file, defaults to None + comment: str | None + The comment of the file, defaults to None + is_sensitive: bool + Whether the file is sensitive or not, defaults to False + force: bool + Whether to force upload the file or not, defaults to False + + Returns + ------- + File + The uploaded file + """ + return await self._client.drive.files.action.create( + file=file, + name=name, + comment=comment, + is_sensitive=is_sensitive, + force=force, + folder_id=self._folder_id, + ) + + async def update( + self, + file_id: str, + name: str | None = MISSING, + is_sensitive: bool = MISSING, + comment: str | None = MISSING, + ): + """Update a file + + Endpoint: `/api/drive/files/update` + + Parameters + ---------- + file_id: str | None + The id of the file to update, defaults to None + name: str | None + The name of the file, defaults to MISSING + is_sensitive: bool + Whether the file is sensitive or not, defaults to MISSING + comment: str | None + The comment of the file, defaults to MISSING + + Returns + ------- + File + The updated file + """ + return await self._client.drive.files.action.update( + file_id=file_id, + name=name, + is_sensitive=is_sensitive, + comment=comment, + folder_id=self._folder_id, + ) + + async def find(self, name: str) -> list[File]: + """Find a file by its name + + Endpoint: `/api/drive/files/find` + + Parameters + ---------- + name: str + The name of the file to find + + Returns + ------- + list[File] + The found files + """ + return await self._client.drive.files.action.find(name=name, folder_id=self._folder_id) + + async def upload_from_url( + self, + url: str, + is_sensitive: bool = False, + comment: str | None = None, + marker: str | None = None, + force: bool = False, + ): + """Upload a file to the drive from a url + + Endpoint: `/api/drive/files/upload-from-url` + + Parameters + ---------- + url: str + The url of the file to upload + is_sensitive: bool + Whether the file is sensitive or not, defaults to False + comment: str | None + The comment of the file, defaults to None + marker: str | None + The marker of the file, defaults to None + force: bool + Whether to force upload the file or not, defaults to False + + Returns + ------- + bool + Whether the file was uploaded or not + """ + return await self._client.drive.files.action.upload_from_url( + url=url, + folder_id=self._folder_id, + is_sensitive=is_sensitive, + comment=comment, + marker=marker, + force=force, + ) + + class ClientFolderActions(AbstractAction): - def __init__(self, folder_id: str | None = None, *, session: HTTPClient, client: ClientManager): + def __init__( + self, folder_id: str | None = None, *, session: HTTPClient, client: ClientManager + ): self.__folder_id: str | None = folder_id self._session: HTTPClient = session self._client: ClientManager = client + async def gets( + self, + limit: int = 10, + since_id: str | None = None, + until_id: str | None = None, + *, + folder_id: str | None = None, + ) -> list[Folder]: + """Get folders + + Endpoint: `/api/drive/folders` + + Parameters + ---------- + limit: int + The limit of folders to get, defaults to 10 + since_id: str | None + The ID of the folder to get since, defaults to None + until_id: str | None + The ID of the folder to get until, defaults to None + folder_id: str | None + The ID of the folder to get, defaults to None + + Returns + ------- + list[Folder] + The found folders + """ + data = { + "limit": limit, + "sinceId": since_id, + "untilId": until_id, + "folderId": folder_id or self.__folder_id, + } + raw_folders: list[IFolder] = await self._session.request( + Route("POST", "/api/drive/folders"), + auth=True, + json=data, + ) + + return [Folder(raw_folder=raw_folder, client=self._client) for raw_folder in raw_folders] + + async def create(self, name: str | None = None, *, parent_id: str | None = None) -> Folder: + """Create a new folder + + Endpoint: `/api/drive/folders/create` + + Parameters + ---------- + name : str, optional + The name of the folder, by default None + parent_id : str, optional + The parent ID of the folder, by default None + + Returns + ------- + Folder + The created folder + """ + parent_id = parent_id or self.__folder_id + + data = {"name": name, "parentId": parent_id} + raw_created_folder: IFolder = await self._session.request( + Route("POST", "/api/drive/folders/create"), auth=True, json=data + ) + + return Folder(raw_folder=raw_created_folder, client=self._client) + + async def delete(self, folder_id: str | None = None) -> bool: + """Delete a folder + + Endpoint: `/api/drive/folders/delete` + + Parameters + ---------- + folder_id : str, optional + The ID of the folder, by default None + + Returns + ------- + bool + Whether the folder was deleted or not + """ + folder_id = folder_id or self.__folder_id + + res: bool = await self._session.request( + Route("POST", "/api/drive/folders/delete"), auth=True, json={"folderId": folder_id} + ) + + return res + + async def update( + self, + name: str | None = MISSING, + parent_id: str | None = MISSING, + *, + folder_id: str | None = None, + ) -> Folder: + """Update a folder + + Endpoint: `/api/drive/folders/update` + + Parameters + ---------- + name : str, optional + The name of the folder, by default MISSING + parent_id : str, optional + The parent ID of the folder, by default MISSING + folder_id : str, optional + The ID of the folder, by default None + + Returns + ------- + Folder + The updated folder + """ + data = remove_dict_missing( + {"folderId": folder_id or self.__folder_id, "name": name, "parentId": parent_id} + ) + raw_updated_folder: IFolder = await self._session.request( + Route("POST", "/api/drive/folders/update"), auth=True, json=data + ) + + return Folder(raw_folder=raw_updated_folder, client=self._client) + class FolderActions(ClientFolderActions): def __init__(self, *, session: HTTPClient, client: ClientManager): super().__init__(session=session, client=client) + async def gets( + self, + limit: int = 10, + since_id: str | None = None, + until_id: str | None = None, + folder_id: str|None=None, + ) -> list[Folder]: + """Get folders + + Endpoint: `/api/drive/folders` + + Parameters + ---------- + folder_id: str | None + The ID of the folder to get, defaults to None + limit: int + The limit of folders to get, defaults to 10 + since_id: str | None + The ID of the folder to get since, defaults to None + until_id: str | None + The ID of the folder to get until, defaults to None + + Returns + ------- + list[Folder] + The found folders + """ + return await super().gets( + limit=limit, since_id=since_id, until_id=until_id, folder_id=folder_id + ) + + async def create(self, name: str | None = None, parent_id: str | None = None) -> Folder: + """Create a new folder + + Endpoint: `/api/drive/folders/create` + + Parameters + ---------- + name : str, optional + The name of the folder, by default None + parent_id : str, optional + The parent ID of the folder, by default None + + Returns + ------- + Folder + The created folder + """ + return await super().create(name=name, parent_id=parent_id) + + async def delete(self, folder_id: str) -> bool: + """Delete a folder + + Endpoint: `/api/drive/folders/delete` + + Parameters + ---------- + folder_id : str + The ID of the folder + + Returns + ------- + bool + Whether the folder was deleted or not + """ + res: bool = await super().delete(folder_id=folder_id) + return res + + async def find(self, name: str, parent_id: str | None = None) -> list[Folder]: + """Find folders + + Endpoint: `/api/drive/folders/find` + + Parameters + ---------- + name : str + The name of the folder + parent_id : str, optional + The parent ID of the folder, by default None + + Returns + ------- + list[Folder] + The found folders + """ + + data = {"name": name, "parentId": parent_id} + raw_folders: list[IFolder] = await self._session.request( + Route("POST", "/api/drive/folders/find"), + auth=True, + json=data, + ) + + return [Folder(raw_folder=raw_folder, client=self._client) for raw_folder in raw_folders] + + async def show(self, folder_id: str) -> Folder: + """Show a folder + + Endpoint: `/api/drive/folders/show` + + Parameters + ---------- + folder_id : str + The ID of the folder + + Returns + ------- + Folder + The found folder + """ + raw_folder: IFolder = await self._session.request( + Route("POST", "/api/drive/folders/show"), + auth=True, + json={"folderId": folder_id}, + ) + + return Folder(raw_folder=raw_folder, client=self._client) + + async def update( + self, folder_id: str |None=None, name: str | None = None, parent_id: str | None = None + ) -> Folder: + """Update a folder + + Endpoint: `/api/drive/folders/update` + + Parameters + ---------- + folder_id : str + The ID of the folder + name : str, optional + The name of the folder, by default None + parent_id : str, optional + The parent ID of the folder, by default None + + Returns + ------- + Folder + The updated folder + """ + return await super().update(name=name, parent_id=parent_id, folder_id=folder_id) diff --git a/mipac/manager/drive/folders.py b/mipac/manager/drive/folders.py index 730ed602..31e70941 100644 --- a/mipac/manager/drive/folders.py +++ b/mipac/manager/drive/folders.py @@ -3,7 +3,7 @@ from typing import TYPE_CHECKING from mipac.abstract.manager import AbstractManager -from mipac.actions.drive.folders import FolderActions, ClientFolderActions +from mipac.actions.drive.folders import ClientFileActionsInFolder, FolderActions, ClientFolderActions from mipac.http import HTTPClient if TYPE_CHECKING: @@ -19,6 +19,7 @@ def __init__(self, folder_id: str, *, session: HTTPClient, client: ClientManager self.__action: ClientFolderActions = ClientFolderActions( folder_id=folder_id, session=session, client=client ) + self.files = ClientFileActionsInFolder(folder_id=folder_id, session=session, client=client) @property def action(self) -> ClientFolderActions: From d098b91eca4f0af3cb03cb4e623915760789df22 Mon Sep 17 00:00:00 2001 From: yupix Date: Sun, 3 Dec 2023 14:11:06 +0900 Subject: [PATCH 16/16] =?UTF-8?q?feat:=20admin/drive/*=20=E3=82=92?= =?UTF-8?q?=E3=82=B5=E3=83=9D=E3=83=BC=E3=83=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- compiler/datas/endpoints.json | 22 +++--- compiler/datas/support_status.md | 24 +++--- mipac/actions/admins/drive.py | 128 +++++++++++++++++++++++++++++++ mipac/manager/admins/admin.py | 2 + mipac/manager/admins/drive.py | 20 +++++ 5 files changed, 173 insertions(+), 23 deletions(-) create mode 100644 mipac/actions/admins/drive.py create mode 100644 mipac/manager/admins/drive.py diff --git a/compiler/datas/endpoints.json b/compiler/datas/endpoints.json index e978f4a6..393a17f5 100644 --- a/compiler/datas/endpoints.json +++ b/compiler/datas/endpoints.json @@ -101,25 +101,25 @@ "path": "/admin/drive/clean-remote-files", "request_body_hash": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "response_body_hash": "df9c0f9df08effe88b00991f12e46077998bd6d6c3442ec518e5f463680a2c54", - "status": "notSupported" + "status": "supported" }, "/admin/drive/cleanup": { "path": "/admin/drive/cleanup", "request_body_hash": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "response_body_hash": "df9c0f9df08effe88b00991f12e46077998bd6d6c3442ec518e5f463680a2c54", - "status": "notSupported" + "status": "supported" }, "/admin/drive/files": { "path": "/admin/drive/files", "request_body_hash": "9ebcea5313a97912edcd89706b8a7ea82b795b8cb3d9e33a8666b4f1103e0a22", "response_body_hash": "576b61217cbc7e929e6656690e9898c5b933ac5b2629b0a4f7e2389d65315ab1", - "status": "notSupported" + "status": "supported" }, "/admin/drive/show-file": { "path": "/admin/drive/show-file", "request_body_hash": "469dab9342135333df7936e6dd1a691a975e6b5c205b5ab3040be3bf31a18a17", "response_body_hash": "d94c18840a05c740b14f2ef553f09c0a2a278a698b21b8cc5cb79a5a7f8dfa25", - "status": "notSupported" + "status": "supported" }, "/admin/emoji/add-aliases-bulk": { "path": "/admin/emoji/add-aliases-bulk", @@ -815,43 +815,43 @@ "path": "/drive/folders", "request_body_hash": "163479964ef668b9c9cd22dc1e66d6802c8c39333935f25db4856b75f49c4fc1", "response_body_hash": "f6400fb425c732cf542f468e2ff22ee8e4d36d83215e7f62e0d193f05febe8ec", - "status": "notSupported" + "status": "supported" }, "/drive/folders/create": { "path": "/drive/folders/create", "request_body_hash": "704fe9fb2fb297fb5ca12b43d10db5f98c593ae7285c93ba3036f3937ec35170", "response_body_hash": "221664a7a1649aac74388f963da64899b866f1f94e8f064edc4e99b2669170db", - "status": "notSupported" + "status": "supported" }, "/drive/folders/delete": { "path": "/drive/folders/delete", "request_body_hash": "7ddbf084df376fe1ced074c9454678879e8908a99fe0432fb2abaadfa597afec", "response_body_hash": "c558d54c2fd862408f9556860946477010c0c5b6896032e5e3674ee638e2011b", - "status": "notSupported" + "status": "supported" }, "/drive/folders/find": { "path": "/drive/folders/find", "request_body_hash": "574816af65798e0bd7e0b75836b95942423d7f4abe90f6e7f589ca84b6398a32", "response_body_hash": "f6400fb425c732cf542f468e2ff22ee8e4d36d83215e7f62e0d193f05febe8ec", - "status": "notSupported" + "status": "supported" }, "/drive/folders/show": { "path": "/drive/folders/show", "request_body_hash": "7ddbf084df376fe1ced074c9454678879e8908a99fe0432fb2abaadfa597afec", "response_body_hash": "2af351d06be9588d35f8d01cd87b292bc624a7e766d694c769c699096628a4ce", - "status": "notSupported" + "status": "supported" }, "/drive/folders/update": { "path": "/drive/folders/update", "request_body_hash": "b6f56d4e2737010dcf22a28754fbd127c4b3cdc61db501412d8da2ab9f002295", "response_body_hash": "e98b858e48224fef52ae844a57f5adb5b18c0bf7155471ee27d423b947be52a1", - "status": "notSupported" + "status": "supported" }, "/drive/stream": { "path": "/drive/stream", "request_body_hash": "bcc9a12d16667bf8e0beeb82f256a9c3acc694244f87fd588eada0896704f2e9", "response_body_hash": "576b61217cbc7e929e6656690e9898c5b933ac5b2629b0a4f7e2389d65315ab1", - "status": "notSupported" + "status": "supported" }, "/email-address/available": { "path": "/email-address/available", diff --git a/compiler/datas/support_status.md b/compiler/datas/support_status.md index fa2c73ee..5f19f974 100644 --- a/compiler/datas/support_status.md +++ b/compiler/datas/support_status.md @@ -1,4 +1,8 @@ -## SUPPORTED ENDPOINTS (54/322) +## SUPPORTED ENDPOINTS (65/322) +- [x] /admin/drive/clean-remote-files +- [x] /admin/drive/cleanup +- [x] /admin/drive/files +- [x] /admin/drive/show-file - [x] /admin/get-index-stats - [x] /admin/get-table-stats - [x] /admin/get-user-ips @@ -19,6 +23,13 @@ - [x] /drive/files/show - [x] /drive/files/update - [x] /drive/files/upload-from-url +- [x] /drive/folders +- [x] /drive/folders/create +- [x] /drive/folders/delete +- [x] /drive/folders/find +- [x] /drive/folders/show +- [x] /drive/folders/update +- [x] /drive/stream - [x] /i - [x] /invite/create - [x] /invite/delete @@ -73,10 +84,6 @@ - [ ] /admin/avatar-decorations/list - [ ] /admin/avatar-decorations/update - [ ] /admin/delete-all-files-of-a-user -- [ ] /admin/drive/clean-remote-files -- [ ] /admin/drive/cleanup -- [ ] /admin/drive/files -- [ ] /admin/drive/show-file - [ ] /admin/emoji/add-aliases-bulk - [ ] /admin/emoji/add - [ ] /admin/emoji/copy @@ -172,13 +179,6 @@ - [ ] /clips/favorite - [ ] /clips/unfavorite - [ ] /clips/my-favorites -- [ ] /drive/folders -- [ ] /drive/folders/create -- [ ] /drive/folders/delete -- [ ] /drive/folders/find -- [ ] /drive/folders/show -- [ ] /drive/folders/update -- [ ] /drive/stream - [ ] /email-address/available - [ ] /endpoint - [ ] /endpoints diff --git a/mipac/actions/admins/drive.py b/mipac/actions/admins/drive.py new file mode 100644 index 00000000..7265e6d2 --- /dev/null +++ b/mipac/actions/admins/drive.py @@ -0,0 +1,128 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Literal + +from mipac.abstract.action import AbstractAction +from mipac.http import HTTPClient, Route +from mipac.models.drive import File +from mipac.types.drive import IFile + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class AdminDriveActions(AbstractAction): + def __init__(self, *, session: HTTPClient, client: ClientManager): + self.__session: HTTPClient = session + self.__client: ClientManager = client + + async def clean_remote_files(self) -> bool: + """Clean remote files + + Endpoint: `/api/admin/drive/clean-remote-files` + + Returns + ------- + bool + Whether the remote files were cleaned + """ + + res: bool = await self.__session.request( + Route("POST", "/api/admin/drive/clean-remote-files"), auth=True + ) + return res + + async def cleanup(self) -> bool: + """Clean up the drive + + Endpoint: `/api/admin/drive/cleanup` + + Returns + ------- + bool + Whether the drive was cleaned up + """ + + res: bool = await self.__session.request( + Route("POST", "/api/admin/drive/cleanup"), auth=True + ) + return res + + async def get_files( + self, + limit: int = 10, + since_id: str | None = None, + until_id: str | None = None, + user_id: str | None = None, + type: str | None = None, + origin: Literal["combined", "local", "remote"] = "local", + hostname: str | None = None, + ) -> list[File]: + """Get all files + + Endpoint: `/api/admin/drive/files` + + Parameters + ---------- + limit: int + The number of files to get + since_id: str + The id of the file to start from + until_id: str + The id of the file to end at + type: str + The type of file to get + user_id: str + The id of the user to get files from + origin: Literal['combined', 'local', 'remote'] + The origin of the files + hostname: str + The hostname of the files + + Returns + ------- + list[File] + A list of files + """ + + data = { + "limit": limit, + "sinceId": since_id, + "untilId": until_id, + "type": type, + "userId": user_id, + "origin": origin, + "hostname": hostname, + } + raw_files: list[IFile] = await self.__session.request( + Route("POST", "/api/admin/drive/files"), auth=True, json=data + ) + return [File(raw_file=file, client=self.__client) for file in raw_files] + + async def show_file(self, file_id: str, url: str | None = None): + """Show a file + + Endpoint: `/api/admin/drive/files/show` + + Parameters + ---------- + file_id: str + The id of the file to show + url: str + The url of the file to show + + Returns + ------- + dict[str, Any] + The file + """ + + data = { + "fileId": file_id, + "url": url, + } + # TODO: IFileではなく、ほぼほぼデータベースの中身が返ってくるのでそれに合わせた型とモデルを作る + raw_file: dict[str, Any] = await self.__session.request( + Route("POST", "/api/admin/drive/show-file"), auth=True, json=data + ) + return raw_file diff --git a/mipac/manager/admins/admin.py b/mipac/manager/admins/admin.py index 59824177..b47c5dd7 100644 --- a/mipac/manager/admins/admin.py +++ b/mipac/manager/admins/admin.py @@ -7,6 +7,7 @@ from mipac.http import HTTPClient from mipac.manager.admins.ad import AdminAdvertisingManager, AdminAdvertisingModelManager from mipac.manager.admins.announcement import AdminAnnouncementManager +from mipac.manager.admins.drive import AdminDriveManager from mipac.manager.admins.emoji import AdminEmojiManager from mipac.manager.admins.invite import AdminInviteManager from mipac.manager.admins.moderator import AdminModeratorManager @@ -32,6 +33,7 @@ def __init__(self, session: HTTPClient, client: ClientManager): ) self.role: AdminRolesManager = AdminRolesManager(session=session, client=client) self.invite: AdminInviteManager = AdminInviteManager(session=session, client=client) + self.drive: AdminDriveManager = AdminDriveManager(session=session, client=client) @property def action(self) -> AdminActions: diff --git a/mipac/manager/admins/drive.py b/mipac/manager/admins/drive.py new file mode 100644 index 00000000..af3c092d --- /dev/null +++ b/mipac/manager/admins/drive.py @@ -0,0 +1,20 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from mipac.abstract.manager import AbstractManager +from mipac.actions.admins.drive import AdminDriveActions +from mipac.http import HTTPClient + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class AdminDriveManager(AbstractManager): + def __init__(self, *, session: HTTPClient, client: ClientManager): + self.__session: HTTPClient = session + self.__client: ClientManager = client + + @property + def action(self) -> AdminDriveActions: + return AdminDriveActions(session=self.__session, client=self.__client)