diff --git a/compiler/datas/endpoints.json b/compiler/datas/endpoints.json index d0857b5..1520bcd 100644 --- a/compiler/datas/endpoints.json +++ b/compiler/datas/endpoints.json @@ -815,13 +815,13 @@ "path": "/drive/folders", "request_body_hash": "c7994546f15ac56e6d47b2c9513c720ffb05b146e2c58f4d5f8b7cdd1fa5fe27", "response_body_hash": "05c56769de07a802e8ac7956c76ec95c025fd4ef2445500a18afdca430fca406", - "status": "needToWork" + "status": "supported" }, "/drive/folders/create": { "path": "/drive/folders/create", "request_body_hash": "9c570cb675f851abf3624e608486d98f75932447e983524d8a2fef010a832c4e", "response_body_hash": "65943f2e45eac25dbbd3acd9ba9249b2b5a83176797b863389a1932cdd5b94f8", - "status": "needToWork" + "status": "supported" }, "/drive/folders/delete": { "path": "/drive/folders/delete", @@ -833,19 +833,19 @@ "path": "/drive/folders/find", "request_body_hash": "f2f302ec5a018e252ce013efab000ef654223cdbd470afb2aee18e1fd0e6865b", "response_body_hash": "05c56769de07a802e8ac7956c76ec95c025fd4ef2445500a18afdca430fca406", - "status": "needToWork" + "status": "supported" }, "/drive/folders/show": { "path": "/drive/folders/show", "request_body_hash": "7ddbf084df376fe1ced074c9454678879e8908a99fe0432fb2abaadfa597afec", "response_body_hash": "5ab07c4af0d6e1e2bbcb44f5e15fae98ce7b37a16583f3a2ebe85eeafd0406ae", - "status": "needToWork" + "status": "supported" }, "/drive/folders/update": { "path": "/drive/folders/update", "request_body_hash": "6b715c2e3d185c4928e6c5f3f714570d9ccc1dbb7880c528f72290d760fa303f", "response_body_hash": "479f18df687ff202504b1964269dedd1ec139c927c446d544809be0645a43042", - "status": "needToWork" + "status": "supported" }, "/drive/stream": { "path": "/drive/stream", @@ -1037,31 +1037,31 @@ "path": "/hashtags/list", "request_body_hash": "94e486046065b88b3864bc95ed283707ab96b7f9f91147e06148406a75086369", "response_body_hash": "2094d4c2bf514341a838d01f1a89477ebd8e2ce1ca04b40374cf60d0d6e4a74c", - "status": "notSupported" + "status": "supported" }, "/hashtags/search": { "path": "/hashtags/search", "request_body_hash": "74c5456046bdb4aa84b5b15d12b3034eca208232c692d68595c455d5d2963952", "response_body_hash": "b27c6f9999974cc9d2aed6a70c37dba1e70641c4fdf5632bca440a6382106105", - "status": "notSupported" + "status": "supported" }, "/hashtags/show": { "path": "/hashtags/show", "request_body_hash": "bcb5dbf5529f1fa586775fca5006b1aa723fe5b3fd61243ae776be03ce3897e3", "response_body_hash": "8c8eb54b8e881447ea295ba25d2689a6936f8385f335b531f98fff4b30e31d3e", - "status": "notSupported" + "status": "supported" }, "/hashtags/trend": { "path": "/hashtags/trend", "request_body_hash": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a", "response_body_hash": "49f1aefa2720add5521ce6999aa347a293810614dc3cc9e88ef986b494ce3684", - "status": "notSupported" + "status": "supported" }, "/hashtags/users": { "path": "/hashtags/users", "request_body_hash": "709f3bb1ebf5a6ea85eca2694ed80dafc896ffa7a8c8bc46bafe3695e70bb90c", "response_body_hash": "f711c88a06f184d959627953fff320b77c3bcfbb871fe226d61cfacd7b6eb5cd", - "status": "notSupported" + "status": "supported" }, "/i": { "path": "/i", @@ -2321,7 +2321,7 @@ "Hashtag": { "name": "Hashtag", "hash": "619643560ecb9b56a12db881efff441954be264ba1f41a9d9d20ae46ad2d1dfd", - "status": "notSupported" + "status": "supported" }, "InviteCode": { "name": "InviteCode", diff --git a/compiler/datas/support_status.md b/compiler/datas/support_status.md index 5cd4b7a..c730b00 100644 --- a/compiler/datas/support_status.md +++ b/compiler/datas/support_status.md @@ -4,7 +4,7 @@ `2024.3.1` -## Supported endpoints (144/368) +## Supported endpoints (149/368) - [x] /admin/accounts/create - [x] /admin/accounts/delete @@ -47,9 +47,19 @@ - [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] /following/create - [x] /following/delete +- [x] /hashtags/list +- [x] /hashtags/search +- [x] /hashtags/show +- [x] /hashtags/trend +- [x] /hashtags/users - [x] /invite/delete - [x] /emojis - [x] /emoji @@ -187,11 +197,6 @@ - [ ] /gallery/posts/unlike - [ ] /gallery/posts/update - [ ] /get-avatar-decorations -- [ ] /hashtags/list -- [ ] /hashtags/search -- [ ] /hashtags/show -- [ ] /hashtags/trend -- [ ] /hashtags/users - [ ] /i/claim-achievement - [ ] /i/favorites - [ ] /i/gallery/likes @@ -312,11 +317,6 @@ - [ ] /admin/roles/show (Need to work) - [ ] /admin/roles/users (Need to work) - [ ] /drive (Need to work) -- [ ] /drive/folders (Need to work) -- [ ] /drive/folders/create (Need to work) -- [ ] /drive/folders/find (Need to work) -- [ ] /drive/folders/show (Need to work) -- [ ] /drive/folders/update (Need to work) - [ ] /drive/stream (Need to work) - [ ] /endpoint (Need to work) - [ ] /federation/instances (Need to work) @@ -410,7 +410,7 @@ - [ ] Muting - [ ] RenoteMuting - [ ] Blocking -- [ ] Hashtag +- [x] Hashtag - [x] InviteCode - [ ] Page - [x] Channel diff --git a/mipac/actions/hashtag.py b/mipac/actions/hashtag.py new file mode 100644 index 0000000..55be28e --- /dev/null +++ b/mipac/actions/hashtag.py @@ -0,0 +1,172 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Literal + +from mipac.abstract.action import AbstractAction +from mipac.http import HTTPClient, Route +from mipac.models.hashtag import Hashtag, TrendHashtag +from mipac.models.user import MeDetailed, UserDetailedNotMe, packed_user +from mipac.types.hashtag import IHashtag, ITrendHashtag +from mipac.types.user import IMeDetailedSchema, IUserDetailedNotMeSchema + +if TYPE_CHECKING: + from mipac.client import ClientManager + + +class HashtagActions(AbstractAction): + def __init__(self, *, session: HTTPClient, client: ClientManager): + self.__session: HTTPClient = session + self.__client: ClientManager = client + + async def get_list( + self, + sort: Literal[ + "+mentionedUsers", + "-mentionedUsers", + "+mentionedLocalUsers", + "-mentionedLocalUsers", + "+mentionedRemoteUsers", + "-mentionedRemoteUsers", + "+attachedUsers", + "-attachedUsers", + "+attachedLocalUsers", + "-attachedLocalUsers", + "+attachedRemoteUsers", + "-attachedRemoteUsers", + ], + limit: int = 10, + attached_to_user_only: bool = False, + attached_to_local_user_only: bool = False, + attached_to_remote_user_only: bool = False, + ) -> list[Hashtag]: + """ハッシュタグのリストを取得します。 + + Parameters + ---------- + sort: Literal[ + "+mentionedUsers", + "-mentionedUsers", + "+mentionedLocalUsers", + "-mentionedLocalUsers", + "+mentionedRemoteUsers", + "-mentionedRemoteUsers", + "+attachedUsers", + "-attachedUsers", + "+attachedLocalUsers", + "-attachedLocalUsers", + "+attachedRemoteUsers", + "-attachedRemoteUsers", + ] + ソートの方法を指定します。 + limit: int, optional + 取得するハッシュタグの数を指定します, default=10 + attached_to_user_only: bool, optional + ユーザーに添付されたハッシュタグのみを取得するかどうかを指定します, default=False + attached_to_local_user_only: bool, optional + ローカルユーザーに添付されたハッシュタグのみを取得するかどうかを指定します, default=False + attached_to_remote_user_only: bool, optional + リモートユーザーに添付されたハッシュタグのみを取得するかどうかを指定します, default=False + + Returns + ------- + list[Hashtag] + 取得したハッシュタグのリストです。 + """ + body = { + "limit": limit, + "attachedToUserOnly": attached_to_user_only, + "attachedToLocalUserOnly": attached_to_local_user_only, + "attachedToRemoteUserOnly": attached_to_remote_user_only, + "sort": sort, + } + + raw_hashtags: list[IHashtag] = await self.__session.request( + Route("POST", "/api/hashtags/list"), json=body + ) + + return [ + Hashtag(raw_hashtag=raw_hashtag, client=self.__client) for raw_hashtag in raw_hashtags + ] + + async def search(self, query: str, limit: int = 10, offset: int = 0) -> list[str]: + """ハッシュタグを検索します + + Parameters + ---------- + query: str + 検索するクエリを指定します。 + limit: int, optional + 取得するハッシュタグの数を指定します, default=10 + offset: int, optional + オフセットを指定します, default=0 + + Returns + ------- + list[Hashtag] + 取得したハッシュタグのリストです。 + """ + + body = {"query": query, "limit": limit, "offset": offset} + + raw_hashtags: list[str] = await self.__session.request( + Route("POST", "/api/hashtags/search"), + json=body, + lower=False, # 戻り値がarrayのstrなのでlowerするとエラーになる + ) + + return raw_hashtags + + async def show(self, tag: str): + """ハッシュタグの情報を取得します。 + + Parameters + ---------- + tag: str + 取得するハッシュタグの名前です。 + + Returns + ------- + Hashtag + 取得したハッシュタグの情報です。 + """ + body = {"tag": tag} + + raw_hashtag: IHashtag = await self.__session.request( + Route("POST", "/api/hashtags/show"), json=body + ) + + return Hashtag(raw_hashtag=raw_hashtag, client=self.__client) + + async def get_trend(self): + """トレンドのハッシュタグを取得します。 + + Returns + ------- + list[TrendHashtag] + 取得したハッシュタグのリストです。 + """ + raw_trend_hashtags: list[ITrendHashtag] = await self.__session.request( + Route("GET", "/api/hashtags/trend") + ) + + return [ + TrendHashtag(raw_trend_hashtag=raw_trend_hashtag, client=self.__client) + for raw_trend_hashtag in raw_trend_hashtags + ] + + async def get_users( + self, + tag: str, + sort: Literal[ + "+follower", "-follower", "+createdAt", "-createdAt", "+updatedAt", "-updatedAt" + ], + state: Literal["all", "alive"] = "all", + origin: Literal["combined", "local", "remote"] = "local", + ) -> list[UserDetailedNotMe | MeDetailed]: + body = {"tag": tag, "sort": sort, "state": state, "origin": origin} + + raw_users: list[ + IUserDetailedNotMeSchema | IMeDetailedSchema + ] = await self.__session.request(Route("POST", "/api/hashtags/users"), json=body) + + return [packed_user(raw_user, client=self.__client) for raw_user in raw_users] diff --git a/mipac/manager/client.py b/mipac/manager/client.py index be2f982..5a57611 100644 --- a/mipac/manager/client.py +++ b/mipac/manager/client.py @@ -12,6 +12,7 @@ from mipac.manager.clip import ClientClipManager, ClipManager from mipac.manager.drive.drive import DriveManager from mipac.manager.emoji import EmojiManager from mipac.manager.follow import FollowManager, FollowRequestManager +from mipac.manager.hashtag import HashtagManager from mipac.manager.invite import ClientInviteManager, InviteManager from mipac.manager.my import MyManager from mipac.manager.note import ClientNoteManager, NoteManager @@ -46,6 +47,7 @@ class ClientManager: session=session, client=self, ) + self.hashtag: HashtagManager = HashtagManager(session=session, client=self) self.clip: ClipManager = ClipManager(session=session, client=self) self.emoji: EmojiManager = EmojiManager(session=session, client=self) self.antenna: AntennaManager = AntennaManager(session=session, client=self) diff --git a/mipac/manager/hashtag.py b/mipac/manager/hashtag.py new file mode 100644 index 0000000..078630c --- /dev/null +++ b/mipac/manager/hashtag.py @@ -0,0 +1,19 @@ +from __future__ import annotations +from typing import TYPE_CHECKING + +from mipac.abstract.manager import AbstractManager +from mipac.http import HTTPClient +from mipac.actions.hashtag import HashtagActions + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class HashtagManager(AbstractManager): + def __init__(self, *, session: HTTPClient, client: ClientManager): + self.__session: HTTPClient = session + self.__client: ClientManager = client + + @property + def action(self) -> HashtagActions: + return HashtagActions(session=self.__session, client=self.__client) diff --git a/mipac/models/hashtag.py b/mipac/models/hashtag.py new file mode 100644 index 0000000..c4da2bb --- /dev/null +++ b/mipac/models/hashtag.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +from mipac.types.hashtag import IHashtag, ITrendHashtag + +if TYPE_CHECKING: + from mipac.manager.client import ClientManager + + +class Hashtag: + def __init__(self, *, raw_hashtag: IHashtag, client: ClientManager) -> None: + self.__client: ClientManager = client + self.__raw_hashtag: IHashtag = raw_hashtag + + @property + def tag(self): + return self.__raw_hashtag["tag"] + + @property + def mentioned_users_count(self): + return self.__raw_hashtag["mentioned_users_count"] + + @property + def mentioned_local_users_count(self): + return self.__raw_hashtag["mentioned_local_users_count"] + + @property + def mentioned_remote_users_count(self): + return self.__raw_hashtag["mentioned_remote_users_count"] + + @property + def attached_users_count(self): + return self.__raw_hashtag["attached_users_count"] + + @property + def attached_local_users_count(self): + return self.__raw_hashtag["attached_local_users_count"] + + @property + def attached_remote_users_count(self): + return self.__raw_hashtag["attached_remote_users_count"] + + def _get(self, key: str) -> Any | None: + """生のレスポンスデータに直接アクセスすることができます + Returns + ------- + Any | None + 生のレスポンスデータ + """ + return self.__raw_hashtag.get(key) + + +class TrendHashtag: + def __init__(self, *, raw_trend_hashtag: ITrendHashtag, client: ClientManager) -> None: + self.__raw_trend_hashtag: ITrendHashtag = raw_trend_hashtag + self.__client: ClientManager = client + + @property + def tag(self): + """ハッシュタグ + + Returns + ------- + str + ハッシュタグ + """ + return self.__raw_trend_hashtag["tag"] + + @property + def chart(self): + """チャート + + Returns + ------- + list[int] + チャート + """ + return self.__raw_trend_hashtag["chart"] + + @property + def users_count(self): + """ユーザー数 + + Returns + ------- + int + ユーザー数 + """ + return self.__raw_trend_hashtag["users_count"] + + def _get(self, key: str) -> Any | None: + """生のレスポンスデータに直接アクセスすることができます + Returns + ------- + Any | None + 生のレスポンスデータ + """ + return self.__raw_trend_hashtag.get(key) diff --git a/mipac/models/user.py b/mipac/models/user.py index 5734c8e..e241fe4 100644 --- a/mipac/models/user.py +++ b/mipac/models/user.py @@ -40,7 +40,23 @@ if TYPE_CHECKING: from mipac.manager.users.list import ClientUserListManager -__all__ = ("PartialUser", "Achievement", "MeDetailed") +__all__ = ( + "FollowCommon", + "Follower", + "Following", + "Achievement", + "UserField", + "UserRole", + "UserDetailedNotMeOnly", + "MeDetailedOnly", + "UserDetailedNotMe", + "MeDetailed", + "packed_user", + "UserList", + "UserListMembership", + "FrequentlyRepliedUser", + "CreatedUser", +) class FollowCommon[FFC: IFederationFollowCommon]: diff --git a/mipac/types/hashtag.py b/mipac/types/hashtag.py new file mode 100644 index 0000000..3e38d1b --- /dev/null +++ b/mipac/types/hashtag.py @@ -0,0 +1,17 @@ +from typing import TypedDict + + +class IHashtag(TypedDict): + tag: str + mentioned_users_count: int + mentioned_local_users_count: int + mentioned_remote_users_count: int + attached_users_count: int + attached_local_users_count: int + attached_remote_users_count: int + + +class ITrendHashtag(TypedDict): + tag: str + chart: list[int] + users_count: int