instauto 9.0.0 → 9.1.2

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": "9.0.0",
3
+ "version": "9.1.2",
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) {
@@ -307,6 +309,9 @@ const Instauto = async (db, browser, options) => {
307
309
  const elementHandles3 = await page.$x("//header//button[*//span[@aria-label='Following']]");
308
310
  if (elementHandles3.length > 0) return elementHandles3[0];
309
311
 
312
+ const elementHandles4 = await page.$x("//header//button[*//*[name()='svg'][@aria-label='Following']]");
313
+ if (elementHandles4.length > 0) return elementHandles4[0];
314
+
310
315
  return undefined;
311
316
  }
312
317
 
@@ -571,10 +576,57 @@ const Instauto = async (db, browser, options) => {
571
576
  await page.evaluate(likeCurrentUserImagesPageCode, { dryRun, likeImagesMin, likeImagesMax });
572
577
  }
573
578
 
574
- 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, {
575
624
  maxFollowsPerUser = 5, skipPrivate = false, enableLikeImages, likeImagesMin, likeImagesMax,
576
625
  } = {}) {
577
- 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}`);
578
630
 
579
631
  await throttle();
580
632
 
@@ -586,86 +638,52 @@ const Instauto = async (db, browser, options) => {
586
638
  logger.log('User followers batch', followersBatch);
587
639
 
588
640
  for (const follower of followersBatch) {
589
- if (getPrevFollowedUser(follower)) {
590
- logger.log('Skipping already followed user', follower);
591
- } else {
592
- try {
593
- if (numFollowedForThisUser >= maxFollowsPerUser) {
594
- logger.log('Have reached followed limit for this user, stopping');
595
- return;
596
- }
641
+ await throttle();
597
642
 
598
- const graphqlUser = await navigateToUserAndGetData(follower);
599
-
600
- const followedByCount = graphqlUser.edge_followed_by.count;
601
- const followsCount = graphqlUser.edge_follow.count;
602
- const isPrivate = graphqlUser.is_private;
603
-
604
- // logger.log('followedByCount:', followedByCount, 'followsCount:', followsCount);
605
-
606
- const ratio = followedByCount / (followsCount || 1);
607
-
608
- if (isPrivate && skipPrivate) {
609
- logger.log('User is private, skipping');
610
- } else if (
611
- (followUserMaxFollowers != null && followedByCount > followUserMaxFollowers) ||
612
- (followUserMaxFollowing != null && followsCount > followUserMaxFollowing) ||
613
- (followUserMinFollowers != null && followedByCount < followUserMinFollowers) ||
614
- (followUserMinFollowing != null && followsCount < followUserMinFollowing)
615
- ) {
616
- logger.log('User has too many or too few followers or following, skipping.', 'followedByCount:', followedByCount, 'followsCount:', followsCount);
617
- } else if (
618
- (followUserRatioMax != null && ratio > followUserRatioMax) ||
619
- (followUserRatioMin != null && ratio < followUserRatioMin)
620
- ) {
621
- logger.log('User has too many followers compared to follows or opposite, skipping');
622
- } else {
623
- await followUser(follower);
624
- numFollowedForThisUser += 1;
643
+ try {
644
+ if (enableFollow && numFollowedForThisUser >= maxFollowsPerUser) {
645
+ logger.log('Have reached followed limit for this user, stopping');
646
+ return;
647
+ }
625
648
 
626
- await sleep(10000);
649
+ let didActuallyFollow = false;
650
+ if (enableFollow) didActuallyFollow = await followUserRespectingRestrictions({ username: follower, skipPrivate });
651
+ if (didActuallyFollow) numFollowedForThisUser += 1;
627
652
 
628
- if (!isPrivate && enableLikeImages && !hasReachedDailyLikesLimit()) {
629
- try {
630
- await likeUserImages({ username: follower, likeImagesMin, likeImagesMax });
631
- } catch (err) {
632
- logger.error(`Failed to follow user's images ${follower}`, err);
633
- await takeScreenshot();
634
- }
635
- }
653
+ const didFailToFollow = enableFollow && !didActuallyFollow;
636
654
 
637
- await sleep(20000);
638
- await throttle();
639
- }
640
- } catch (err) {
641
- logger.error(`Failed to process follower ${follower}`, err);
642
- await sleep(20000);
655
+ if (enableLikeImages && !didFailToFollow) {
656
+ // Note: throws error if user isPrivate
657
+ await likeUserImages({ username: follower, likeImagesMin, likeImagesMax });
643
658
  }
659
+ } catch (err) {
660
+ logger.error(`Failed to process follower ${follower}`, err);
661
+ await takeScreenshot();
662
+ await sleep(20000);
644
663
  }
645
664
  }
646
665
  }
647
666
  }
648
667
 
649
- async function followUsersFollowers({ usersToFollowFollowersOf, maxFollowsTotal = 150, skipPrivate, enableLikeImages = false, likeImagesMin = 1, likeImagesMax = 2 }) {
650
- if (!maxFollowsTotal || maxFollowsTotal <= 2) {
651
- throw new Error(`Invalid parameter maxFollowsTotal ${maxFollowsTotal}`);
652
- }
653
-
654
-
668
+ async function processUsersFollowers({ usersToFollowFollowersOf, maxFollowsTotal = 150, skipPrivate, enableFollow = true, enableLikeImages = false, likeImagesMin = 1, likeImagesMax = 2 }) {
655
669
  // If maxFollowsTotal turns out to be lower than the user list size, slice off the user list
656
670
  const usersToFollowFollowersOfSliced = shuffleArray(usersToFollowFollowersOf).slice(0, maxFollowsTotal);
657
671
 
658
- // Round up or we risk following none
659
- 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
+ }
660
678
 
661
679
  for (const username of usersToFollowFollowersOfSliced) {
662
680
  try {
663
- await followUserFollowers(username, { maxFollowsPerUser, skipPrivate, enableLikeImages, likeImagesMin, likeImagesMax });
681
+ await processUserFollowers(username, { maxFollowsPerUser, skipPrivate, enableLikeImages, likeImagesMin, likeImagesMax });
664
682
 
665
683
  await sleep(10 * 60 * 1000);
666
684
  await throttle();
667
685
  } catch (err) {
668
- console.error('Failed to follow user followers, continuing', err);
686
+ logger.error('Failed to process user followers, continuing', username, err);
669
687
  await takeScreenshot();
670
688
  await sleep(60 * 1000);
671
689
  }
@@ -725,6 +743,22 @@ const Instauto = async (db, browser, options) => {
725
743
  return j;
726
744
  }
727
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
+
728
762
  function getPage() {
729
763
  return page;
730
764
  }
@@ -1011,7 +1045,7 @@ const Instauto = async (db, browser, options) => {
1011
1045
  }
1012
1046
 
1013
1047
  return {
1014
- followUserFollowers,
1048
+ followUserFollowers: processUserFollowers,
1015
1049
  unfollowNonMutualFollowers,
1016
1050
  unfollowAllUnknown,
1017
1051
  unfollowOldFollowed,
@@ -1023,8 +1057,9 @@ const Instauto = async (db, browser, options) => {
1023
1057
  getFollowersOrFollowing,
1024
1058
  getUsersWhoLikedContent,
1025
1059
  safelyUnfollowUserList,
1060
+ safelyFollowUserList,
1026
1061
  getPage,
1027
- followUsersFollowers,
1062
+ followUsersFollowers: processUsersFollowers,
1028
1063
  doesUserFollowMe,
1029
1064
  };
1030
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