instauto 9.1.3 → 9.1.6
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/index.js +73 -34
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -45,6 +45,8 @@ const Instauto = async (db, browser, options) => {
|
|
|
45
45
|
followUserMinFollowers = null,
|
|
46
46
|
followUserMinFollowing = null,
|
|
47
47
|
|
|
48
|
+
shouldFollowUser = null,
|
|
49
|
+
|
|
48
50
|
dontUnfollowUntilTimeElapsed = 3 * 24 * 60 * 60 * 1000,
|
|
49
51
|
|
|
50
52
|
excludeUsers = [],
|
|
@@ -58,6 +60,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
58
60
|
} = options;
|
|
59
61
|
|
|
60
62
|
let myUsername = myUsernameIn;
|
|
63
|
+
const userDataCache = {};
|
|
61
64
|
|
|
62
65
|
assert(cookiesPath);
|
|
63
66
|
assert(db);
|
|
@@ -168,15 +171,20 @@ const Instauto = async (db, browser, options) => {
|
|
|
168
171
|
}
|
|
169
172
|
|
|
170
173
|
async function gotoWithRetry(url) {
|
|
174
|
+
const maxAttempts = 3;
|
|
171
175
|
for (let attempt = 0; ; attempt += 1) {
|
|
172
176
|
logger.log(`Goto ${url}`);
|
|
173
177
|
const response = await page.goto(url);
|
|
174
|
-
await sleep(
|
|
178
|
+
await sleep(2000);
|
|
175
179
|
const status = response.status();
|
|
176
180
|
|
|
177
181
|
// https://www.reddit.com/r/Instagram/comments/kwrt0s/error_560/
|
|
178
182
|
// https://github.com/mifi/instauto/issues/60
|
|
179
|
-
if (![560, 429].includes(status)
|
|
183
|
+
if (![560, 429].includes(status)) return status;
|
|
184
|
+
|
|
185
|
+
if (attempt > maxAttempts) {
|
|
186
|
+
throw new Error(`Navigate to user failed after ${maxAttempts} attempts, last status: ${status}`);
|
|
187
|
+
}
|
|
180
188
|
|
|
181
189
|
logger.info(`Got ${status} - Retrying request later...`);
|
|
182
190
|
if (status === 429) logger.warn('429 Too Many Requests could mean that Instagram suspects you\'re using a bot. You could try to use the Instagram Mobile app from the same IP for a few days first');
|
|
@@ -184,42 +192,61 @@ const Instauto = async (db, browser, options) => {
|
|
|
184
192
|
}
|
|
185
193
|
}
|
|
186
194
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
// example: https://www.instagram.com/victorialarson__/
|
|
194
|
-
// so we check if the page has the user's name on it
|
|
195
|
-
return page.evaluate((username) => window.find(username), checkPageForUsername);
|
|
196
|
-
}
|
|
197
|
-
return true;
|
|
198
|
-
}
|
|
199
|
-
if (status === 404) {
|
|
200
|
-
logger.log('User not found');
|
|
201
|
-
return false;
|
|
202
|
-
}
|
|
203
|
-
throw new Error(`Navigate to user failed with status ${status}`);
|
|
195
|
+
const getUserPageUrl = (username) => `${instagramBaseUrl}/${encodeURIComponent(username)}`;
|
|
196
|
+
|
|
197
|
+
function isAlreadyOnUserPage(username) {
|
|
198
|
+
const url = getUserPageUrl(username);
|
|
199
|
+
// optimization: already on URL? (ignore trailing slash)
|
|
200
|
+
return (page.url().replace(/\/$/, '') === url.replace(/\/$/, ''));
|
|
204
201
|
}
|
|
205
202
|
|
|
206
203
|
async function navigateToUser(username) {
|
|
207
|
-
|
|
208
|
-
|
|
204
|
+
if (isAlreadyOnUserPage(username)) return true;
|
|
205
|
+
|
|
209
206
|
// logger.log('navigating from', page.url(), 'to', url);
|
|
210
207
|
logger.log(`Navigating to user ${username}`);
|
|
211
|
-
|
|
208
|
+
|
|
209
|
+
const url = getUserPageUrl(username);
|
|
210
|
+
const status = await gotoWithRetry(url);
|
|
211
|
+
if (status === 404) {
|
|
212
|
+
logger.warn('User page returned 404');
|
|
213
|
+
return false;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (status === 200) {
|
|
217
|
+
// some pages return 200 but nothing there (I think deleted accounts)
|
|
218
|
+
// https://github.com/mifi/SimpleInstaBot/issues/48
|
|
219
|
+
// example: https://www.instagram.com/victorialarson__/
|
|
220
|
+
// so we check if the page has the user's name on it
|
|
221
|
+
const foundUsernameOnPage = await page.evaluate((u) => window.find(u), username);
|
|
222
|
+
if (!foundUsernameOnPage) logger.warn(`Cannot find "${username}" on page`);
|
|
223
|
+
return foundUsernameOnPage;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
throw new Error(`Navigate to user failed with status ${status}`);
|
|
212
227
|
}
|
|
213
228
|
|
|
214
229
|
async function navigateToUserWithCheck(username) {
|
|
215
230
|
if (!(await navigateToUser(username))) throw new Error('User not found');
|
|
216
231
|
}
|
|
217
232
|
|
|
218
|
-
async function getPageJson() {
|
|
219
|
-
return JSON.parse(await (await (await page.$('pre')).getProperty('textContent')).jsonValue());
|
|
220
|
-
}
|
|
221
|
-
|
|
222
233
|
async function navigateToUserAndGetData(username) {
|
|
234
|
+
const cachedUserData = userDataCache[username];
|
|
235
|
+
|
|
236
|
+
if (isAlreadyOnUserPage(username)) {
|
|
237
|
+
// assume we have data
|
|
238
|
+
return cachedUserData;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
if (cachedUserData != null) {
|
|
242
|
+
// if we already have userData, just navigate
|
|
243
|
+
await navigateToUserWithCheck(username);
|
|
244
|
+
return cachedUserData;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// intercept special XHR network request that fetches user's data and store it in a cache
|
|
248
|
+
// TODO fallback to DOM to get user ID if this request fails?
|
|
249
|
+
// https://github.com/mifi/SimpleInstaBot/issues/125#issuecomment-1145354294
|
|
223
250
|
const [foundResponse] = await Promise.all([
|
|
224
251
|
page.waitForResponse((response) => {
|
|
225
252
|
const request = response.request();
|
|
@@ -230,7 +257,13 @@ const Instauto = async (db, browser, options) => {
|
|
|
230
257
|
]);
|
|
231
258
|
|
|
232
259
|
const json = JSON.parse(await foundResponse.text());
|
|
233
|
-
|
|
260
|
+
const userData = json.data.user;
|
|
261
|
+
userDataCache[username] = userData;
|
|
262
|
+
return userData;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
async function getPageJson() {
|
|
266
|
+
return JSON.parse(await (await (await page.$('pre')).getProperty('textContent')).jsonValue());
|
|
234
267
|
}
|
|
235
268
|
|
|
236
269
|
async function isActionBlocked() {
|
|
@@ -300,7 +333,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
300
333
|
}
|
|
301
334
|
|
|
302
335
|
async function followUser(username) {
|
|
303
|
-
await
|
|
336
|
+
await navigateToUserAndGetData(username);
|
|
304
337
|
const elementHandle = await findFollowButton();
|
|
305
338
|
|
|
306
339
|
if (!elementHandle) {
|
|
@@ -342,7 +375,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
342
375
|
// See https://github.com/timgrossmann/InstaPy/pull/2345
|
|
343
376
|
// https://github.com/timgrossmann/InstaPy/issues/2355
|
|
344
377
|
async function unfollowUser(username) {
|
|
345
|
-
await
|
|
378
|
+
await navigateToUserAndGetData(username);
|
|
346
379
|
logger.log(`Unfollowing user ${username}`);
|
|
347
380
|
|
|
348
381
|
const res = { username, time: new Date().getTime() };
|
|
@@ -520,7 +553,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
520
553
|
|
|
521
554
|
await window.instautoSleep(3000);
|
|
522
555
|
|
|
523
|
-
const closeButtonChild = document.querySelector('
|
|
556
|
+
const closeButtonChild = document.querySelector('svg[aria-label="Close"]');
|
|
524
557
|
|
|
525
558
|
if (!closeButtonChild) throw new Error('Close button not found (aria-label)');
|
|
526
559
|
|
|
@@ -541,7 +574,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
541
574
|
async function likeUserImages({ username, likeImagesMin, likeImagesMax } = {}) {
|
|
542
575
|
if (!likeImagesMin || !likeImagesMax || likeImagesMax < likeImagesMin || likeImagesMin < 1) throw new Error('Invalid arguments');
|
|
543
576
|
|
|
544
|
-
await
|
|
577
|
+
await navigateToUserAndGetData(username);
|
|
545
578
|
|
|
546
579
|
logger.log(`Liking ${likeImagesMin}-${likeImagesMax} user images`);
|
|
547
580
|
try {
|
|
@@ -563,9 +596,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
563
596
|
|
|
564
597
|
const graphqlUser = await navigateToUserAndGetData(username);
|
|
565
598
|
|
|
566
|
-
const followedByCount = graphqlUser
|
|
567
|
-
const followsCount = graphqlUser.edge_follow.count;
|
|
568
|
-
const isPrivate = graphqlUser.is_private;
|
|
599
|
+
const { edge_followed_by: { count: followedByCount }, edge_follow: { count: followsCount }, is_private: isPrivate, is_verified: isVerified, is_business_account: isBusinessAccount, is_professional_account: isProfessionalAccount, full_name: fullName, biography, profile_pic_url_hd: profilePicUrlHd, external_url: externalUrl, business_category_name: businessCategoryName, category_name: categoryName } = graphqlUser;
|
|
569
600
|
|
|
570
601
|
// logger.log('followedByCount:', followedByCount, 'followsCount:', followsCount);
|
|
571
602
|
|
|
@@ -591,6 +622,10 @@ const Instauto = async (db, browser, options) => {
|
|
|
591
622
|
logger.log('User has too many followers compared to follows or opposite, skipping');
|
|
592
623
|
return false;
|
|
593
624
|
}
|
|
625
|
+
if (shouldFollowUser !== null && (typeof shouldFollowUser === 'function' && !shouldFollowUser({ username, isVerified, isBusinessAccount, isProfessionalAccount, fullName, biography, profilePicUrlHd, externalUrl, businessCategoryName, categoryName }) === true)) {
|
|
626
|
+
logger.log(`Custom follow logic returned false for ${username}, skipping`);
|
|
627
|
+
return false;
|
|
628
|
+
}
|
|
594
629
|
|
|
595
630
|
await followUser(username);
|
|
596
631
|
|
|
@@ -686,6 +721,8 @@ const Instauto = async (db, browser, options) => {
|
|
|
686
721
|
const userFound = await navigateToUser(username);
|
|
687
722
|
|
|
688
723
|
if (!userFound) {
|
|
724
|
+
// to avoid repeatedly unfollowing failed users, flag them as already unfollowed
|
|
725
|
+
logger.log('User not found for unfollow');
|
|
689
726
|
await addPrevUnfollowedUser({ username, time: new Date().getTime(), noActionTaken: true });
|
|
690
727
|
await sleep(3000);
|
|
691
728
|
} else {
|
|
@@ -720,6 +757,8 @@ const Instauto = async (db, browser, options) => {
|
|
|
720
757
|
}
|
|
721
758
|
}
|
|
722
759
|
|
|
760
|
+
logger.log('Done with unfollowing', i, j);
|
|
761
|
+
|
|
723
762
|
return j;
|
|
724
763
|
}
|
|
725
764
|
|