viruagent-cli 0.4.2 → 0.5.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.
- package/package.json +1 -1
- package/src/providers/insta/apiClient.js +54 -54
- package/src/providers/insta/auth.js +10 -10
- package/src/providers/insta/index.js +17 -17
- package/src/providers/insta/session.js +2 -2
- package/src/providers/insta/smartComment.js +8 -8
- package/src/providers/insta/utils.js +5 -5
- package/src/providers/naver/auth.js +19 -19
- package/src/providers/naver/editorConvert.js +16 -16
- package/src/providers/naver/imageUpload.js +7 -7
- package/src/providers/naver/index.js +9 -9
- package/src/providers/naver/session.js +2 -2
- package/src/providers/naver/utils.js +8 -8
- package/src/providers/tistory/auth.js +12 -12
- package/src/providers/tistory/fetchLayer.js +5 -5
- package/src/providers/tistory/imageEnrichment.js +19 -19
- package/src/providers/tistory/imageNormalization.js +1 -1
- package/src/providers/tistory/imageSources.js +5 -5
- package/src/providers/tistory/index.js +15 -15
- package/src/providers/tistory/session.js +3 -3
- package/src/providers/tistory/utils.js +14 -14
- package/src/runner.js +1 -1
- package/src/services/naverApiClient.js +17 -17
- package/src/services/providerManager.js +1 -1
- package/src/services/tistoryApiClient.js +13 -13
package/package.json
CHANGED
|
@@ -8,40 +8,40 @@ const randomDelay = (minSec, maxSec) => {
|
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
// ──────────────────────────────────────────────────────────────
|
|
11
|
-
// Instagram
|
|
11
|
+
// Instagram Safe Action Rules (2026, research-based)
|
|
12
12
|
//
|
|
13
|
-
// [
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
13
|
+
// [Hourly limits by account age]
|
|
14
|
+
// New (0~20 days) | Mature (20+ days)
|
|
15
|
+
// Like 15/h | 60/h
|
|
16
|
+
// Comment 5/h | 20/h
|
|
17
|
+
// Follow 15/h | 60/h
|
|
18
|
+
// Unfollow 10/h | 30/h
|
|
19
19
|
// DM 5/h | 50/h
|
|
20
|
-
//
|
|
20
|
+
// Publish 3/h | 10/h
|
|
21
21
|
//
|
|
22
|
-
// [
|
|
23
|
-
//
|
|
24
|
-
//
|
|
22
|
+
// [Daily limits]
|
|
23
|
+
// Like 500/day Comment 100/day Follow 250/day
|
|
24
|
+
// Unfollow 200/day DM 30/day Publish 25/day
|
|
25
25
|
//
|
|
26
|
-
// [
|
|
27
|
-
//
|
|
28
|
-
//
|
|
26
|
+
// [Minimum action intervals (new accounts)]
|
|
27
|
+
// Like: 20~40s | Comment: 300~420s (5~7min) | Follow: 60~120s
|
|
28
|
+
// Unfollow: 60~120s | DM: 120~300s | Publish: 60~120s
|
|
29
29
|
//
|
|
30
|
-
// [
|
|
31
|
-
// -
|
|
32
|
-
// -
|
|
33
|
-
// -
|
|
34
|
-
// -
|
|
35
|
-
// -
|
|
30
|
+
// [Notes]
|
|
31
|
+
// - Max 15 total actions/hour (new) / 40 (mature)
|
|
32
|
+
// - Uniform intervals trigger bot detection → random delay required
|
|
33
|
+
// - Repeated actions on the same user are prohibited
|
|
34
|
+
// - Challenge requires manual verification in the browser
|
|
35
|
+
// - Wait 24~48 hours after a challenge is recommended
|
|
36
36
|
// ──────────────────────────────────────────────────────────────
|
|
37
37
|
|
|
38
38
|
const DELAY = {
|
|
39
|
-
like: [20, 40], // 20~
|
|
40
|
-
comment: [300, 420], // 5~
|
|
41
|
-
follow: [60, 120], // 1~
|
|
42
|
-
unfollow: [60, 120], // 1~
|
|
43
|
-
dm: [120, 300], // 2~
|
|
44
|
-
publish: [60, 120], // 1~
|
|
39
|
+
like: [20, 40], // 20~40s
|
|
40
|
+
comment: [300, 420], // 5~7min
|
|
41
|
+
follow: [60, 120], // 1~2min
|
|
42
|
+
unfollow: [60, 120], // 1~2min
|
|
43
|
+
dm: [120, 300], // 2~5min
|
|
44
|
+
publish: [60, 120], // 1~2min
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const HOURLY_LIMIT = {
|
|
@@ -69,7 +69,7 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
69
69
|
let cachedUserId = null;
|
|
70
70
|
let countersCache = null;
|
|
71
71
|
|
|
72
|
-
// ──
|
|
72
|
+
// ── Session file-based Rate Limit counters ──
|
|
73
73
|
|
|
74
74
|
const loadCounters = () => {
|
|
75
75
|
if (countersCache) return countersCache;
|
|
@@ -90,7 +90,7 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
90
90
|
if (!userId || !countersCache) return;
|
|
91
91
|
saveRateLimits(sessionPath, userId, countersCache);
|
|
92
92
|
} catch {
|
|
93
|
-
//
|
|
93
|
+
// Save failure does not affect operation
|
|
94
94
|
}
|
|
95
95
|
};
|
|
96
96
|
|
|
@@ -112,10 +112,10 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
112
112
|
const dailyMax = DAILY_LIMIT[type];
|
|
113
113
|
if (hourlyMax && c.hourly >= hourlyMax) {
|
|
114
114
|
const waitMin = Math.ceil((3600000 - (Date.now() - c.hourStart)) / 60000);
|
|
115
|
-
throw new Error(`hourly_limit: ${type}
|
|
115
|
+
throw new Error(`hourly_limit: ${type} exceeded hourly limit of ${hourlyMax}. Retry in ${waitMin} minutes.`);
|
|
116
116
|
}
|
|
117
117
|
if (dailyMax && c.daily >= dailyMax) {
|
|
118
|
-
throw new Error(`daily_limit: ${type}
|
|
118
|
+
throw new Error(`daily_limit: ${type} exceeded daily limit of ${dailyMax}. Try again tomorrow.`);
|
|
119
119
|
}
|
|
120
120
|
};
|
|
121
121
|
|
|
@@ -130,11 +130,11 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
130
130
|
if (cachedCookies) return cachedCookies;
|
|
131
131
|
const cookies = loadInstaSession(sessionPath);
|
|
132
132
|
if (!cookies) {
|
|
133
|
-
throw new Error('
|
|
133
|
+
throw new Error('No session file found. Please log in first.');
|
|
134
134
|
}
|
|
135
135
|
const sessionid = cookies.find((c) => c.name === 'sessionid');
|
|
136
136
|
if (!sessionid?.value) {
|
|
137
|
-
throw new Error('
|
|
137
|
+
throw new Error('No valid cookies in session. Please log in again.');
|
|
138
138
|
}
|
|
139
139
|
cachedCookies = cookies;
|
|
140
140
|
return cookies;
|
|
@@ -172,21 +172,21 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
172
172
|
if (res.status === 302 || res.status === 301) {
|
|
173
173
|
const location = res.headers.get('location') || '';
|
|
174
174
|
if (location.includes('/accounts/login')) {
|
|
175
|
-
throw new Error('
|
|
175
|
+
throw new Error('Session expired. Please log in again.');
|
|
176
176
|
}
|
|
177
177
|
if (location.includes('/challenge')) {
|
|
178
|
-
//
|
|
178
|
+
// Attempt automatic challenge resolution
|
|
179
179
|
const resolved = await resolveChallenge();
|
|
180
180
|
if (resolved) {
|
|
181
|
-
//
|
|
181
|
+
// Retry original request after resolution
|
|
182
182
|
return fetch(url, { ...options, headers, redirect: 'manual' });
|
|
183
183
|
}
|
|
184
|
-
throw new Error('challenge_required:
|
|
184
|
+
throw new Error('challenge_required: Identity verification required. Please complete it manually in the browser.');
|
|
185
185
|
}
|
|
186
|
-
throw new Error(
|
|
186
|
+
throw new Error(`Redirect occurred: ${res.status} → ${location}`);
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
// challenge_required JSON
|
|
189
|
+
// Handle challenge_required JSON response
|
|
190
190
|
if (res.status === 400 && !options.allowError) {
|
|
191
191
|
const cloned = res.clone();
|
|
192
192
|
try {
|
|
@@ -196,20 +196,20 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
196
196
|
if (resolved) {
|
|
197
197
|
return fetch(url, { ...options, headers, redirect: 'manual' });
|
|
198
198
|
}
|
|
199
|
-
throw new Error('challenge_required:
|
|
199
|
+
throw new Error('challenge_required: Identity verification required.');
|
|
200
200
|
}
|
|
201
201
|
} catch (e) {
|
|
202
202
|
if (e.message.includes('challenge_required')) throw e;
|
|
203
|
-
// JSON
|
|
203
|
+
// Ignore JSON parse failure and continue original flow
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
if (res.status === 401 || res.status === 403) {
|
|
208
|
-
throw new Error(
|
|
208
|
+
throw new Error(`Authentication error (${res.status}). Please log in again.`);
|
|
209
209
|
}
|
|
210
210
|
|
|
211
211
|
if (!res.ok && !options.allowError) {
|
|
212
|
-
throw new Error(`Instagram API
|
|
212
|
+
throw new Error(`Instagram API error: ${res.status} ${res.statusText}`);
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
return res;
|
|
@@ -221,7 +221,7 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
221
221
|
);
|
|
222
222
|
const data = await res.json();
|
|
223
223
|
const user = data?.data?.user;
|
|
224
|
-
if (!user) throw new Error(
|
|
224
|
+
if (!user) throw new Error(`Profile not found: ${username}`);
|
|
225
225
|
return {
|
|
226
226
|
id: user.id,
|
|
227
227
|
username: user.username,
|
|
@@ -322,7 +322,7 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
322
322
|
has_threaded_comments: true,
|
|
323
323
|
});
|
|
324
324
|
const media = data?.data?.xdt_shortcode_media || data?.data?.shortcode_media;
|
|
325
|
-
if (!media) throw new Error(
|
|
325
|
+
if (!media) throw new Error(`Post not found: ${shortcode}`);
|
|
326
326
|
return {
|
|
327
327
|
id: media.id,
|
|
328
328
|
code: media.shortcode,
|
|
@@ -343,7 +343,7 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
343
343
|
};
|
|
344
344
|
};
|
|
345
345
|
|
|
346
|
-
// ── Challenge
|
|
346
|
+
// ── Automatic Challenge Resolution ──
|
|
347
347
|
|
|
348
348
|
const resolveChallenge = async () => {
|
|
349
349
|
try {
|
|
@@ -379,17 +379,17 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
379
379
|
if (data.spam) {
|
|
380
380
|
throw new Error(`rate_limit: ${data.feedback_title || 'Try Again Later'}`);
|
|
381
381
|
}
|
|
382
|
-
//
|
|
382
|
+
// Already liked/unliked state
|
|
383
383
|
return { status: 'already', message: data.message };
|
|
384
384
|
}
|
|
385
|
-
throw new Error(`Instagram API
|
|
385
|
+
throw new Error(`Instagram API error: ${res.status}`);
|
|
386
386
|
};
|
|
387
387
|
|
|
388
388
|
const withDelay = async (type, fn) => {
|
|
389
|
-
//
|
|
389
|
+
// Check limits
|
|
390
390
|
checkLimit(type);
|
|
391
391
|
|
|
392
|
-
//
|
|
392
|
+
// Random delay
|
|
393
393
|
const [min, max] = DELAY[type] || [20, 40];
|
|
394
394
|
const elapsed = (Date.now() - lastActionTime) / 1000;
|
|
395
395
|
if (lastActionTime > 0 && elapsed < min) {
|
|
@@ -575,7 +575,7 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
575
575
|
);
|
|
576
576
|
const data = await res.json();
|
|
577
577
|
if (data.status !== 'ok') {
|
|
578
|
-
throw new Error(
|
|
578
|
+
throw new Error(`Image upload failed: ${data.message || 'unknown'}`);
|
|
579
579
|
}
|
|
580
580
|
return uploadId;
|
|
581
581
|
};
|
|
@@ -601,7 +601,7 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
601
601
|
});
|
|
602
602
|
const data = await res.json();
|
|
603
603
|
if (data.status !== 'ok') {
|
|
604
|
-
throw new Error(
|
|
604
|
+
throw new Error(`Post creation failed: ${data.message || 'unknown'}`);
|
|
605
605
|
}
|
|
606
606
|
return {
|
|
607
607
|
id: data.media?.pk,
|
|
@@ -617,10 +617,10 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
617
617
|
imageBuffer = fs.readFileSync(imagePath);
|
|
618
618
|
} else if (imageUrl) {
|
|
619
619
|
const res = await fetch(imageUrl);
|
|
620
|
-
if (!res.ok) throw new Error(
|
|
620
|
+
if (!res.ok) throw new Error(`Image download failed: ${res.status}`);
|
|
621
621
|
imageBuffer = Buffer.from(await res.arrayBuffer());
|
|
622
622
|
} else {
|
|
623
|
-
throw new Error('imageUrl
|
|
623
|
+
throw new Error('Either imageUrl or imagePath is required.');
|
|
624
624
|
}
|
|
625
625
|
|
|
626
626
|
const uploadId = await uploadPhoto(imageBuffer);
|
|
@@ -676,7 +676,7 @@ const createInstaApiClient = ({ sessionPath }) => {
|
|
|
676
676
|
status[type] = {
|
|
677
677
|
hourly: `${c.hourly}/${HOURLY_LIMIT[type]}`,
|
|
678
678
|
daily: `${c.daily}/${DAILY_LIMIT[type]}`,
|
|
679
|
-
delay: `${DELAY[type]?.[0]}~${DELAY[type]?.[1]}
|
|
679
|
+
delay: `${DELAY[type]?.[0]}~${DELAY[type]?.[1]}s`,
|
|
680
680
|
};
|
|
681
681
|
}
|
|
682
682
|
return status;
|
|
@@ -50,12 +50,12 @@ const createAskForAuthentication = ({ sessionPath }) => async ({
|
|
|
50
50
|
|
|
51
51
|
if (!resolvedUsername || !resolvedPassword) {
|
|
52
52
|
throw new Error(
|
|
53
|
-
'
|
|
54
|
-
'
|
|
53
|
+
'Instagram login requires username/password. ' +
|
|
54
|
+
'Please set the INSTA_USERNAME / INSTA_PASSWORD environment variables.',
|
|
55
55
|
);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
// Step 1: GET login page -> csrftoken + mid
|
|
58
|
+
// Step 1: GET login page -> obtain csrftoken + mid cookies
|
|
59
59
|
const initRes = await fetch('https://www.instagram.com/accounts/login/', {
|
|
60
60
|
headers: { 'User-Agent': USER_AGENT },
|
|
61
61
|
redirect: 'manual',
|
|
@@ -64,7 +64,7 @@ const createAskForAuthentication = ({ sessionPath }) => async ({
|
|
|
64
64
|
|
|
65
65
|
const csrfCookie = cookies.find((c) => c.name === 'csrftoken');
|
|
66
66
|
if (!csrfCookie) {
|
|
67
|
-
throw new Error('
|
|
67
|
+
throw new Error('Failed to retrieve csrftoken from the Instagram login page.');
|
|
68
68
|
}
|
|
69
69
|
const csrfToken = csrfCookie.value;
|
|
70
70
|
const cookieHeader = cookies.map((c) => `${c.name}=${c.value}`).join('; ');
|
|
@@ -100,7 +100,7 @@ const createAskForAuthentication = ({ sessionPath }) => async ({
|
|
|
100
100
|
const loginData = await loginRes.json();
|
|
101
101
|
|
|
102
102
|
if (loginData.checkpoint_url || loginData.message === 'challenge_required') {
|
|
103
|
-
//
|
|
103
|
+
// Attempt automatic challenge resolution (choice=0 = "This was me")
|
|
104
104
|
const challengeRes = await fetch('https://www.instagram.com/api/v1/challenge/web/action/', {
|
|
105
105
|
method: 'POST',
|
|
106
106
|
headers: {
|
|
@@ -120,11 +120,11 @@ const createAskForAuthentication = ({ sessionPath }) => async ({
|
|
|
120
120
|
const challengeData = await challengeRes.json().catch(() => ({}));
|
|
121
121
|
if (challengeData.status !== 'ok') {
|
|
122
122
|
throw new Error(
|
|
123
|
-
'challenge_required:
|
|
123
|
+
'challenge_required: Automatic resolution failed. Please complete identity verification in the browser. ' +
|
|
124
124
|
(loginData.checkpoint_url || ''),
|
|
125
125
|
);
|
|
126
126
|
}
|
|
127
|
-
//
|
|
127
|
+
// Re-login after challenge resolution
|
|
128
128
|
const retryRes = await fetch('https://www.instagram.com/api/v1/web/accounts/login/ajax/', {
|
|
129
129
|
method: 'POST',
|
|
130
130
|
headers: {
|
|
@@ -158,16 +158,16 @@ const createAskForAuthentication = ({ sessionPath }) => async ({
|
|
|
158
158
|
|
|
159
159
|
if (loginData.two_factor_required) {
|
|
160
160
|
throw new Error(
|
|
161
|
-
'
|
|
161
|
+
'Two-factor authentication (2FA) is required. Please complete verification in the browser first.',
|
|
162
162
|
);
|
|
163
163
|
}
|
|
164
164
|
|
|
165
165
|
if (!loginData.authenticated) {
|
|
166
166
|
const reason = loginData.message || loginData.status || 'unknown';
|
|
167
|
-
throw new Error(
|
|
167
|
+
throw new Error(`Instagram login failed: ${reason}`);
|
|
168
168
|
}
|
|
169
169
|
|
|
170
|
-
//
|
|
170
|
+
// Save session
|
|
171
171
|
saveInstaSession(sessionPath, cookies);
|
|
172
172
|
|
|
173
173
|
return {
|
|
@@ -52,8 +52,8 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
52
52
|
|
|
53
53
|
if (!resolved.username || !resolved.password) {
|
|
54
54
|
throw new Error(
|
|
55
|
-
'
|
|
56
|
-
'
|
|
55
|
+
'Instagram login requires username/password. ' +
|
|
56
|
+
'Please set the INSTA_USERNAME / INSTA_PASSWORD environment variables.',
|
|
57
57
|
);
|
|
58
58
|
}
|
|
59
59
|
|
|
@@ -73,7 +73,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
73
73
|
async getProfile({ username } = {}) {
|
|
74
74
|
return withProviderSession(async () => {
|
|
75
75
|
if (!username) {
|
|
76
|
-
throw new Error('username
|
|
76
|
+
throw new Error('username is required.');
|
|
77
77
|
}
|
|
78
78
|
const profile = await instaApi.getProfile(username);
|
|
79
79
|
return {
|
|
@@ -99,7 +99,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
99
99
|
async listPosts({ username, limit = 12 } = {}) {
|
|
100
100
|
return withProviderSession(async () => {
|
|
101
101
|
if (!username) {
|
|
102
|
-
throw new Error('username
|
|
102
|
+
throw new Error('username is required.');
|
|
103
103
|
}
|
|
104
104
|
const posts = await instaApi.getUserPosts(username, limit);
|
|
105
105
|
return {
|
|
@@ -120,7 +120,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
120
120
|
provider: 'insta',
|
|
121
121
|
mode: 'post',
|
|
122
122
|
status: 'invalid_post_id',
|
|
123
|
-
message: 'postId(shortcode)
|
|
123
|
+
message: 'postId (shortcode) is required.',
|
|
124
124
|
};
|
|
125
125
|
}
|
|
126
126
|
const post = await instaApi.getPostDetail(shortcode);
|
|
@@ -134,7 +134,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
134
134
|
|
|
135
135
|
async follow({ username } = {}) {
|
|
136
136
|
return withProviderSession(async () => {
|
|
137
|
-
if (!username) throw new Error('username
|
|
137
|
+
if (!username) throw new Error('username is required.');
|
|
138
138
|
const profile = await instaApi.getProfile(username);
|
|
139
139
|
const result = await instaApi.followUser(profile.id);
|
|
140
140
|
return {
|
|
@@ -151,7 +151,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
151
151
|
|
|
152
152
|
async unfollow({ username } = {}) {
|
|
153
153
|
return withProviderSession(async () => {
|
|
154
|
-
if (!username) throw new Error('username
|
|
154
|
+
if (!username) throw new Error('username is required.');
|
|
155
155
|
const profile = await instaApi.getProfile(username);
|
|
156
156
|
const result = await instaApi.unfollowUser(profile.id);
|
|
157
157
|
return {
|
|
@@ -168,7 +168,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
168
168
|
async like({ postId } = {}) {
|
|
169
169
|
return withProviderSession(async () => {
|
|
170
170
|
const shortcode = String(postId || '').trim();
|
|
171
|
-
if (!shortcode) throw new Error('postId(shortcode)
|
|
171
|
+
if (!shortcode) throw new Error('postId (shortcode) is required.');
|
|
172
172
|
const mediaId = await instaApi.getMediaIdFromShortcode(shortcode);
|
|
173
173
|
const result = await instaApi.likePost(mediaId);
|
|
174
174
|
return { provider: 'insta', mode: 'like', postId: shortcode, status: result.status };
|
|
@@ -178,7 +178,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
178
178
|
async unlike({ postId } = {}) {
|
|
179
179
|
return withProviderSession(async () => {
|
|
180
180
|
const shortcode = String(postId || '').trim();
|
|
181
|
-
if (!shortcode) throw new Error('postId(shortcode)
|
|
181
|
+
if (!shortcode) throw new Error('postId (shortcode) is required.');
|
|
182
182
|
const mediaId = await instaApi.getMediaIdFromShortcode(shortcode);
|
|
183
183
|
const result = await instaApi.unlikePost(mediaId);
|
|
184
184
|
return { provider: 'insta', mode: 'unlike', postId: shortcode, status: result.status };
|
|
@@ -187,7 +187,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
187
187
|
|
|
188
188
|
async likeComment({ commentId } = {}) {
|
|
189
189
|
return withProviderSession(async () => {
|
|
190
|
-
if (!commentId) throw new Error('commentId
|
|
190
|
+
if (!commentId) throw new Error('commentId is required.');
|
|
191
191
|
const result = await instaApi.likeComment(commentId);
|
|
192
192
|
return { provider: 'insta', mode: 'likeComment', commentId, status: result.status };
|
|
193
193
|
});
|
|
@@ -195,7 +195,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
195
195
|
|
|
196
196
|
async unlikeComment({ commentId } = {}) {
|
|
197
197
|
return withProviderSession(async () => {
|
|
198
|
-
if (!commentId) throw new Error('commentId
|
|
198
|
+
if (!commentId) throw new Error('commentId is required.');
|
|
199
199
|
const result = await instaApi.unlikeComment(commentId);
|
|
200
200
|
return { provider: 'insta', mode: 'unlikeComment', commentId, status: result.status };
|
|
201
201
|
});
|
|
@@ -206,10 +206,10 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
206
206
|
const shortcode = String(postId || '').trim();
|
|
207
207
|
const commentText = String(text || '').trim();
|
|
208
208
|
if (!shortcode) {
|
|
209
|
-
throw new Error('postId(shortcode)
|
|
209
|
+
throw new Error('postId (shortcode) is required.');
|
|
210
210
|
}
|
|
211
211
|
if (!commentText) {
|
|
212
|
-
throw new Error('
|
|
212
|
+
throw new Error('Comment text is required.');
|
|
213
213
|
}
|
|
214
214
|
const mediaId = await instaApi.getMediaIdFromShortcode(shortcode);
|
|
215
215
|
const result = await instaApi.addComment(mediaId, commentText);
|
|
@@ -228,7 +228,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
228
228
|
async publish({ imageUrl, imagePath, caption = '' } = {}) {
|
|
229
229
|
return withProviderSession(async () => {
|
|
230
230
|
if (!imageUrl && !imagePath) {
|
|
231
|
-
throw new Error('imageUrl
|
|
231
|
+
throw new Error('Either imageUrl or imagePath is required.');
|
|
232
232
|
}
|
|
233
233
|
const result = await instaApi.publishPost({ imageUrl, imagePath, caption });
|
|
234
234
|
return {
|
|
@@ -243,7 +243,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
243
243
|
return withProviderSession(async () => {
|
|
244
244
|
const shortcode = String(postId || '').trim();
|
|
245
245
|
if (!shortcode) {
|
|
246
|
-
throw new Error('postId(shortcode)
|
|
246
|
+
throw new Error('postId (shortcode) is required.');
|
|
247
247
|
}
|
|
248
248
|
const analysis = await smart.analyzePost({ shortcode });
|
|
249
249
|
return {
|
|
@@ -258,7 +258,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
258
258
|
return withProviderSession(async () => {
|
|
259
259
|
const shortcode = String(postId || '').trim();
|
|
260
260
|
if (!shortcode) {
|
|
261
|
-
throw new Error('postId(shortcode)
|
|
261
|
+
throw new Error('postId (shortcode) is required.');
|
|
262
262
|
}
|
|
263
263
|
const mediaId = await instaApi.getMediaIdFromShortcode(shortcode);
|
|
264
264
|
const result = await instaApi.deletePost(mediaId);
|
|
@@ -277,7 +277,7 @@ const createInstaProvider = ({ sessionPath }) => {
|
|
|
277
277
|
provider: 'insta',
|
|
278
278
|
mode: 'resolveChallenge',
|
|
279
279
|
resolved,
|
|
280
|
-
message: resolved ? 'Challenge
|
|
280
|
+
message: resolved ? 'Challenge resolved successfully.' : 'Challenge resolution failed. Please handle it manually in the browser.',
|
|
281
281
|
};
|
|
282
282
|
},
|
|
283
283
|
|
|
@@ -33,7 +33,7 @@ const loadInstaSession = (sessionPath) => {
|
|
|
33
33
|
return Array.isArray(raw?.cookies) ? raw.cookies : null;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
// ── Rate Limit
|
|
36
|
+
// ── Rate Limit persistence (per userId) ──
|
|
37
37
|
|
|
38
38
|
const loadRateLimits = (sessionPath, userId) => {
|
|
39
39
|
const raw = readSessionFile(sessionPath);
|
|
@@ -92,7 +92,7 @@ const createInstaWithProviderSession = (askForAuthentication) => async (fn) => {
|
|
|
92
92
|
});
|
|
93
93
|
|
|
94
94
|
if (!loginResult.loggedIn) {
|
|
95
|
-
throw new Error(loginResult.message || '
|
|
95
|
+
throw new Error(loginResult.message || 'Login status could not be confirmed after session refresh.');
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
return fn();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const createSmartComment = (instaApi) => {
|
|
2
2
|
const analyzePost = async ({ shortcode }) => {
|
|
3
|
-
// 1.
|
|
3
|
+
// 1. Post detail
|
|
4
4
|
const post = await instaApi.getPostDetail(shortcode);
|
|
5
5
|
const caption = post.caption || '';
|
|
6
6
|
const isVideo = post.isVideo;
|
|
@@ -8,15 +8,15 @@ const createSmartComment = (instaApi) => {
|
|
|
8
8
|
const thumbnailUrl = post.imageUrl;
|
|
9
9
|
const ownerUsername = post.owner?.username || '';
|
|
10
10
|
|
|
11
|
-
// 2.
|
|
11
|
+
// 2. Owner profile
|
|
12
12
|
let ownerProfile = null;
|
|
13
13
|
try {
|
|
14
14
|
ownerProfile = await instaApi.getProfile(ownerUsername);
|
|
15
15
|
} catch {
|
|
16
|
-
//
|
|
16
|
+
// Ignore failures (e.g., private account)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
// 3.
|
|
19
|
+
// 3. Thumbnail image base64 (for Claude Code Vision)
|
|
20
20
|
let thumbnailBase64 = null;
|
|
21
21
|
let thumbnailMediaType = 'image/jpeg';
|
|
22
22
|
if (thumbnailUrl) {
|
|
@@ -29,15 +29,15 @@ const createSmartComment = (instaApi) => {
|
|
|
29
29
|
if (ct) thumbnailMediaType = ct;
|
|
30
30
|
}
|
|
31
31
|
} catch {
|
|
32
|
-
//
|
|
32
|
+
// Proceed with caption only on failure
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
const contentType = isVideo
|
|
37
|
-
? '
|
|
37
|
+
? 'video (reel)'
|
|
38
38
|
: mediaType?.includes('Sidecar')
|
|
39
|
-
? '
|
|
40
|
-
: '
|
|
39
|
+
? 'carousel (multiple images)'
|
|
40
|
+
: 'photo';
|
|
41
41
|
|
|
42
42
|
return {
|
|
43
43
|
shortcode,
|
|
@@ -10,10 +10,10 @@ const readInstaCredentials = () => {
|
|
|
10
10
|
const parseInstaSessionError = (error) => {
|
|
11
11
|
const message = String(error?.message || '').toLowerCase();
|
|
12
12
|
return [
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
13
|
+
'no session file found',
|
|
14
|
+
'no valid cookies in session',
|
|
15
|
+
'session expired',
|
|
16
|
+
'login required',
|
|
17
17
|
'login_required',
|
|
18
18
|
'checkpoint_required',
|
|
19
19
|
'401',
|
|
@@ -22,7 +22,7 @@ const parseInstaSessionError = (error) => {
|
|
|
22
22
|
].some((token) => message.includes(token.toLowerCase()));
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
-
const buildLoginErrorMessage = (error) => String(error?.message || '
|
|
25
|
+
const buildLoginErrorMessage = (error) => String(error?.message || 'Session validation failed.');
|
|
26
26
|
|
|
27
27
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
28
28
|
|