instauto 9.1.3 → 9.1.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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +65 -30
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instauto",
3
- "version": "9.1.3",
3
+ "version": "9.1.4",
4
4
  "description": "Instagram automation library written in Node.js",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
package/src/index.js CHANGED
@@ -58,6 +58,7 @@ const Instauto = async (db, browser, options) => {
58
58
  } = options;
59
59
 
60
60
  let myUsername = myUsernameIn;
61
+ const userDataCache = {};
61
62
 
62
63
  assert(cookiesPath);
63
64
  assert(db);
@@ -168,15 +169,20 @@ const Instauto = async (db, browser, options) => {
168
169
  }
169
170
 
170
171
  async function gotoWithRetry(url) {
172
+ const maxAttempts = 3;
171
173
  for (let attempt = 0; ; attempt += 1) {
172
174
  logger.log(`Goto ${url}`);
173
175
  const response = await page.goto(url);
174
- await sleep(1000);
176
+ await sleep(2000);
175
177
  const status = response.status();
176
178
 
177
179
  // https://www.reddit.com/r/Instagram/comments/kwrt0s/error_560/
178
180
  // https://github.com/mifi/instauto/issues/60
179
- if (![560, 429].includes(status) || attempt > 3) return status;
181
+ if (![560, 429].includes(status)) return status;
182
+
183
+ if (attempt > maxAttempts) {
184
+ throw new Error(`Navigate to user failed after ${maxAttempts} attempts, last status: ${status}`);
185
+ }
180
186
 
181
187
  logger.info(`Got ${status} - Retrying request later...`);
182
188
  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 +190,61 @@ const Instauto = async (db, browser, options) => {
184
190
  }
185
191
  }
186
192
 
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}`);
193
+ const getUserPageUrl = (username) => `${instagramBaseUrl}/${encodeURIComponent(username)}`;
194
+
195
+ function isAlreadyOnUserPage(username) {
196
+ const url = getUserPageUrl(username);
197
+ // optimization: already on URL? (ignore trailing slash)
198
+ return (page.url().replace(/\/$/, '') === url.replace(/\/$/, ''));
204
199
  }
205
200
 
206
201
  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)
202
+ if (isAlreadyOnUserPage(username)) return true;
203
+
209
204
  // logger.log('navigating from', page.url(), 'to', url);
210
205
  logger.log(`Navigating to user ${username}`);
211
- return safeGotoUser(url, username);
206
+
207
+ const url = getUserPageUrl(username);
208
+ const status = await gotoWithRetry(url);
209
+ if (status === 404) {
210
+ logger.warn('User page returned 404');
211
+ return false;
212
+ }
213
+
214
+ if (status === 200) {
215
+ // some pages return 200 but nothing there (I think deleted accounts)
216
+ // https://github.com/mifi/SimpleInstaBot/issues/48
217
+ // example: https://www.instagram.com/victorialarson__/
218
+ // so we check if the page has the user's name on it
219
+ const foundUsernameOnPage = await page.evaluate((u) => window.find(u), username);
220
+ if (!foundUsernameOnPage) logger.warn(`Cannot find "${username}" on page`);
221
+ return foundUsernameOnPage;
222
+ }
223
+
224
+ throw new Error(`Navigate to user failed with status ${status}`);
212
225
  }
213
226
 
214
227
  async function navigateToUserWithCheck(username) {
215
228
  if (!(await navigateToUser(username))) throw new Error('User not found');
216
229
  }
217
230
 
218
- async function getPageJson() {
219
- return JSON.parse(await (await (await page.$('pre')).getProperty('textContent')).jsonValue());
220
- }
221
-
222
231
  async function navigateToUserAndGetData(username) {
232
+ const cachedUserData = userDataCache[username];
233
+
234
+ if (isAlreadyOnUserPage(username)) {
235
+ // assume we have data
236
+ return cachedUserData;
237
+ }
238
+
239
+ if (cachedUserData != null) {
240
+ // if we already have userData, just navigate
241
+ await navigateToUserWithCheck(username);
242
+ return cachedUserData;
243
+ }
244
+
245
+ // intercept special XHR network request that fetches user's data and store it in a cache
246
+ // TODO fallback to DOM to get user ID if this request fails?
247
+ // https://github.com/mifi/SimpleInstaBot/issues/125#issuecomment-1145354294
223
248
  const [foundResponse] = await Promise.all([
224
249
  page.waitForResponse((response) => {
225
250
  const request = response.request();
@@ -230,7 +255,13 @@ const Instauto = async (db, browser, options) => {
230
255
  ]);
231
256
 
232
257
  const json = JSON.parse(await foundResponse.text());
233
- return json.data.user;
258
+ const userData = json.data.user;
259
+ userDataCache[username] = userData;
260
+ return userData;
261
+ }
262
+
263
+ async function getPageJson() {
264
+ return JSON.parse(await (await (await page.$('pre')).getProperty('textContent')).jsonValue());
234
265
  }
235
266
 
236
267
  async function isActionBlocked() {
@@ -300,7 +331,7 @@ const Instauto = async (db, browser, options) => {
300
331
  }
301
332
 
302
333
  async function followUser(username) {
303
- await navigateToUserWithCheck(username);
334
+ await navigateToUserAndGetData(username);
304
335
  const elementHandle = await findFollowButton();
305
336
 
306
337
  if (!elementHandle) {
@@ -342,7 +373,7 @@ const Instauto = async (db, browser, options) => {
342
373
  // See https://github.com/timgrossmann/InstaPy/pull/2345
343
374
  // https://github.com/timgrossmann/InstaPy/issues/2355
344
375
  async function unfollowUser(username) {
345
- await navigateToUserWithCheck(username);
376
+ await navigateToUserAndGetData(username);
346
377
  logger.log(`Unfollowing user ${username}`);
347
378
 
348
379
  const res = { username, time: new Date().getTime() };
@@ -541,7 +572,7 @@ const Instauto = async (db, browser, options) => {
541
572
  async function likeUserImages({ username, likeImagesMin, likeImagesMax } = {}) {
542
573
  if (!likeImagesMin || !likeImagesMax || likeImagesMax < likeImagesMin || likeImagesMin < 1) throw new Error('Invalid arguments');
543
574
 
544
- await navigateToUserWithCheck(username);
575
+ await navigateToUserAndGetData(username);
545
576
 
546
577
  logger.log(`Liking ${likeImagesMin}-${likeImagesMax} user images`);
547
578
  try {
@@ -686,6 +717,8 @@ const Instauto = async (db, browser, options) => {
686
717
  const userFound = await navigateToUser(username);
687
718
 
688
719
  if (!userFound) {
720
+ // to avoid repeatedly unfollowing failed users, flag them as already unfollowed
721
+ logger.log('User not found for unfollow');
689
722
  await addPrevUnfollowedUser({ username, time: new Date().getTime(), noActionTaken: true });
690
723
  await sleep(3000);
691
724
  } else {
@@ -720,6 +753,8 @@ const Instauto = async (db, browser, options) => {
720
753
  }
721
754
  }
722
755
 
756
+ logger.log('Done with unfollowing', i, j);
757
+
723
758
  return j;
724
759
  }
725
760