instauto 7.2.3 → 8.0.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/.eslintrc +4 -3
- package/index.js +230 -203
- package/package.json +4 -3
package/.eslintrc
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"env": {
|
|
3
|
-
"node": true
|
|
3
|
+
"node": true
|
|
4
4
|
},
|
|
5
5
|
"extends": "airbnb/base",
|
|
6
6
|
"parserOptions": {
|
|
7
|
-
"sourceType": "script"
|
|
7
|
+
"sourceType": "script",
|
|
8
|
+
"ecmaVersion": 2022
|
|
8
9
|
},
|
|
9
10
|
"globals": {
|
|
10
11
|
"window": true,
|
|
11
|
-
"document": true
|
|
12
|
+
"document": true
|
|
12
13
|
},
|
|
13
14
|
"rules": {
|
|
14
15
|
"max-len": 0,
|
package/index.js
CHANGED
|
@@ -117,8 +117,9 @@ const Instauto = async (db, browser, options) => {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
const sleep = (ms,
|
|
121
|
-
|
|
120
|
+
const sleep = (ms, deviation = 1) => {
|
|
121
|
+
let msWithDev = ((Math.random() * deviation) + 1) * ms;
|
|
122
|
+
if (dryRun) msWithDev = Math.min(3000, msWithDev); // for dryRun, no need to wait so long
|
|
122
123
|
logger.log('Waiting', Math.round(msWithDev / 1000), 'sec');
|
|
123
124
|
return new Promise(resolve => setTimeout(resolve, msWithDev));
|
|
124
125
|
};
|
|
@@ -202,8 +203,11 @@ const Instauto = async (db, browser, options) => {
|
|
|
202
203
|
}
|
|
203
204
|
|
|
204
205
|
async function navigateToUser(username) {
|
|
206
|
+
const url = `${instagramBaseUrl}/${encodeURIComponent(username)}`;
|
|
207
|
+
if (page.url().replace(/\/$/, '') === url.replace(/\/$/, '')) return true; // optimization: already on URL? (ignore trailing slash)
|
|
208
|
+
// logger.log('navigating from', page.url(), 'to', url);
|
|
205
209
|
logger.log(`Navigating to user ${username}`);
|
|
206
|
-
return safeGotoUser(
|
|
210
|
+
return safeGotoUser(url, username);
|
|
207
211
|
}
|
|
208
212
|
|
|
209
213
|
async function getPageJson() {
|
|
@@ -391,7 +395,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
391
395
|
|
|
392
396
|
const isLoggedIn = async () => (await page.$x('//*[@aria-label="Home"]')).length === 1;
|
|
393
397
|
|
|
394
|
-
async function graphqlQueryUsers({ queryHash, getResponseProp,
|
|
398
|
+
async function* graphqlQueryUsers({ queryHash, getResponseProp, graphqlVariables: graphqlVariablesIn }) {
|
|
395
399
|
const graphqlUrl = `${instagramBaseUrl}/graphql/query/?query_hash=${queryHash}`;
|
|
396
400
|
|
|
397
401
|
const graphqlVariables = {
|
|
@@ -404,14 +408,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
404
408
|
let hasNextPage = true;
|
|
405
409
|
let i = 0;
|
|
406
410
|
|
|
407
|
-
|
|
408
|
-
if (!hasNextPage) return false;
|
|
409
|
-
const isBelowMaxPages = maxPages == null || i < maxPages;
|
|
410
|
-
if (shouldProceedArg) return isBelowMaxPages && shouldProceedArg(outUsers);
|
|
411
|
-
return isBelowMaxPages;
|
|
412
|
-
};
|
|
413
|
-
|
|
414
|
-
while (shouldProceed()) {
|
|
411
|
+
while (hasNextPage) {
|
|
415
412
|
const url = `${graphqlUrl}&variables=${JSON.stringify(graphqlVariables)}`;
|
|
416
413
|
// logger.log(url);
|
|
417
414
|
await page.goto(url);
|
|
@@ -421,44 +418,47 @@ const Instauto = async (db, browser, options) => {
|
|
|
421
418
|
const pageInfo = subProp.page_info;
|
|
422
419
|
const { edges } = subProp;
|
|
423
420
|
|
|
424
|
-
|
|
421
|
+
const ret = [];
|
|
422
|
+
edges.forEach(e => ret.push(e.node.username));
|
|
425
423
|
|
|
426
424
|
graphqlVariables.after = pageInfo.end_cursor;
|
|
427
425
|
hasNextPage = pageInfo.has_next_page;
|
|
428
426
|
i += 1;
|
|
429
427
|
|
|
430
|
-
if (
|
|
428
|
+
if (hasNextPage) {
|
|
431
429
|
logger.log(`Has more pages (current ${i})`);
|
|
432
430
|
// await sleep(300);
|
|
433
431
|
}
|
|
432
|
+
|
|
433
|
+
yield ret;
|
|
434
434
|
}
|
|
435
435
|
|
|
436
436
|
return outUsers;
|
|
437
437
|
}
|
|
438
438
|
|
|
439
|
-
|
|
440
|
-
userId, getFollowers = false, maxPages, shouldProceed,
|
|
441
|
-
}) {
|
|
439
|
+
function getFollowersOrFollowingGenerator({ userId, getFollowers = false }) {
|
|
442
440
|
return graphqlQueryUsers({
|
|
443
441
|
getResponseProp: (json) => json.data.user[getFollowers ? 'edge_followed_by' : 'edge_follow'],
|
|
444
442
|
graphqlVariables: { id: userId },
|
|
445
|
-
shouldProceed,
|
|
446
|
-
maxPages,
|
|
447
443
|
queryHash: getFollowers ? '37479f2b8209594dde7facb0d904896a' : '58712303d941c6855d4e888c5f0cd22f',
|
|
448
444
|
});
|
|
449
445
|
}
|
|
450
446
|
|
|
451
|
-
async function
|
|
452
|
-
|
|
453
|
-
|
|
447
|
+
async function getFollowersOrFollowing({ userId, getFollowers = false }) {
|
|
448
|
+
let users = [];
|
|
449
|
+
for await (const usersBatch of getFollowersOrFollowingGenerator({ userId, getFollowers })) {
|
|
450
|
+
users = [...users, ...usersBatch];
|
|
451
|
+
}
|
|
452
|
+
return users;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function getUsersWhoLikedContent({ contentId }) {
|
|
454
456
|
return graphqlQueryUsers({
|
|
455
457
|
getResponseProp: (json) => json.data.shortcode_media.edge_liked_by,
|
|
456
458
|
graphqlVariables: {
|
|
457
459
|
shortcode: contentId,
|
|
458
460
|
include_reel: true,
|
|
459
461
|
},
|
|
460
|
-
shouldProceed,
|
|
461
|
-
maxPages,
|
|
462
462
|
queryHash: 'd5d763b1e2acf209d62d22d184488e57',
|
|
463
463
|
});
|
|
464
464
|
}
|
|
@@ -575,73 +575,66 @@ const Instauto = async (db, browser, options) => {
|
|
|
575
575
|
|
|
576
576
|
const userData = await navigateToUserAndGetData(username);
|
|
577
577
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
usersSoFar.filter(u => !getPrevFollowedUser(u)).length < maxFollowsPerUser + 5 // 5 is just a margin
|
|
581
|
-
);
|
|
582
|
-
let followers = await getFollowersOrFollowing({
|
|
583
|
-
userId: userData.id,
|
|
584
|
-
getFollowers: true,
|
|
585
|
-
shouldProceed,
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
logger.log('Followers', followers);
|
|
578
|
+
for await (const followersBatch of getFollowersOrFollowingGenerator({ userId: userData.id, getFollowers: true })) {
|
|
579
|
+
logger.log('User followers batch', followersBatch);
|
|
589
580
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
for (const follower of followers) {
|
|
594
|
-
try {
|
|
595
|
-
if (numFollowedForThisUser >= maxFollowsPerUser) {
|
|
596
|
-
logger.log('Have reached followed limit for this user, stopping');
|
|
597
|
-
return;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
const graphqlUser = await navigateToUserAndGetData(follower);
|
|
601
|
-
|
|
602
|
-
const followedByCount = graphqlUser.edge_followed_by.count;
|
|
603
|
-
const followsCount = graphqlUser.edge_follow.count;
|
|
604
|
-
const isPrivate = graphqlUser.is_private;
|
|
605
|
-
|
|
606
|
-
// logger.log('followedByCount:', followedByCount, 'followsCount:', followsCount);
|
|
607
|
-
|
|
608
|
-
const ratio = followedByCount / (followsCount || 1);
|
|
609
|
-
|
|
610
|
-
if (isPrivate && skipPrivate) {
|
|
611
|
-
logger.log('User is private, skipping');
|
|
612
|
-
} else if (
|
|
613
|
-
(followUserMaxFollowers != null && followedByCount > followUserMaxFollowers) ||
|
|
614
|
-
(followUserMaxFollowing != null && followsCount > followUserMaxFollowing) ||
|
|
615
|
-
(followUserMinFollowers != null && followedByCount < followUserMinFollowers) ||
|
|
616
|
-
(followUserMinFollowing != null && followsCount < followUserMinFollowing)
|
|
617
|
-
) {
|
|
618
|
-
logger.log('User has too many or too few followers or following, skipping.', 'followedByCount:', followedByCount, 'followsCount:', followsCount);
|
|
619
|
-
} else if (
|
|
620
|
-
(followUserRatioMax != null && ratio > followUserRatioMax) ||
|
|
621
|
-
(followUserRatioMin != null && ratio < followUserRatioMin)
|
|
622
|
-
) {
|
|
623
|
-
logger.log('User has too many followers compared to follows or opposite, skipping');
|
|
581
|
+
for (const follower of followersBatch) {
|
|
582
|
+
if (getPrevFollowedUser(follower)) {
|
|
583
|
+
logger.log('Skipping already followed user', follower);
|
|
624
584
|
} else {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
585
|
+
try {
|
|
586
|
+
if (numFollowedForThisUser >= maxFollowsPerUser) {
|
|
587
|
+
logger.log('Have reached followed limit for this user, stopping');
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
629
590
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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);
|
|
620
|
+
|
|
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
|
+
}
|
|
629
|
+
|
|
630
|
+
await sleep(20000);
|
|
631
|
+
await throttle();
|
|
636
632
|
}
|
|
633
|
+
} catch (err) {
|
|
634
|
+
logger.error(`Failed to process follower ${follower}`, err);
|
|
635
|
+
await sleep(20000);
|
|
637
636
|
}
|
|
638
|
-
|
|
639
|
-
await sleep(20000);
|
|
640
|
-
await throttle();
|
|
641
637
|
}
|
|
642
|
-
} catch (err) {
|
|
643
|
-
logger.error(`Failed to process follower ${follower}`, err);
|
|
644
|
-
await sleep(20000);
|
|
645
638
|
}
|
|
646
639
|
}
|
|
647
640
|
}
|
|
@@ -672,136 +665,57 @@ const Instauto = async (db, browser, options) => {
|
|
|
672
665
|
}
|
|
673
666
|
}
|
|
674
667
|
|
|
675
|
-
async function safelyUnfollowUserList(usersToUnfollow, limit) {
|
|
676
|
-
logger.log(
|
|
668
|
+
async function safelyUnfollowUserList(usersToUnfollow, limit, condition = () => true) {
|
|
669
|
+
logger.log('Unfollowing users, up to limit', limit);
|
|
677
670
|
|
|
678
671
|
let i = 0; // Number of people processed
|
|
679
672
|
let j = 0; // Number of people actually unfollowed (button pressed)
|
|
680
673
|
|
|
681
|
-
for (const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
674
|
+
for await (const listOrUsername of usersToUnfollow) {
|
|
675
|
+
// backward compatible:
|
|
676
|
+
const list = Array.isArray(listOrUsername) ? listOrUsername : [listOrUsername];
|
|
677
|
+
|
|
678
|
+
for (const username of list) {
|
|
679
|
+
if (await condition(username)) {
|
|
680
|
+
try {
|
|
681
|
+
const userFound = await navigateToUser(username);
|
|
682
|
+
|
|
683
|
+
if (!userFound) {
|
|
684
|
+
await addPrevUnfollowedUser({ username, time: new Date().getTime(), noActionTaken: true });
|
|
685
|
+
await sleep(3000);
|
|
686
|
+
} else {
|
|
687
|
+
const { noActionTaken } = await unfollowCurrentUser(username);
|
|
688
|
+
|
|
689
|
+
if (noActionTaken) {
|
|
690
|
+
await sleep(3000);
|
|
691
|
+
} else {
|
|
692
|
+
await sleep(15000);
|
|
693
|
+
j += 1;
|
|
694
|
+
|
|
695
|
+
if (j % 10 === 0) {
|
|
696
|
+
logger.log('Have unfollowed 10 users since last break. Taking a break');
|
|
697
|
+
await sleep(10 * 60 * 1000, 0.1);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
690
701
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
} else {
|
|
694
|
-
await sleep(15000);
|
|
695
|
-
j += 1;
|
|
702
|
+
i += 1;
|
|
703
|
+
logger.log(`Have now unfollowed (or tried to unfollow) ${i} users`);
|
|
696
704
|
|
|
697
|
-
if (
|
|
698
|
-
logger.log(
|
|
699
|
-
|
|
705
|
+
if (limit && j >= limit) {
|
|
706
|
+
logger.log(`Have unfollowed limit of ${limit}, stopping`);
|
|
707
|
+
return j;
|
|
700
708
|
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
i += 1;
|
|
705
|
-
logger.log(`Have now unfollowed ${i} users of total ${usersToUnfollow.length}`);
|
|
706
709
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
+
await throttle();
|
|
711
|
+
} catch (err) {
|
|
712
|
+
logger.error('Failed to unfollow, continuing with next', err);
|
|
713
|
+
}
|
|
710
714
|
}
|
|
711
|
-
|
|
712
|
-
await throttle();
|
|
713
|
-
} catch (err) {
|
|
714
|
-
logger.error('Failed to unfollow, continuing with next', err);
|
|
715
715
|
}
|
|
716
716
|
}
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
async function unfollowNonMutualFollowers({ limit } = {}) {
|
|
720
|
-
logger.log('Unfollowing non-mutual followers...');
|
|
721
|
-
const userData = await navigateToUserAndGetData(myUsername);
|
|
722
|
-
|
|
723
|
-
const allFollowers = await getFollowersOrFollowing({
|
|
724
|
-
userId: userData.id,
|
|
725
|
-
getFollowers: true,
|
|
726
|
-
});
|
|
727
|
-
const allFollowing = await getFollowersOrFollowing({
|
|
728
|
-
userId: userData.id,
|
|
729
|
-
getFollowers: false,
|
|
730
|
-
});
|
|
731
|
-
// logger.log('allFollowers:', allFollowers, 'allFollowing:', allFollowing);
|
|
732
|
-
|
|
733
|
-
const usersToUnfollow = allFollowing.filter((u) => {
|
|
734
|
-
if (allFollowers.includes(u)) return false; // Follows us
|
|
735
|
-
if (excludeUsers.includes(u)) return false; // User is excluded by exclude list
|
|
736
|
-
if (haveRecentlyFollowedUser(u)) {
|
|
737
|
-
logger.log(`Have recently followed user ${u}, skipping`);
|
|
738
|
-
return false;
|
|
739
|
-
}
|
|
740
|
-
return true;
|
|
741
|
-
});
|
|
742
|
-
|
|
743
|
-
logger.log('usersToUnfollow', JSON.stringify(usersToUnfollow));
|
|
744
|
-
|
|
745
|
-
await safelyUnfollowUserList(usersToUnfollow, limit);
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
async function unfollowAllUnknown({ limit } = {}) {
|
|
749
|
-
logger.log('Unfollowing all except excludes and auto followed');
|
|
750
|
-
const userData = await navigateToUserAndGetData(myUsername);
|
|
751
|
-
|
|
752
|
-
const allFollowing = await getFollowersOrFollowing({
|
|
753
|
-
userId: userData.id,
|
|
754
|
-
getFollowers: false,
|
|
755
|
-
});
|
|
756
|
-
// logger.log('allFollowing', allFollowing);
|
|
757
|
-
|
|
758
|
-
const usersToUnfollow = allFollowing.filter((u) => {
|
|
759
|
-
if (getPrevFollowedUser(u)) return false;
|
|
760
|
-
if (excludeUsers.includes(u)) return false; // User is excluded by exclude list
|
|
761
|
-
return true;
|
|
762
|
-
});
|
|
763
|
-
|
|
764
|
-
logger.log('usersToUnfollow', JSON.stringify(usersToUnfollow));
|
|
765
|
-
|
|
766
|
-
await safelyUnfollowUserList(usersToUnfollow, limit);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
async function unfollowOldFollowed({ ageInDays, limit } = {}) {
|
|
770
|
-
assert(ageInDays);
|
|
771
|
-
|
|
772
|
-
logger.log(`Unfollowing currently followed users who were auto-followed more than ${ageInDays} days ago...`);
|
|
773
|
-
|
|
774
|
-
const userData = await navigateToUserAndGetData(myUsername);
|
|
775
|
-
|
|
776
|
-
const allFollowing = await getFollowersOrFollowing({
|
|
777
|
-
userId: userData.id,
|
|
778
|
-
getFollowers: false,
|
|
779
|
-
});
|
|
780
|
-
// logger.log('allFollowing', allFollowing);
|
|
781
717
|
|
|
782
|
-
|
|
783
|
-
getPrevFollowedUser(u) &&
|
|
784
|
-
!excludeUsers.includes(u) &&
|
|
785
|
-
(new Date().getTime() - getPrevFollowedUser(u).time) / (1000 * 60 * 60 * 24) > ageInDays)
|
|
786
|
-
.slice(0, limit);
|
|
787
|
-
|
|
788
|
-
logger.log('usersToUnfollow', JSON.stringify(usersToUnfollow));
|
|
789
|
-
|
|
790
|
-
await safelyUnfollowUserList(usersToUnfollow, limit);
|
|
791
|
-
|
|
792
|
-
return usersToUnfollow.length;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
async function listManuallyFollowedUsers() {
|
|
796
|
-
const userData = await navigateToUserAndGetData(myUsername);
|
|
797
|
-
|
|
798
|
-
const allFollowing = await getFollowersOrFollowing({
|
|
799
|
-
userId: userData.id,
|
|
800
|
-
getFollowers: false,
|
|
801
|
-
});
|
|
802
|
-
|
|
803
|
-
return allFollowing.filter(u =>
|
|
804
|
-
!getPrevFollowedUser(u) && !excludeUsers.includes(u));
|
|
718
|
+
return j;
|
|
805
719
|
}
|
|
806
720
|
|
|
807
721
|
function getPage() {
|
|
@@ -978,6 +892,119 @@ const Instauto = async (db, browser, options) => {
|
|
|
978
892
|
logger.error('Failed to detect username', err);
|
|
979
893
|
}
|
|
980
894
|
|
|
895
|
+
if (!myUsername) {
|
|
896
|
+
throw new Error('Don\'t know what\'s my username');
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
const myUserData = await navigateToUserAndGetData(myUsername);
|
|
900
|
+
const myUserId = myUserData.id;
|
|
901
|
+
|
|
902
|
+
// --- END OF INITIALIZATION
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
async function doesUserFollowMe(username) {
|
|
906
|
+
try {
|
|
907
|
+
logger.info('Checking if user', username, 'follows us');
|
|
908
|
+
const userData = await navigateToUserAndGetData(username);
|
|
909
|
+
const userId = userData.id;
|
|
910
|
+
|
|
911
|
+
if (!(await navigateToUser(username))) throw new Error('User not found');
|
|
912
|
+
|
|
913
|
+
const elementHandles = await page.$x("//a[contains(.,' following')][contains(@href,'/following')]");
|
|
914
|
+
if (elementHandles.length === 0) throw new Error('Following button not found');
|
|
915
|
+
|
|
916
|
+
const [foundResponse] = await Promise.all([
|
|
917
|
+
page.waitForResponse((response) => {
|
|
918
|
+
const request = response.request();
|
|
919
|
+
return request.method() === 'GET' && new RegExp(`instagram.com/api/v1/friendships/${userId}/following/`).test(request.url());
|
|
920
|
+
}),
|
|
921
|
+
elementHandles[0].click(),
|
|
922
|
+
// page.waitForNavigation({ waitUntil: 'networkidle0' }),
|
|
923
|
+
]);
|
|
924
|
+
|
|
925
|
+
const { users } = JSON.parse(await foundResponse.text());
|
|
926
|
+
if (users.length < 2) throw new Error('Unable to find user follows list');
|
|
927
|
+
return users.some((user) => user.pk === myUserId); // If they follow us, we will show at the top of the list
|
|
928
|
+
} catch (err) {
|
|
929
|
+
logger.error('Failed to check if user follows us', err);
|
|
930
|
+
return undefined;
|
|
931
|
+
}
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
async function unfollowNonMutualFollowers({ limit } = {}) {
|
|
935
|
+
logger.log(`Unfollowing non-mutual followers (limit ${limit})...`);
|
|
936
|
+
|
|
937
|
+
/* const allFollowers = await getFollowersOrFollowing({
|
|
938
|
+
userId: myUserId,
|
|
939
|
+
getFollowers: true,
|
|
940
|
+
}); */
|
|
941
|
+
const allFollowingGenerator = getFollowersOrFollowingGenerator({
|
|
942
|
+
userId: myUserId,
|
|
943
|
+
getFollowers: false,
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
async function condition(username) {
|
|
947
|
+
// if (allFollowers.includes(u)) return false; // Follows us
|
|
948
|
+
if (excludeUsers.includes(username)) return false; // User is excluded by exclude list
|
|
949
|
+
if (haveRecentlyFollowedUser(username)) {
|
|
950
|
+
logger.log(`Have recently followed user ${username}, skipping`);
|
|
951
|
+
return false;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const followsMe = await doesUserFollowMe(username);
|
|
955
|
+
logger.info('User follows us?', followsMe);
|
|
956
|
+
return followsMe === false;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
await safelyUnfollowUserList(allFollowingGenerator, limit, condition);
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
async function unfollowAllUnknown({ limit } = {}) {
|
|
963
|
+
logger.log('Unfollowing all except excludes and auto followed');
|
|
964
|
+
|
|
965
|
+
const unfollowUsersGenerator = getFollowersOrFollowingGenerator({
|
|
966
|
+
userId: myUserId,
|
|
967
|
+
getFollowers: false,
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
function condition(username) {
|
|
971
|
+
if (getPrevFollowedUser(username)) return false; // we followed this user, so it's not unknown
|
|
972
|
+
if (excludeUsers.includes(username)) return false; // User is excluded by exclude list
|
|
973
|
+
return true;
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
await safelyUnfollowUserList(unfollowUsersGenerator, limit, condition);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
async function unfollowOldFollowed({ ageInDays, limit } = {}) {
|
|
980
|
+
assert(ageInDays);
|
|
981
|
+
|
|
982
|
+
logger.log(`Unfollowing currently followed users who were auto-followed more than ${ageInDays} days ago (limit ${limit})...`);
|
|
983
|
+
|
|
984
|
+
const followingUsersGenerator = getFollowersOrFollowingGenerator({
|
|
985
|
+
userId: myUserId,
|
|
986
|
+
getFollowers: false,
|
|
987
|
+
});
|
|
988
|
+
|
|
989
|
+
function condition(username) {
|
|
990
|
+
return getPrevFollowedUser(username) &&
|
|
991
|
+
!excludeUsers.includes(username) &&
|
|
992
|
+
(new Date().getTime() - getPrevFollowedUser(username).time) / (1000 * 60 * 60 * 24) > ageInDays;
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
return safelyUnfollowUserList(followingUsersGenerator, limit, condition);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
async function listManuallyFollowedUsers() {
|
|
999
|
+
const allFollowing = await getFollowersOrFollowing({
|
|
1000
|
+
userId: myUserId,
|
|
1001
|
+
getFollowers: false,
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
return allFollowing.filter(u =>
|
|
1005
|
+
!getPrevFollowedUser(u) && !excludeUsers.includes(u));
|
|
1006
|
+
}
|
|
1007
|
+
|
|
981
1008
|
return {
|
|
982
1009
|
followUserFollowers,
|
|
983
1010
|
unfollowNonMutualFollowers,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instauto",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "Instagram automation library written in Node.js",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -19,9 +19,10 @@
|
|
|
19
19
|
"user-agents": "^1.0.559"
|
|
20
20
|
},
|
|
21
21
|
"devDependencies": {
|
|
22
|
-
"eslint": "^
|
|
22
|
+
"eslint": "^7.32.0 || ^8.2.0",
|
|
23
23
|
"eslint-config-airbnb": "^16.1.0",
|
|
24
|
-
"eslint-
|
|
24
|
+
"eslint-config-airbnb-base": "^15.0.0",
|
|
25
|
+
"eslint-plugin-import": "^2.25.2",
|
|
25
26
|
"puppeteer": "^1.19.0"
|
|
26
27
|
}
|
|
27
28
|
}
|