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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +73 -34
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instauto",
3
- "version": "9.1.3",
3
+ "version": "9.1.6",
4
4
  "description": "Instagram automation library written in Node.js",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
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(1000);
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) || attempt > 3) return 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
- async function safeGotoUser(url, checkPageForUsername) {
188
- const status = await gotoWithRetry(url);
189
- if (status === 200) {
190
- if (checkPageForUsername != null) {
191
- // some pages return 200 but nothing there (I think deleted accounts)
192
- // https://github.com/mifi/SimpleInstaBot/issues/48
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
- const url = `${instagramBaseUrl}/${encodeURIComponent(username)}`;
208
- if (page.url().replace(/\/$/, '') === url.replace(/\/$/, '')) return true; // optimization: already on URL? (ignore trailing slash)
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
- return safeGotoUser(url, username);
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
- return json.data.user;
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 navigateToUserWithCheck(username);
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 navigateToUserWithCheck(username);
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('button [aria-label=Close]');
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 navigateToUserWithCheck(username);
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.edge_followed_by.count;
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