8 Commits

Author SHA1 Message Date
firedotguy
1a606da55f feat: add verification; add auth - change password and logout 2026-01-30 16:12:05 +03:00
firedotguy
aa20199ebe feat: add get liked post and update privacy 2026-01-30 15:39:52 +03:00
firedotguy
49427a5535 refactor: move api calls to routes folder; feat: add update_banner user-friendly method; fix: change file data type 2026-01-30 15:16:29 +03:00
firedotguy
d27db1d905 docs: add note about cookie 2026-01-30 14:56:06 +03:00
firedotguy
70bb1e75d6 docs: add plans 2026-01-30 14:51:54 +03:00
firedotguy
97c6812819 docs: change package name in install guide; add banner change and name change examples 2026-01-29 23:54:11 +03:00
firedotguy
f1d9a0b2f0 feat: add files uploading; add banner updating 2026-01-29 23:39:35 +03:00
firedotguy
10751f9ddb fix: use json for non-get 2026-01-29 23:26:03 +03:00
18 changed files with 174 additions and 31 deletions

View File

@@ -5,7 +5,7 @@
## Установка ## Установка
```bash ```bash
pip install pyITDclient pip install itd-sdk
``` ```
## Пример ## Пример
@@ -19,6 +19,41 @@ c = ITDClient('TOKEN', 'refresh_token=...; __ddg1_=...; __ddgid_=...; is_auth=1;
print(c.get_me()) print(c.get_me())
``` ```
> [!NOTE]
> Берите куки из запроса /auth/refresh. В остальных запросах нету refresh_token
> ![cookie](cookie-screen.png)
---
### Скрипт на обновление имени
Этот код сейчас работает на @itd_sdk (обновляется имя и пост)
```python
from itd import ITDClient
from time import sleep
from random import randint
from datetime import datetime
from datetime import timezone
c = ITDClient(None, '...')
while True:
c.update_profile(display_name=f'PYTHON ITD SDK | Рандом: {randint(1, 100)} | {datetime.now().strftime("%m.%d %H:%M:%S")}')
# редактирование поста
# c.edit_post('82ea8a4f-a49e-485e-b0dc-94d7da9df990', f'рил ща {datetime.now(timezone.utc).isoformat(" ")} по UTC (обновляется каждую секунду)')
sleep(1)
```
### Скрипт на смену баннера
```python
from itd import ITDClient
c = ITDClient(None, '...')
id = c.upload_file('любое-имя.png', open('реальное-имя-файла.png', 'rb'))['id']
c.update_profile(banner_id=id)
print('баннер обновлен')
```
### Встроенные запросы ### Встроенные запросы
Существуют встроенные эндпоинты для комментариев, хэштэгов, уведомлений, постов, репортов, поиска, пользователей, итд. Существуют встроенные эндпоинты для комментариев, хэштэгов, уведомлений, постов, репортов, поиска, пользователей, итд.
```python ```python
@@ -39,12 +74,20 @@ fetch(c.token, 'метод', 'эндпоинт', {'данные': 'данные'
Из методов поддерживается `get`, `post`, `put` итд, которые есть в `requests` Из методов поддерживается `get`, `post`, `put` итд, которые есть в `requests`
К названию эндпоинта добавляется домен итд и `api`, то есть в этом примере отпарвится `https://xn--d1ah4a.com/api/эндпоинт`. К названию эндпоинта добавляется домен итд и `api`, то есть в этом примере отпарвится `https://xn--d1ah4a.com/api/эндпоинт`.
> ![INFO] > [!NOTE]
> `xn--d1ah4a.com` - punycode от "итд.com" > `xn--d1ah4a.com` - punycode от "итд.com"
## прочее ## Планы
- Форматированные сообщения об ошибках
- Логирование (через logging)
- Добавление ООП (отдеьные классы по типу User или Post вместо обычного JSON)
- Голосовые сообщения
## Прочее
Лицезия: [MIT](./LICENSE) Лицезия: [MIT](./LICENSE)
Идея (и часть эндпоинтов): https://github.com/FriceKa/ITD-SDK-js Идея (и часть эндпоинтов): https://github.com/FriceKa/ITD-SDK-js
- По сути этот проект является реворком, просто на другом языке - По сути этот проект является реворком, просто на другом языке
Автор: [SizedBox](https://xn--d1ah4a.com/SizedBox) (в итд) [@desicars](https://t.me/desicars) (в тг) Автор: [itd_sdk](https://xn--d1ah4a.com/itd_sdk) (в итд) [@desicars](https://t.me/desicars) (в тг)

BIN
cookie-screen.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

View File

@@ -1,14 +1,19 @@
from _io import BufferedReader
from typing import cast
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
from itd.users import get_user, update_profile, follow, unfollow, get_followers, get_following from itd.routes.users import get_user, update_profile, follow, unfollow, get_followers, get_following, update_privacy
from itd.etc import get_top_clans, get_who_to_follow, get_platform_status from itd.routes.etc import get_top_clans, get_who_to_follow, get_platform_status
from itd.comments import get_comments, add_comment, delete_comment, like_comment, unlike_comment from itd.routes.comments import get_comments, add_comment, delete_comment, like_comment, unlike_comment
from itd.hashtags import get_hastags, get_posts_by_hastag from itd.routes.hashtags import get_hastags, get_posts_by_hastag
from itd.notifications import get_notifications, mark_as_read, mark_all_as_read, get_unread_notifications_count from itd.routes.notifications import get_notifications, mark_as_read, mark_all_as_read, get_unread_notifications_count
from itd.posts import create_post, get_posts, get_post, edit_post, delete_post, pin_post, repost, view_post from itd.routes.posts import create_post, get_posts, get_post, edit_post, delete_post, pin_post, repost, view_post, get_liked_posts
from itd.reports import report from itd.routes.reports import report
from itd.search import search from itd.routes.search import search
from itd.request import refresh_auth from itd.routes.files import upload_file
from itd.routes.auth import refresh_token, change_password, logout
from itd.routes.verification import verificate, get_verification_status
def refresh_on_error(func): def refresh_on_error(func):
@@ -34,12 +39,27 @@ class Client:
else: else:
raise ValueError('Provide token or cookie') raise ValueError('Provide token or cookie')
@refresh_on_error
def refresh_auth(self): def refresh_auth(self):
if self.cookies: if self.cookies:
self.token = refresh_auth(self.cookies) self.token = refresh_token(self.cookies)
return self.token
else: else:
print('no cookies!') print('no cookies')
@refresh_on_error
def change_password(self, old: str, new: str):
if not self.cookies:
print('no cookies')
return
return change_password(self.cookies, self.token, old, new)
@refresh_on_error
def logout(self):
if not self.cookies:
print('no cookies')
return
return logout(self.cookies)
@refresh_on_error @refresh_on_error
def get_user(self, username: str) -> dict: def get_user(self, username: str) -> dict:
@@ -50,8 +70,12 @@ class Client:
return self.get_user('me') return self.get_user('me')
@refresh_on_error @refresh_on_error
def update_profile(self, username: str | None = None, display_name: str | None = None, bio: str | None = None) -> dict: def update_profile(self, username: str | None = None, display_name: str | None = None, bio: str | None = None, banner_id: str | None = None) -> dict:
return update_profile(self.token, bio, display_name, username) return update_profile(self.token, bio, display_name, username, banner_id)
@refresh_on_error
def update_privacy(self, wall_closed: bool = False, private: bool = False):
return update_privacy(self.token, wall_closed, private)
@refresh_on_error @refresh_on_error
def follow(self, username: str) -> dict: def follow(self, username: str) -> dict:
@@ -70,6 +94,15 @@ class Client:
return get_following(self.token, username) return get_following(self.token, username)
@refresh_on_error
def verificate(self, file_url: str):
return verificate(self.token, file_url)
@refresh_on_error
def get_verification_status(self):
return get_verification_status(self.token)
@refresh_on_error @refresh_on_error
def get_who_to_follow(self) -> dict: def get_who_to_follow(self) -> dict:
return get_who_to_follow(self.token) return get_who_to_follow(self.token)
@@ -162,6 +195,10 @@ class Client:
def view_post(self, id: str): def view_post(self, id: str):
return view_post(self.token, id) return view_post(self.token, id)
@refresh_on_error
def get_liked_posts(self, username: str, limit: int = 20, cursor: int = 0):
return get_liked_posts(self.token, username, limit, cursor)
@refresh_on_error @refresh_on_error
def report(self, id: str, type: str = 'post', reason: str = 'other', description: str = ''): def report(self, id: str, type: str = 'post', reason: str = 'other', description: str = ''):
@@ -190,4 +227,13 @@ class Client:
@refresh_on_error @refresh_on_error
def search_hashtag(self, query: str, limit: int = 5): def search_hashtag(self, query: str, limit: int = 5):
return search(self.token, query, 0, limit) return search(self.token, query, 0, limit)
@refresh_on_error
def upload_file(self, name: str, data: BufferedReader):
return upload_file(self.token, name, data)
def update_banner(self, name: str):
id = self.upload_file(name, cast(BufferedReader, open(name, 'rb')))['id']
return self.update_profile(banner_id=id)

View File

@@ -1,10 +1,13 @@
from _io import BufferedReader
from requests import Session from requests import Session
s = Session() s = Session()
def fetch(token: str, method: str, url: str, params: dict = {}): def fetch(token: str, method: str, url: str, params: dict = {}, files: dict[str, tuple[str, BufferedReader]] = {}):
res = eval(f's.{method}')(f'https://xn--d1ah4a.com/api/{url}', timeout=20, params=params, headers={ base = f'https://xn--d1ah4a.com/api/{url}'
headers = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3", "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
"Accept-Encoding": "gzip, deflate, br, zstd", "Accept-Encoding": "gzip, deflate, br, zstd",
@@ -19,7 +22,13 @@ def fetch(token: str, method: str, url: str, params: dict = {}):
"Pragma": "no-cache", "Pragma": "no-cache",
"Cache-Control": "no-cache", "Cache-Control": "no-cache",
"TE": "trailers" "TE": "trailers"
}) }
method = method.lower()
if method == "get":
res = s.get(base, timeout=120 if files else 20, params=params, headers=headers)
else:
res = s.request(method.upper(), base, timeout=20, json=params, headers=headers, files=files)
res.raise_for_status() res.raise_for_status()
return res.json() return res.json()
@@ -27,9 +36,8 @@ def set_cookies(cookies: str):
for cookie in cookies.split('; '): for cookie in cookies.split('; '):
s.cookies.set(cookie.split('=')[0], cookie.split('=')[-1], path='/', domain='xn--d1ah4a.com.com') s.cookies.set(cookie.split('=')[0], cookie.split('=')[-1], path='/', domain='xn--d1ah4a.com.com')
def refresh_auth(cookies: str): def auth_fetch(cookies: str, method: str, url: str, params: dict = {}, token: str | None = None):
print('refresh') headers = {
res = s.post(f'https://xn--d1ah4a.com/api/v1/auth/refresh', timeout=10, headers={
"Host": "xn--d1ah4a.com", "Host": "xn--d1ah4a.com",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0", "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0",
"Accept": "*/*", "Accept": "*/*",
@@ -49,6 +57,13 @@ def refresh_auth(cookies: str):
"Cache-Control": "no-cache", "Cache-Control": "no-cache",
"Content-Length": "0", "Content-Length": "0",
"TE": "trailers", "TE": "trailers",
}) }
if token:
headers['Authorization'] = 'Bearer ' + token
if method == 'get':
res = s.get(f'https://xn--d1ah4a.com/api/{url}', timeout=20, params=params, headers=headers)
else:
res = s.request(method, f'https://xn--d1ah4a.com/api/{url}', timeout=20, json=params, headers=headers)
res.raise_for_status() res.raise_for_status()
return res.json()['accessToken'] return res.json()

10
itd/routes/auth.py Normal file
View File

@@ -0,0 +1,10 @@
from itd.request import auth_fetch
def refresh_token(cookies: str):
return auth_fetch(cookies, 'post', 'v1/auth/refresh')['accessToken']
def change_password(cookies: str, token: str, old: str, new: str):
return auth_fetch(cookies, 'post', 'v1/auth/change-password', {'newPassword': new, 'oldPassword': old}, token)
def logout(cookies: str):
return auth_fetch(cookies, 'post', 'v1/auth/logout')

7
itd/routes/files.py Normal file
View File

@@ -0,0 +1,7 @@
from _io import BufferedReader
from itd.request import fetch
def upload_file(token: str, name: str, data: BufferedReader):
return fetch(token, 'post', 'files/upload', files={'file': (name, data)})

View File

@@ -39,4 +39,7 @@ def repost(token: str, id: str, content: str | None = None):
return fetch(token, 'post', f'posts/{id}/repost', data) return fetch(token, 'post', f'posts/{id}/repost', data)
def view_post(token: str, id: str): def view_post(token: str, id: str):
return fetch(token, 'post', f'posts/{id}/view') return fetch(token, 'post', f'posts/{id}/view')
def get_liked_posts(token: str, username: str, limit: int = 20, cursor: int = 0):
return fetch(token, 'get', f'posts/user/{username}/liked', {'limit': limit, 'cursor': cursor})

View File

@@ -4,7 +4,7 @@ from itd.request import fetch
def get_user(token: str, username: str): def get_user(token: str, username: str):
return fetch(token, 'get', f'users/{username}') return fetch(token, 'get', f'users/{username}')
def update_profile(token: str, bio: str | None = None, display_name: str | None = None, username: str | None = None): def update_profile(token: str, bio: str | None = None, display_name: str | None = None, username: str | None = None, banner_id: str | None = None):
data = {} data = {}
if bio: if bio:
data['bio'] = bio data['bio'] = bio
@@ -12,8 +12,18 @@ def update_profile(token: str, bio: str | None = None, display_name: str | None
data['displayName'] = display_name data['displayName'] = display_name
if username: if username:
data['username'] = username data['username'] = username
if banner_id:
data['bannerId'] = banner_id
return fetch(token, 'put', 'users/me', data) return fetch(token, 'put', 'users/me', data)
def update_privacy(token: str, wall_closed: bool = False, private: bool = False):
data = {}
if wall_closed:
data['wallClosed'] = wall_closed
if private:
data['isPrivate'] = private
return fetch(token, 'put', 'users/me/privacy', data)
def follow(token: str, username: str): def follow(token: str, username: str):
return fetch(token, 'post', f'users/{username}/follow') return fetch(token, 'post', f'users/{username}/follow')
@@ -25,3 +35,4 @@ def get_followers(token: str, username: str, limit: int = 30, page: int = 1):
def get_following(token: str, username: str, limit: int = 30, page: int = 1): def get_following(token: str, username: str, limit: int = 30, page: int = 1):
return fetch(token, 'get', f'users/{username}/following', {'limit': limit, 'page': page}) return fetch(token, 'get', f'users/{username}/following', {'limit': limit, 'page': page})

View File

@@ -0,0 +1,8 @@
from itd.request import fetch
def verificate(token: str, file_url: str):
# {"success":true,"request":{"id":"fc54e54f-8586-4d8c-809e-df93161f99da","userId":"9096a85b-c319-483e-8940-6921be427ad0","videoUrl":"https://943701f000610900cbe86b72234e451d.bckt.ru/videos/354f28a6-9ac7-48a6-879a-a454062b1d6b.mp4","status":"pending","rejectionReason":null,"reviewedBy":null,"reviewedAt":null,"createdAt":"2026-01-30T12:58:14.228Z","updatedAt":"2026-01-30T12:58:14.228Z"}}
return fetch(token, 'post', 'verification/submit', {'videoUrl': file_url})
def get_verification_status(token: str):
return fetch(token, 'get', 'verification/status')

BIN
nowkie.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB

View File

@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "itd-sdk" name = "itd-sdk"
version = "0.1.0" version = "0.2.0"
description = "ITD client for python" description = "ITD client for python"
readme = "README.md" readme = "README.md"
authors = [ authors = [

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name='itd-sdk', name='itd-sdk',
version='0.1.0', version='0.2.0',
packages=find_packages(), packages=find_packages(),
install_requires=[ install_requires=[
'requests' 'requests'