Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1a606da55f | ||
|
|
aa20199ebe | ||
|
|
49427a5535 | ||
|
|
d27db1d905 | ||
|
|
70bb1e75d6 | ||
|
|
97c6812819 |
49
README.md
49
README.md
@@ -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
|
||||||
|
> 
|
||||||
|
|
||||||
|
---
|
||||||
|
### Скрипт на обновление имени
|
||||||
|
Этот код сейчас работает на @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
|
||||||
@@ -42,9 +77,17 @@ fetch(c.token, 'метод', 'эндпоинт', {'данные': 'данные'
|
|||||||
> [!NOTE]
|
> [!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
|
||||||
- По сути этот проект является реворком, просто на другом языке
|
- По сути этот проект является реворком, просто на другом языке
|
||||||
|
|
||||||
Автор: [itd_sdk](https://xn--d1ah4a.com/itd_sdk) (в итд) [@desicars](https://t.me/desicars) (в тг)
|
Автор: [itd_sdk](https://xn--d1ah4a.com/itd_sdk) (в итд) [@desicars](https://t.me/desicars) (в тг)
|
||||||
|
|||||||
BIN
cookie-screen.png
Normal file
BIN
cookie-screen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 293 KiB |
@@ -1,15 +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.files import upload_file
|
from itd.routes.files import upload_file
|
||||||
from itd.request import refresh_auth
|
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):
|
||||||
@@ -35,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:
|
||||||
@@ -54,6 +73,10 @@ class Client:
|
|||||||
def update_profile(self, username: str | None = None, display_name: str | None = None, bio: str | None = None, banner_id: 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, banner_id)
|
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:
|
||||||
return follow(self.token, username)
|
return follow(self.token, username)
|
||||||
@@ -71,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)
|
||||||
@@ -163,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 = ''):
|
||||||
@@ -195,5 +231,9 @@ class Client:
|
|||||||
|
|
||||||
|
|
||||||
@refresh_on_error
|
@refresh_on_error
|
||||||
def upload_file(self, name: str, data: bytes):
|
def upload_file(self, name: str, data: BufferedReader):
|
||||||
return upload_file(self.token, name, data)
|
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)
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
|
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 = {}, files: dict[str, tuple[str, bytes]] = {}):
|
def fetch(token: str, method: str, url: str, params: dict = {}, files: dict[str, tuple[str, BufferedReader]] = {}):
|
||||||
base = f'https://xn--d1ah4a.com/api/{url}'
|
base = f'https://xn--d1ah4a.com/api/{url}'
|
||||||
headers = {
|
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",
|
||||||
@@ -34,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": "*/*",
|
||||||
@@ -56,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
10
itd/routes/auth.py
Normal 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')
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
|
from _io import BufferedReader
|
||||||
|
|
||||||
from itd.request import fetch
|
from itd.request import fetch
|
||||||
|
|
||||||
|
|
||||||
def upload_file(token: str, name: str, data: bytes):
|
def upload_file(token: str, name: str, data: BufferedReader):
|
||||||
return fetch(token, 'post', 'files/upload', files={'file': (name, data)})
|
return fetch(token, 'post', 'files/upload', files={'file': (name, data)})
|
||||||
@@ -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})
|
||||||
@@ -16,6 +16,14 @@ def update_profile(token: str, bio: str | None = None, display_name: str | None
|
|||||||
data['bannerId'] = 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')
|
||||||
|
|
||||||
@@ -27,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})
|
||||||
|
|
||||||
8
itd/routes/verification.py
Normal file
8
itd/routes/verification.py
Normal 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
BIN
nowkie.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 MiB |
@@ -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 = [
|
||||||
|
|||||||
Reference in New Issue
Block a user