itd-sdk-js 1.0.7 → 1.0.9

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.
package/API_REFERENCE.md CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  Техническое руководство по методам и настройке библиотеки для работы с API сайта `итд.com`.
4
4
 
5
+ ## Структура SDK
6
+
7
+ | Файл | Назначение |
8
+ |------|------------|
9
+ | `client.js` | Главный клиент: создание axios, загрузка cookies, хранение токена, менеджеры, хелперы `get/post/put/patch/delete` |
10
+ | `auth.js` | Авторизация: refresh, logout, checkAuth, ensureAuthenticated, validateAndRefreshToken |
11
+ | `token-storage.js` | Сохранение токена в .env и cookies в .cookies (используется auth при refresh) |
12
+ | `posts.js` | Посты: createPost, getPosts, editPost, deletePost и др. |
13
+ | `comments.js` | Комментарии: addComment, replyToComment, getComments, likeComment и др. |
14
+ | `users.js` | Пользователи: getMyProfile, getUserProfile, followUser, getTopClans и др. |
15
+ | `notifications.js` | Уведомления |
16
+ | `hashtags.js` | Хэштеги |
17
+ | `files.js` | Загрузка файлов |
18
+ | `search.js` | Поиск |
19
+ | `reports.js` | Жалобы |
20
+
5
21
  ## Установка
6
22
 
7
23
  ### Через npm
@@ -38,7 +54,7 @@ import { ITDClient } from 'itd-sdk-js';
38
54
 
39
55
  ### Инициализация клиента
40
56
 
41
- **Простой вариант (корень проекта = текущая рабочая директория):**
57
+ **Простой вариант:**
42
58
 
43
59
  ```javascript
44
60
  import { ITDClient } from 'itd-sdk-js';
@@ -47,8 +63,14 @@ import dotenv from 'dotenv';
47
63
  dotenv.config();
48
64
 
49
65
  const client = new ITDClient();
50
- client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
51
- client.auth.isAuthenticated = true;
66
+ // Токен подхватывается из .env автоматически
67
+ ```
68
+
69
+ **Только .cookies (без ITD_ACCESS_TOKEN в .env):**
70
+
71
+ ```javascript
72
+ const client = new ITDClient();
73
+ await client.ensureAuthenticated(); // получит токен через refresh из .cookies
52
74
  ```
53
75
 
54
76
  **С опциями (projectRoot / envPath / cookiesPath):**
@@ -59,13 +81,11 @@ client.auth.isAuthenticated = true;
59
81
  const client = new ITDClient({
60
82
  baseUrl: 'https://xn--d1ah4a.com',
61
83
  userAgent: '...',
62
- projectRoot: process.cwd(), // корень проекта (по умолчанию process.cwd())
63
- // envPath: '/path/to/project/.env',
64
- // cookiesPath: '/path/to/project/.cookies',
65
- requestTimeout: 60000, // таймаут обычных запросов, мс (по умолчанию 60 с)
66
- uploadTimeout: 120000, // таймаут загрузки файлов и создания поста, мс (по умолчанию 120 с)
84
+ projectRoot: process.cwd(),
85
+ requestTimeout: 60000,
86
+ uploadTimeout: 120000,
87
+ accessToken: '...', // опционально; по умолчанию из .env ITD_ACCESS_TOKEN
67
88
  });
