itd-sdk-js 1.0.0

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,66 @@
1
+ /**
2
+ * Утилита для сохранения токена в .env файл
3
+ */
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+ import { fileURLToPath } from 'url';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ /**
12
+ * Обновляет ITD_ACCESS_TOKEN в .env файле
13
+ *
14
+ * @param {string} newToken - Новый access token
15
+ * @returns {Promise<boolean>} True если успешно
16
+ */
17
+ export async function saveAccessToken(newToken) {
18
+ try {
19
+ const envPath = path.join(__dirname, '..', '.env');
20
+
21
+ if (!fs.existsSync(envPath)) {
22
+ console.warn('⚠️ Файл .env не найден, токен не сохранен');
23
+ return false;
24
+ }
25
+
26
+ let content = fs.readFileSync(envPath, 'utf8');
27
+
28
+ // Ищем строку с ITD_ACCESS_TOKEN
29
+ const tokenRegex = /^ITD_ACCESS_TOKEN=.*$/m;
30
+
31
+ if (tokenRegex.test(content)) {
32
+ // Заменяем существующий токен
33
+ content = content.replace(tokenRegex, `ITD_ACCESS_TOKEN=${newToken}`);
34
+ } else {
35
+ // Добавляем новую строку, если токена нет
36
+ content += `\nITD_ACCESS_TOKEN=${newToken}\n`;
37
+ }
38
+
39
+ fs.writeFileSync(envPath, content, 'utf8');
40
+ console.log('✅ Токен сохранен в .env');
41
+ return true;
42
+ } catch (error) {
43
+ console.error('❌ Ошибка сохранения токена в .env:', error.message);
44
+ return false;
45
+ }
46
+ }
47
+
48
+ /**
49
+ * Сохраняет cookies в файл .cookies
50
+ *
51
+ * @param {string} newCookieHeader - Новый cookie header
52
+ * @returns {Promise<boolean>} True если успешно
53
+ */
54
+ export async function saveCookieHeader(newCookieHeader) {
55
+ try {
56
+ const cookiesPath = path.join(__dirname, '..', '.cookies');
57
+
58
+ // Просто записываем cookies в файл (одна строка)
59
+ fs.writeFileSync(cookiesPath, newCookieHeader, 'utf8');
60
+ console.log('✅ Cookies сохранены в .cookies');
61
+ return true;
62
+ } catch (error) {
63
+ console.error('❌ Ошибка сохранения cookies в .cookies:', error.message);
64
+ return false;
65
+ }
66
+ }
package/src/users.js ADDED
@@ -0,0 +1,416 @@
1
+ /**
2
+ * Модуль для работы с пользователями
3
+ */
4
+ export class UsersManager {
5
+ constructor(client) {
6
+ this.client = client;
7
+ this.axios = client.axios;
8
+ }
9
+
10
+ /**
11
+ * Обновляет описание профиля текущего пользователя
12
+ *
13
+ * @param {string} bio - Новое описание профиля
14
+ * @param {string|null} displayName - Новое отображаемое имя (опционально)
15
+ * @returns {Promise<Object|null>} Обновленные данные профиля или null при ошибке
16
+ */
17
+ async updateProfile(bio, displayName = null) {
18
+ if (!await this.client.auth.checkAuth()) {
19
+ console.error('Ошибка: необходимо войти в аккаунт');
20
+ return null;
21
+ }
22
+
23
+ try {
24
+ const updateUrl = `${this.client.baseUrl}/api/users/me`;
25
+
26
+ const updateData = {};
27
+ if (bio !== null && bio !== undefined) {
28
+ updateData.bio = bio;
29
+ }
30
+ if (displayName !== null && displayName !== undefined) {
31
+ updateData.displayName = displayName;
32
+ }
33
+
34
+ const response = await this.axios.put(updateUrl, updateData);
35
+
36
+ if (response.status === 200) {
37
+ return response.data;
38
+ } else {
39
+ console.error(`Ошибка обновления профиля: ${response.status}`);
40
+ if (response.data) {
41
+ console.error('Response data:', response.data);
42
+ }
43
+ return null;
44
+ }
45
+ } catch (error) {
46
+ console.error('Исключение при обновлении профиля:', error.message);
47
+ if (error.response) {
48
+ console.error('Response status:', error.response.status);
49
+ console.error('Response data:', error.response.data);
50
+ }
51
+ return null;
52
+ }
53
+ }
54
+
55
+ /**
56
+ * Получает данные текущего пользователя
57
+ *
58
+ * @returns {Promise<Object|null>} Данные профиля или null при ошибке
59
+ */
60
+ async getMyProfile() {
61
+ if (!await this.client.auth.checkAuth()) {
62
+ console.error('Ошибка: необходимо войти в аккаунт');
63
+ return null;
64
+ }
65
+
66
+ try {
67
+ const profileUrl = `${this.client.baseUrl}/api/users/me`;
68
+ const response = await this.axios.get(profileUrl);
69
+
70
+ if (response.status === 200) {
71
+ return response.data;
72
+ } else {
73
+ console.error(`Ошибка получения профиля: ${response.status}`);
74
+ if (response.data) {
75
+ console.error('Response data:', response.data);
76
+ }
77
+ return null;
78
+ }
79
+ } catch (error) {
80
+ console.error('Исключение при получении профиля:', error.message);
81
+ if (error.response) {
82
+ console.error('Response status:', error.response.status);
83
+ console.error('Response data:', error.response.data);
84
+ }
85
+ return null;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Получает профиль пользователя по username
91
+ *
92
+ * @param {string} username - Имя пользователя
93
+ * @returns {Promise<Object|null>} Данные профиля или null при ошибке
94
+ *
95
+ * Примечание: Авторизация не требуется для просмотра публичных профилей
96
+ */
97
+ async getUserProfile(username) {
98
+ try {
99
+ const profileUrl = `${this.client.baseUrl}/api/users/${username}`;
100
+ const response = await this.axios.get(profileUrl);
101
+
102
+ if (response.status === 200) {
103
+ // Структура может быть { data: {...} } или просто {...}
104
+ return response.data.data || response.data;
105
+ } else {
106
+ console.error(`Ошибка получения профиля пользователя: ${response.status}`);
107
+ if (response.data) {
108
+ console.error('Response data:', response.data);
109
+ }
110
+ return null;
111
+ }
112
+ } catch (error) {
113
+ console.error('Исключение при получении профиля пользователя:', error.message);
114
+ if (error.response) {
115
+ console.error('Response status:', error.response.status);
116
+ console.error('Response data:', error.response.data);
117
+ }
118
+ return null;
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Подписывается на пользователя
124
+ *
125
+ * @param {string} username - Имя пользователя
126
+ * @returns {Promise<Object|null>} { following: true, followersCount: number } или null при ошибке
127
+ */
128
+ async followUser(username) {
129
+ if (!await this.client.auth.checkAuth()) {
130
+ console.error('Ошибка: необходимо войти в аккаунт');
131
+ return null;
132
+ }
133
+
134
+ try {
135
+ const followUrl = `${this.client.baseUrl}/api/users/${username}/follow`;
136
+ const response = await this.axios.post(followUrl);
137
+
138
+ if (response.status === 200 || response.status === 201) {
139
+ return response.data; // { following: true, followersCount: number }
140
+ } else {
141
+ console.error(`Ошибка подписки: ${response.status}`);
142
+ if (response.data) {
143
+ console.error('Response data:', response.data);
144
+ }
145
+ return null;
146
+ }
147
+ } catch (error) {
148
+ console.error('Исключение при подписке:', error.message);
149
+ if (error.response) {
150
+ console.error('Response status:', error.response.status);
151
+ console.error('Response data:', error.response.data);
152
+ }
153
+ return null;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Отписывается от пользователя
159
+ *
160
+ * @param {string} username - Имя пользователя
161
+ * @returns {Promise<Object|null>} { following: false, followersCount: number } или null при ошибке
162
+ */
163
+ async unfollowUser(username) {
164
+ if (!await this.client.auth.checkAuth()) {
165
+ console.error('Ошибка: необходимо войти в аккаунт');
166
+ return null;
167
+ }
168
+
169
+ try {
170
+ const unfollowUrl = `${this.client.baseUrl}/api/users/${username}/follow`;
171
+ const response = await this.axios.delete(unfollowUrl);
172
+
173
+ if (response.status === 200 || response.status === 204) {
174
+ return response.data || { following: false, followersCount: 0 };
175
+ } else {
176
+ console.error(`Ошибка отписки: ${response.status}`);
177
+ if (response.data) {
178
+ console.error('Response data:', response.data);
179
+ }
180
+ return null;
181
+ }
182
+ } catch (error) {
183
+ console.error('Исключение при отписке:', error.message);
184
+ if (error.response) {
185
+ console.error('Response status:', error.response.status);
186
+ console.error('Response data:', error.response.data);
187
+ }
188
+ return null;
189
+ }
190
+ }
191
+
192
+ /**
193
+ * Получает список подписчиков пользователя
194
+ *
195
+ * @param {string} username - Имя пользователя
196
+ * @param {number} page - Номер страницы (начиная с 1)
197
+ * @param {number} limit - Количество на странице
198
+ * @returns {Promise<Object|null>} { users: [], pagination: {} } или null при ошибке
199
+ *
200
+ * Примечание: Авторизация не требуется для просмотра подписчиков
201
+ */
202
+ async getFollowers(username, page = 1, limit = 30) {
203
+ try {
204
+ const followersUrl = `${this.client.baseUrl}/api/users/${username}/followers`;
205
+ const params = { page, limit };
206
+ const response = await this.axios.get(followersUrl, { params });
207
+
208
+ if (response.status === 200) {
209
+ const data = response.data;
210
+ // Структура: { data: { users: [...], pagination: {...} } }
211
+ if (data.data && data.data.users) {
212
+ return {
213
+ users: data.data.users,
214
+ pagination: data.data.pagination || {}
215
+ };
216
+ } else if (data.users) {
217
+ return {
218
+ users: data.users,
219
+ pagination: data.pagination || {}
220
+ };
221
+ }
222
+ return { users: [], pagination: {} };
223
+ } else {
224
+ console.error(`Ошибка получения подписчиков: ${response.status}`);
225
+ return null;
226
+ }
227
+ } catch (error) {
228
+ console.error('Исключение при получении подписчиков:', error.message);
229
+ if (error.response) {
230
+ console.error('Response status:', error.response.status);
231
+ console.error('Response data:', error.response.data);
232
+ }
233
+ return null;
234
+ }
235
+ }
236
+
237
+ /**
238
+ * Получает список подписок пользователя
239
+ *
240
+ * @param {string} username - Имя пользователя
241
+ * @param {number} page - Номер страницы (начиная с 1)
242
+ * @param {number} limit - Количество на странице
243
+ * @returns {Promise<Object|null>} { users: [], pagination: {} } или null при ошибке
244
+ *
245
+ * Примечание: Авторизация не требуется для просмотра подписок
246
+ */
247
+ async getFollowing(username, page = 1, limit = 30) {
248
+ try {
249
+ const followingUrl = `${this.client.baseUrl}/api/users/${username}/following`;
250
+ const params = { page, limit };
251
+ const response = await this.axios.get(followingUrl, { params });
252
+
253
+ if (response.status === 200) {
254
+ const data = response.data;
255
+ // Структура: { data: { users: [...], pagination: {...} } }
256
+ if (data.data && data.data.users) {
257
+ return {
258
+ users: data.data.users,
259
+ pagination: data.data.pagination || {}
260
+ };
261
+ } else if (data.users) {
262
+ return {
263
+ users: data.users,
264
+ pagination: data.pagination || {}
265
+ };
266
+ }
267
+ return { users: [], pagination: {} };
268
+ } else {
269
+ console.error(`Ошибка получения подписок: ${response.status}`);
270
+ return null;
271
+ }
272
+ } catch (error) {
273
+ console.error('Исключение при получении подписок:', error.message);
274
+ if (error.response) {
275
+ console.error('Response status:', error.response.status);
276
+ console.error('Response data:', error.response.data);
277
+ }
278
+ return null;
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Получает клан пользователя (эмодзи из avatar)
284
+ *
285
+ * @param {string} username - Имя пользователя
286
+ * @returns {Promise<string|null>} Эмодзи клана или null при ошибке
287
+ */
288
+ async getUserClan(username) {
289
+ const profile = await this.getUserProfile(username);
290
+ if (!profile) {
291
+ return null;
292
+ }
293
+ // Клан - это эмодзи в поле avatar
294
+ return profile.avatar || null;
295
+ }
296
+
297
+ /**
298
+ * Получает топ кланов по количеству участников
299
+ *
300
+ * @returns {Promise<Array|null>} Массив кланов [{ avatar: "🦎", memberCount: 3794 }, ...] или null при ошибке
301
+ */
302
+ async getTopClans() {
303
+ try {
304
+ const topClansUrl = `${this.client.baseUrl}/api/users/stats/top-clans`;
305
+ const response = await this.axios.get(topClansUrl);
306
+
307
+ if (response.status === 200) {
308
+ const data = response.data;
309
+ // Структура: { clans: [...] }
310
+ return data.clans || [];
311
+ } else {
312
+ console.error(`Ошибка получения топ кланов: ${response.status}`);
313
+ return null;
314
+ }
315
+ } catch (error) {
316
+ console.error('Исключение при получении топ кланов:', error.message);
317
+ if (error.response) {
318
+ console.error('Response status:', error.response.status);
319
+ console.error('Response data:', error.response.data);
320
+ }
321
+ return null;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Получает рекомендации кого подписаться
327
+ *
328
+ * @returns {Promise<Array|null>} Массив пользователей или null при ошибке
329
+ */
330
+ async getWhoToFollow() {
331
+ if (!await this.client.auth.checkAuth()) {
332
+ console.error('Ошибка: необходимо войти в аккаунт');
333
+ return null;
334
+ }
335
+
336
+ try {
337
+ const suggestionsUrl = `${this.client.baseUrl}/api/users/suggestions/who-to-follow`;
338
+ const response = await this.axios.get(suggestionsUrl);
339
+
340
+ if (response.status === 200) {
341
+ const data = response.data;
342
+ // Структура: { users: [...] }
343
+ return data.users || [];
344
+ } else {
345
+ console.error(`Ошибка получения рекомендаций: ${response.status}`);
346
+ return null;
347
+ }
348
+ } catch (error) {
349
+ console.error('Исключение при получении рекомендаций:', error.message);
350
+ if (error.response) {
351
+ console.error('Response status:', error.response.status);
352
+ console.error('Response data:', error.response.data);
353
+ }
354
+ return null;
355
+ }
356
+ }
357
+
358
+ // ========== USER-FRIENDLY МЕТОДЫ ==========
359
+
360
+ // getMyProfile() уже реализован выше (строка 60) - это удобный метод по умолчанию
361
+
362
+ /**
363
+ * Проверяет, подписан ли текущий пользователь на указанного (удобный метод)
364
+ *
365
+ * @param {string} username - Имя пользователя для проверки
366
+ * @returns {Promise<boolean>} True если подписан, false если нет или ошибка
367
+ */
368
+ async isFollowing(username) {
369
+ if (!await this.client.auth.checkAuth()) {
370
+ return false;
371
+ }
372
+ const profile = await this.getUserProfile(username);
373
+ return profile ? (profile.isFollowing === true) : false;
374
+ }
375
+
376
+ /**
377
+ * Получает количество своих подписчиков (удобный метод)
378
+ *
379
+ * @returns {Promise<number>} Количество подписчиков
380
+ */
381
+ async getMyFollowersCount() {
382
+ const profile = await this.getMyProfile();
383
+ return profile ? (profile.followersCount || 0) : 0;
384
+ }
385
+
386
+ /**
387
+ * Получает количество своих подписок (удобный метод)
388
+ *
389
+ * @returns {Promise<number>} Количество подписок
390
+ */
391
+ async getMyFollowingCount() {
392
+ const profile = await this.getMyProfile();
393
+ return profile ? (profile.followingCount || 0) : 0;
394
+ }
395
+
396
+ /**
397
+ * Получает свой клан (эмодзи аватара) (удобный метод)
398
+ *
399
+ * @returns {Promise<string|null>} Эмодзи клана или null
400
+ */
401
+ async getMyClan() {
402
+ const profile = await this.getMyProfile();
403
+ return profile ? (profile.avatar || null) : null;
404
+ }
405
+
406
+ /**
407
+ * Получает клан пользователя (эмодзи аватара) (удобный метод)
408
+ *
409
+ * @param {string} username - Имя пользователя
410
+ * @returns {Promise<string|null>} Эмодзи клана или null
411
+ */
412
+ async getUserClan(username) {
413
+ const profile = await this.getUserProfile(username);
414
+ return profile ? (profile.avatar || null) : null;
415
+ }
416
+ }