diff --git a/CHANGELOG.md b/CHANGELOG.md index e044ab9..5ec2ed8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,9 +9,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ### Added -- none - - +- Achievements + - added `IAchievementNf` class. + - added `NotificationAchievement` class. + - added `Achievement` class. + - added `get_achievements` method at `UserActions` class. + - added `achievements` property at `UserDetailed` class. ## [0.4.0] 2023-01-18 diff --git a/mipac/actions/user.py b/mipac/actions/user.py index e308461..a590f55 100644 --- a/mipac/actions/user.py +++ b/mipac/actions/user.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, AsyncIterator, Literal, Optional from mipac.errors.base import NotExistRequiredData, ParameterError from mipac.http import HTTPClient, Route -from mipac.models.user import LiteUser, UserDetailed +from mipac.models.user import LiteUser, UserDetailed, Achievement from mipac.util import cache, check_multi_arg, remove_dict_empty if TYPE_CHECKING: @@ -30,7 +30,9 @@ class UserActions: ログインしているユーザーの情報を取得します """ - res = await self.__session.request(Route('POST', '/api/i'), auth=True) + res = await self.__session.request( + Route('POST', '/api/i'), auth=True, lower=True, + ) return UserDetailed(res, client=self.__client) # TODO: 自分用のクラスに変更する def get_profile_link( @@ -309,3 +311,23 @@ class UserActions: else LiteUser(user, client=self.__client) for user in res ] + + async def get_achievements( + self, + user_id: str | None = None + ) -> list[Achievement]: + """ Get achievements of user. """ + + user_id = user_id or self.__user and self.__user.id + + if not user_id: + raise ParameterError('user_id is required') + + data = { + 'userId': user_id, + } + res = await self.__session.request( + Route('POST', '/api/users/achievements'), + json=data, auth=True, lower=True, + ) + return [Achievement(i) for i in res] diff --git a/mipac/models/notification.py b/mipac/models/notification.py index 03737a9..0ea6ccc 100644 --- a/mipac/models/notification.py +++ b/mipac/models/notification.py @@ -15,6 +15,7 @@ if TYPE_CHECKING: INoteNf, IPollEndNf, IReactionNf, + IAchievementNf, ) @@ -141,3 +142,16 @@ class NotificationReaction(Notification): @property def reaction(self) -> str: return self.__notification['reaction'] + + +class NotificationAchievement(Notification): + def __init__( + self, notification: IAchievementNf, *, client: ClientManager, + ) -> None: + super().__init__(notification, client=client) + self.__notification: IAchievementNf = notification + self.__client: ClientManager = client + + @property + def achievement(self) -> str: + return self.__notification['achievement'] diff --git a/mipac/models/user.py b/mipac/models/user.py index 5340c00..fa6f619 100644 --- a/mipac/models/user.py +++ b/mipac/models/user.py @@ -5,12 +5,13 @@ from typing import TYPE_CHECKING, Literal from mipac.models.lite.user import LiteUser from mipac.models.note import Note from mipac.types.page import IPage -from mipac.types.user import IFollowRequest, IUserDetailed, IUserDetailedField +from mipac.types.user import IFollowRequest, IUserDetailed, \ + IUserDetailedField, IAchievement if TYPE_CHECKING: from mipac.manager.client import ClientManager -__all__ = ('UserDetailed', 'FollowRequest', 'LiteUser') +__all__ = ('UserDetailed', 'FollowRequest', 'LiteUser', 'Achievement') class FollowRequest: @@ -31,6 +32,19 @@ class FollowRequest: return LiteUser(self.__request['followee'], client=self.__client) +class Achievement: + def __init__(self, detail: IAchievement): + self.__detail: IAchievement = detail + + @property + def name(self) -> str: + return self.__detail['name'] + + @property + def unlocked_at(self) -> int: + return self.__detail['unlocked_at'] + + class UserDetailed(LiteUser): __slots__ = ( '__detail', @@ -41,6 +55,10 @@ class UserDetailed(LiteUser): super().__init__(user=user, client=client) self.__detail = user + @property + def achievements(self) -> list[Achievement]: + return [Achievement(i) for i in self.__detail.get('achievements', [])] + @property def fields(self) -> list[IUserDetailedField]: return self.__detail['fields'] @@ -178,6 +196,10 @@ class UserDetailed(LiteUser): def location(self) -> str | None: return self.__detail.get('location') + @property + def logged_in_days(self) -> int | None: + return self.__detail.get('logged_in_days') + @property def pinned_page(self) -> IPage | None: return self.__detail.get('pinned_page') diff --git a/mipac/types/endpoints.py b/mipac/types/endpoints.py index b88f242..6ef8960 100644 --- a/mipac/types/endpoints.py +++ b/mipac/types/endpoints.py @@ -274,6 +274,7 @@ ENDPOINTS = Literal[ '/api/test', '/api/username/available', '/api/users', + '/api/users/achievements', '/api/users/clips', '/api/users/followers', '/api/users/following', diff --git a/mipac/types/notification.py b/mipac/types/notification.py index 618a633..9602cee 100644 --- a/mipac/types/notification.py +++ b/mipac/types/notification.py @@ -40,3 +40,7 @@ class IReactionNf(INotification): user: ILiteUser user_id: str note: INote + + +class IAchievementNf(INotification): + achievement: str diff --git a/mipac/types/user.py b/mipac/types/user.py index ff914dc..0216645 100644 --- a/mipac/types/user.py +++ b/mipac/types/user.py @@ -13,6 +13,7 @@ __all__ = ( 'ILiteUser', 'IUserDetailed', 'IUserDetailedField', + 'IAchievement', ) @@ -25,6 +26,11 @@ class ISignin(TypedDict): success: bool +class IAchievement(TypedDict): + name: str + unlocked_at: int + + class IUserRequired(TypedDict): id: str username: str @@ -73,6 +79,7 @@ class IUserDetailedRequired(ILiteUser): class IUserDetailed(IUserDetailedRequired, total=False): + achievements: List[IAchievement] banner_blurhash: str banner_color: str banner_url: str @@ -83,6 +90,7 @@ class IUserDetailed(IUserDetailedRequired, total=False): lang: str last_fetched_at: str location: str + logged_in_days: int pinned_page: IPage pinned_page_id: str updated_at: str diff --git a/mipac/util.py b/mipac/util.py index 1da4efe..02c848c 100644 --- a/mipac/util.py +++ b/mipac/util.py @@ -373,6 +373,11 @@ def upper_to_lower( field[default_key] = data[attr] if isinstance(field[default_key], dict) and nest: field[default_key] = upper_to_lower(field[default_key]) + elif isinstance(field[default_key], list) and nest: + field[default_key] = [ + upper_to_lower(i) if isinstance(i, dict) + else i for i in field[default_key] + ] return field