From 8aef43e11d8de2b4e09ce6d98e5f681c688b0b27 Mon Sep 17 00:00:00 2001 From: firedotguy Date: Sat, 7 Feb 2026 17:47:09 +0300 Subject: [PATCH] feat: add pins --- itd/client.py | 38 ++++++++++++++++++++++++++++++++++++-- itd/exceptions.py | 6 ++++++ itd/models/pin.py | 12 +++++++++--- itd/models/user.py | 4 ++-- itd/routes/pins.py | 10 ++++++++++ 5 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 itd/routes/pins.py diff --git a/itd/client.py b/itd/client.py index 0192714..2b5deaa 100644 --- a/itd/client.py +++ b/itd/client.py @@ -17,6 +17,7 @@ from itd.routes.search import search from itd.routes.files import upload_file from itd.routes.auth import refresh_token, change_password, logout from itd.routes.verification import verify, get_verification_status +from itd.routes.pins import get_pins, remove_pin, set_pin from itd.models.comment import Comment from itd.models.notification import Notification @@ -28,13 +29,14 @@ from itd.models.pagination import Pagination, PostsPagintaion, LikedPostsPaginta from itd.models.verification import Verification, VerificationStatus from itd.models.report import NewReport from itd.models.file import File +from itd.models.pin import Pin from itd.enums import PostsTab, ReportTargetType, ReportTargetReason from itd.request import set_cookies from itd.exceptions import ( NoCookie, NoAuthData, SamePassword, InvalidOldPassword, NotFound, ValidationError, UserBanned, PendingRequestExists, Forbidden, UsernameTaken, CantFollowYourself, Unauthorized, - CantRepostYourPost, AlreadyReposted, AlreadyReported, TooLarge + CantRepostYourPost, AlreadyReposted, AlreadyReported, TooLarge, PinNotOwned ) @@ -52,7 +54,7 @@ def refresh_on_error(func): class Client: - def __init__(self, token: str | None, cookies: str | None = None): + def __init__(self, token: str | None = None, cookies: str | None = None): self.cookies = cookies if token: @@ -984,3 +986,35 @@ class Client: raise NotFound("Post not found") return res.json()['likesCount'] + + + @refresh_on_error + def get_pins(self) -> tuple[list[Pin], str]: + """Список пинов + + Returns: + list[Pin]: Список пинов + str: Активный пин + """ + res = get_pins(self.token) + res.raise_for_status() + data = res.json()['data'] + + return [Pin.model_validate(pin) for pin in data['pins']], data['activePin'] + + @refresh_on_error + def remove_pin(self): + """Снять пин""" + res = remove_pin(self.token) + res.raise_for_status() + + @refresh_on_error + def set_pin(self, slug: str): + res = set_pin(self.token, slug) + if res.status_code == 422 and 'found' in res.json(): + raise ValidationError(*list(res.json()['found'].items())[0]) + if res.json().get('error', {}).get('code') == 'PIN_NOT_OWNED': + raise PinNotOwned(slug) + res.raise_for_status() + + return res.json()['pin'] \ No newline at end of file diff --git a/itd/exceptions.py b/itd/exceptions.py index 63b5925..121929a 100644 --- a/itd/exceptions.py +++ b/itd/exceptions.py @@ -93,3 +93,9 @@ class AlreadyReported(Exception): class TooLarge(Exception): def __str__(self): return 'Search query too large' + +class PinNotOwned(Exception): + def __init__(self, pin: str) -> None: + self.pin = pin + def __str__(self): + return f'You do not own "{self.pin}" pin' \ No newline at end of file diff --git a/itd/models/pin.py b/itd/models/pin.py index dd74d97..b0a07b1 100644 --- a/itd/models/pin.py +++ b/itd/models/pin.py @@ -1,6 +1,12 @@ -from pydantic import BaseModel +from datetime import datetime -class Pin(BaseModel): +from pydantic import BaseModel, Field + +class ShortPin(BaseModel): slug: str name: str - description: str \ No newline at end of file + description: str + + +class Pin(ShortPin): + granted_at: datetime = Field(alias='grantedAt') \ No newline at end of file diff --git a/itd/models/user.py b/itd/models/user.py index 64913eb..5a90f02 100644 --- a/itd/models/user.py +++ b/itd/models/user.py @@ -3,7 +3,7 @@ from datetime import datetime from pydantic import BaseModel, Field -from itd.models.pin import Pin +from itd.models.pin import ShortPin class UserPrivacy(BaseModel): @@ -26,7 +26,7 @@ class UserNewPost(BaseModel): username: str | None = None display_name: str = Field(alias='displayName') avatar: str - pin: Pin | None = None + pin: ShortPin | None = None verified: bool = False diff --git a/itd/routes/pins.py b/itd/routes/pins.py new file mode 100644 index 0000000..e53e257 --- /dev/null +++ b/itd/routes/pins.py @@ -0,0 +1,10 @@ +from itd.request import fetch + +def get_pins(token: str): + return fetch(token, 'get', 'users/me/pins') + +def remove_pin(token: str): + return fetch(token, 'delete', 'users/me/pin') + +def set_pin(token: str, slug: str): + return fetch(token, 'put', 'users/me/pin', {'slug': slug}) \ No newline at end of file