itdpy 0.1.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.
itdpy-0.1.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gam5510
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.
itdpy-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: itdpy
3
+ Version: 0.1.2
4
+ Summary: Python SDK for ИТД.com API
5
+ Author: Gam5510
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Gam5510/ITDpy
8
+ Project-URL: Repository, https://github.com/Gam5510/ITDpy
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: requests>=2.28.0
13
+ Dynamic: license-file
14
+
15
+ # ITDpy
16
+
17
+ Python SDK для социальной сети ITD.
18
+ Упрощает работу с SDK API и позволяет быстро писать ботов и автоматизации.
19
+
20
+
21
+ ## Установка без pip
22
+
23
+ Пока библиотека не опубликована в PyPI, можно установить её вручную.
24
+
25
+ ### Через git
26
+
27
+ ```bash
28
+ git clone https://github.com/Gam5510/ITDpy
29
+ cd itdpy
30
+ pip install -r requirements.txt
31
+ pip install -e .
32
+ ```
33
+
34
+ ## Быстрый старт
35
+
36
+ > Blockquote ![Получение токена](https://i.ibb.co/DH1m8GL7/Assistant.png)
37
+ Как получить токен
38
+
39
+ ```python
40
+ from itdpy.client import ITDClient
41
+ from itdpy.auth import AuthManager
42
+ from itdpy.api import get_me
43
+
44
+ client = ITDClient(refresh_token="Ваш refresh token")
45
+
46
+ auth = AuthManager(client)
47
+ auth.refresh_access_token()
48
+
49
+ me = get_me(client)
50
+ print(me.id)
51
+ print(me.username)
52
+ ```
53
+
54
+ ### Скрипт на обновление имени
55
+
56
+ ```python
57
+ from itdpy.client import ITDClient
58
+ from itdpy.auth import AuthManager
59
+ from itdpy.api import update_profile
60
+ from datetime import datetime
61
+ import time
62
+
63
+ client = ITDClient(refresh_token="Ваш_токен")
64
+ auth = AuthManager(client)
65
+
66
+ auth.refresh_access_token()
67
+
68
+ while True:
69
+ update_profile(client, display_name=f"Фазлиддин |{datetime.now().strftime('%m.%d %H:%M:%S')}|")
70
+ time.sleep(1)
71
+ ```
72
+
73
+ ### Скрипт на обновление баннера
74
+ ```python
75
+ from itdpy.client import ITDClient
76
+ from itdpy.auth import AuthManager
77
+ from itdpy.api import update_profile, upload_file
78
+ from datetime import datetime
79
+ import time
80
+
81
+ client = ITDClient(refresh_token="Ваш_токен")
82
+ auth = AuthManager(client)
83
+ auth.refresh_access_token()
84
+
85
+ file = upload_file(client, "matrix-rain-effect-animation-photoshop-editor.gif")
86
+ print(file.id)
87
+ update = update_profile(client, banner_id=file.id)
88
+ print(update.banner)
89
+ ```
90
+
91
+ # Костомные запросы
92
+
93
+ ## ✅ Базовый пример кастомного GET
94
+ ```python
95
+ response = client.get("/api/users/me")
96
+ data = response.json()
97
+ print(data)
98
+ ```
99
+ ### Можно добавить любой эндпоинт
100
+ ----------
101
+
102
+ ## ✅ POST с JSON
103
+ ```python
104
+ response = client.post(
105
+ "/api/posts",
106
+ json={ "content": "Привет из кастомного запроса" }
107
+ )
108
+ print(response.status_code)
109
+ print(response.json())
110
+ ```
111
+ ----------
112
+
113
+ ## ✅ PUT / PATCH
114
+ ```python
115
+ response = client.patch( "/api/profile",
116
+ json={ "displayName": "Фазлиддин 😎" }
117
+ )
118
+ ```
119
+ ----------
120
+
121
+ ## ✅ DELETE
122
+ ```python
123
+ client.delete("/api/posts/POST_ID")
124
+ ```
125
+ ----------
126
+
127
+ ## ✅ Передача query-параметров
128
+ ```python
129
+ response = client.get( "/api/posts",
130
+ params={ "limit": 50, "sort": "popular" }
131
+ )
132
+ ```
133
+
134
+ ## Планы
135
+
136
+ - Асинхронная версия библиотеки (`aioitd`)
137
+ - Улучшенная обработка и форматирование ошибок
138
+ - Логирование (через `logging`)
139
+ - Расширение объектной модели (Post, Comment, User и др.)
140
+ - Дополнительные API-эндпоинты по мере появления
141
+ - Улучшение документации и примеров
142
+
143
+
144
+ ## Прочее
145
+
146
+ Проект активно развивается.
147
+ Если у вас есть идеи или предложения — создавайте issue или pull request.
itdpy-0.1.2/README.md ADDED
@@ -0,0 +1,133 @@
1
+ # ITDpy
2
+
3
+ Python SDK для социальной сети ITD.
4
+ Упрощает работу с SDK API и позволяет быстро писать ботов и автоматизации.
5
+
6
+
7
+ ## Установка без pip
8
+
9
+ Пока библиотека не опубликована в PyPI, можно установить её вручную.
10
+
11
+ ### Через git
12
+
13
+ ```bash
14
+ git clone https://github.com/Gam5510/ITDpy
15
+ cd itdpy
16
+ pip install -r requirements.txt
17
+ pip install -e .
18
+ ```
19
+
20
+ ## Быстрый старт
21
+
22
+ > Blockquote ![Получение токена](https://i.ibb.co/DH1m8GL7/Assistant.png)
23
+ Как получить токен
24
+
25
+ ```python
26
+ from itdpy.client import ITDClient
27
+ from itdpy.auth import AuthManager
28
+ from itdpy.api import get_me
29
+
30
+ client = ITDClient(refresh_token="Ваш refresh token")
31
+
32
+ auth = AuthManager(client)
33
+ auth.refresh_access_token()
34
+
35
+ me = get_me(client)
36
+ print(me.id)
37
+ print(me.username)
38
+ ```
39
+
40
+ ### Скрипт на обновление имени
41
+
42
+ ```python
43
+ from itdpy.client import ITDClient
44
+ from itdpy.auth import AuthManager
45
+ from itdpy.api import update_profile
46
+ from datetime import datetime
47
+ import time
48
+
49
+ client = ITDClient(refresh_token="Ваш_токен")
50
+ auth = AuthManager(client)
51
+
52
+ auth.refresh_access_token()
53
+
54
+ while True:
55
+ update_profile(client, display_name=f"Фазлиддин |{datetime.now().strftime('%m.%d %H:%M:%S')}|")
56
+ time.sleep(1)
57
+ ```
58
+
59
+ ### Скрипт на обновление баннера
60
+ ```python
61
+ from itdpy.client import ITDClient
62
+ from itdpy.auth import AuthManager
63
+ from itdpy.api import update_profile, upload_file
64
+ from datetime import datetime
65
+ import time
66
+
67
+ client = ITDClient(refresh_token="Ваш_токен")
68
+ auth = AuthManager(client)
69
+ auth.refresh_access_token()
70
+
71
+ file = upload_file(client, "matrix-rain-effect-animation-photoshop-editor.gif")
72
+ print(file.id)
73
+ update = update_profile(client, banner_id=file.id)
74
+ print(update.banner)
75
+ ```
76
+
77
+ # Костомные запросы
78
+
79
+ ## ✅ Базовый пример кастомного GET
80
+ ```python
81
+ response = client.get("/api/users/me")
82
+ data = response.json()
83
+ print(data)
84
+ ```
85
+ ### Можно добавить любой эндпоинт
86
+ ----------
87
+
88
+ ## ✅ POST с JSON
89
+ ```python
90
+ response = client.post(
91
+ "/api/posts",
92
+ json={ "content": "Привет из кастомного запроса" }
93
+ )
94
+ print(response.status_code)
95
+ print(response.json())
96
+ ```
97
+ ----------
98
+
99
+ ## ✅ PUT / PATCH
100
+ ```python
101
+ response = client.patch( "/api/profile",
102
+ json={ "displayName": "Фазлиддин 😎" }
103
+ )
104
+ ```
105
+ ----------
106
+
107
+ ## ✅ DELETE
108
+ ```python
109
+ client.delete("/api/posts/POST_ID")
110
+ ```
111
+ ----------
112
+
113
+ ## ✅ Передача query-параметров
114
+ ```python
115
+ response = client.get( "/api/posts",
116
+ params={ "limit": 50, "sort": "popular" }
117
+ )
118
+ ```
119
+
120
+ ## Планы
121
+
122
+ - Асинхронная версия библиотеки (`aioitd`)
123
+ - Улучшенная обработка и форматирование ошибок
124
+ - Логирование (через `logging`)
125
+ - Расширение объектной модели (Post, Comment, User и др.)
126
+ - Дополнительные API-эндпоинты по мере появления
127
+ - Улучшение документации и примеров
128
+
129
+
130
+ ## Прочее
131
+
132
+ Проект активно развивается.
133
+ Если у вас есть идеи или предложения — создавайте issue или pull request.
@@ -0,0 +1,47 @@
1
+ # posts
2
+ from .posts import (
3
+ get_post,
4
+ get_posts,
5
+ create_post,
6
+ update_post,
7
+ delete_post,
8
+ like_post,
9
+ unlike_post,
10
+ repost_post,
11
+ get_user_posts,
12
+ )
13
+
14
+ # comments
15
+ from .comments import (
16
+ create_comment,
17
+ reply_to_comment,
18
+ like_comment,
19
+ unlike_comment,
20
+ delete_comment,
21
+ )
22
+
23
+ # users
24
+ from .users import (
25
+ get_me,
26
+ get_user,
27
+ get_followers,
28
+ get_following,
29
+ follow_user,
30
+ unfollow_user,
31
+ )
32
+
33
+ # notifications
34
+ from .notifications import (
35
+ get_notifications,
36
+ mark_notification_read,
37
+ mark_all_notification_read,
38
+ )
39
+
40
+ # clans
41
+ from .clans import get_top_clans
42
+
43
+ # files
44
+ from .files import upload_file
45
+
46
+ # profile
47
+ from .profile import update_profile
@@ -0,0 +1,3 @@
1
+ def get_top_clans(client):
2
+ r = client.get("/api/users/stats/top-clans")
3
+ return r.json()
@@ -0,0 +1,64 @@
1
+ from ..models import Comment
2
+
3
+ def create_comment(client, post_id: str, content: str, attachment_ids: list[str] | str | None = None):
4
+ if attachment_ids is None:
5
+ attachment_ids = []
6
+ elif isinstance(attachment_ids, str):
7
+ attachment_ids = [attachment_ids]
8
+
9
+ payload = {
10
+ "content": content,
11
+ "attachmentIds": attachment_ids
12
+ }
13
+
14
+ r = client.post(
15
+ f"/api/posts/{post_id}/comments",
16
+ json=payload
17
+ )
18
+
19
+ r.raise_for_status()
20
+ return Comment(r.json())
21
+
22
+ def reply_to_comment(client, comment_id: str, content: str, attachment_ids: list[str] | str | None = None):
23
+
24
+ if attachment_ids is None:
25
+ attachment_ids = []
26
+ elif isinstance(attachment_ids, str):
27
+ attachment_ids = [attachment_ids]
28
+
29
+ payload = {
30
+ "content": content,
31
+ "attachmentIds": attachment_ids
32
+ }
33
+
34
+ r = client.post(
35
+ f"/api/comments/{comment_id}/replies",
36
+ json=payload
37
+ )
38
+
39
+ r.raise_for_status()
40
+ return Comment(r.json())
41
+
42
+ def delete_comment(client, comment_id: str) -> bool:
43
+ r = client.delete(f"/api/comments/{comment_id}")
44
+
45
+ if r.status_code == 204:
46
+ return True
47
+
48
+ r.raise_for_status()
49
+ return False
50
+
51
+ def like_comment(client, comment_id: str):
52
+ r = client.post(f"/api/comments/{comment_id}/like")
53
+ r.raise_for_status()
54
+ if r.status_code == 200:
55
+ return True
56
+ return False
57
+
58
+
59
+ def unlike_comment(client, comment_id: str):
60
+ r = client.delete(f"/api/comments/{comment_id}/like")
61
+ r.raise_for_status()
62
+ if r.status_code == 200:
63
+ return True
64
+ return False
@@ -0,0 +1,11 @@
1
+ from ..models import Attachment
2
+
3
+ def upload_file(client, file_path: str):
4
+ with open(file_path, "rb") as f:
5
+ r = client.post(
6
+ "/api/files/upload",
7
+ files={"file": f}
8
+ )
9
+
10
+ r.raise_for_status()
11
+ return Attachment(r.json())
@@ -0,0 +1,39 @@
1
+ from ..models.notifications import Notifications
2
+
3
+
4
+ def get_notifications(client, offset: int = 0, limit: int = 20):
5
+ r = client.get(
6
+ f"/api/notifications/?offset={offset}&limit={limit}"
7
+ )
8
+ r.raise_for_status()
9
+
10
+ return Notifications(r.json())
11
+
12
+
13
+ def mark_notification_read(client, notification_id: str) -> bool:
14
+ r = client.post(
15
+ f"/api/notifications/{notification_id}/read"
16
+ )
17
+ r.raise_for_status()
18
+
19
+ data = r.json()
20
+ return data.get("success", False)
21
+
22
+ def mark_all_notification_read(client, notification_ids: list[str]) -> int:
23
+
24
+ if not notification_ids:
25
+ return 0
26
+
27
+ if notification_ids is None:
28
+ notification_ids = []
29
+ elif isinstance(notification_ids, str):
30
+ notification_ids = [notification_ids]
31
+
32
+ r = client.post(
33
+ "/api/notifications/read-batch",
34
+ json=notification_ids
35
+ )
36
+ r.raise_for_status()
37
+
38
+ data = r.json()
39
+ return data.get("count", 0)
@@ -0,0 +1,83 @@
1
+ from ..models import Posts, Post
2
+
3
+ def get_posts(client, limit: int = 20, tab: str = "popular"):
4
+ r = client.get(f"/api/posts?limit={limit}&tab={tab}")
5
+ r.raise_for_status()
6
+
7
+ return Posts(r.json())
8
+
9
+ def get_post(client, post_id: str):
10
+ r = client.get(f"/api/posts/{post_id}")
11
+ return Post(r.json())
12
+
13
+ def create_post(client,content: str = "",attachment_ids: list[str] | str | None = None,wall_recipient_id: str | None = None):
14
+
15
+ if attachment_ids is None:
16
+ attachment_ids = []
17
+ elif isinstance(attachment_ids, str):
18
+ attachment_ids = [attachment_ids]
19
+
20
+ payload = {
21
+ "content": content,
22
+ "attachmentIds": attachment_ids
23
+ }
24
+
25
+ if wall_recipient_id is not None:
26
+ payload["wallRecipientId"] = wall_recipient_id
27
+
28
+ r = client.post(
29
+ "/api/posts",
30
+ json=payload
31
+ )
32
+
33
+ r.raise_for_status()
34
+ return Post(r.json())
35
+
36
+ def update_post(client, post_id: str, content: str):
37
+ payload = {
38
+ "content": content
39
+ }
40
+
41
+ r = client.put(
42
+ f"/api/posts/{post_id}",
43
+ json=payload
44
+ )
45
+
46
+ r.raise_for_status()
47
+ return r.json()
48
+
49
+ def delete_post(client, post_id: str) -> bool:
50
+ r = client.delete(f"/api/posts/{post_id}")
51
+
52
+ if r.status_code == 204:
53
+ return True
54
+
55
+ r.raise_for_status()
56
+ return False
57
+
58
+ def like_post(client, post_id: str):
59
+ r = client.post(f"/api/posts/{post_id}/like")
60
+ r.raise_for_status()
61
+ if r.status_code == 200:
62
+ return True
63
+ return False
64
+
65
+ def unlike_post(client, post_id: str):
66
+ r = client.delete(f"/api/posts/{post_id}/like")
67
+ r.raise_for_status()
68
+ if r.status_code == 200:
69
+ return True
70
+ return False
71
+
72
+ def repost_post(client, post_id: str):
73
+ r = client.post(f"/api/posts/{post_id}/repost")
74
+ r.raise_for_status()
75
+ return True
76
+
77
+ def get_user_posts(client, username: str, limit: int = 20, sort: str = "new"):# new | popular
78
+ r = client.get(
79
+ f"/api/posts/user/{username}?limit={limit}&sort={sort}"
80
+ )
81
+ r.raise_for_status()
82
+
83
+ return Posts(r.json())
@@ -0,0 +1,26 @@
1
+ from ..models.me import Me
2
+
3
+
4
+ def update_profile(client, *, display_name: str | None = None, username: str | None = None, bio: str | None = None, banner_id: str | None = None) -> Me:
5
+ payload = {}
6
+
7
+ if display_name is not None:
8
+ payload["displayName"] = display_name
9
+
10
+ if username is not None:
11
+ payload["username"] = username
12
+
13
+ if bio is not None:
14
+ payload["bio"] = bio
15
+
16
+ if banner_id is not None:
17
+ payload["bannerId"] = banner_id
18
+
19
+ if not payload:
20
+ raise ValueError("No profile fields provided to update")
21
+
22
+ r = client.put("/api/users/me", json=payload)
23
+ r.raise_for_status()
24
+
25
+ return Me(r.json())
26
+
@@ -0,0 +1,33 @@
1
+ from ..models import User, Me, Users
2
+
3
+ def get_me(client):
4
+ r = client.get("/api/users/me")
5
+ return Me(r.json())
6
+
7
+ def get_user(client, username):
8
+ r = client.get(f"/api/users/{username}")
9
+ return User(r.json())
10
+
11
+ def follow_user(client, username: str):
12
+ r = client.post(f"/api/users/{username}/follow")
13
+ r.raise_for_status()
14
+ return True
15
+
16
+ def unfollow_user(client, username: str):
17
+ r = client.delete(f"/api/users/{username}/follow")
18
+ r.raise_for_status()
19
+ return True
20
+
21
+ def get_followers(client, username: str, page: int = 1, limit: int = 30):
22
+ r = client.get(
23
+ f"/api/users/{username}/followers?page={page}&limit={limit}"
24
+ )
25
+ r.raise_for_status()
26
+ return Users(r.json())
27
+
28
+ def get_following(client, username: str, page: int = 1, limit: int = 30):
29
+ r = client.get(
30
+ f"/api/users/{username}/following?page={page}&limit={limit}"
31
+ )
32
+ r.raise_for_status()
33
+ return Users(r.json())
@@ -0,0 +1,41 @@
1
+ from .client import ITDClient
2
+
3
+
4
+ class AuthManager:
5
+ def __init__(self, client: ITDClient):
6
+ self.client = client
7
+ self.client._bind_auth_manager(self)
8
+
9
+ def _has_refresh_token(self) -> bool:
10
+ return any(
11
+ c.name == "refresh_token"
12
+ for c in self.client.session.cookies
13
+ )
14
+
15
+ def refresh_access_token(self) -> str | None:
16
+
17
+ if not self._has_refresh_token():
18
+ raise RuntimeError("refresh_token not found in cookies")
19
+
20
+ r = self.client.post("/api/v1/auth/refresh")
21
+ if r.status_code != 200:
22
+ return None
23
+
24
+ token = r.json().get("accessToken")
25
+ if not token:
26
+ return None
27
+
28
+ self.client._set_access_token(token)
29
+
30
+ self._bootstrap_identity()
31
+
32
+ return token
33
+
34
+ def _bootstrap_identity(self):
35
+ r = self.client.get("/api/users/me")
36
+ if r.status_code != 200:
37
+ return
38
+
39
+ user_id = r.json().get("id")
40
+ if user_id:
41
+ self.client._set_user_id(user_id)
@@ -0,0 +1,105 @@
1
+ import requests
2
+
3
+
4
+ class ITDClient:
5
+
6
+ _DEFAULT_TIMEOUT = 15
7
+ _UPLOAD_TIMEOUT = 3600
8
+ _SDK_NAME = "itd-sdk-python"
9
+ _SDK_VERSION = "0.1"
10
+ _PLATFORM = "python"
11
+
12
+
13
+ def __init__(self, refresh_token: str):
14
+ self.base_url = "https://xn--d1ah4a.com"
15
+ self.session = requests.Session()
16
+
17
+ self._access_token = None
18
+ self._user_id = None
19
+ self._auth_manager = None
20
+
21
+ self.session.headers.update({
22
+ "Origin": self.base_url,
23
+ "Referer": self.base_url + "/"
24
+ })
25
+
26
+ self.session.cookies.set(
27
+ name="refresh_token",
28
+ value=refresh_token,
29
+ domain="xn--d1ah4a.com",
30
+ path="/api"
31
+ )
32
+
33
+ self._apply_user_agent(initial=True)
34
+
35
+ def _bind_auth_manager(self, auth_manager):
36
+ self._auth_manager = auth_manager
37
+
38
+ def _set_access_token(self, token: str):
39
+ self._access_token = token
40
+ self.session.headers["Authorization"] = f"Bearer {token}"
41
+
42
+ def _set_user_id(self, user_id: str):
43
+ self._user_id = user_id
44
+ self._apply_user_agent()
45
+
46
+ def _build_user_agent(self, initial: bool = False) -> str:
47
+ if initial or not self._user_id:
48
+ return (
49
+ f"{self._SDK_NAME}/{self._SDK_VERSION} "
50
+ f"(initial; platform={self._PLATFORM})"
51
+ )
52
+
53
+ return (
54
+ f"{self._SDK_NAME}/{self._SDK_VERSION} "
55
+ f"(userid={self._user_id}; platform={self._PLATFORM})"
56
+ )
57
+
58
+ def _apply_user_agent(self, initial: bool = False):
59
+ # всегда перезаписываем — анти-подмена
60
+ self.session.headers["User-Agent"] = self._build_user_agent(initial)
61
+
62
+ def _request(self, method: str, path: str, retry: bool = True, **kwargs):
63
+ self._apply_user_agent()
64
+
65
+ if not path.startswith("/"):
66
+ path = "/" + path
67
+
68
+ url = self.base_url + path
69
+
70
+ timeout = kwargs.pop("timeout", None)
71
+ if timeout is None:
72
+ if path.startswith("/api/files"):
73
+ timeout = self._UPLOAD_TIMEOUT
74
+ else:
75
+ timeout = self._DEFAULT_TIMEOUT
76
+
77
+ response = self.session.request(
78
+ method,
79
+ url,
80
+ timeout=timeout,
81
+ **kwargs
82
+ )
83
+
84
+ if response.status_code == 401 and retry and self._auth_manager:
85
+ refreshed = self._auth_manager.refresh_access_token()
86
+ if refreshed:
87
+ return self._request(method, path, retry=False, **kwargs)
88
+
89
+ return response
90
+
91
+
92
+ def get(self, path: str, **kwargs):
93
+ return self._request("GET", path, **kwargs)
94
+
95
+ def post(self, path: str, **kwargs):
96
+ return self._request("POST", path, **kwargs)
97
+
98
+ def put(self, path: str, **kwargs):
99
+ return self._request("PUT", path, **kwargs)
100
+
101
+ def patch(self, path: str, **kwargs):
102
+ return self._request("PATCH", path, **kwargs)
103
+
104
+ def delete(self, path: str, **kwargs):
105
+ return self._request("DELETE", path, **kwargs)
@@ -0,0 +1,8 @@
1
+ from .user import User
2
+ from .me import Me
3
+ from .user_lite import UserLite
4
+ from .post import Post
5
+ from .comment import Comment
6
+ from .attachment import Attachment
7
+ from .posts import Posts
8
+ from .users import Users
@@ -0,0 +1,9 @@
1
+ class Actor:
2
+ def __init__(self, data: dict):
3
+ self.id = data.get("id")
4
+ self.username = data.get("username")
5
+ self.displayName = data.get("displayName")
6
+ self.avatar = data.get("avatar")
7
+
8
+ def __repr__(self):
9
+ return f"<Actor @{self.username}>"
@@ -0,0 +1,12 @@
1
+ import json
2
+
3
+ class Attachment:
4
+ def __init__(self, data: dict):
5
+ self._data = data
6
+ self.id = data.get("id")
7
+ self.url = data.get("url")
8
+ self.filename = data.get("filename")
9
+ self.mimeType = data.get("mimeType")
10
+ self.size = data.get("size")
11
+ def __str__(self):
12
+ return json.dumps(self._data, ensure_ascii=False)
@@ -0,0 +1,6 @@
1
+ class BaseModel:
2
+ def __init__(self, data: dict):
3
+ self._data = data
4
+
5
+ def to_dict(self):
6
+ return self._data
@@ -0,0 +1,26 @@
1
+ from .user_lite import UserLite
2
+ from .attachment import Attachment
3
+ import json
4
+
5
+ class Comment:
6
+ def __init__(self, data: dict):
7
+ self.id = data.get("id")
8
+ self.content = data.get("content")
9
+ self.likesCount = data.get("likesCount")
10
+ self.repliesCount = data.get("repliesCount")
11
+ self.isLiked = data.get("isLiked")
12
+ self.createdAt = data.get("createdAt")
13
+
14
+ self.author = UserLite(data.get("author"))
15
+ self.attachments = [
16
+ Attachment(a) for a in data.get("attachments", [])
17
+ ]
18
+
19
+ self.replies = [
20
+ Comment(r) for r in data.get("replies", [])
21
+ ]
22
+
23
+ self.data = data
24
+
25
+ def __str__(self):
26
+ return json.dumps(self.data, ensure_ascii=False)
@@ -0,0 +1,12 @@
1
+ from .user import User
2
+ import json
3
+
4
+ class Me(User):
5
+ def __init__(self, data: dict):
6
+ super().__init__(data)
7
+ self.data = data
8
+
9
+ self.isPrivate = data.get("isPrivate")
10
+
11
+ def __str__(self):
12
+ return json.dumps(self.data, ensure_ascii=False)
@@ -0,0 +1,19 @@
1
+ from .actor import Actor
2
+
3
+ class Notification:
4
+ def __init__(self, data: dict):
5
+ self.id = data.get("id")
6
+ self.type = data.get("type") # comment | like | follow | reply
7
+ self.target_type = data.get("targetType") # post | None
8
+ self.target_id = data.get("targetId")
9
+ self.preview = data.get("preview")
10
+
11
+ self.read = data.get("read")
12
+ self.read_at = data.get("readAt")
13
+ self.created_at = data.get("createdAt")
14
+
15
+ actor_data = data.get("actor")
16
+ self.actor = Actor(actor_data) if actor_data else None
17
+
18
+ def __repr__(self):
19
+ return f"<Notification {self.type} from @{self.actor.username if self.actor else '?'}>"
@@ -0,0 +1,21 @@
1
+ from .notification import Notification
2
+
3
+
4
+ class Notifications:
5
+ def __init__(self, response: dict):
6
+ items = response.get("notifications", [])
7
+
8
+ self._items = [Notification(n) for n in items]
9
+ self.has_more = response.get("hasMore", False)
10
+
11
+ def __getitem__(self, index):
12
+ return self._items[index]
13
+
14
+ def __len__(self):
15
+ return len(self._items)
16
+
17
+ def __iter__(self):
18
+ return iter(self._items)
19
+
20
+ def __repr__(self):
21
+ return f"<Notifications count={len(self)}>"
@@ -0,0 +1,47 @@
1
+ from .user_lite import UserLite
2
+ from .attachment import Attachment
3
+ from .comment import Comment
4
+ import json
5
+
6
+ class Post:
7
+ def __init__(self, data: dict):
8
+
9
+ if "data" in data:
10
+ data = data["data"]
11
+
12
+ self._data = data
13
+
14
+ self.id = data.get("id")
15
+ self.content = data.get("content")
16
+ self.likesCount = data.get("likesCount")
17
+ self.commentsCount = data.get("commentsCount")
18
+ self.repostsCount = data.get("repostsCount")
19
+ self.viewsCount = data.get("viewsCount")
20
+ self.isLiked = data.get("isLiked")
21
+ self.isReposted = data.get("isReposted")
22
+ self.isViewed = data.get("isViewed")
23
+ self.isOwner = data.get("isOwner")
24
+ self.createdAt = data.get("createdAt")
25
+
26
+ self.author = UserLite(data.get("author"))
27
+
28
+ self.attachments = [
29
+ Attachment(a) for a in data.get("attachments", [])
30
+ ]
31
+
32
+ self.comments = [
33
+ Comment(c) for c in data.get("comments", [])
34
+ ]
35
+
36
+ # wall
37
+ self.wallRecipientId = data.get("wallRecipientId")
38
+ self.wallRecipient = (
39
+ UserLite(data["wallRecipient"])
40
+ if data.get("wallRecipient") else None
41
+ )
42
+
43
+ def __repr__(self):
44
+ return f"<Post {self.id}>"
45
+
46
+ def __str__(self):
47
+ return json.dumps(self._data, ensure_ascii=False)
@@ -0,0 +1,40 @@
1
+ from .post import Post
2
+ import json
3
+
4
+ class Posts:
5
+ def __init__(self, response: dict):
6
+ data = response.get("data")
7
+ self._data = data
8
+
9
+ if isinstance(data, list):
10
+ items = data
11
+ pagination = {}
12
+
13
+ elif isinstance(data, dict):
14
+ items = data.get("posts", [])
15
+ pagination = data.get("pagination", {})
16
+
17
+ else:
18
+ items = []
19
+ pagination = {}
20
+
21
+ self._items = [Post(item) for item in items]
22
+
23
+ self.limit = pagination.get("limit")
24
+ self.next_cursor = pagination.get("nextCursor")
25
+ self.has_more = pagination.get("hasMore")
26
+
27
+ def __getitem__(self, index):
28
+ return self._items[index]
29
+
30
+ def __len__(self):
31
+ return len(self._items)
32
+
33
+ def __iter__(self):
34
+ return iter(self._items)
35
+
36
+ def __repr__(self):
37
+ return f"<Posts count={len(self)}>"
38
+
39
+ def __str__(self):
40
+ return json.dumps(self._data, ensure_ascii=False)
@@ -0,0 +1,23 @@
1
+ import json
2
+ from .user_lite import UserLite
3
+
4
+
5
+ class User(UserLite):
6
+ def __init__(self, data: dict):
7
+ super().__init__(data)
8
+
9
+ self.banner = data.get("banner")
10
+ self.bio = data.get("bio")
11
+ self.pinnedPostId = data.get("pinnedPostId")
12
+ self.wallClosed = data.get("wallClosed")
13
+ self.followersCount = data.get("followersCount")
14
+ self.followingCount = data.get("followingCount")
15
+ self.postsCount = data.get("postsCount")
16
+ self.isFollowing = data.get("isFollowing")
17
+ self.isFollowedBy = data.get("isFollowedBy")
18
+ self.createdAt = data.get("createdAt")
19
+
20
+ self._data = data
21
+
22
+ def __str__(self):
23
+ return json.dumps(self._data, ensure_ascii=False)
@@ -0,0 +1,18 @@
1
+ import json
2
+
3
+ class UserLite:
4
+ def __init__(self, data: dict):
5
+ self._data = data or {}
6
+
7
+ self.id = data.get("id")
8
+ self.username = data.get("username")
9
+ self.displayName = data.get("displayName")
10
+ self.avatar = data.get("avatar")
11
+ self.verified = data.get("verified")
12
+ self.isFollowing = data.get("isFollowing")
13
+
14
+ def __repr__(self):
15
+ return f"<UserLite @{self.username}>"
16
+
17
+ def __str__(self):
18
+ return json.dumps(self._data, ensure_ascii=False)
@@ -0,0 +1,26 @@
1
+ from .user_lite import UserLite
2
+
3
+
4
+ class Users:
5
+ def __init__(self, response: dict):
6
+ data = response.get("data", {})
7
+
8
+ self._items = [UserLite(u) for u in data.get("users", [])]
9
+
10
+ pagination = data.get("pagination", {})
11
+ self.page = pagination.get("page")
12
+ self.limit = pagination.get("limit")
13
+ self.total = pagination.get("total")
14
+ self.has_more = pagination.get("hasMore")
15
+
16
+ def __getitem__(self, index):
17
+ return self._items[index]
18
+
19
+ def __len__(self):
20
+ return len(self._items)
21
+
22
+ def __iter__(self):
23
+ return iter(self._items)
24
+
25
+ def __repr__(self):
26
+ return f"<Users count={len(self)}>"
@@ -0,0 +1,15 @@
1
+ from .auth import AuthManager
2
+
3
+ def validate_and_refresh(auth: AuthManager) -> bool:
4
+ if not auth.client.access_token:
5
+ return False
6
+
7
+ r = auth.client.get("/api/users/me")
8
+
9
+ if r.status_code == 200:
10
+ return True
11
+
12
+ if r.status_code == 401:
13
+ return bool(auth.refresh_access_token())
14
+
15
+ return False
@@ -0,0 +1,147 @@
1
+ Metadata-Version: 2.4
2
+ Name: itdpy
3
+ Version: 0.1.2
4
+ Summary: Python SDK for ИТД.com API
5
+ Author: Gam5510
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Gam5510/ITDpy
8
+ Project-URL: Repository, https://github.com/Gam5510/ITDpy
9
+ Requires-Python: >=3.9
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: requests>=2.28.0
13
+ Dynamic: license-file
14
+
15
+ # ITDpy
16
+
17
+ Python SDK для социальной сети ITD.
18
+ Упрощает работу с SDK API и позволяет быстро писать ботов и автоматизации.
19
+
20
+
21
+ ## Установка без pip
22
+
23
+ Пока библиотека не опубликована в PyPI, можно установить её вручную.
24
+
25
+ ### Через git
26
+
27
+ ```bash
28
+ git clone https://github.com/Gam5510/ITDpy
29
+ cd itdpy
30
+ pip install -r requirements.txt
31
+ pip install -e .
32
+ ```
33
+
34
+ ## Быстрый старт
35
+
36
+ > Blockquote ![Получение токена](https://i.ibb.co/DH1m8GL7/Assistant.png)
37
+ Как получить токен
38
+
39
+ ```python
40
+ from itdpy.client import ITDClient
41
+ from itdpy.auth import AuthManager
42
+ from itdpy.api import get_me
43
+
44
+ client = ITDClient(refresh_token="Ваш refresh token")
45
+
46
+ auth = AuthManager(client)
47
+ auth.refresh_access_token()
48
+
49
+ me = get_me(client)
50
+ print(me.id)
51
+ print(me.username)
52
+ ```
53
+
54
+ ### Скрипт на обновление имени
55
+
56
+ ```python
57
+ from itdpy.client import ITDClient
58
+ from itdpy.auth import AuthManager
59
+ from itdpy.api import update_profile
60
+ from datetime import datetime
61
+ import time
62
+
63
+ client = ITDClient(refresh_token="Ваш_токен")
64
+ auth = AuthManager(client)
65
+
66
+ auth.refresh_access_token()
67
+
68
+ while True:
69
+ update_profile(client, display_name=f"Фазлиддин |{datetime.now().strftime('%m.%d %H:%M:%S')}|")
70
+ time.sleep(1)
71
+ ```
72
+
73
+ ### Скрипт на обновление баннера
74
+ ```python
75
+ from itdpy.client import ITDClient
76
+ from itdpy.auth import AuthManager
77
+ from itdpy.api import update_profile, upload_file
78
+ from datetime import datetime
79
+ import time
80
+
81
+ client = ITDClient(refresh_token="Ваш_токен")
82
+ auth = AuthManager(client)
83
+ auth.refresh_access_token()
84
+
85
+ file = upload_file(client, "matrix-rain-effect-animation-photoshop-editor.gif")
86
+ print(file.id)
87
+ update = update_profile(client, banner_id=file.id)
88
+ print(update.banner)
89
+ ```
90
+
91
+ # Костомные запросы
92
+
93
+ ## ✅ Базовый пример кастомного GET
94
+ ```python
95
+ response = client.get("/api/users/me")
96
+ data = response.json()
97
+ print(data)
98
+ ```
99
+ ### Можно добавить любой эндпоинт
100
+ ----------
101
+
102
+ ## ✅ POST с JSON
103
+ ```python
104
+ response = client.post(
105
+ "/api/posts",
106
+ json={ "content": "Привет из кастомного запроса" }
107
+ )
108
+ print(response.status_code)
109
+ print(response.json())
110
+ ```
111
+ ----------
112
+
113
+ ## ✅ PUT / PATCH
114
+ ```python
115
+ response = client.patch( "/api/profile",
116
+ json={ "displayName": "Фазлиддин 😎" }
117
+ )
118
+ ```
119
+ ----------
120
+
121
+ ## ✅ DELETE
122
+ ```python
123
+ client.delete("/api/posts/POST_ID")
124
+ ```
125
+ ----------
126
+
127
+ ## ✅ Передача query-параметров
128
+ ```python
129
+ response = client.get( "/api/posts",
130
+ params={ "limit": 50, "sort": "popular" }
131
+ )
132
+ ```
133
+
134
+ ## Планы
135
+
136
+ - Асинхронная версия библиотеки (`aioitd`)
137
+ - Улучшенная обработка и форматирование ошибок
138
+ - Логирование (через `logging`)
139
+ - Расширение объектной модели (Post, Comment, User и др.)
140
+ - Дополнительные API-эндпоинты по мере появления
141
+ - Улучшение документации и примеров
142
+
143
+
144
+ ## Прочее
145
+
146
+ Проект активно развивается.
147
+ Если у вас есть идеи или предложения — создавайте issue или pull request.
@@ -0,0 +1,32 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ itdpy/auth.py
5
+ itdpy/client.py
6
+ itdpy/validate.py
7
+ itdpy.egg-info/PKG-INFO
8
+ itdpy.egg-info/SOURCES.txt
9
+ itdpy.egg-info/dependency_links.txt
10
+ itdpy.egg-info/requires.txt
11
+ itdpy.egg-info/top_level.txt
12
+ itdpy/api/__init__.py
13
+ itdpy/api/clans.py
14
+ itdpy/api/comments.py
15
+ itdpy/api/files.py
16
+ itdpy/api/notifications.py
17
+ itdpy/api/posts.py
18
+ itdpy/api/profile.py
19
+ itdpy/api/users.py
20
+ itdpy/models/__init__.py
21
+ itdpy/models/actor.py
22
+ itdpy/models/attachment.py
23
+ itdpy/models/base.py
24
+ itdpy/models/comment.py
25
+ itdpy/models/me.py
26
+ itdpy/models/notification.py
27
+ itdpy/models/notifications.py
28
+ itdpy/models/post.py
29
+ itdpy/models/posts.py
30
+ itdpy/models/user.py
31
+ itdpy/models/user_lite.py
32
+ itdpy/models/users.py
@@ -0,0 +1 @@
1
+ requests>=2.28.0
@@ -0,0 +1 @@
1
+ itdpy
@@ -0,0 +1,22 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "itdpy"
7
+ version = "0.1.2"
8
+ description = "Python SDK for ИТД.com API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [
13
+ { name = "Gam5510" }
14
+ ]
15
+
16
+ dependencies = [
17
+ "requests>=2.28.0"
18
+ ]
19
+
20
+ [project.urls]
21
+ Homepage = "https://github.com/Gam5510/ITDpy"
22
+ Repository = "https://github.com/Gam5510/ITDpy"
itdpy-0.1.2/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+