viruagent-cli 0.3.2 → 0.3.4
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/package.json +1 -1
- package/src/providers/tistory/auth.js +167 -0
- package/src/providers/tistory/browserHelpers.js +91 -0
- package/src/providers/tistory/chromeImport.js +745 -0
- package/src/providers/tistory/fetchLayer.js +237 -0
- package/src/providers/tistory/imageEnrichment.js +574 -0
- package/src/providers/tistory/imageNormalization.js +301 -0
- package/src/providers/tistory/imageSources.js +270 -0
- package/src/providers/tistory/index.js +561 -0
- package/src/providers/tistory/selectors.js +51 -0
- package/src/providers/tistory/session.js +117 -0
- package/src/providers/tistory/utils.js +235 -0
- package/src/services/providerManager.js +1 -1
- package/src/providers/tistoryProvider.js +0 -2862
|
@@ -0,0 +1,561 @@
|
|
|
1
|
+
const { saveProviderMeta, clearProviderMeta, getProviderMeta } = require('../../storage/sessionStore');
|
|
2
|
+
const createTistoryApiClient = require('../../services/tistoryApiClient');
|
|
3
|
+
const {
|
|
4
|
+
readCredentialsFromEnv,
|
|
5
|
+
mapVisibility,
|
|
6
|
+
normalizeTagList,
|
|
7
|
+
isPublishLimitError,
|
|
8
|
+
isProvidedCategory,
|
|
9
|
+
buildCategoryList,
|
|
10
|
+
promptCategorySelection,
|
|
11
|
+
MAX_IMAGE_UPLOAD_COUNT,
|
|
12
|
+
} = require('./utils');
|
|
13
|
+
const { normalizeThumbnailForPublish } = require('./imageNormalization');
|
|
14
|
+
const { enrichContentWithUploadedImages, resolveMandatoryThumbnail } = require('./imageEnrichment');
|
|
15
|
+
const { createWithProviderSession } = require('./session');
|
|
16
|
+
const { importSessionFromChrome } = require('./chromeImport');
|
|
17
|
+
const { createAskForAuthentication } = require('./auth');
|
|
18
|
+
|
|
19
|
+
const createTistoryProvider = ({ sessionPath }) => {
|
|
20
|
+
const tistoryApi = createTistoryApiClient({ sessionPath });
|
|
21
|
+
|
|
22
|
+
const pending2faResult = (mode = 'kakao') => ({
|
|
23
|
+
provider: 'tistory',
|
|
24
|
+
status: 'pending_2fa',
|
|
25
|
+
loggedIn: false,
|
|
26
|
+
message: mode === 'otp'
|
|
27
|
+
? '2차 인증이 필요합니다. otp 코드를 twoFactorCode로 전달해 주세요.'
|
|
28
|
+
: '카카오 2차 인증이 필요합니다. 앱에서 인증 후 다시 실행하면 됩니다.',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const askForAuthentication = createAskForAuthentication({
|
|
32
|
+
sessionPath,
|
|
33
|
+
tistoryApi,
|
|
34
|
+
pending2faResult,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const withProviderSession = createWithProviderSession(askForAuthentication);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
id: 'tistory',
|
|
41
|
+
name: 'Tistory',
|
|
42
|
+
|
|
43
|
+
async authStatus() {
|
|
44
|
+
return withProviderSession(async () => {
|
|
45
|
+
try {
|
|
46
|
+
const blogName = await tistoryApi.initBlog();
|
|
47
|
+
return {
|
|
48
|
+
provider: 'tistory',
|
|
49
|
+
loggedIn: true,
|
|
50
|
+
blogName,
|
|
51
|
+
blogUrl: `https://${blogName}.tistory.com`,
|
|
52
|
+
sessionPath,
|
|
53
|
+
metadata: getProviderMeta('tistory') || {},
|
|
54
|
+
};
|
|
55
|
+
} catch (error) {
|
|
56
|
+
return {
|
|
57
|
+
provider: 'tistory',
|
|
58
|
+
loggedIn: false,
|
|
59
|
+
sessionPath,
|
|
60
|
+
error: error.message,
|
|
61
|
+
metadata: getProviderMeta('tistory') || {},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async login({
|
|
68
|
+
headless = false,
|
|
69
|
+
manual = false,
|
|
70
|
+
username,
|
|
71
|
+
password,
|
|
72
|
+
twoFactorCode,
|
|
73
|
+
fromChrome,
|
|
74
|
+
profile,
|
|
75
|
+
} = {}) {
|
|
76
|
+
if (fromChrome) {
|
|
77
|
+
await importSessionFromChrome(sessionPath, profile || 'Default');
|
|
78
|
+
tistoryApi.resetState();
|
|
79
|
+
const blogName = await tistoryApi.initBlog();
|
|
80
|
+
const result = {
|
|
81
|
+
provider: 'tistory',
|
|
82
|
+
loggedIn: true,
|
|
83
|
+
blogName,
|
|
84
|
+
blogUrl: `https://${blogName}.tistory.com`,
|
|
85
|
+
sessionPath,
|
|
86
|
+
source: 'chrome-import',
|
|
87
|
+
};
|
|
88
|
+
saveProviderMeta('tistory', { loggedIn: true, blogName, blogUrl: result.blogUrl, sessionPath });
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const creds = readCredentialsFromEnv();
|
|
93
|
+
const resolved = {
|
|
94
|
+
headless,
|
|
95
|
+
manual,
|
|
96
|
+
username: username || creds.username,
|
|
97
|
+
password: password || creds.password,
|
|
98
|
+
twoFactorCode,
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (!resolved.manual && (!resolved.username || !resolved.password)) {
|
|
102
|
+
throw new Error('티스토리 자동 로그인을 진행하려면 username/password가 필요합니다. 요청 값으로 전달하거나, 환경변수 TISTORY_USERNAME / TISTORY_PASSWORD를 설정해 주세요.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const result = await askForAuthentication(resolved);
|
|
106
|
+
saveProviderMeta('tistory', {
|
|
107
|
+
loggedIn: result.loggedIn,
|
|
108
|
+
blogName: result.blogName,
|
|
109
|
+
blogUrl: result.blogUrl,
|
|
110
|
+
sessionPath: result.sessionPath,
|
|
111
|
+
});
|
|
112
|
+
return result;
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
async publish(payload) {
|
|
116
|
+
return withProviderSession(async () => {
|
|
117
|
+
const title = payload.title || '제목 없음';
|
|
118
|
+
const rawContent = payload.content || '';
|
|
119
|
+
const visibility = mapVisibility(payload.visibility);
|
|
120
|
+
const tag = normalizeTagList(payload.tags);
|
|
121
|
+
const rawThumbnail = payload.thumbnail || null;
|
|
122
|
+
const relatedImageKeywords = payload.relatedImageKeywords || [];
|
|
123
|
+
const imageUrls = payload.imageUrls || [];
|
|
124
|
+
const autoUploadImages = payload.autoUploadImages !== false;
|
|
125
|
+
const safeImageUploadLimit = MAX_IMAGE_UPLOAD_COUNT;
|
|
126
|
+
const safeMinimumImageCount = MAX_IMAGE_UPLOAD_COUNT;
|
|
127
|
+
|
|
128
|
+
if (autoUploadImages) {
|
|
129
|
+
await tistoryApi.initBlog();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const enrichedImages = await enrichContentWithUploadedImages({
|
|
133
|
+
api: tistoryApi,
|
|
134
|
+
rawContent,
|
|
135
|
+
autoUploadImages,
|
|
136
|
+
relatedImageKeywords,
|
|
137
|
+
imageUrls,
|
|
138
|
+
imageUploadLimit: safeImageUploadLimit,
|
|
139
|
+
minimumImageCount: safeMinimumImageCount,
|
|
140
|
+
});
|
|
141
|
+
if (enrichedImages.status === 'need_image_urls') {
|
|
142
|
+
return {
|
|
143
|
+
mode: 'publish',
|
|
144
|
+
status: 'need_image_urls',
|
|
145
|
+
loggedIn: true,
|
|
146
|
+
provider: 'tistory',
|
|
147
|
+
title,
|
|
148
|
+
visibility,
|
|
149
|
+
tags: tag,
|
|
150
|
+
message: enrichedImages.message,
|
|
151
|
+
requestedKeywords: enrichedImages.requestedKeywords,
|
|
152
|
+
requestedCount: enrichedImages.requestedCount,
|
|
153
|
+
providedImageUrls: enrichedImages.providedImageUrls,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (enrichedImages.status === 'insufficient_images') {
|
|
158
|
+
return {
|
|
159
|
+
mode: 'publish',
|
|
160
|
+
status: 'insufficient_images',
|
|
161
|
+
loggedIn: true,
|
|
162
|
+
provider: 'tistory',
|
|
163
|
+
title,
|
|
164
|
+
visibility,
|
|
165
|
+
tags: tag,
|
|
166
|
+
message: enrichedImages.message,
|
|
167
|
+
imageCount: enrichedImages.uploadedCount,
|
|
168
|
+
requestedCount: enrichedImages.requestedCount,
|
|
169
|
+
uploadedCount: enrichedImages.uploadedCount,
|
|
170
|
+
uploadErrors: enrichedImages.uploadErrors || [],
|
|
171
|
+
providedImageUrls: enrichedImages.providedImageUrls,
|
|
172
|
+
missingImageCount: enrichedImages.missingImageCount || 0,
|
|
173
|
+
imageLimit: enrichedImages.imageLimit || safeImageUploadLimit,
|
|
174
|
+
minimumImageCount: safeMinimumImageCount,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (enrichedImages.status === 'image_upload_failed' || enrichedImages.status === 'image_upload_partial') {
|
|
179
|
+
return {
|
|
180
|
+
mode: 'publish',
|
|
181
|
+
status: enrichedImages.status,
|
|
182
|
+
loggedIn: true,
|
|
183
|
+
provider: 'tistory',
|
|
184
|
+
title,
|
|
185
|
+
visibility,
|
|
186
|
+
tags: tag,
|
|
187
|
+
thumbnail: normalizeThumbnailForPublish(payload.thumbnail) || null,
|
|
188
|
+
message: enrichedImages.message,
|
|
189
|
+
imageCount: enrichedImages.uploadedCount,
|
|
190
|
+
requestedCount: enrichedImages.requestedCount,
|
|
191
|
+
uploadedCount: enrichedImages.uploadedCount,
|
|
192
|
+
uploadErrors: enrichedImages.uploadErrors || [],
|
|
193
|
+
providedImageUrls: enrichedImages.providedImageUrls,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const content = enrichedImages.content;
|
|
197
|
+
const uploadedImages = enrichedImages?.images || enrichedImages?.uploaded || [];
|
|
198
|
+
const finalThumbnail = await resolveMandatoryThumbnail({
|
|
199
|
+
rawThumbnail,
|
|
200
|
+
content,
|
|
201
|
+
uploadedImages,
|
|
202
|
+
relatedImageKeywords,
|
|
203
|
+
title,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
await tistoryApi.initBlog();
|
|
207
|
+
const rawCategories = await tistoryApi.getCategories();
|
|
208
|
+
const categories = buildCategoryList(rawCategories);
|
|
209
|
+
|
|
210
|
+
if (!isProvidedCategory(payload.category)) {
|
|
211
|
+
if (categories.length === 0) {
|
|
212
|
+
return {
|
|
213
|
+
provider: 'tistory',
|
|
214
|
+
mode: 'publish',
|
|
215
|
+
status: 'need_category',
|
|
216
|
+
loggedIn: true,
|
|
217
|
+
title,
|
|
218
|
+
visibility,
|
|
219
|
+
tags: tag,
|
|
220
|
+
message: '발행을 위해 카테고리가 필요합니다. categories를 확인하고 category를 지정해 주세요.',
|
|
221
|
+
categories,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (categories.length === 1) {
|
|
226
|
+
payload = { ...payload, category: categories[0].id };
|
|
227
|
+
} else {
|
|
228
|
+
if (!process.stdin || !process.stdin.isTTY) {
|
|
229
|
+
const sampleCategory = categories.slice(0, 5).map((item) => `${item.id}: ${item.name}`).join(', ');
|
|
230
|
+
const sampleText = sampleCategory.length > 0 ? ` 예: ${sampleCategory}` : '';
|
|
231
|
+
return {
|
|
232
|
+
provider: 'tistory',
|
|
233
|
+
mode: 'publish',
|
|
234
|
+
status: 'need_category',
|
|
235
|
+
loggedIn: true,
|
|
236
|
+
title,
|
|
237
|
+
visibility,
|
|
238
|
+
tags: tag,
|
|
239
|
+
message: `카테고리가 지정되지 않았습니다. 비대화형 환경에서는 --category 옵션이 필수입니다. 사용법: --category <카테고리ID>.${sampleText}`,
|
|
240
|
+
categories,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const selectedCategoryId = await promptCategorySelection(categories);
|
|
245
|
+
if (!selectedCategoryId) {
|
|
246
|
+
return {
|
|
247
|
+
provider: 'tistory',
|
|
248
|
+
mode: 'publish',
|
|
249
|
+
status: 'need_category',
|
|
250
|
+
loggedIn: true,
|
|
251
|
+
title,
|
|
252
|
+
visibility,
|
|
253
|
+
tags: tag,
|
|
254
|
+
message: '카테고리가 지정되지 않았습니다. 카테고리를 입력해 발행을 진행해 주세요.',
|
|
255
|
+
categories,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
payload = { ...payload, category: selectedCategoryId };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const category = Number(payload.category);
|
|
264
|
+
if (!Number.isInteger(category) || Number.isNaN(category)) {
|
|
265
|
+
return {
|
|
266
|
+
provider: 'tistory',
|
|
267
|
+
mode: 'publish',
|
|
268
|
+
status: 'invalid_category',
|
|
269
|
+
loggedIn: true,
|
|
270
|
+
title,
|
|
271
|
+
visibility,
|
|
272
|
+
tags: tag,
|
|
273
|
+
message: '유효한 category를 숫자로 지정해 주세요.',
|
|
274
|
+
categories,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const validCategoryIds = categories.map((item) => item.id);
|
|
279
|
+
if (!validCategoryIds.includes(category) && categories.length > 0) {
|
|
280
|
+
return {
|
|
281
|
+
provider: 'tistory',
|
|
282
|
+
mode: 'publish',
|
|
283
|
+
status: 'invalid_category',
|
|
284
|
+
loggedIn: true,
|
|
285
|
+
title,
|
|
286
|
+
visibility,
|
|
287
|
+
tags: tag,
|
|
288
|
+
message: '존재하지 않는 category입니다. categories를 확인해 주세요.',
|
|
289
|
+
categories,
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
try {
|
|
294
|
+
const result = await tistoryApi.publishPost({
|
|
295
|
+
title,
|
|
296
|
+
content,
|
|
297
|
+
visibility,
|
|
298
|
+
category,
|
|
299
|
+
tag,
|
|
300
|
+
thumbnail: finalThumbnail,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
return {
|
|
304
|
+
provider: 'tistory',
|
|
305
|
+
mode: 'publish',
|
|
306
|
+
title,
|
|
307
|
+
category,
|
|
308
|
+
visibility,
|
|
309
|
+
tags: tag,
|
|
310
|
+
thumbnail: finalThumbnail,
|
|
311
|
+
images: enrichedImages.images,
|
|
312
|
+
imageCount: enrichedImages.uploadedCount,
|
|
313
|
+
minimumImageCount: safeMinimumImageCount,
|
|
314
|
+
url: result.entryUrl || null,
|
|
315
|
+
raw: result,
|
|
316
|
+
};
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (!isPublishLimitError(error)) {
|
|
319
|
+
throw error;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
try {
|
|
323
|
+
const fallbackPublishResult = await tistoryApi.publishPost({
|
|
324
|
+
title,
|
|
325
|
+
content,
|
|
326
|
+
visibility: 0,
|
|
327
|
+
category,
|
|
328
|
+
tag,
|
|
329
|
+
thumbnail: finalThumbnail,
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
return {
|
|
333
|
+
provider: 'tistory',
|
|
334
|
+
mode: 'publish',
|
|
335
|
+
status: 'publish_fallback_to_private',
|
|
336
|
+
title,
|
|
337
|
+
category,
|
|
338
|
+
visibility: 0,
|
|
339
|
+
tags: tag,
|
|
340
|
+
thumbnail: finalThumbnail,
|
|
341
|
+
images: enrichedImages.images,
|
|
342
|
+
imageCount: enrichedImages.uploadedCount,
|
|
343
|
+
minimumImageCount: safeMinimumImageCount,
|
|
344
|
+
url: fallbackPublishResult.entryUrl || null,
|
|
345
|
+
raw: fallbackPublishResult,
|
|
346
|
+
message: '발행 제한(403)으로 인해 비공개로 발행했습니다.',
|
|
347
|
+
fallbackThumbnail: finalThumbnail,
|
|
348
|
+
};
|
|
349
|
+
} catch (fallbackError) {
|
|
350
|
+
if (!isPublishLimitError(fallbackError)) {
|
|
351
|
+
throw fallbackError;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
provider: 'tistory',
|
|
356
|
+
mode: 'publish',
|
|
357
|
+
status: 'publish_fallback_to_private_failed',
|
|
358
|
+
title,
|
|
359
|
+
category,
|
|
360
|
+
visibility: 0,
|
|
361
|
+
tags: tag,
|
|
362
|
+
thumbnail: finalThumbnail,
|
|
363
|
+
images: enrichedImages.images,
|
|
364
|
+
imageCount: enrichedImages.uploadedCount,
|
|
365
|
+
minimumImageCount: safeMinimumImageCount,
|
|
366
|
+
message: '발행 제한(403)으로 인해 공개/비공개 모두 실패했습니다.',
|
|
367
|
+
raw: {
|
|
368
|
+
success: false,
|
|
369
|
+
error: fallbackError.message,
|
|
370
|
+
},
|
|
371
|
+
};
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
},
|
|
376
|
+
|
|
377
|
+
async saveDraft(payload) {
|
|
378
|
+
return withProviderSession(async () => {
|
|
379
|
+
const title = payload.title || '임시저장';
|
|
380
|
+
const rawContent = payload.content || '';
|
|
381
|
+
const rawThumbnail = payload.thumbnail || null;
|
|
382
|
+
const tag = normalizeTagList(payload.tags);
|
|
383
|
+
const relatedImageKeywords = payload.relatedImageKeywords || [];
|
|
384
|
+
const imageUrls = payload.imageUrls || [];
|
|
385
|
+
const autoUploadImages = payload.autoUploadImages !== false;
|
|
386
|
+
const safeImageUploadCount = MAX_IMAGE_UPLOAD_COUNT;
|
|
387
|
+
const safeMinimumImageCount = MAX_IMAGE_UPLOAD_COUNT;
|
|
388
|
+
|
|
389
|
+
if (autoUploadImages) {
|
|
390
|
+
await tistoryApi.initBlog();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const enrichedImages = await enrichContentWithUploadedImages({
|
|
394
|
+
api: tistoryApi,
|
|
395
|
+
rawContent,
|
|
396
|
+
autoUploadImages,
|
|
397
|
+
relatedImageKeywords,
|
|
398
|
+
imageUrls,
|
|
399
|
+
imageUploadLimit: safeImageUploadCount,
|
|
400
|
+
minimumImageCount: safeMinimumImageCount,
|
|
401
|
+
});
|
|
402
|
+
if (enrichedImages.status === 'need_image_urls') {
|
|
403
|
+
return {
|
|
404
|
+
mode: 'draft',
|
|
405
|
+
status: 'need_image_urls',
|
|
406
|
+
loggedIn: true,
|
|
407
|
+
provider: 'tistory',
|
|
408
|
+
title,
|
|
409
|
+
message: enrichedImages.message,
|
|
410
|
+
requestedKeywords: enrichedImages.requestedKeywords,
|
|
411
|
+
requestedCount: enrichedImages.requestedCount,
|
|
412
|
+
providedImageUrls: enrichedImages.providedImageUrls,
|
|
413
|
+
imageCount: enrichedImages.imageCount,
|
|
414
|
+
minimumImageCount: safeMinimumImageCount,
|
|
415
|
+
images: enrichedImages.uploaded || [],
|
|
416
|
+
uploadedCount: enrichedImages.uploadedCount,
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (enrichedImages.status === 'insufficient_images') {
|
|
421
|
+
return {
|
|
422
|
+
mode: 'draft',
|
|
423
|
+
status: 'insufficient_images',
|
|
424
|
+
loggedIn: true,
|
|
425
|
+
provider: 'tistory',
|
|
426
|
+
title,
|
|
427
|
+
message: enrichedImages.message,
|
|
428
|
+
imageCount: enrichedImages.imageCount,
|
|
429
|
+
requestedCount: enrichedImages.requestedCount,
|
|
430
|
+
uploadedCount: enrichedImages.uploadedCount,
|
|
431
|
+
uploadErrors: enrichedImages.uploadErrors,
|
|
432
|
+
providedImageUrls: enrichedImages.providedImageUrls,
|
|
433
|
+
minimumImageCount: safeMinimumImageCount,
|
|
434
|
+
imageLimit: enrichedImages.imageLimit || safeImageUploadCount,
|
|
435
|
+
images: enrichedImages.uploaded || [],
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (enrichedImages.status === 'image_upload_failed' || enrichedImages.status === 'image_upload_partial') {
|
|
440
|
+
return {
|
|
441
|
+
mode: 'draft',
|
|
442
|
+
status: enrichedImages.status,
|
|
443
|
+
loggedIn: true,
|
|
444
|
+
provider: 'tistory',
|
|
445
|
+
title,
|
|
446
|
+
message: enrichedImages.message,
|
|
447
|
+
imageCount: enrichedImages.imageCount,
|
|
448
|
+
requestedCount: enrichedImages.requestedCount,
|
|
449
|
+
uploadedCount: enrichedImages.uploadedCount,
|
|
450
|
+
uploadErrors: enrichedImages.uploadErrors || [],
|
|
451
|
+
providedImageUrls: enrichedImages.providedImageUrls,
|
|
452
|
+
images: enrichedImages.uploaded || [],
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const content = enrichedImages.content;
|
|
457
|
+
const thumbnail = await resolveMandatoryThumbnail({
|
|
458
|
+
rawThumbnail,
|
|
459
|
+
content,
|
|
460
|
+
uploadedImages: enrichedImages?.uploaded || [],
|
|
461
|
+
relatedImageKeywords,
|
|
462
|
+
title,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
await tistoryApi.initBlog();
|
|
466
|
+
const result = await tistoryApi.saveDraft({ title, content });
|
|
467
|
+
return {
|
|
468
|
+
provider: 'tistory',
|
|
469
|
+
mode: 'draft',
|
|
470
|
+
title,
|
|
471
|
+
status: 'ok',
|
|
472
|
+
category: Number(payload.category) || 0,
|
|
473
|
+
tags: tag,
|
|
474
|
+
sequence: result.draft?.sequence || null,
|
|
475
|
+
thumbnail,
|
|
476
|
+
minimumImageCount: safeMinimumImageCount,
|
|
477
|
+
imageCount: enrichedImages.imageCount,
|
|
478
|
+
images: enrichedImages.uploaded || [],
|
|
479
|
+
uploadErrors: enrichedImages.uploadErrors || null,
|
|
480
|
+
draftContent: content,
|
|
481
|
+
raw: result,
|
|
482
|
+
};
|
|
483
|
+
});
|
|
484
|
+
},
|
|
485
|
+
|
|
486
|
+
async listCategories() {
|
|
487
|
+
return withProviderSession(async () => {
|
|
488
|
+
await tistoryApi.initBlog();
|
|
489
|
+
const categories = await tistoryApi.getCategories();
|
|
490
|
+
return {
|
|
491
|
+
provider: 'tistory',
|
|
492
|
+
categories: Object.entries(categories).map(([name, id]) => ({
|
|
493
|
+
name,
|
|
494
|
+
id: Number(id),
|
|
495
|
+
})),
|
|
496
|
+
};
|
|
497
|
+
});
|
|
498
|
+
},
|
|
499
|
+
|
|
500
|
+
async listPosts({ limit = 20 } = {}) {
|
|
501
|
+
return withProviderSession(async () => {
|
|
502
|
+
await tistoryApi.initBlog();
|
|
503
|
+
const result = await tistoryApi.getPosts();
|
|
504
|
+
const items = Array.isArray(result?.items) ? result.items : [];
|
|
505
|
+
return {
|
|
506
|
+
provider: 'tistory',
|
|
507
|
+
totalCount: result.totalCount || items.length,
|
|
508
|
+
posts: items.slice(0, Math.max(1, Number(limit) || 20)),
|
|
509
|
+
};
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
|
|
513
|
+
async getPost({ postId, includeDraft = false } = {}) {
|
|
514
|
+
return withProviderSession(async () => {
|
|
515
|
+
const resolvedPostId = String(postId || '').trim();
|
|
516
|
+
if (!resolvedPostId) {
|
|
517
|
+
return {
|
|
518
|
+
provider: 'tistory',
|
|
519
|
+
mode: 'post',
|
|
520
|
+
status: 'invalid_post_id',
|
|
521
|
+
message: 'postId가 필요합니다.',
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
await tistoryApi.initBlog();
|
|
526
|
+
const post = await tistoryApi.getPost({
|
|
527
|
+
postId: resolvedPostId,
|
|
528
|
+
includeDraft: Boolean(includeDraft),
|
|
529
|
+
});
|
|
530
|
+
if (!post) {
|
|
531
|
+
return {
|
|
532
|
+
provider: 'tistory',
|
|
533
|
+
mode: 'post',
|
|
534
|
+
status: 'not_found',
|
|
535
|
+
postId: resolvedPostId,
|
|
536
|
+
includeDraft: Boolean(includeDraft),
|
|
537
|
+
message: '해당 postId의 글을 찾을 수 없습니다.',
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
return {
|
|
541
|
+
provider: 'tistory',
|
|
542
|
+
mode: 'post',
|
|
543
|
+
postId: resolvedPostId,
|
|
544
|
+
post,
|
|
545
|
+
includeDraft: Boolean(includeDraft),
|
|
546
|
+
};
|
|
547
|
+
});
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
async logout() {
|
|
551
|
+
clearProviderMeta('tistory');
|
|
552
|
+
return {
|
|
553
|
+
provider: 'tistory',
|
|
554
|
+
loggedOut: true,
|
|
555
|
+
sessionPath,
|
|
556
|
+
};
|
|
557
|
+
},
|
|
558
|
+
};
|
|
559
|
+
};
|
|
560
|
+
|
|
561
|
+
module.exports = createTistoryProvider;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
const LOGIN_OTP_SELECTORS = [
|
|
2
|
+
'input[name*="otp"]',
|
|
3
|
+
'input[placeholder*="인증"]',
|
|
4
|
+
'input[autocomplete="one-time-code"]',
|
|
5
|
+
'input[name*="code"]',
|
|
6
|
+
];
|
|
7
|
+
|
|
8
|
+
const KAKAO_TRIGGER_SELECTORS = [
|
|
9
|
+
'a.link_kakao_id',
|
|
10
|
+
'a:has-text("카카오계정으로 로그인")',
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const KAKAO_LOGIN_SELECTORS = {
|
|
14
|
+
username: ['input[name="loginId"]', '#loginId--1', 'input[placeholder*="카카오메일"]'],
|
|
15
|
+
password: ['input[name="password"]', '#password--2', 'input[type="password"]'],
|
|
16
|
+
submit: ['button[type="submit"]', 'button:has-text("로그인")', '.btn_g.highlight.submit'],
|
|
17
|
+
rememberLogin: ['#saveSignedIn--4', 'input[name="saveSignedIn"]'],
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const KAKAO_2FA_SELECTORS = {
|
|
21
|
+
start: ['#tmsTwoStepVerification', '#emailTwoStepVerification'],
|
|
22
|
+
emailModeButton: ['button:has-text("이메일로 인증하기")', '.link_certify'],
|
|
23
|
+
codeInput: ['input[name="email_passcode"]', '#passcode--6', 'input[placeholder*="인증번호"]'],
|
|
24
|
+
confirm: ['button:has-text("확인")', 'button.btn_g.submit', 'button[type="submit"]'],
|
|
25
|
+
rememberDevice: ['#isRememberBrowser--5', 'input[name="isRememberBrowser"]'],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const KAKAO_ACCOUNT_CONFIRM_SELECTORS = {
|
|
29
|
+
textMarker: [
|
|
30
|
+
'text=해당 카카오 계정으로',
|
|
31
|
+
'text=티스토리\n해당 카카오 계정으로',
|
|
32
|
+
'text=해당 카카오계정으로 로그인',
|
|
33
|
+
],
|
|
34
|
+
continue: [
|
|
35
|
+
'button:has-text("계속하기")',
|
|
36
|
+
'a:has-text("계속하기")',
|
|
37
|
+
'button:has-text("다음")',
|
|
38
|
+
],
|
|
39
|
+
otherAccount: [
|
|
40
|
+
'button:has-text("다른 카카오계정으로 로그인")',
|
|
41
|
+
'a:has-text("다른 카카오계정으로 로그인")',
|
|
42
|
+
],
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
module.exports = {
|
|
46
|
+
LOGIN_OTP_SELECTORS,
|
|
47
|
+
KAKAO_TRIGGER_SELECTORS,
|
|
48
|
+
KAKAO_LOGIN_SELECTORS,
|
|
49
|
+
KAKAO_2FA_SELECTORS,
|
|
50
|
+
KAKAO_ACCOUNT_CONFIRM_SELECTORS,
|
|
51
|
+
};
|