viruagent-cli 0.4.1 → 0.4.2
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/bin/index.js +6 -0
- package/package.json +1 -1
- package/src/providers/insta/apiClient.js +57 -0
- package/src/providers/insta/auth.js +55 -4
- package/src/providers/insta/index.js +10 -0
- package/src/runner.js +3 -0
package/bin/index.js
CHANGED
|
@@ -212,6 +212,12 @@ analyzePostCmd
|
|
|
212
212
|
.option('--post-id <shortcode>', 'Post shortcode')
|
|
213
213
|
.action((opts) => execute('analyze-post', opts));
|
|
214
214
|
|
|
215
|
+
const resolveChallengeCmd = program
|
|
216
|
+
.command('resolve-challenge')
|
|
217
|
+
.description('Resolve Instagram challenge (auto-verify identity)');
|
|
218
|
+
addProviderOption(resolveChallengeCmd);
|
|
219
|
+
resolveChallengeCmd.action((opts) => execute('resolve-challenge', opts));
|
|
220
|
+
|
|
215
221
|
const rateLimitCmd = program
|
|
216
222
|
.command('rate-limit-status')
|
|
217
223
|
.description('Show current rate limit usage');
|
package/package.json
CHANGED
|
@@ -174,9 +174,36 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
174
174
|
if (location.includes('/accounts/login')) {
|
|
175
175
|
throw new Error('세션이 만료되었습니다. 다시 로그인해 주세요.');
|
|
176
176
|
}
|
|
177
|
+
if (location.includes('/challenge')) {
|
|
178
|
+
// challenge 자동 해결 시도
|
|
179
|
+
const resolved = await resolveChallenge();
|
|
180
|
+
if (resolved) {
|
|
181
|
+
// 해결 후 원래 요청 재시도
|
|
182
|
+
return fetch(url, { ...options, headers, redirect: 'manual' });
|
|
183
|
+
}
|
|
184
|
+
throw new Error('challenge_required: 본인 인증이 필요합니다. 브라우저에서 수동으로 처리해 주세요.');
|
|
185
|
+
}
|
|
177
186
|
throw new Error(`리다이렉트 발생: ${res.status} → ${location}`);
|
|
178
187
|
}
|
|
179
188
|
|
|
189
|
+
// challenge_required JSON 응답 처리
|
|
190
|
+
if (res.status === 400 && !options.allowError) {
|
|
191
|
+
const cloned = res.clone();
|
|
192
|
+
try {
|
|
193
|
+
const data = await cloned.json();
|
|
194
|
+
if (data.message === 'challenge_required') {
|
|
195
|
+
const resolved = await resolveChallenge();
|
|
196
|
+
if (resolved) {
|
|
197
|
+
return fetch(url, { ...options, headers, redirect: 'manual' });
|
|
198
|
+
}
|
|
199
|
+
throw new Error('challenge_required: 본인 인증이 필요합니다.');
|
|
200
|
+
}
|
|
201
|
+
} catch (e) {
|
|
202
|
+
if (e.message.includes('challenge_required')) throw e;
|
|
203
|
+
// JSON 파싱 실패는 무시하고 원래 흐름
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
180
207
|
if (res.status === 401 || res.status === 403) {
|
|
181
208
|
throw new Error(`인증 오류 (${res.status}). 다시 로그인해 주세요.`);
|
|
182
209
|
}
|
|
@@ -316,6 +343,35 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
316
343
|
};
|
|
317
344
|
};
|
|
318
345
|
|
|
346
|
+
// ── Challenge 자동 해결 ──
|
|
347
|
+
|
|
348
|
+
const resolveChallenge = async () => {
|
|
349
|
+
try {
|
|
350
|
+
const cookies = getCookies();
|
|
351
|
+
const res = await fetch('https://www.instagram.com/api/v1/challenge/web/action/', {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
headers: {
|
|
354
|
+
'User-Agent': USER_AGENT,
|
|
355
|
+
'X-IG-App-ID': IG_APP_ID,
|
|
356
|
+
'X-CSRFToken': getCsrfToken(),
|
|
357
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
358
|
+
'X-Instagram-AJAX': '1',
|
|
359
|
+
Referer: 'https://www.instagram.com/challenge/',
|
|
360
|
+
Origin: 'https://www.instagram.com',
|
|
361
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
362
|
+
Cookie: cookiesToHeader(cookies),
|
|
363
|
+
},
|
|
364
|
+
body: 'choice=0',
|
|
365
|
+
redirect: 'manual',
|
|
366
|
+
});
|
|
367
|
+
if (!res.ok) return false;
|
|
368
|
+
const data = await res.json();
|
|
369
|
+
return data.status === 'ok';
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
|
|
319
375
|
const parseLikeResponse = async (res) => {
|
|
320
376
|
if (res.ok) return res.json();
|
|
321
377
|
if (res.status === 400) {
|
|
@@ -611,6 +667,7 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
611
667
|
configurePost,
|
|
612
668
|
publishPost,
|
|
613
669
|
deletePost,
|
|
670
|
+
resolveChallenge,
|
|
614
671
|
resetState,
|
|
615
672
|
getRateLimitStatus: () => {
|
|
616
673
|
const status = {};
|
|
@@ -99,10 +99,61 @@ const createAskForAuthentication = ({ sessionPath }) => async ({
|
|
|
99
99
|
|
|
100
100
|
const loginData = await loginRes.json();
|
|
101
101
|
|
|
102
|
-
if (loginData.checkpoint_url) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
if (loginData.checkpoint_url || loginData.message === 'challenge_required') {
|
|
103
|
+
// challenge 자동 해결 시도 (choice=0 = 본인입니다)
|
|
104
|
+
const challengeRes = await fetch('https://www.instagram.com/api/v1/challenge/web/action/', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
headers: {
|
|
107
|
+
'User-Agent': USER_AGENT,
|
|
108
|
+
'X-CSRFToken': csrfToken,
|
|
109
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
110
|
+
'X-Instagram-AJAX': '1',
|
|
111
|
+
'X-IG-App-ID': IG_APP_ID,
|
|
112
|
+
Referer: 'https://www.instagram.com/challenge/',
|
|
113
|
+
Origin: 'https://www.instagram.com',
|
|
114
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
115
|
+
Cookie: cookieHeader,
|
|
116
|
+
},
|
|
117
|
+
body: 'choice=0',
|
|
118
|
+
redirect: 'manual',
|
|
119
|
+
});
|
|
120
|
+
const challengeData = await challengeRes.json().catch(() => ({}));
|
|
121
|
+
if (challengeData.status !== 'ok') {
|
|
122
|
+
throw new Error(
|
|
123
|
+
'challenge_required: 자동 해결 실패. 브라우저에서 본인 인증을 완료해 주세요. ' +
|
|
124
|
+
(loginData.checkpoint_url || ''),
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
// challenge 해결 후 재로그인
|
|
128
|
+
const retryRes = await fetch('https://www.instagram.com/api/v1/web/accounts/login/ajax/', {
|
|
129
|
+
method: 'POST',
|
|
130
|
+
headers: {
|
|
131
|
+
'User-Agent': USER_AGENT,
|
|
132
|
+
'X-CSRFToken': csrfToken,
|
|
133
|
+
'X-Requested-With': 'XMLHttpRequest',
|
|
134
|
+
'X-IG-App-ID': IG_APP_ID,
|
|
135
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
136
|
+
Referer: 'https://www.instagram.com/accounts/login/',
|
|
137
|
+
Origin: 'https://www.instagram.com',
|
|
138
|
+
Cookie: cookieHeader,
|
|
139
|
+
},
|
|
140
|
+
body: body.toString(),
|
|
141
|
+
redirect: 'manual',
|
|
142
|
+
});
|
|
143
|
+
const retryCookies = parseCookiesFromHeaders(retryRes.headers);
|
|
144
|
+
cookies = mergeCookies(cookies, retryCookies);
|
|
145
|
+
const retryData = await retryRes.json().catch(() => ({}));
|
|
146
|
+
if (retryData.authenticated) {
|
|
147
|
+
saveInstaSession(sessionPath, cookies);
|
|
148
|
+
return {
|
|
149
|
+
provider: 'insta',
|
|
150
|
+
loggedIn: true,
|
|
151
|
+
userId: retryData.userId || null,
|
|
152
|
+
username: resolvedUsername,
|
|
153
|
+
sessionPath,
|
|
154
|
+
challengeResolved: true,
|
|
155
|
+
};
|
|
156
|
+
}
|
|
106
157
|
}
|
|
107
158
|
|
|
108
159
|
if (loginData.two_factor_required) {
|
|
@@ -271,6 +271,16 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
271
271
|
});
|
|
272
272
|
},
|
|
273
273
|
|
|
274
|
+
async resolveChallenge() {
|
|
275
|
+
const resolved = await instaApi.resolveChallenge();
|
|
276
|
+
return {
|
|
277
|
+
provider: 'insta',
|
|
278
|
+
mode: 'resolveChallenge',
|
|
279
|
+
resolved,
|
|
280
|
+
message: resolved ? 'Challenge 해결 완료' : 'Challenge 해결 실패. 브라우저에서 수동으로 처리해 주세요.',
|
|
281
|
+
};
|
|
282
|
+
},
|
|
283
|
+
|
|
274
284
|
rateLimitStatus() {
|
|
275
285
|
return {
|
|
276
286
|
provider: 'insta',
|
package/src/runner.js
CHANGED
|
@@ -237,6 +237,9 @@ const runCommand = async (command, opts = {}) => {
|
|
|
237
237
|
}
|
|
238
238
|
return withProvider(() => provider.analyzePost({ postId: opts.postId }))();
|
|
239
239
|
|
|
240
|
+
case 'resolve-challenge':
|
|
241
|
+
return withProvider(() => provider.resolveChallenge())();
|
|
242
|
+
|
|
240
243
|
case 'rate-limit-status':
|
|
241
244
|
return withProvider(() => Promise.resolve(provider.rateLimitStatus()))();
|
|
242
245
|
|