instauto 9.0.0 → 9.1.0
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 +1 -0
- package/example.js +6 -0
- package/index.js +105 -73
- package/package.json +1 -1
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/example.js
CHANGED
|
@@ -4,6 +4,10 @@ const puppeteer = require('puppeteer'); // eslint-disable-line import/no-extrane
|
|
|
4
4
|
|
|
5
5
|
const Instauto = require('instauto'); // eslint-disable-line import/no-unresolved
|
|
6
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
|
+
|
|
7
11
|
const options = {
|
|
8
12
|
cookiesPath: './cookies.json',
|
|
9
13
|
|
|
@@ -42,6 +46,8 @@ const options = {
|
|
|
42
46
|
|
|
43
47
|
// If true, will not do any actions (defaults to true)
|
|
44
48
|
dryRun: false,
|
|
49
|
+
|
|
50
|
+
logger,
|
|
45
51
|
};
|
|
46
52
|
|
|
47
53
|
(async () => {
|
package/index.js
CHANGED
|
@@ -136,28 +136,30 @@ const Instauto = async (db, browser, options) => {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
async function checkReachedFollowedUserDayLimit() {
|
|
139
|
-
|
|
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
|
-
|
|
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) {
|
|
@@ -571,10 +573,57 @@ const Instauto = async (db, browser, options) => {
|
|
|
571
573
|
await page.evaluate(likeCurrentUserImagesPageCode, { dryRun, likeImagesMin, likeImagesMax });
|
|
572
574
|
}
|
|
573
575
|
|
|
574
|
-
async function
|
|
576
|
+
async function followUserRespectingRestrictions({ username, skipPrivate = false }) {
|
|
577
|
+
if (getPrevFollowedUser(username)) {
|
|
578
|
+
logger.log('Skipping already followed user', username);
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
const graphqlUser = await navigateToUserAndGetData(username);
|
|
582
|
+
|
|
583
|
+
const followedByCount = graphqlUser.edge_followed_by.count;
|
|
584
|
+
const followsCount = graphqlUser.edge_follow.count;
|
|
585
|
+
const isPrivate = graphqlUser.is_private;
|
|
586
|
+
|
|
587
|
+
// logger.log('followedByCount:', followedByCount, 'followsCount:', followsCount);
|
|
588
|
+
|
|
589
|
+
const ratio = followedByCount / (followsCount || 1);
|
|
590
|
+
|
|
591
|
+
if (isPrivate && skipPrivate) {
|
|
592
|
+
logger.log('User is private, skipping');
|
|
593
|
+
return false;
|
|
594
|
+
}
|
|
595
|
+
if (
|
|
596
|
+
(followUserMaxFollowers != null && followedByCount > followUserMaxFollowers) ||
|
|
597
|
+
(followUserMaxFollowing != null && followsCount > followUserMaxFollowing) ||
|
|
598
|
+
(followUserMinFollowers != null && followedByCount < followUserMinFollowers) ||
|
|
599
|
+
(followUserMinFollowing != null && followsCount < followUserMinFollowing)
|
|
600
|
+
) {
|
|
601
|
+
logger.log('User has too many or too few followers or following, skipping.', 'followedByCount:', followedByCount, 'followsCount:', followsCount);
|
|
602
|
+
return false;
|
|
603
|
+
}
|
|
604
|
+
if (
|
|
605
|
+
(followUserRatioMax != null && ratio > followUserRatioMax) ||
|
|
606
|
+
(followUserRatioMin != null && ratio < followUserRatioMin)
|
|
607
|
+
) {
|
|
608
|
+
logger.log('User has too many followers compared to follows or opposite, skipping');
|
|
609
|
+
return false;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
await followUser(username);
|
|
613
|
+
|
|
614
|
+
await sleep(30000);
|
|
615
|
+
await throttle();
|
|
616
|
+
|
|
617
|
+
return true;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
async function processUserFollowers(username, {
|
|
575
621
|
maxFollowsPerUser = 5, skipPrivate = false, enableLikeImages, likeImagesMin, likeImagesMax,
|
|
576
622
|
} = {}) {
|
|
577
|
-
|
|
623
|
+
const enableFollow = maxFollowsPerUser > 0;
|
|
624
|
+
|
|
625
|
+
if (enableFollow) logger.log(`Following up to ${maxFollowsPerUser} followers of ${username}`);
|
|
626
|
+
if (enableLikeImages) logger.log(`Liking images of up to ${likeImagesMax} followers of ${username}`);
|
|
578
627
|
|
|
579
628
|
await throttle();
|
|
580
629
|
|
|
@@ -586,86 +635,52 @@ const Instauto = async (db, browser, options) => {
|
|
|
586
635
|
logger.log('User followers batch', followersBatch);
|
|
587
636
|
|
|
588
637
|
for (const follower of followersBatch) {
|
|
589
|
-
|
|
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
|
-
}
|
|
597
|
-
|
|
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;
|
|
625
|
-
|
|
626
|
-
await sleep(10000);
|
|
638
|
+
await throttle();
|
|
627
639
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
await takeScreenshot();
|
|
634
|
-
}
|
|
635
|
-
}
|
|
640
|
+
try {
|
|
641
|
+
if (enableFollow && numFollowedForThisUser >= maxFollowsPerUser) {
|
|
642
|
+
logger.log('Have reached followed limit for this user, stopping');
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
636
645
|
|
|
637
|
-
|
|
638
|
-
|
|
646
|
+
if (enableFollow) {
|
|
647
|
+
if (await followUserRespectingRestrictions({ username: follower, skipPrivate })) {
|
|
648
|
+
numFollowedForThisUser += 1;
|
|
639
649
|
}
|
|
640
|
-
} catch (err) {
|
|
641
|
-
logger.error(`Failed to process follower ${follower}`, err);
|
|
642
|
-
await sleep(20000);
|
|
643
650
|
}
|
|
651
|
+
|
|
652
|
+
if (enableLikeImages) {
|
|
653
|
+
// Note: throws error if user isPrivate
|
|
654
|
+
await likeUserImages({ username: follower, likeImagesMin, likeImagesMax });
|
|
655
|
+
}
|
|
656
|
+
} catch (err) {
|
|
657
|
+
logger.error(`Failed to process follower ${follower}`, err);
|
|
658
|
+
await takeScreenshot();
|
|
659
|
+
await sleep(20000);
|
|
644
660
|
}
|
|
645
661
|
}
|
|
646
662
|
}
|
|
647
663
|
}
|
|
648
664
|
|
|
649
|
-
async function
|
|
650
|
-
if (!maxFollowsTotal || maxFollowsTotal <= 2) {
|
|
651
|
-
throw new Error(`Invalid parameter maxFollowsTotal ${maxFollowsTotal}`);
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
|
|
665
|
+
async function processUsersFollowers({ usersToFollowFollowersOf, maxFollowsTotal = 150, skipPrivate, enableFollow = true, enableLikeImages = false, likeImagesMin = 1, likeImagesMax = 2 }) {
|
|
655
666
|
// If maxFollowsTotal turns out to be lower than the user list size, slice off the user list
|
|
656
667
|
const usersToFollowFollowersOfSliced = shuffleArray(usersToFollowFollowersOf).slice(0, maxFollowsTotal);
|
|
657
668
|
|
|
658
|
-
|
|
659
|
-
|
|
669
|
+
const maxFollowsPerUser = enableFollow && usersToFollowFollowersOfSliced.length > 0 ? Math.floor(maxFollowsTotal / usersToFollowFollowersOfSliced.length) : 0;
|
|
670
|
+
|
|
671
|
+
if (maxFollowsPerUser === 0 && (!enableLikeImages || likeImagesMin < 1 || likeImagesMax < 1)) {
|
|
672
|
+
logger.warn('Nothing to follow or like');
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
660
675
|
|
|
661
676
|
for (const username of usersToFollowFollowersOfSliced) {
|
|
662
677
|
try {
|
|
663
|
-
await
|
|
678
|
+
await processUserFollowers(username, { maxFollowsPerUser, skipPrivate, enableLikeImages, likeImagesMin, likeImagesMax });
|
|
664
679
|
|
|
665
680
|
await sleep(10 * 60 * 1000);
|
|
666
681
|
await throttle();
|
|
667
682
|
} catch (err) {
|
|
668
|
-
|
|
683
|
+
logger.error('Failed to process user followers, continuing', username, err);
|
|
669
684
|
await takeScreenshot();
|
|
670
685
|
await sleep(60 * 1000);
|
|
671
686
|
}
|
|
@@ -725,6 +740,22 @@ const Instauto = async (db, browser, options) => {
|
|
|
725
740
|
return j;
|
|
726
741
|
}
|
|
727
742
|
|
|
743
|
+
async function safelyFollowUserList({ users, skipPrivate, limit }) {
|
|
744
|
+
logger.log('Following users, up to limit', limit);
|
|
745
|
+
|
|
746
|
+
for (const username of users) {
|
|
747
|
+
await throttle();
|
|
748
|
+
|
|
749
|
+
try {
|
|
750
|
+
await followUserRespectingRestrictions({ username, skipPrivate });
|
|
751
|
+
} catch (err) {
|
|
752
|
+
logger.error(`Failed to follow user ${username}, continuing`, err);
|
|
753
|
+
await takeScreenshot();
|
|
754
|
+
await sleep(20000);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
728
759
|
function getPage() {
|
|
729
760
|
return page;
|
|
730
761
|
}
|
|
@@ -1011,7 +1042,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
1011
1042
|
}
|
|
1012
1043
|
|
|
1013
1044
|
return {
|
|
1014
|
-
followUserFollowers,
|
|
1045
|
+
followUserFollowers: processUserFollowers,
|
|
1015
1046
|
unfollowNonMutualFollowers,
|
|
1016
1047
|
unfollowAllUnknown,
|
|
1017
1048
|
unfollowOldFollowed,
|
|
@@ -1023,8 +1054,9 @@ const Instauto = async (db, browser, options) => {
|
|
|
1023
1054
|
getFollowersOrFollowing,
|
|
1024
1055
|
getUsersWhoLikedContent,
|
|
1025
1056
|
safelyUnfollowUserList,
|
|
1057
|
+
safelyFollowUserList,
|
|
1026
1058
|
getPage,
|
|
1027
|
-
followUsersFollowers,
|
|
1059
|
+
followUsersFollowers: processUsersFollowers,
|
|
1028
1060
|
doesUserFollowMe,
|
|
1029
1061
|
};
|
|
1030
1062
|
};
|