instauto 8.0.1 → 9.1.1

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
@@ -9,6 +9,7 @@ Now there is a GUI application for those who don't want to code: [SimpleInstaBot
9
9
  ## Setup
10
10
 
11
11
  - First install [Node.js](https://nodejs.org/en/) 8 or newer.
12
+ - On MacOS, it's recommended to use [homebrew](https://brew.sh/): `brew install node`
12
13
 
13
14
  - Create a new directory with a file like [example.js](https://github.com/mifi/instauto/blob/master/example.js)
14
15
 
package/package.json CHANGED
@@ -1,12 +1,15 @@
1
1
  {
2
2
  "name": "instauto",
3
- "version": "8.0.1",
3
+ "version": "9.1.1",
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
@@ -136,28 +136,30 @@ const Instauto = async (db, browser, options) => {
136
136
  }
137
137
 
138
138
  async function checkReachedFollowedUserDayLimit() {
139
- const reachedFollowedUserDayLimit = getNumFollowedUsersThisTimeUnit(dayMs) >= maxFollowsPerDay;
140
- if (reachedFollowedUserDayLimit) {
139
+ if (getNumFollowedUsersThisTimeUnit(dayMs) >= maxFollowsPerDay) {
141
140
  logger.log('Have reached daily follow/unfollow limit, waiting 10 min');
142
141
  await sleep(10 * 60 * 1000);
143
142
  }
144
143
  }
145
144
 
146
145
  async function checkReachedFollowedUserHourLimit() {
147
- const hasReachedFollowedUserHourLimit = getNumFollowedUsersThisTimeUnit(hourMs) >= maxFollowsPerHour;
148
- if (hasReachedFollowedUserHourLimit) {
146
+ if (getNumFollowedUsersThisTimeUnit(hourMs) >= maxFollowsPerHour) {
149
147
  logger.log('Have reached hourly follow rate limit, pausing 10 min');
150
148
  await sleep(10 * 60 * 1000);
151
149
  }
152
150
  }
153
151
 
152
+ async function checkReachedLikedUserDayLimit() {
153
+ if (getNumLikesThisTimeUnit(dayMs) >= maxLikesPerDay) {
154
+ logger.log('Have reached daily like rate limit, pausing 10 min');
155
+ await sleep(10 * 60 * 1000);
156
+ }
157
+ }
158
+
154
159
  async function throttle() {
155
160
  await checkReachedFollowedUserDayLimit();
156
161
  await checkReachedFollowedUserHourLimit();
157
- }
158
-
159
- function hasReachedDailyLikesLimit() {
160
- return getNumLikesThisTimeUnit(dayMs) >= maxLikesPerDay;
162
+ await checkReachedLikedUserDayLimit();
161
163
  }
162
164
 
163
165
  function haveRecentlyFollowedUser(username) {
@@ -210,6 +212,10 @@ const Instauto = async (db, browser, options) => {
210
212
  return safeGotoUser(url, username);
211
213
  }
212
214
 
215
+ async function navigateToUserWithCheck(username) {
216
+ if (!(await navigateToUser(username))) throw new Error('User not found');
217
+ }
218
+
213
219
  async function getPageJson() {
214
220
  return JSON.parse(await (await (await page.$('pre')).getProperty('textContent')).jsonValue());
215
221
  }
@@ -226,11 +232,11 @@ const Instauto = async (db, browser, options) => {
226
232
 
227
233
  const { user } = json.graphql;
228
234
 
229
- await navigateToUser(username);
235
+ await navigateToUserWithCheck(username);
230
236
  return user;
231
237
  }
232
238
 
233
- await navigateToUser(username);
239
+ await navigateToUserWithCheck(username);
234
240
 
235
241
  // eslint-disable-next-line no-underscore-dangle
236
242
  const sharedData = await page.evaluate(() => window._sharedData);
@@ -303,6 +309,9 @@ const Instauto = async (db, browser, options) => {
303
309
  const elementHandles3 = await page.$x("//header//button[*//span[@aria-label='Following']]");
304
310
  if (elementHandles3.length > 0) return elementHandles3[0];
305
311
 
312
+ const elementHandles4 = await page.$x("//header//button[*//*[name()='svg'][@aria-label='Following']]");
313
+ if (elementHandles4.length > 0) return elementHandles4[0];
314
+
306
315
  return undefined;
307
316
  }
308
317
 
@@ -311,8 +320,8 @@ const Instauto = async (db, browser, options) => {
311
320
  return elementHandles[0];
312
321
  }
313
322
 
314
- // NOTE: assumes we are on this page
315
- async function followCurrentUser(username) {
323
+ async function followUser(username) {
324
+ await navigateToUserWithCheck(username);
316
325
  const elementHandle = await findFollowButton();
317
326
 
318
327
  if (!elementHandle) {
@@ -353,7 +362,8 @@ const Instauto = async (db, browser, options) => {
353
362
 
354
363
  // See https://github.com/timgrossmann/InstaPy/pull/2345
355
364
  // https://github.com/timgrossmann/InstaPy/issues/2355
356
- async function unfollowCurrentUser(username) {
365
+ async function unfollowUser(username) {
366
+ await navigateToUserWithCheck(username);
357
367
  logger.log(`Unfollowing user ${username}`);
358
368
 
359
369
  const res = { username, time: new Date().getTime() };
@@ -549,9 +559,11 @@ const Instauto = async (db, browser, options) => {
549
559
  /* eslint-enable no-undef */
550
560
 
551
561
 
552
- async function likeCurrentUserImages({ username, likeImagesMin, likeImagesMax } = {}) {
562
+ async function likeUserImages({ username, likeImagesMin, likeImagesMax } = {}) {
553
563
  if (!likeImagesMin || !likeImagesMax || likeImagesMax < likeImagesMin || likeImagesMin < 1) throw new Error('Invalid arguments');
554
564
 
565
+ await navigateToUserWithCheck(username);
566
+
555
567
  logger.log(`Liking ${likeImagesMin}-${likeImagesMax} user images`);
556
568
  try {
557
569
  await page.exposeFunction('instautoSleep', sleep);
@@ -564,10 +576,57 @@ const Instauto = async (db, browser, options) => {
564
576
  await page.evaluate(likeCurrentUserImagesPageCode, { dryRun, likeImagesMin, likeImagesMax });
565
577
  }
566
578
 
567
- async function followUserFollowers(username, {
579
+ async function followUserRespectingRestrictions({ username, skipPrivate = false }) {
580
+ if (getPrevFollowedUser(username)) {
581
+ logger.log('Skipping already followed user', username);
582
+ return false;
583
+ }
584
+ const graphqlUser = await navigateToUserAndGetData(username);
585
+
586
+ const followedByCount = graphqlUser.edge_followed_by.count;
587
+ const followsCount = graphqlUser.edge_follow.count;
588
+ const isPrivate = graphqlUser.is_private;
589
+
590
+ // logger.log('followedByCount:', followedByCount, 'followsCount:', followsCount);
591
+
592
+ const ratio = followedByCount / (followsCount || 1);
593
+
594
+ if (isPrivate && skipPrivate) {
595
+ logger.log('User is private, skipping');
596
+ return false;
597
+ }
598
+ if (
599
+ (followUserMaxFollowers != null && followedByCount > followUserMaxFollowers) ||
600
+ (followUserMaxFollowing != null && followsCount > followUserMaxFollowing) ||
601
+ (followUserMinFollowers != null && followedByCount < followUserMinFollowers) ||
602
+ (followUserMinFollowing != null && followsCount < followUserMinFollowing)
603
+ ) {
604
+ logger.log('User has too many or too few followers or following, skipping.', 'followedByCount:', followedByCount, 'followsCount:', followsCount);
605
+ return false;
606
+ }
607
+ if (
608
+ (followUserRatioMax != null && ratio > followUserRatioMax) ||
609
+ (followUserRatioMin != null && ratio < followUserRatioMin)
610
+ ) {
611
+ logger.log('User has too many followers compared to follows or opposite, skipping');
612
+ return false;
613
+ }
614
+
615
+ await followUser(username);
616
+
617
+ await sleep(30000);
618
+ await throttle();
619
+
620
+ return true;
621
+ }
622
+
623
+ async function processUserFollowers(username, {
568
624
  maxFollowsPerUser = 5, skipPrivate = false, enableLikeImages, likeImagesMin, likeImagesMax,
569
625
  } = {}) {
570
- logger.log(`Following up to ${maxFollowsPerUser} followers of ${username}`);
626
+ const enableFollow = maxFollowsPerUser > 0;
627
+
628
+ if (enableFollow) logger.log(`Following up to ${maxFollowsPerUser} followers of ${username}`);
629
+ if (enableLikeImages) logger.log(`Liking images of up to ${likeImagesMax} followers of ${username}`);
571
630
 
572
631
  await throttle();
573
632
 
@@ -579,86 +638,52 @@ const Instauto = async (db, browser, options) => {
579
638
  logger.log('User followers batch', followersBatch);
580
639
 
581
640
  for (const follower of followersBatch) {
582
- if (getPrevFollowedUser(follower)) {
583
- logger.log('Skipping already followed user', follower);
584
- } else {
585
- try {
586
- if (numFollowedForThisUser >= maxFollowsPerUser) {
587
- logger.log('Have reached followed limit for this user, stopping');
588
- return;
589
- }
590
-
591
- const graphqlUser = await navigateToUserAndGetData(follower);
592
-
593
- const followedByCount = graphqlUser.edge_followed_by.count;
594
- const followsCount = graphqlUser.edge_follow.count;
595
- const isPrivate = graphqlUser.is_private;
596
-
597
- // logger.log('followedByCount:', followedByCount, 'followsCount:', followsCount);
598
-
599
- const ratio = followedByCount / (followsCount || 1);
600
-
601
- if (isPrivate && skipPrivate) {
602
- logger.log('User is private, skipping');
603
- } else if (
604
- (followUserMaxFollowers != null && followedByCount > followUserMaxFollowers) ||
605
- (followUserMaxFollowing != null && followsCount > followUserMaxFollowing) ||
606
- (followUserMinFollowers != null && followedByCount < followUserMinFollowers) ||
607
- (followUserMinFollowing != null && followsCount < followUserMinFollowing)
608
- ) {
609
- logger.log('User has too many or too few followers or following, skipping.', 'followedByCount:', followedByCount, 'followsCount:', followsCount);
610
- } else if (
611
- (followUserRatioMax != null && ratio > followUserRatioMax) ||
612
- (followUserRatioMin != null && ratio < followUserRatioMin)
613
- ) {
614
- logger.log('User has too many followers compared to follows or opposite, skipping');
615
- } else {
616
- await followCurrentUser(follower);
617
- numFollowedForThisUser += 1;
618
-
619
- await sleep(10000);
641
+ await throttle();
620
642
 
621
- if (!isPrivate && enableLikeImages && !hasReachedDailyLikesLimit()) {
622
- try {
623
- await likeCurrentUserImages({ username: follower, likeImagesMin, likeImagesMax });
624
- } catch (err) {
625
- logger.error(`Failed to follow user's images ${follower}`, err);
626
- await takeScreenshot();
627
- }
628
- }
643
+ try {
644
+ if (enableFollow && numFollowedForThisUser >= maxFollowsPerUser) {
645
+ logger.log('Have reached followed limit for this user, stopping');
646
+ return;
647
+ }
629
648
 
630
- await sleep(20000);
631
- await throttle();
649
+ if (enableFollow) {
650
+ if (await followUserRespectingRestrictions({ username: follower, skipPrivate })) {
651
+ numFollowedForThisUser += 1;
632
652
  }
633
- } catch (err) {
634
- logger.error(`Failed to process follower ${follower}`, err);
635
- await sleep(20000);
636
653
  }
654
+
655
+ if (enableLikeImages) {
656
+ // Note: throws error if user isPrivate
657
+ await likeUserImages({ username: follower, likeImagesMin, likeImagesMax });
658
+ }
659
+ } catch (err) {
660
+ logger.error(`Failed to process follower ${follower}`, err);
661
+ await takeScreenshot();
662
+ await sleep(20000);
637
663
  }
638
664
  }
639
665
  }
640
666
  }
641
667
 
642
- async function followUsersFollowers({ usersToFollowFollowersOf, maxFollowsTotal = 150, skipPrivate, enableLikeImages = false, likeImagesMin = 1, likeImagesMax = 2 }) {
643
- if (!maxFollowsTotal || maxFollowsTotal <= 2) {
644
- throw new Error(`Invalid parameter maxFollowsTotal ${maxFollowsTotal}`);
645
- }
646
-
647
-
668
+ async function processUsersFollowers({ usersToFollowFollowersOf, maxFollowsTotal = 150, skipPrivate, enableFollow = true, enableLikeImages = false, likeImagesMin = 1, likeImagesMax = 2 }) {
648
669
  // If maxFollowsTotal turns out to be lower than the user list size, slice off the user list
649
670
  const usersToFollowFollowersOfSliced = shuffleArray(usersToFollowFollowersOf).slice(0, maxFollowsTotal);
650
671
 
651
- // Round up or we risk following none
652
- const maxFollowsPerUser = Math.floor(maxFollowsTotal / usersToFollowFollowersOfSliced.length) + 1;
672
+ const maxFollowsPerUser = enableFollow && usersToFollowFollowersOfSliced.length > 0 ? Math.floor(maxFollowsTotal / usersToFollowFollowersOfSliced.length) : 0;
673
+
674
+ if (maxFollowsPerUser === 0 && (!enableLikeImages || likeImagesMin < 1 || likeImagesMax < 1)) {
675
+ logger.warn('Nothing to follow or like');
676
+ return;
677
+ }
653
678
 
654
679
  for (const username of usersToFollowFollowersOfSliced) {
655
680
  try {
656
- await followUserFollowers(username, { maxFollowsPerUser, skipPrivate, enableLikeImages, likeImagesMin, likeImagesMax });
681
+ await processUserFollowers(username, { maxFollowsPerUser, skipPrivate, enableLikeImages, likeImagesMin, likeImagesMax });
657
682
 
658
683
  await sleep(10 * 60 * 1000);
659
684
  await throttle();
660
685
  } catch (err) {
661
- console.error('Failed to follow user followers, continuing', err);
686
+ logger.error('Failed to process user followers, continuing', username, err);
662
687
  await takeScreenshot();
663
688
  await sleep(60 * 1000);
664
689
  }
@@ -684,7 +709,7 @@ const Instauto = async (db, browser, options) => {
684
709
  await addPrevUnfollowedUser({ username, time: new Date().getTime(), noActionTaken: true });
685
710
  await sleep(3000);
686
711
  } else {
687
- const { noActionTaken } = await unfollowCurrentUser(username);
712
+ const { noActionTaken } = await unfollowUser(username);
688
713
 
689
714
  if (noActionTaken) {
690
715
  await sleep(3000);
@@ -718,6 +743,22 @@ const Instauto = async (db, browser, options) => {
718
743
  return j;
719
744
  }
720
745
 
746
+ async function safelyFollowUserList({ users, skipPrivate, limit }) {
747
+ logger.log('Following users, up to limit', limit);
748
+
749
+ for (const username of users) {
750
+ await throttle();
751
+
752
+ try {
753
+ await followUserRespectingRestrictions({ username, skipPrivate });
754
+ } catch (err) {
755
+ logger.error(`Failed to follow user ${username}, continuing`, err);
756
+ await takeScreenshot();
757
+ await sleep(20000);
758
+ }
759
+ }
760
+ }
761
+
721
762
  function getPage() {
722
763
  return page;
723
764
  }
@@ -901,15 +942,12 @@ const Instauto = async (db, browser, options) => {
901
942
 
902
943
  // --- END OF INITIALIZATION
903
944
 
904
-
905
945
  async function doesUserFollowMe(username) {
906
946
  try {
907
947
  logger.info('Checking if user', username, 'follows us');
908
948
  const userData = await navigateToUserAndGetData(username);
909
949
  const userId = userData.id;
910
950
 
911
- if (!(await navigateToUser(username))) throw new Error('User not found');
912
-
913
951
  const elementHandles = await page.$x("//a[contains(.,' following')][contains(@href,'/following')]");
914
952
  if (elementHandles.length === 0) throw new Error('Following button not found');
915
953
 
@@ -1007,19 +1045,21 @@ const Instauto = async (db, browser, options) => {
1007
1045
  }
1008
1046
 
1009
1047
  return {
1010
- followUserFollowers,
1048
+ followUserFollowers: processUserFollowers,
1011
1049
  unfollowNonMutualFollowers,
1012
1050
  unfollowAllUnknown,
1013
1051
  unfollowOldFollowed,
1014
- followCurrentUser,
1015
- unfollowCurrentUser,
1052
+ followUser,
1053
+ unfollowUser,
1054
+ likeUserImages,
1016
1055
  sleep,
1017
1056
  listManuallyFollowedUsers,
1018
1057
  getFollowersOrFollowing,
1019
1058
  getUsersWhoLikedContent,
1020
1059
  safelyUnfollowUserList,
1060
+ safelyFollowUserList,
1021
1061
  getPage,
1022
- followUsersFollowers,
1062
+ followUsersFollowers: processUsersFollowers,
1023
1063
  doesUserFollowMe,
1024
1064
  };
1025
1065
  };
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,101 +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
- const options = {
8
- cookiesPath: './cookies.json',
9
-
10
- username: 'your-ig-username',
11
- password: 'your-ig-password',
12
-
13
- // Global limit that prevents follow or unfollows (total) to exceed this number over a sliding window of one hour:
14
- maxFollowsPerHour: 20,
15
- // Global limit that prevents follow or unfollows (total) to exceed this number over a sliding window of one day:
16
- maxFollowsPerDay: 150,
17
- // (NOTE setting the above parameters too high will cause temp ban/throttle)
18
-
19
- maxLikesPerDay: 50,
20
-
21
- // Don't follow users that have a followers / following ratio less than this:
22
- followUserRatioMin: 0.2,
23
- // Don't follow users that have a followers / following ratio higher than this:
24
- followUserRatioMax: 4.0,
25
- // Don't follow users who have more followers than this:
26
- followUserMaxFollowers: null,
27
- // Don't follow users who have more people following them than this:
28
- followUserMaxFollowing: null,
29
- // Don't follow users who have less followers than this:
30
- followUserMinFollowers: null,
31
- // Don't follow users who have more people following them than this:
32
- followUserMinFollowing: null,
33
-
34
- // NOTE: The dontUnfollowUntilTimeElapsed option is ONLY for the unfollowNonMutualFollowers function
35
- // This specifies the time during which the bot should not touch users that it has previously followed (in milliseconds)
36
- // After this time has passed, it will be able to unfollow them again.
37
- // TODO should remove this option from here
38
- dontUnfollowUntilTimeElapsed: 3 * 24 * 60 * 60 * 1000,
39
-
40
- // Usernames that we should not touch, e.g. your friends and actual followings
41
- excludeUsers: [],
42
-
43
- // If true, will not do any actions (defaults to true)
44
- dryRun: false,
45
- };
46
-
47
- (async () => {
48
- let browser;
49
-
50
- try {
51
- browser = await puppeteer.launch({ headless: false });
52
-
53
- // Create a database where state will be loaded/saved to
54
- const instautoDb = await Instauto.JSONDB({
55
- // Will store a list of all users that have been followed before, to prevent future re-following.
56
- followedDbPath: './followed.json',
57
- // Will store all unfollowed users here
58
- unfollowedDbPath: './unfollowed.json',
59
- // Will store all likes here
60
- likedPhotosDbPath: './liked-photos.json',
61
- });
62
-
63
- const instauto = await Instauto(instautoDb, browser, options);
64
-
65
- // This can be used to unfollow people:
66
- // Will unfollow auto-followed AND manually followed accounts who are not following us back, after some time has passed
67
- // The time is specified by config option dontUnfollowUntilTimeElapsed
68
- // await instauto.unfollowNonMutualFollowers();
69
- // await instauto.sleep(10 * 60 * 1000);
70
-
71
- // Unfollow previously auto-followed users (regardless of whether or not they are following us back)
72
- // after a certain amount of days (2 weeks)
73
- // Leave room to do following after this too (unfollow 2/3 of maxFollowsPerDay)
74
- const unfollowedCount = await instauto.unfollowOldFollowed({ ageInDays: 14, limit: options.maxFollowsPerDay * (2 / 3) });
75
-
76
- if (unfollowedCount > 0) await instauto.sleep(10 * 60 * 1000);
77
-
78
- // List of usernames that we should follow the followers of, can be celebrities etc.
79
- const usersToFollowFollowersOf = ['lostleblanc', 'sam_kolder'];
80
-
81
- // Now go through each of these and follow a certain amount of their followers
82
- await instauto.followUsersFollowers({
83
- usersToFollowFollowersOf,
84
- maxFollowsTotal: options.maxFollowsPerDay - unfollowedCount,
85
- skipPrivate: true,
86
- enableLikeImages: true,
87
- likeImagesMax: 3,
88
- });
89
-
90
- await instauto.sleep(10 * 60 * 1000);
91
-
92
- console.log('Done running');
93
-
94
- await instauto.sleep(30000);
95
- } catch (err) {
96
- console.error(err);
97
- } finally {
98
- console.log('Closing browser');
99
- if (browser) await browser.close();
100
- }
101
- })();
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