68
- client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
69
89
  ```
70
90
 
71
91
  - `projectRoot` — директория, в которой ищутся `.env` и `.cookies` (по умолчанию `process.cwd()`).
@@ -213,9 +233,12 @@ const post = await client.createPost('Текст поста', 'image.jpg');
213
233
  ## Методы API: Управление токенами
214
234
 
215
235
  - `hasRefreshToken()` — проверяет наличие refresh_token в cookies. Возвращает `boolean`.
236
+ - `ensureAuthenticated()` — если нет accessToken, но есть refresh_token — вызывает refresh и получает токен. Возвращает `Promise<boolean>`. Для сценария «только .cookies»: `await client.ensureAuthenticated()` перед первым запросом.
216
237
  - `validateAndRefreshToken()` — проверяет валидность токена и обновляет его при необходимости. Возвращает `Promise<boolean>`.
217
238
  - `refreshAccessToken()` — принудительно обновляет токен через refresh endpoint. Возвращает `Promise<string|null>`.
218
239
 
240
+ **Кастомные запросы:** `client.get(path)`, `client.post(path, data)`, `client.put(path, data)`, `client.patch(path, data)`, `client.delete(path)` — для произвольных эндпоинтов (baseURL уже подставлен).
241
+
219
242
  **Рекомендация:** При множественных запросах с интервалами (более 10-15 минут) вызывайте `validateAndRefreshToken()` перед каждым запросом.
220
243
 
221
244
  ---
package/README.md CHANGED
@@ -27,8 +27,8 @@ npm install
27
27
  ## Настройка
28
28
 
29
29
  1. Создайте `.env` в корне проекта на основе `.env.example` (или используйте переменные окружения).
30
- 2. Вставьте свой `ITD_ACCESS_TOKEN` (его можно вытащить из Network в DevTools).
31
- 3. Для работы авто-обновления сессии создайте файл `.cookies` в корне проекта и вставьте туда строку `Cookie` из любого запроса к сайту в браузере.
30
+ 2. Токен: добавьте `ITD_ACCESS_TOKEN` в .env или положите `.cookies` с `refresh_token` — клиент сам подхватит токен из .env или получит через refresh.
31
+ 3. Для авто-обновления токена создайте файл `.cookies` с Cookie из браузера (обязательно должен быть `refresh_token`).
32
32
 
33
33
  SDK по умолчанию читает и пишет `.env` и `.cookies` в корне проекта (`process.cwd()`). При обновлении токена изменения сохраняются в ваш проект. При необходимости можно задать `projectRoot` или явные пути в конструкторе — см. [API_REFERENCE.md](API_REFERENCE.md).
34
34
 
@@ -45,8 +45,7 @@ import dotenv from 'dotenv';
45
45
  dotenv.config();
46
46
 
47
47
  const client = new ITDClient();
48
- client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
49
- client.auth.isAuthenticated = true;
48
+ // Токен подхватывается из .env. Если только .cookies — await client.ensureAuthenticated();
50
49
 
51
50
  // Получаем профиль и тренды
52
51
  const myProfile = await client.getMyProfile();
@@ -14,10 +14,7 @@ async function main() {
14
14
  console.log('🔄 === Автоматическое обновление токена ===\n');
15
15
 
16
16
  const client = new ITDClient();
17
-
18
- // Устанавливаем токен (может быть уже истёкшим)
19
- client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
20
- client.auth.isAuthenticated = true;
17
+ // Токен подхватывается из .env автоматически
21
18
 
22
19
  console.log('💡 SDK автоматически обновит токен при истечении!');
23
20
  console.log(' Вы просто используете API - всё работает "из коробки"\n');
@@ -12,10 +12,8 @@ dotenv.config();
12
12
  async function main() {
13
13
  console.log('📝 === Базовое использование SDK ===\n');
14
14
 
15
- // Создаём клиент
15
+ // Создаём клиент (токен подхватывается из .env автоматически)
16
16
  const client = new ITDClient();
17
- client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
18
- client.auth.isAuthenticated = true;
19
17
 
20
18
  try {
21
19
  // Получаем свой профиль
@@ -37,31 +37,26 @@ async function quickStart() {
37
37
  // ============================================
38
38
  // ШАГ 2: Настройка авторизации
39
39
  // ============================================
40
- console.log('🔐 Шаг 2: Настраиваю авторизацию...\n');
40
+ console.log('🔐 Шаг 2: Проверяю авторизацию...\n');
41
41
 
42
- // Получаем токен из переменных окружения (.env файл)
43
- const accessToken = process.env.ITD_ACCESS_TOKEN;
44
-
45
- if (!accessToken) {
46
- console.log('❌ ОШИБКА: ITD_ACCESS_TOKEN не найден в .env\n');
42
+ // Токен подхватывается из .env автоматически. Если нет — пробуем refresh из .cookies
43
+ if (!client.accessToken && client.hasRefreshToken()) {
44
+ console.log(' Токена в .env нет, получаю через refresh из .cookies...\n');
45
+ const ok = await client.ensureAuthenticated();
46
+ if (!ok) {
47
+ console.log('❌ Не удалось получить токен из refresh_token\n');
48
+ return;
49
+ }
50
+ }
51
+ if (!client.accessToken) {
52
+ console.log('❌ Нет токена. Добавьте ITD_ACCESS_TOKEN в .env или refresh_token в .cookies\n');
47
53
  console.log('📋 Как получить токен:');
48
54
  console.log('1. Откройте итд.com в браузере и войдите');
49
- console.log('2. Откройте DevTools (F12) → вкладка Network');
50
- console.log('3. Найдите запрос POST /api/v1/auth/refresh');
51
- console.log('4. Откройте Response → скопируйте accessToken');
52
- console.log('5. Вставьте в .env: ITD_ACCESS_TOKEN=ваш_токен\n');
55
+ console.log('2. DevTools (F12) → Network → скопируйте Cookie в .cookies');
56
+ console.log('3. Или скопируйте accessToken из ответа /api/v1/auth/refresh в .env\n');
53
57
  return;
54
58
  }
55
-
56
- // Устанавливаем токен в клиент
57
- // Это нужно для авторизации всех запросов
58
- client.setAccessToken(accessToken);
59
-
60
- // Помечаем клиент как авторизованный
61
- // Это позволяет использовать методы, требующие авторизации
62
- client.auth.isAuthenticated = true;
63
-
64
- console.log('✅ Авторизация настроена (токен загружен из .env)\n');
59
+ console.log('✅ Авторизация настроена\n');
65
60
 
66
61
  // ============================================
67
62
  // ШАГ 3: Получение информации о себе
@@ -12,8 +12,6 @@ dotenv.config();
12
12
 
13
13
  async function publishMultiplePosts() {
14
14
  const client = new ITDClient();
15
- client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
16
- client.auth.isAuthenticated = true;
17
15
 
18
16
  console.log('📝 Публикация нескольких постов с проверкой токена\n');
19
17
 
@@ -14,8 +14,6 @@ async function main() {
14
14
  console.log('✨ === Удобные методы SDK ===\n');
15
15
 
16
16
  const client = new ITDClient();
17
- client.setAccessToken(process.env.ITD_ACCESS_TOKEN);
18
- client.auth.isAuthenticated = true;
19
17
 
20
18
  try {
21
19
  // Пример 1: Проверка подписки - одна строка вместо сложного запроса
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "itd-sdk-js",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "Unofficial SDK for итд.com - Node.js library for working with API. Automatic token refresh, session management, and convenient methods for posts, comments, users, and notifications.",
5
5
  "main": "src/client.js",
6
6
  "type": "module",
package/src/auth.js CHANGED
@@ -12,8 +12,6 @@ export class AuthManager {
12
12
  constructor(client) {
13
13
  this.client = client;
14
14
  this.axios = client.axios;
15
- this.isAuthenticated = false;
16
- this.userData = null;
17
15
  }
18
16
 
19
17
  /**
@@ -66,7 +64,6 @@ export class AuthManager {
66
64
 
67
65
  // Обновляем токен в клиенте
68
66
  this.client.setAccessToken(newToken);
69
- this.isAuthenticated = true;
70
67
 
71
68
  // Сохраняем токен в .env файл (в корне проекта)
72
69
  await saveAccessToken(newToken, this.client.envPath);
@@ -132,11 +129,12 @@ export class AuthManager {
132
129
  const response = await this.axios.post(logoutUrl);
133
130
 
134
131
  if (response.status === 200) {
135
- this.isAuthenticated = false;
136
- this.userData = null;
137
132
  this.client.setAccessToken(null);
138
- // Очистка cookies
139
- this.axios.defaults.headers.common['Cookie'] = '';
133
+ try {
134
+ this.client.cookieJar.removeAllCookiesSync();
135
+ } catch (e) {
136
+ // MemoryCookieStore поддерживает removeAllCookiesSync
137
+ }
140
138
  return true;
141
139
  }
142
140
  return false;
@@ -152,9 +150,8 @@ export class AuthManager {
152
150
  * @returns {Promise<boolean>} True если авторизован
153
151
  */
154
152
  async checkAuth() {
155
- // Проверяем наличие accessToken - если он есть, считаем что авторизован
156
- // Реальная проверка происходит на уровне API (401 ошибка)
157
- return !!(this.client.accessToken || this.isAuthenticated);
153
+ // Авторизован, если есть accessToken (реальная проверка 401 при истечении)
154
+ return !!this.client.accessToken;
158
155
  }
159
156
 
160
157
  /**
package/src/client.js CHANGED
@@ -34,9 +34,10 @@ export class ITDClient {
34
34
  * @param {string} [options.cookiesPath] - Полный путь к .cookies (переопределяет projectRoot для .cookies)
35
35
  * @param {number} [options.requestTimeout] - Таймаут обычных запросов в мс (по умолчанию 60000)
36
36
  * @param {number} [options.uploadTimeout] - Таймаут загрузки файлов и создания поста в мс (по умолчанию 120000)
37
+ * @param {string} [options.accessToken] - JWT токен (если не указан — берётся из .env ITD_ACCESS_TOKEN)
37
38
  */
38
39
  constructor(baseUrlOrOptions = null, userAgent = null) {
39
- let baseUrl, projectRoot, envPath, cookiesPath, requestTimeout, uploadTimeout;
40
+ let baseUrl, projectRoot, envPath, cookiesPath, requestTimeout, uploadTimeout, accessToken;
40
41
 
41
42
  if (baseUrlOrOptions && typeof baseUrlOrOptions === 'object' && !(baseUrlOrOptions instanceof URL)) {
42
43
  const opts = baseUrlOrOptions;
@@ -47,6 +48,7 @@ export class ITDClient {
47
48
  cookiesPath = opts.cookiesPath ?? path.join(projectRoot, '.cookies');
48
49
  requestTimeout = opts.requestTimeout ?? 60000;
49
50
  uploadTimeout = opts.uploadTimeout ?? 120000;
51
+ accessToken = opts.accessToken ?? process.env.ITD_ACCESS_TOKEN ?? null;
50
52
  } else {
51
53
  projectRoot = process.cwd();
52
54
  baseUrl = baseUrlOrOptions || process.env.ITD_BASE_URL || 'https://xn--d1ah4a.com';
@@ -54,6 +56,7 @@ export class ITDClient {
54
56
  cookiesPath = path.join(projectRoot, '.cookies');
55
57
  requestTimeout = 60000;
56
58
  uploadTimeout = 120000;
59
+ accessToken = process.env.ITD_ACCESS_TOKEN ?? null;
57
60
  }
58
61
 
59
62
  // Используем реальный домен (IDN: итд.com = xn--d1ah4a.com)
@@ -71,7 +74,7 @@ export class ITDClient {
71
74
  this.uploadTimeout = uploadTimeout;
72
75
 
73
76
  /** @type {string|null} */
74
- this.accessToken = null;
77
+ this.accessToken = accessToken || null;
75
78
 
76
79
  // Прокси (важно, если браузер ходит через 127.0.0.1:10808)
77
80
  // Можно задать: ITD_PROXY=http://127.0.0.1:10808
@@ -196,7 +199,7 @@ export class ITDClient {
196
199
  this.hashtags = new HashtagsManager(this);
197
200
  this.files = new FilesManager(this);
198
201
  this.reports = new ReportsManager(this);
199
- this.search = new SearchManager(this);
202
+ this.searchManager = new SearchManager(this);
200
203
  }
201
204
 
202
205
  /**
@@ -206,6 +209,66 @@ export class ITDClient {
206
209
  setAccessToken(token) {
207
210
  this.accessToken = token || null;
208
211
  }
212
+
213
+ /**
214
+ * Убедиться, что есть accessToken. Если нет, но есть refresh_token в cookies — вызывает refresh.
215
+ * Для сценария «только .cookies»: await client.ensureAuthenticated() перед первым запросом.
216
+ * @returns {Promise<boolean>} true если токен есть или получен, false если нет
217
+ */
218
+ async ensureAuthenticated() {
219
+ if (this.accessToken) return true;
220
+ if (!this.hasRefreshToken()) return false;
221
+ const token = await this.refreshAccessToken();
222
+ return !!token;
223
+ }
224
+
225
+ /**
226
+ * Кастомный GET запрос (baseURL уже подставлен)
227
+ * @param {string} path - Путь, например /api/users/me
228
+ * @param {Object} [config] - Доп. опции axios
229
+ */
230
+ get(path, config = {}) {
231
+ return this.axios.get(path, config);
232
+ }
233
+
234
+ /**
235
+ * Кастомный POST запрос
236
+ * @param {string} path - Путь, например /api/posts
237
+ * @param {Object} [data] - Тело запроса (JSON)
238
+ * @param {Object} [config] - Доп. опции axios
239
+ */
240
+ post(path, data = {}, config = {}) {
241
+ return this.axios.post(path, data, config);
242
+ }
243
+
244
+ /**
245
+ * Кастомный PUT запрос
246
+ * @param {string} path - Путь
247
+ * @param {Object} [data] - Тело запроса
248
+ * @param {Object} [config] - Доп. опции axios
249
+ */
250
+ put(path, data = {}, config = {}) {
251
+ return this.axios.put(path, data, config);
252
+ }
253
+
254
+ /**
255
+ * Кастомный PATCH запрос
256
+ * @param {string} path - Путь
257
+ * @param {Object} [data] - Тело запроса
258
+ * @param {Object} [config] - Доп. опции axios
259
+ */
260
+ patch(path, data = {}, config = {}) {
261
+ return this.axios.patch(path, data, config);
262
+ }
263
+
264
+ /**
265
+ * Кастомный DELETE запрос
266
+ * @param {string} path - Путь
267
+ * @param {Object} [config] - Доп. опции axios
268
+ */
269
+ delete(path, config = {}) {
270
+ return this.axios.delete(path, config);
271
+ }
209
272
 
210
273
  /**
211
274
  * Загружает cookies из файла .cookies
@@ -320,7 +383,7 @@ export class ITDClient {
320
383
  *
321
384
  * @param {string|null} username - Имя пользователя (null = лента/свои посты)
322
385
  * @param {number} limit - Количество постов
323
- * @param {string} sort - Сортировка: "new", "old", "popular"
386
+ * @param {string} sort - Сортировка: "newest", "oldest", "popular"
324
387
  * @param {string|null} cursor - Курсор для пагинации
325
388
  * @param {string|null} tab - Тип ленты: "popular" (популярные), "following" (из подписок), null (обычная лента)
326
389
  * @returns {Promise<Object>} { posts: [], pagination: {} }
@@ -780,7 +843,7 @@ export class ITDClient {
780
843
  * @returns {Promise<Object|null>} { users: [], hashtags: [] } или null при ошибке
781
844
  */
782
845
  async search(query, userLimit = 5, hashtagLimit = 5) {
783
- return await this.search.search(query, userLimit, hashtagLimit);
846
+ return await this.searchManager.search(query, userLimit, hashtagLimit);
784
847
  }
785
848
 
786
849
  /**
@@ -791,7 +854,7 @@ export class ITDClient {
791
854
  * @returns {Promise<Array|null>} Массив пользователей или null при ошибке
792
855
  */
793
856
  async searchUsers(query, limit = 5) {
794
- return await this.search.searchUsers(query, limit);
857
+ return await this.searchManager.searchUsers(query, limit);
795
858
  }
796
859
 
797
860
  /**
@@ -802,7 +865,7 @@ export class ITDClient {
802
865
  * @returns {Promise<Array|null>} Массив хэштегов или null при ошибке
803
866
  */
804
867
  async searchHashtags(query, limit = 5) {
805
- return await this.search.searchHashtags(query, limit);
868
+ return await this.searchManager.searchHashtags(query, limit);
806
869
  }
807
870
 
808
871
  // ========== USER-FRIENDLY МЕТОДЫ ==========
@@ -895,15 +958,6 @@ export class ITDClient {
895
958
 
896
959
  // === Пользователи ===
897
960
 
898
- /**
899
- * Получает свой профиль (удобный метод)
900
- *
901
- * @returns {Promise<Object|null>} Данные профиля или null
902
- */
903
- async getMyProfile() {
904
- return await this.users.getMyProfile();
905
- }
906
-
907
961
  /**
908
962
  * Проверяет, подписан ли текущий пользователь на указанного (удобный метод)
909
963
  *
@@ -941,16 +995,6 @@ export class ITDClient {
941
995
  return await this.users.getMyClan();
942
996
  }
943
997
 
944
- /**
945
- * Получает клан пользователя (эмодзи аватара) (удобный метод)
946
- *
947
- * @param {string} username - Имя пользователя
948
- * @returns {Promise<string|null>} Эмодзи клана или null
949
- */
950
- async getUserClan(username) {
951
- return await this.users.getUserClan(username);
952
- }
953
-
954
998
  // === Комментарии ===
955
999
 
956
1000
  /**
@@ -143,7 +143,8 @@ export class NotificationsManager {
143
143
  }
144
144
 
145
145
  /**
146
- * Отмечает все уведомления как прочитанные
146
+ * Отмечает все уведомления как прочитанные.
147
+ * Экспериментально: endpoint /api/notifications/read-all может отличаться.
147
148
  *
148
149
  * @returns {Promise<boolean>} True если успешно
149
150
  */
package/src/posts.js CHANGED
@@ -314,7 +314,7 @@ export class PostsManager {
314
314
  const response = await this.axios.put(editUrl, postData);
315
315
 
316
316
  if (response.status === 200) {
317
- return response.data;
317
+ return response.data?.data ?? response.data;
318
318
  } else {
319
319
  console.error(`Ошибка редактирования поста: ${response.status} - ${JSON.stringify(response.data)}`);
320
320
  return null;
@@ -424,7 +424,7 @@ export class PostsManager {
424
424
  const response = await this.axios.post(repostUrl, repostData);
425
425
 
426
426
  if (response.status === 200 || response.status === 201) {
427
- return response.data;
427
+ return response.data?.data ?? response.data;
428
428
  } else {
429
429
  console.error(`Ошибка репоста: ${response.status}`);
430
430
  if (response.data) {
package/src/users.js CHANGED
@@ -306,9 +306,8 @@ export class UsersManager {
306
306
  const response = await this.axios.get(topClansUrl);
307
307
 
308
308
  if (response.status === 200) {
309
- const data = response.data;
310
- // Структура: { clans: [...] }
311
- return data.clans || [];
309
+ const data = response.data?.data ?? response.data;
310
+ return data?.clans || [];
312
311
  } else {
313
312
  console.error(`Ошибка получения топ кланов: ${response.status}`);
314
313
  return null;
@@ -403,15 +402,4 @@ export class UsersManager {
403
402
  const profile = await this.getMyProfile();
404
403
  return profile ? (profile.avatar || null) : null;
405
404
  }
406
-
407
- /**
408
- * Получает клан пользователя (эмодзи аватара) (удобный метод)
409
- *
410
- * @param {string} username - Имя пользователя
411
- * @returns {Promise<string|null>} Эмодзи клана или null
412
- */
413
- async getUserClan(username) {
414
- const profile = await this.getUserProfile(username);
415
- return profile ? (profile.avatar || null) : null;
416
- }
417
405
  }