instauto 9.1.0 → 9.1.3

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/README.md CHANGED
@@ -25,7 +25,7 @@ Now there is a GUI application for those who don't want to code: [SimpleInstaBot
25
25
 
26
26
  You can run this code for example once every day using cron or pm2 or similar
27
27
 
28
- See [index.js](https://github.com/mifi/instauto/blob/master/index.js) for available options.
28
+ See [index.js](https://github.com/mifi/instauto/blob/master/src/index.js) for available options.
29
29
 
30
30
  ## Supported functionality
31
31
 
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "instauto",
3
- "version": "9.1.0",
3
+ "version": "9.1.3",
4
4
  "description": "Instagram automation library written in Node.js",
5
- "main": "index.js",
5
+ "main": "src/index.js",
6
6
  "scripts": {
7
7
  "lint": "eslint .",
8
8
  "test": "exit 0"
9
9
  },
10
+ "files": [
11
+ "src"
12
+ ],
10
13
  "author": "Mikael Finstad <finstaden@gmail.com>",
11
14
  "repository": "mifi/instauto",
12
15
  "license": "MIT",
File without changes
@@ -73,7 +73,6 @@ const Instauto = async (db, browser, options) => {
73
73
 
74
74
  // State
75
75
  let page;
76
- let graphqlUserMissing = false;
77
76
 
78
77
  async function takeScreenshot() {
79
78
  if (!screenshotOnError) return;
@@ -221,37 +220,17 @@ const Instauto = async (db, browser, options) => {
221
220
  }
222
221
 
223
222
  async function navigateToUserAndGetData(username) {
224
- // https://github.com/mifi/SimpleInstaBot/issues/36
225
- if (graphqlUserMissing) {
226
- // https://stackoverflow.com/questions/37593025/instagram-api-get-the-userid
227
- // https://stackoverflow.com/questions/17373886/how-can-i-get-a-users-media-from-instagram-without-authenticating-as-a-user
228
- const found = await safeGotoUser(`${instagramBaseUrl}/${encodeURIComponent(username)}?__a=1`);
229
- if (!found) throw new Error('User not found');
230
-
231
- const json = await getPageJson();
232
-
233
- const { user } = json.graphql;
234
-
235
- await navigateToUserWithCheck(username);
236
- return user;
237
- }
238
-
239
- await navigateToUserWithCheck(username);
240
-
241
- // eslint-disable-next-line no-underscore-dangle
242
- const sharedData = await page.evaluate(() => window._sharedData);
243
- try {
244
- // eslint-disable-next-line prefer-destructuring
245
- return sharedData.entry_data.ProfilePage[0].graphql.user;
246
-
247
- // JSON.parse(Array.from(document.getElementsByTagName('script')).find(el => el.innerHTML.startsWith('window.__additionalDataLoaded(\'feed\',')).innerHTML.replace(/^window.__additionalDataLoaded\('feed',({.*})\);$/, '$1'));
248
- // JSON.parse(Array.from(document.getElementsByTagName('script')).find(el => el.innerHTML.startsWith('window._sharedData')).innerHTML.replace(/^window._sharedData ?= ?({.*});$/, '$1'));
249
- // Array.from(document.getElementsByTagName('a')).find(el => el.attributes?.href?.value.includes(`${username}/followers`)).innerText
250
- } catch (err) {
251
- logger.warn('Missing graphql in page, falling back to alternative method...');
252
- graphqlUserMissing = true; // Store as state so we don't have to do this every time from now on.
253
- return navigateToUserAndGetData(username); // Now try again with alternative method
254
- }
223
+ const [foundResponse] = await Promise.all([
224
+ page.waitForResponse((response) => {
225
+ const request = response.request();
226
+ return request.method() === 'GET' && new RegExp(`https:\\/\\/i\\.instagram\\.com\\/api\\/v1\\/users\\/web_profile_info\\/\\?username=${encodeURIComponent(username.toLowerCase())}`).test(request.url());
227
+ }),
228
+ navigateToUserWithCheck(username),
229
+ // page.waitForNavigation({ waitUntil: 'networkidle0' }),
230
+ ]);
231
+
232
+ const json = JSON.parse(await foundResponse.text());
233
+ return json.data.user;
255
234
  }
256
235
 
257
236
  async function isActionBlocked() {
@@ -309,6 +288,9 @@ const Instauto = async (db, browser, options) => {
309
288
  const elementHandles3 = await page.$x("//header//button[*//span[@aria-label='Following']]");
310
289
  if (elementHandles3.length > 0) return elementHandles3[0];
311
290
 
291
+ const elementHandles4 = await page.$x("//header//button[*//*[name()='svg'][@aria-label='Following']]");
292
+ if (elementHandles4.length > 0) return elementHandles4[0];
293
+
312
294
  return undefined;
313
295
  }
314
296
 
@@ -578,6 +560,7 @@ const Instauto = async (db, browser, options) => {
578
560
  logger.log('Skipping already followed user', username);
579
561
  return false;
580
562
  }
563
+
581
564
  const graphqlUser = await navigateToUserAndGetData(username);
582
565
 
583
566
  const followedByCount = graphqlUser.edge_followed_by.count;
@@ -629,9 +612,9 @@ const Instauto = async (db, browser, options) => {
629
612
 
630
613
  let numFollowedForThisUser = 0;
631
614
 
632
- const userData = await navigateToUserAndGetData(username);
615
+ const { id: userId } = await navigateToUserAndGetData(username);
633
616
 
634
- for await (const followersBatch of getFollowersOrFollowingGenerator({ userId: userData.id, getFollowers: true })) {
617
+ for await (const followersBatch of getFollowersOrFollowingGenerator({ userId, getFollowers: true })) {
635
618
  logger.log('User followers batch', followersBatch);
636
619
 
637
620
  for (const follower of followersBatch) {
@@ -643,13 +626,13 @@ const Instauto = async (db, browser, options) => {
643
626
  return;
644
627
  }
645
628
 
646
- if (enableFollow) {
647
- if (await followUserRespectingRestrictions({ username: follower, skipPrivate })) {
648
- numFollowedForThisUser += 1;
649
- }
650
- }
629
+ let didActuallyFollow = false;
630
+ if (enableFollow) didActuallyFollow = await followUserRespectingRestrictions({ username: follower, skipPrivate });
631
+ if (didActuallyFollow) numFollowedForThisUser += 1;
632
+
633
+ const didFailToFollow = enableFollow && !didActuallyFollow;
651
634
 
652
- if (enableLikeImages) {
635
+ if (enableLikeImages && !didFailToFollow) {
653
636
  // Note: throws error if user isPrivate
654
637
  await likeUserImages({ username: follower, likeImagesMin, likeImagesMax });
655
638
  }
@@ -934,16 +917,14 @@ const Instauto = async (db, browser, options) => {
934
917
  throw new Error('Don\'t know what\'s my username');
935
918
  }
936
919
 
937
- const myUserData = await navigateToUserAndGetData(myUsername);
938
- const myUserId = myUserData.id;
920
+ const { id: myUserId } = await navigateToUserAndGetData(myUsername);
939
921
 
940
922
  // --- END OF INITIALIZATION
941
923
 
942
924
  async function doesUserFollowMe(username) {
943
925
  try {
944
926
  logger.info('Checking if user', username, 'follows us');
945
- const userData = await navigateToUserAndGetData(username);
946
- const userId = userData.id;
927
+ const { id: userId } = await navigateToUserAndGetData(username);
947
928
 
948
929
  const elementHandles = await page.$x("//a[contains(.,' following')][contains(@href,'/following')]");
949
930
  if (elementHandles.length === 0) throw new Error('Following button not found');
package/.eslintrc DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "env": {
3
- "node": true
4
- },
5
- "extends": "airbnb/base",
6
- "parserOptions": {
7
- "sourceType": "script",
8
- "ecmaVersion": 2022
9
- },
10
- "globals": {
11
- "window": true,
12
- "document": true
13
- },
14
- "rules": {
15
- "max-len": 0,
16
- "arrow-parens": 0,
17
- "no-console": 0,
18
- "no-await-in-loop": 0,
19
- "object-curly-newline": 0,
20
- 'no-restricted-syntax': [
21
- 'error',
22
- {
23
- selector: 'ForInStatement',
24
- message: 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
25
- },
26
- {
27
- selector: 'LabeledStatement',
28
- message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
29
- },
30
- {
31
- selector: 'WithStatement',
32
- message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
33
- },
34
- ],
35
- }
36
- }
@@ -1,2 +0,0 @@
1
- github: mifi
2
- custom: https://mifi.no/thanks
package/example.js DELETED
@@ -1,107 +0,0 @@
1
- 'use strict';
2
-
3
- const puppeteer = require('puppeteer'); // eslint-disable-line import/no-extraneous-dependencies
4
-
5
- const Instauto = require('instauto'); // eslint-disable-line import/no-unresolved
6
-
7
- // Optional: Custom logger with timestamps
8
- const log = (fn, ...args) => console[fn](new Date().toISOString(), ...args);
9
- const logger = Object.fromEntries(['log', 'info', 'debug', 'error', 'trace', 'warn'].map((fn) => [fn, (...args) => log(fn, ...args)]));
10
-
11
- const options = {
12
- cookiesPath: './cookies.json',
13
-
14
- username: 'your-ig-username',
15
- password: 'your-ig-password',
16
-
17
- // Global limit that prevents follow or unfollows (total) to exceed this number over a sliding window of one hour:
18
- maxFollowsPerHour: 20,
19
- // Global limit that prevents follow or unfollows (total) to exceed this number over a sliding window of one day:
20
- maxFollowsPerDay: 150,
21
- // (NOTE setting the above parameters too high will cause temp ban/throttle)
22
-
23
- maxLikesPerDay: 50,
24
-
25
- // Don't follow users that have a followers / following ratio less than this:
26
- followUserRatioMin: 0.2,
27
- // Don't follow users that have a followers / following ratio higher than this:
28
- followUserRatioMax: 4.0,
29
- // Don't follow users who have more followers than this:
30
- followUserMaxFollowers: null,
31
- // Don't follow users who have more people following them than this:
32
- followUserMaxFollowing: null,
33
- // Don't follow users who have less followers than this:
34
- followUserMinFollowers: null,
35
- // Don't follow users who have more people following them than this:
36
- followUserMinFollowing: null,
37
-
38
- // NOTE: The dontUnfollowUntilTimeElapsed option is ONLY for the unfollowNonMutualFollowers function
39
- // This specifies the time during which the bot should not touch users that it has previously followed (in milliseconds)
40
- // After this time has passed, it will be able to unfollow them again.
41
- // TODO should remove this option from here
42
- dontUnfollowUntilTimeElapsed: 3 * 24 * 60 * 60 * 1000,
43
-
44
- // Usernames that we should not touch, e.g. your friends and actual followings
45
- excludeUsers: [],
46
-
47
- // If true, will not do any actions (defaults to true)
48
- dryRun: false,
49
-
50
- logger,
51
- };
52
-
53
- (async () => {
54
- let browser;
55
-
56
- try {
57
- browser = await puppeteer.launch({ headless: false });
58
-
59
- // Create a database where state will be loaded/saved to
60
- const instautoDb = await Instauto.JSONDB({
61
- // Will store a list of all users that have been followed before, to prevent future re-following.
62
- followedDbPath: './followed.json',
63
- // Will store all unfollowed users here
64
- unfollowedDbPath: './unfollowed.json',
65
- // Will store all likes here
66
- likedPhotosDbPath: './liked-photos.json',
67
- });
68
-
69
- const instauto = await Instauto(instautoDb, browser, options);
70
-
71
- // This can be used to unfollow people:
72
- // Will unfollow auto-followed AND manually followed accounts who are not following us back, after some time has passed
73
- // The time is specified by config option dontUnfollowUntilTimeElapsed
74
- // await instauto.unfollowNonMutualFollowers();
75
- // await instauto.sleep(10 * 60 * 1000);
76
-
77
- // Unfollow previously auto-followed users (regardless of whether or not they are following us back)
78
- // after a certain amount of days (2 weeks)
79
- // Leave room to do following after this too (unfollow 2/3 of maxFollowsPerDay)
80
- const unfollowedCount = await instauto.unfollowOldFollowed({ ageInDays: 14, limit: options.maxFollowsPerDay * (2 / 3) });
81
-
82
- if (unfollowedCount > 0) await instauto.sleep(10 * 60 * 1000);
83
-
84
- // List of usernames that we should follow the followers of, can be celebrities etc.
85
- const usersToFollowFollowersOf = ['lostleblanc', 'sam_kolder'];
86
-
87
- // Now go through each of these and follow a certain amount of their followers
88
- await instauto.followUsersFollowers({
89
- usersToFollowFollowersOf,
90
- maxFollowsTotal: options.maxFollowsPerDay - unfollowedCount,
91
- skipPrivate: true,
92
- enableLikeImages: true,
93
- likeImagesMax: 3,
94
- });
95
-
96
- await instauto.sleep(10 * 60 * 1000);
97
-
98
- console.log('Done running');
99
-
100
- await instauto.sleep(30000);
101
- } catch (err) {
102
- console.error(err);
103
- } finally {
104
- console.log('Closing browser');
105
- if (browser) await browser.close();
106
- }
107
- })();
package/instabot.yml DELETED
@@ -1,5 +0,0 @@
1
- apps:
2
- - script: example.js
3
- name: instabot
4
- restart_delay: 60000
5
- log_date_format: 'YYYY-MM-DD HH:mm Z'
package/logo.png DELETED
Binary file