itd-iter-api 1.0.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 firedotguy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: itd-iter-api
3
+ Version: 1.0.2
4
+ Summary: ITD client for python
5
+ Author: sharkow
6
+ Author-email: firedotguy <nta16022013@gmail.com>
7
+ License-Expression: MIT
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: requests
12
+ Dynamic: license-file
13
+ Dynamic: requires-python
14
+
15
+ # pyITDclient
16
+ Клиент ITD для python
17
+
18
+
19
+ ## Установка
20
+
21
+ ```bash
22
+ pip install itd-sdk
23
+ ```
24
+
25
+ ## Пример
26
+
27
+ ```python
28
+ from itd import ITDClient
29
+
30
+ c = ITDClient('TOKEN', 'refresh_token=...; __ddg1_=...; __ddgid_=...; is_auth=1; __ddg2_=...; ddg_last_challenge=...; __ddg8_=...; __ddg10_=...; __ddg9_=...')
31
+ # можно указать только токен, тогда после просрочки перестанет работать, либо только куки чтобы токен сразу подтянулся, либо оба сразу
32
+
33
+ print(c.get_me())
34
+ ```
35
+
36
+ > [!NOTE]
37
+ > Берите куки из запроса /auth/refresh. В остальных запросах нету refresh_token
38
+ > ![cookie](cookie-screen.png)
39
+
40
+ ---
41
+ ### Скрипт на обновление имени
42
+ Этот код сейчас работает на @itd_sdk (обновляется имя и пост)
43
+ ```python
44
+ from itd import ITDClient
45
+ from time import sleep
46
+ from random import randint
47
+ from datetime import datetime
48
+ from datetime import timezone
49
+
50
+ c = ITDClient(None, '...')
51
+
52
+ while True:
53
+ c.update_profile(display_name=f'PYTHON ITD SDK | Рандом: {randint(1, 100)} | {datetime.now().strftime("%m.%d %H:%M:%S")}')
54
+ # редактирование поста
55
+ # c.edit_post('82ea8a4f-a49e-485e-b0dc-94d7da9df990', f'рил ща {datetime.now(timezone.utc).isoformat(" ")} по UTC (обновляется каждую секунду)')
56
+ sleep(1)
57
+ ```
58
+
59
+ ### Скрипт на смену баннера
60
+ ```python
61
+ from itd import ITDClient
62
+
63
+ c = ITDClient(None, '...')
64
+
65
+ id = c.upload_file('любое-имя.png', open('реальное-имя-файла.png', 'rb'))['id']
66
+ c.update_profile(banner_id=id)
67
+ print('баннер обновлен')
68
+
69
+ ```
70
+
71
+ ### Встроенные запросы
72
+ Существуют встроенные эндпоинты для комментариев, хэштэгов, уведомлений, постов, репортов, поиска, пользователей, итд.
73
+ ```python
74
+ c.get_user('ITD_API') # получение данных пользователя
75
+ c.get_me() # получение своих данных (me)
76
+ c.update_profile(display_name='22:26') # изменение данных профиля, например имя, био итд
77
+ c.create_post('тест1') # создание постов
78
+ # итд
79
+ ```
80
+
81
+ ### Кастомные запросы
82
+
83
+ ```python
84
+ from iter.request import fetch
85
+
86
+ fetch(c.token, 'метод', 'эндпоинт', {'данные': 'данные'})
87
+ ```
88
+ Из методов поддерживается `get`, `post`, `put` итд, которые есть в `requests`
89
+ К названию эндпоинта добавляется домен итд и `api`, то есть в этом примере отпарвится `https://xn--d1ah4a.com/api/эндпоинт`.
90
+
91
+ > [!NOTE]
92
+ > `xn--d1ah4a.com` - punycode от "итд.com"
93
+
94
+ ## Планы
95
+
96
+ - Форматированные сообщения об ошибках
97
+ - Логирование (через logging)
98
+ - Добавление ООП (отдеьные классы по типу User или Post вместо обычного JSON)
99
+ - Голосовые сообщения
100
+
101
+
102
+ ## Прочее
103
+ Лицезия: [MIT](./LICENSE)
104
+ Идея (и часть эндпоинтов): https://github.com/FriceKa/ITD-SDK-js
105
+ - По сути этот проект является реворком, просто на другом языке
106
+
107
+ Автор: [itd_sdk](https://xn--d1ah4a.com/itd_sdk) (в итд) [@desicars](https://t.me/desicars) (в тг)
@@ -0,0 +1,93 @@
1
+ # pyITDclient
2
+ Клиент ITD для python
3
+
4
+
5
+ ## Установка
6
+
7
+ ```bash
8
+ pip install itd-sdk
9
+ ```
10
+
11
+ ## Пример
12
+
13
+ ```python
14
+ from itd import ITDClient
15
+
16
+ c = ITDClient('TOKEN', 'refresh_token=...; __ddg1_=...; __ddgid_=...; is_auth=1; __ddg2_=...; ddg_last_challenge=...; __ddg8_=...; __ddg10_=...; __ddg9_=...')
17
+ # можно указать только токен, тогда после просрочки перестанет работать, либо только куки чтобы токен сразу подтянулся, либо оба сразу
18
+
19
+ print(c.get_me())
20
+ ```
21
+
22
+ > [!NOTE]
23
+ > Берите куки из запроса /auth/refresh. В остальных запросах нету refresh_token
24
+ > ![cookie](cookie-screen.png)
25
+
26
+ ---
27
+ ### Скрипт на обновление имени
28
+ Этот код сейчас работает на @itd_sdk (обновляется имя и пост)
29
+ ```python
30
+ from itd import ITDClient
31
+ from time import sleep
32
+ from random import randint
33
+ from datetime import datetime
34
+ from datetime import timezone
35
+
36
+ c = ITDClient(None, '...')
37
+
38
+ while True:
39
+ c.update_profile(display_name=f'PYTHON ITD SDK | Рандом: {randint(1, 100)} | {datetime.now().strftime("%m.%d %H:%M:%S")}')
40
+ # редактирование поста
41
+ # c.edit_post('82ea8a4f-a49e-485e-b0dc-94d7da9df990', f'рил ща {datetime.now(timezone.utc).isoformat(" ")} по UTC (обновляется каждую секунду)')
42
+ sleep(1)
43
+ ```
44
+
45
+ ### Скрипт на смену баннера
46
+ ```python
47
+ from itd import ITDClient
48
+
49
+ c = ITDClient(None, '...')
50
+
51
+ id = c.upload_file('любое-имя.png', open('реальное-имя-файла.png', 'rb'))['id']
52
+ c.update_profile(banner_id=id)
53
+ print('баннер обновлен')
54
+
55
+ ```
56
+
57
+ ### Встроенные запросы
58
+ Существуют встроенные эндпоинты для комментариев, хэштэгов, уведомлений, постов, репортов, поиска, пользователей, итд.
59
+ ```python
60
+ c.get_user('ITD_API') # получение данных пользователя
61
+ c.get_me() # получение своих данных (me)
62
+ c.update_profile(display_name='22:26') # изменение данных профиля, например имя, био итд
63
+ c.create_post('тест1') # создание постов
64
+ # итд
65
+ ```
66
+
67
+ ### Кастомные запросы
68
+
69
+ ```python
70
+ from iter.request import fetch
71
+
72
+ fetch(c.token, 'метод', 'эндпоинт', {'данные': 'данные'})
73
+ ```
74
+ Из методов поддерживается `get`, `post`, `put` итд, которые есть в `requests`
75
+ К названию эндпоинта добавляется домен итд и `api`, то есть в этом примере отпарвится `https://xn--d1ah4a.com/api/эндпоинт`.
76
+
77
+ > [!NOTE]
78
+ > `xn--d1ah4a.com` - punycode от "итд.com"
79
+
80
+ ## Планы
81
+
82
+ - Форматированные сообщения об ошибках
83
+ - Логирование (через logging)
84
+ - Добавление ООП (отдеьные классы по типу User или Post вместо обычного JSON)
85
+ - Голосовые сообщения
86
+
87
+
88
+ ## Прочее
89
+ Лицезия: [MIT](./LICENSE)
90
+ Идея (и часть эндпоинтов): https://github.com/FriceKa/ITD-SDK-js
91
+ - По сути этот проект является реворком, просто на другом языке
92
+
93
+ Автор: [itd_sdk](https://xn--d1ah4a.com/itd_sdk) (в итд) [@desicars](https://t.me/desicars) (в тг)
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: itd-iter-api
3
+ Version: 1.0.2
4
+ Summary: ITD client for python
5
+ Author: sharkow
6
+ Author-email: firedotguy <nta16022013@gmail.com>
7
+ License-Expression: MIT
8
+ Requires-Python: >=3.9
9
+ Description-Content-Type: text/markdown
10
+ License-File: LICENSE
11
+ Requires-Dist: requests
12
+ Dynamic: license-file
13
+ Dynamic: requires-python
14
+
15
+ # pyITDclient
16
+ Клиент ITD для python
17
+
18
+
19
+ ## Установка
20
+
21
+ ```bash
22
+ pip install itd-sdk
23
+ ```
24
+
25
+ ## Пример
26
+
27
+ ```python
28
+ from itd import ITDClient
29
+
30
+ c = ITDClient('TOKEN', 'refresh_token=...; __ddg1_=...; __ddgid_=...; is_auth=1; __ddg2_=...; ddg_last_challenge=...; __ddg8_=...; __ddg10_=...; __ddg9_=...')
31
+ # можно указать только токен, тогда после просрочки перестанет работать, либо только куки чтобы токен сразу подтянулся, либо оба сразу
32
+
33
+ print(c.get_me())
34
+ ```
35
+
36
+ > [!NOTE]
37
+ > Берите куки из запроса /auth/refresh. В остальных запросах нету refresh_token
38
+ > ![cookie](cookie-screen.png)
39
+
40
+ ---
41
+ ### Скрипт на обновление имени
42
+ Этот код сейчас работает на @itd_sdk (обновляется имя и пост)
43
+ ```python
44
+ from itd import ITDClient
45
+ from time import sleep
46
+ from random import randint
47
+ from datetime import datetime
48
+ from datetime import timezone
49
+
50
+ c = ITDClient(None, '...')
51
+
52
+ while True:
53
+ c.update_profile(display_name=f'PYTHON ITD SDK | Рандом: {randint(1, 100)} | {datetime.now().strftime("%m.%d %H:%M:%S")}')
54
+ # редактирование поста
55
+ # c.edit_post('82ea8a4f-a49e-485e-b0dc-94d7da9df990', f'рил ща {datetime.now(timezone.utc).isoformat(" ")} по UTC (обновляется каждую секунду)')
56
+ sleep(1)
57
+ ```
58
+
59
+ ### Скрипт на смену баннера
60
+ ```python
61
+ from itd import ITDClient
62
+
63
+ c = ITDClient(None, '...')
64
+
65
+ id = c.upload_file('любое-имя.png', open('реальное-имя-файла.png', 'rb'))['id']
66
+ c.update_profile(banner_id=id)
67
+ print('баннер обновлен')
68
+
69
+ ```
70
+
71
+ ### Встроенные запросы
72
+ Существуют встроенные эндпоинты для комментариев, хэштэгов, уведомлений, постов, репортов, поиска, пользователей, итд.
73
+ ```python
74
+ c.get_user('ITD_API') # получение данных пользователя
75
+ c.get_me() # получение своих данных (me)
76
+ c.update_profile(display_name='22:26') # изменение данных профиля, например имя, био итд
77
+ c.create_post('тест1') # создание постов
78
+ # итд
79
+ ```
80
+
81
+ ### Кастомные запросы
82
+
83
+ ```python
84
+ from iter.request import fetch
85
+
86
+ fetch(c.token, 'метод', 'эндпоинт', {'данные': 'данные'})
87
+ ```
88
+ Из методов поддерживается `get`, `post`, `put` итд, которые есть в `requests`
89
+ К названию эндпоинта добавляется домен итд и `api`, то есть в этом примере отпарвится `https://xn--d1ah4a.com/api/эндпоинт`.
90
+
91
+ > [!NOTE]
92
+ > `xn--d1ah4a.com` - punycode от "итд.com"
93
+
94
+ ## Планы
95
+
96
+ - Форматированные сообщения об ошибках
97
+ - Логирование (через logging)
98
+ - Добавление ООП (отдеьные классы по типу User или Post вместо обычного JSON)
99
+ - Голосовые сообщения
100
+
101
+
102
+ ## Прочее
103
+ Лицезия: [MIT](./LICENSE)
104
+ Идея (и часть эндпоинтов): https://github.com/FriceKa/ITD-SDK-js
105
+ - По сути этот проект является реворком, просто на другом языке
106
+
107
+ Автор: [itd_sdk](https://xn--d1ah4a.com/itd_sdk) (в итд) [@desicars](https://t.me/desicars) (в тг)
@@ -0,0 +1,13 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ itd_iter_api.egg-info/PKG-INFO
6
+ itd_iter_api.egg-info/SOURCES.txt
7
+ itd_iter_api.egg-info/dependency_links.txt
8
+ itd_iter_api.egg-info/requires.txt
9
+ itd_iter_api.egg-info/top_level.txt
10
+ iter/__init__.py
11
+ iter/client.py
12
+ iter/manual_auth.py
13
+ iter/request.py
@@ -0,0 +1 @@
1
+ requests
@@ -0,0 +1,14 @@
1
+ from iter.client import Client as Client
2
+ from iter.types.responses import (
3
+ SearchResponse,
4
+ CommentsResponse,
5
+ PostFeedResponse,
6
+ HashtagFeedResponse,
7
+ UserListResponse,
8
+ LikeResponse,
9
+ FollowResponse,
10
+ PostUpdateResponse,
11
+ PinResponse,
12
+ ProfileUpdateResponse,
13
+ PrivacyUpdateResponse
14
+ )
@@ -0,0 +1,168 @@
1
+ import json
2
+ import os
3
+ from _io import BufferedReader
4
+ from typing import cast, Optional
5
+
6
+ from requests.exceptions import HTTPError
7
+
8
+ # Import your routes
9
+ from iter.routes.users import get_user, update_profile, follow, unfollow, get_followers, get_following, update_privacy
10
+ from iter.routes.etc import get_top_clans, get_who_to_follow, get_platform_status
11
+ from iter.routes.comments import get_comments, add_comment, delete_comment, like_comment, unlike_comment
12
+ from iter.routes.hashtags import get_hastags, get_posts_by_hastag
13
+ from iter.routes.notifications import get_notifications, mark_as_read, get_unread_notifications_count
14
+ from iter.routes.posts import create_post, get_posts, get_post, edit_post, delete_post, pin_post, repost, view_post, get_liked_posts
15
+ from iter.routes.reports import report
16
+ from iter.routes.search import search
17
+ from iter.routes.files import upload_file
18
+ from iter.routes.auth import refresh_token, change_password, logout
19
+ from iter.routes.verification import verificate, get_verification_status
20
+
21
+ from iter.manual_auth import auth
22
+
23
+ def refresh_on_error(func):
24
+ def wrapper(self, *args, **kwargs):
25
+ try:
26
+ return func(self, *args, **kwargs)
27
+ except HTTPError as e:
28
+ # If Access Token is expired (401)
29
+ if e.response is not None and e.response.status_code == 401:
30
+ print("Access token expired, attempting refresh")
31
+ self.auth()
32
+ return func(self, *args, **kwargs)
33
+ raise e
34
+ return wrapper
35
+
36
+
37
+ class Client:
38
+ def __init__(self, token: Optional[str] = None, cookies: Optional[str] = None, session_file: Optional[str] = "session.json", email: Optional[str] = None, password: Optional[str] = None, use_manual_login: bool = True):
39
+ self.token = token.replace('Bearer ', '') if token else None
40
+ self.cookies = cookies
41
+
42
+ self.manual_login = use_manual_login
43
+ self.session_file = session_file
44
+
45
+ self.email = email
46
+ self.password = password
47
+
48
+ is_auth = self.auth()
49
+ if not is_auth:
50
+ raise RuntimeError('Cannot login')
51
+
52
+ def auth(self):
53
+ if (self.session_file
54
+ and not self.token
55
+ and not self.cookies):
56
+ self._load_session()
57
+
58
+ if (self.manual_login
59
+ and not self.token
60
+ and not self.cookies):
61
+ self._manual_login()
62
+ elif self.cookies and not self.token:
63
+ self._refresh_auth()
64
+
65
+ return self.token and self.cookies
66
+
67
+ def _save_session(self):
68
+ """Saves current credentials to a JSON file."""
69
+ data = {
70
+ "token": self.token,
71
+ "cookies": self.cookies
72
+ }
73
+ with open(self.session_file, 'w', encoding='utf-8') as f:
74
+ json.dump(data, f, indent=4)
75
+
76
+ def _load_session(self):
77
+ """Loads credentials from the session file."""
78
+ if os.path.exists(self.session_file):
79
+ try:
80
+ with open(self.session_file, 'r', encoding='utf-8') as f:
81
+ data = json.load(f)
82
+ self.token = data.get("token")
83
+ self.cookies = data.get("cookies")
84
+ except Exception as e:
85
+ print(f"Failed to load session file: {e}")
86
+
87
+ def _manual_login(self):
88
+ """Triggers the manual authentication flow."""
89
+ print("Starting manual login...")
90
+ new_data = auth(self.email, self.password)
91
+ if new_data:
92
+ self.token = new_data.get('token', '').replace('Bearer ', '')
93
+ self.cookies = new_data.get('cookies')
94
+ self._save_session()
95
+ return True
96
+ print("Manual login failed")
97
+
98
+ def _refresh_auth(self):
99
+ """Attempts to get a new access token using cookies. Falls back to manual login if cookies expired."""
100
+ if not self.cookies:
101
+ return self._manual_login()
102
+
103
+ try:
104
+ print("Refreshing access token...")
105
+ self.token = refresh_token(self.cookies).replace('Bearer ', '')
106
+ self._save_session()
107
+ return self.token
108
+ except HTTPError as e:
109
+ if e.response is not None and e.response.status_code in [401, 403]:
110
+ print("Refresh token expired. Manual login required.")
111
+ return self._manual_login()
112
+ raise e
113
+
114
+ @refresh_on_error
115
+ def logout(self):
116
+ if not self.cookies:
117
+ print('no cookies')
118
+ return
119
+ res = logout(self.cookies)
120
+ self.token = None
121
+ self.cookies = None
122
+ if os.path.exists(self.session_file):
123
+ os.remove(self.session_file)
124
+ return res
125
+
126
+ @refresh_on_error
127
+ def get_user(self, username: str) -> dict:
128
+ return get_user(self.token, username)
129
+
130
+ @refresh_on_error
131
+ def get_me(self) -> dict:
132
+ return self.get_user('me')
133
+
134
+ @refresh_on_error
135
+ def update_profile(self, username: str | None = None, display_name: str | None = None, bio: str | None = None, banner_id: str | None = None) -> dict:
136
+ return update_profile(self.token, bio, display_name, username, banner_id)
137
+
138
+ @refresh_on_error
139
+ def update_privacy(self, wall_closed: bool = False, private: bool = False):
140
+ return update_privacy(self.token, wall_closed, private)
141
+
142
+ @refresh_on_error
143
+ def follow(self, username: str) -> dict:
144
+ return follow(self.token, username)
145
+
146
+ @refresh_on_error
147
+ def unfollow(self, username: str) -> dict:
148
+ return unfollow(self.token, username)
149
+
150
+ @refresh_on_error
151
+ def get_followers(self, username: str) -> dict:
152
+ return get_followers(self.token, username)
153
+
154
+ @refresh_on_error
155
+ def get_following(self, username: str) -> dict:
156
+ return get_following(self.token, username)
157
+
158
+ @refresh_on_error
159
+ def add_comment(self, post_id: str, content: str, reply_comment_id: str | None = None):
160
+ return add_comment(self.token, post_id, content, reply_comment_id)
161
+
162
+ @refresh_on_error
163
+ def create_post(self, content: str, wall_recipient_id: int | None = None, attach_ids: list[str] = []):
164
+ return create_post(self.token, content, wall_recipient_id, attach_ids)
165
+
166
+ @refresh_on_error
167
+ def upload_file(self, name: str, data: BufferedReader):
168
+ return upload_file(self.token, name, data)
@@ -0,0 +1,146 @@
1
+ import os
2
+ import zipfile
3
+ import platform
4
+ import requests
5
+ import logging
6
+ import stat
7
+ from pathlib import Path
8
+ from DrissionPage import ChromiumPage, ChromiumOptions
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class BrowserManager:
13
+ def __init__(self, folder_name="browser"):
14
+ package_dir = Path(__file__).resolve().parent
15
+ self.base_dir = package_dir / folder_name
16
+
17
+ self.system = platform.system()
18
+ self.machine = platform.machine().lower()
19
+ self._set_platform_configs()
20
+
21
+ def _set_platform_configs(self):
22
+ """Maps system architecture to Chrome for Testing API keys and paths."""
23
+ if self.system == "Windows":
24
+ self.platform_key = "win64"
25
+ self.exec_name = "chrome.exe"
26
+ self.relative_path = Path("chrome-win64") / self.exec_name
27
+ elif self.system == "Linux":
28
+ self.platform_key = "linux64"
29
+ self.exec_name = "chrome"
30
+ self.relative_path = Path("chrome-linux64") / self.exec_name
31
+ elif self.system == "Darwin":
32
+ self.platform_key = "mac-arm64" if "arm" in self.machine or "apple" in self.machine else "mac-x64"
33
+ self.exec_name = "Google Chrome for Testing"
34
+ folder_name = f"chrome-{self.platform_key}"
35
+ self.relative_path = Path(folder_name) / "Google Chrome for Testing.app" / "Contents" / "MacOS" / "Google Chrome for Testing"
36
+ else:
37
+ raise OSError(f"Unsupported operating system: {self.system}")
38
+
39
+ self.chrome_exe = self.base_dir / self.relative_path
40
+
41
+ def get_chrome_path(self):
42
+ """Returns the path to the executable, downloading it if necessary."""
43
+ if not self.chrome_exe.exists():
44
+ logger.info(f"Chrome not found for {self.system}. Starting download...")
45
+ self._download_chrome()
46
+
47
+ # On Linux/Mac, we must ensure the binary is executable
48
+ if self.system != "Windows":
49
+ self._ensure_executable(self.chrome_exe)
50
+
51
+ return str(self.chrome_exe.absolute())
52
+
53
+ def _ensure_executable(self, path):
54
+ """Sets chmod +x on the binary."""
55
+ st = os.stat(path)
56
+ os.chmod(path, st.st_mode | stat.S_IEXEC)
57
+
58
+ def _download_chrome(self):
59
+ # 1. Get latest stable download URL
60
+ api_url = "https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json"
61
+ try:
62
+ resp = requests.get(api_url).json()
63
+ downloads = resp['channels']['Stable']['downloads']['chrome']
64
+
65
+ download_url = next(item['url'] for item in downloads if item['platform'] == self.platform_key)
66
+ except Exception as e:
67
+ raise RuntimeError(f"Failed to fetch download URL from Google API: {e}")
68
+
69
+ self.base_dir.mkdir(parents=True, exist_ok=True)
70
+ zip_path = self.base_dir / "chrome_temp.zip"
71
+
72
+ # 2. Download with progress bar
73
+ logger.info(f"Downloading Chrome ({self.platform_key}) from: {download_url}")
74
+ r = requests.get(download_url, stream=True)
75
+
76
+ with open(zip_path, 'wb') as f:
77
+ for data in r.iter_content(chunk_size=1024):
78
+ f.write(data)
79
+
80
+ # 3. Extract
81
+ logger.info("Extracting browser...")
82
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
83
+ zip_ref.extractall(self.base_dir)
84
+
85
+ # 4. Cleanup
86
+ if zip_path.exists():
87
+ os.remove(zip_path)
88
+ logger.info(f"Browser successfully installed to {self.chrome_exe}")
89
+
90
+ def auth(email = None, password = None):
91
+ auth_data = None
92
+ page = None
93
+ try:
94
+ bm = BrowserManager()
95
+ chrome_path = bm.get_chrome_path()
96
+
97
+ co = ChromiumOptions()
98
+ co.set_browser_path(chrome_path)
99
+
100
+ target_url = 'https://xn--d1ah4a.com/login'
101
+ co.set_argument(f'--app={target_url}')
102
+
103
+ co.incognito()
104
+
105
+ width, height = 450, 700
106
+ co.set_argument(f'--window-size={width},{height}')
107
+ co.set_argument('--window-position=500,200')
108
+
109
+ page = ChromiumPage(co)
110
+
111
+ page.listen.start('auth/sign-in')
112
+ page.get(target_url)
113
+
114
+ logger.info(f"Attempting to autofill login for: {email}")
115
+
116
+ if email:
117
+ email_field = page.ele('#login-email')
118
+ email_field.input(email)
119
+
120
+ if password:
121
+ pass_field = page.ele('#login-password')
122
+ pass_field.input(password)
123
+
124
+ logger.info("Waiting for authentication response")
125
+
126
+ res = None
127
+ try:
128
+ res = page.listen.wait()
129
+ except Exception: pass
130
+ if res:
131
+ target_data = res.response.body
132
+ if isinstance(target_data, dict) and 'accessToken' in target_data:
133
+ token = target_data['accessToken']
134
+ all_raw_cookies = page.run_cdp('Network.getAllCookies')['cookies']
135
+ auth_data = {"token": token, "cookies": all_raw_cookies}
136
+ logger.info("Successfully authenticated!")
137
+ else:
138
+ logger.error("Login failed or response structure changed.")
139
+
140
+ except Exception as e:
141
+ logger.exception(f"Error during auth: {e}")
142
+ finally:
143
+ if page:
144
+ page.quit()
145
+
146
+ return auth_data
@@ -0,0 +1,75 @@
1
+ from _io import BufferedReader
2
+
3
+ from requests import Session
4
+ from typing import Optional
5
+ from pydantic import BaseModel
6
+
7
+ s = Session()
8
+
9
+
10
+ def fetch(token: str, method: str, url: str, params: dict = {}, files: dict[str, tuple[str, BufferedReader]] = {}, response_schema: Optional[BaseModel] = None):
11
+ base = f'https://xn--d1ah4a.com/api/{url}'
12
+ headers = {
13
+ "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
14
+ "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
15
+ "Accept-Encoding": "gzip, deflate, br, zstd",
16
+ "Authorization": 'Bearer ' + token,
17
+ "Sec-GPC": "1",
18
+ "Upgrade-Insecure-Requests": "1",
19
+ "Sec-Fetch-Dest": "document",
20
+ "Sec-Fetch-Mode": "navigate",
21
+ "Sec-Fetch-Site": "none",
22
+ "Sec-Fetch-User": "?1",
23
+ "Priority": "u=0, i",
24
+ "Pragma": "no-cache",
25
+ "Cache-Control": "no-cache",
26
+ "TE": "trailers"
27
+ }
28
+ method = method.lower()
29
+ if method == "get":
30
+ res = s.get(base, timeout=120 if files else 20, params=params, headers=headers)
31
+ else:
32
+ res = s.request(method.upper(), base, timeout=20, json=params, headers=headers, files=files)
33
+
34
+ res.raise_for_status()
35
+
36
+ if res and res.ok and response_schema:
37
+ return response_schema.model_validate(res.json())
38
+
39
+ return res
40
+
41
+ def set_cookies(cookies: str):
42
+ for cookie in cookies.split('; '):
43
+ s.cookies.set(cookie.split('=')[0], cookie.split('=')[-1], path='/', domain='xn--d1ah4a.com.com')
44
+
45
+ def auth_fetch(cookies: str, method: str, url: str, params: dict = {}, token: str | None = None):
46
+ headers = {
47
+ "Host": "xn--d1ah4a.com",
48
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:140.0) Gecko/20100101 Firefox/140.0",
49
+ "Accept": "*/*",
50
+ "Accept-Language": "ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3",
51
+ "Accept-Encoding": "gzip, deflate, br, zstd",
52
+ "Referer": "https://xn--d1ah4a.com/",
53
+ "Content-Type": "application/json",
54
+ "Origin": "https://xn--d1ah4a.com",
55
+ "Sec-GPC": "1",
56
+ "Connection": "keep-alive",
57
+ "Cookie": cookies,
58
+ "Sec-Fetch-Dest": "empty",
59
+ "Sec-Fetch-Mode": "cors",
60
+ "Sec-Fetch-Site": "same-origin",
61
+ "Priority": "u=4",
62
+ "Pragma": "no-cache",
63
+ "Cache-Control": "no-cache",
64
+ "Content-Length": "0",
65
+ "TE": "trailers",
66
+ }
67
+ if token:
68
+ headers['Authorization'] = 'Bearer ' + token
69
+
70
+ if method == 'get':
71
+ res = s.get(f'https://xn--d1ah4a.com/api/{url}', timeout=20, params=params, headers=headers)
72
+ else:
73
+ res = s.request(method, f'https://xn--d1ah4a.com/api/{url}', timeout=20, json=params, headers=headers)
74
+ res.raise_for_status()
75
+ return res.json()
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "itd-iter-api"
7
+ version = 'v1.0.2'
8
+ description = "ITD client for python"
9
+ readme = "README.md"
10
+ authors = [
11
+ { name = "firedotguy", email = "nta16022013@gmail.com" },
12
+ { name = "sharkow" }
13
+ ]
14
+ license = "MIT"
15
+ dependencies = [
16
+ "requests"
17
+ ]
18
+ requires-python = ">=3.9"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,13 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ version = 'v1.0.2'
4
+
5
+ setup(
6
+ name='itd-iter-api',
7
+ version=version,
8
+ packages=find_packages(),
9
+ install_requires=[
10
+ 'requests', 'DrissionPage'
11
+ ],
12
+ python_requires=">=3.9"
13
+ )