instauto 7.2.3 → 9.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 +245 -211
- 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,15 @@ 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);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function navigateToUserWithCheck(username) {
|
|
214
|
+
if (!(await navigateToUser(username))) throw new Error('User not found');
|
|
207
215
|
}
|
|
208
216
|
|
|
209
217
|
async function getPageJson() {
|
|
@@ -222,11 +230,11 @@ const Instauto = async (db, browser, options) => {
|
|
|
222
230
|
|
|
223
231
|
const { user } = json.graphql;
|
|
224
232
|
|
|
225
|
-
await
|
|
233
|
+
await navigateToUserWithCheck(username);
|
|
226
234
|
return user;
|
|
227
235
|
}
|
|
228
236
|
|
|
229
|
-
await
|
|
237
|
+
await navigateToUserWithCheck(username);
|
|
230
238
|
|
|
231
239
|
// eslint-disable-next-line no-underscore-dangle
|
|
232
240
|
const sharedData = await page.evaluate(() => window._sharedData);
|
|
@@ -307,8 +315,8 @@ const Instauto = async (db, browser, options) => {
|
|
|
307
315
|
return elementHandles[0];
|
|
308
316
|
}
|
|
309
317
|
|
|
310
|
-
|
|
311
|
-
|
|
318
|
+
async function followUser(username) {
|
|
319
|
+
await navigateToUserWithCheck(username);
|
|
312
320
|
const elementHandle = await findFollowButton();
|
|
313
321
|
|
|
314
322
|
if (!elementHandle) {
|
|
@@ -349,7 +357,8 @@ const Instauto = async (db, browser, options) => {
|
|
|
349
357
|
|
|
350
358
|
// See https://github.com/timgrossmann/InstaPy/pull/2345
|
|
351
359
|
// https://github.com/timgrossmann/InstaPy/issues/2355
|
|
352
|
-
async function
|
|
360
|
+
async function unfollowUser(username) {
|
|
361
|
+
await navigateToUserWithCheck(username);
|
|
353
362
|
logger.log(`Unfollowing user ${username}`);
|
|
354
363
|
|
|
355
364
|
const res = { username, time: new Date().getTime() };
|
|
@@ -391,7 +400,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
391
400
|
|
|
392
401
|
const isLoggedIn = async () => (await page.$x('//*[@aria-label="Home"]')).length === 1;
|
|
393
402
|
|
|
394
|
-
async function graphqlQueryUsers({ queryHash, getResponseProp,
|
|
403
|
+
async function* graphqlQueryUsers({ queryHash, getResponseProp, graphqlVariables: graphqlVariablesIn }) {
|
|
395
404
|
const graphqlUrl = `${instagramBaseUrl}/graphql/query/?query_hash=${queryHash}`;
|
|
396
405
|
|
|
397
406
|
const graphqlVariables = {
|
|
@@ -404,14 +413,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
404
413
|
let hasNextPage = true;
|
|
405
414
|
let i = 0;
|
|
406
415
|
|
|
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()) {
|
|
416
|
+
while (hasNextPage) {
|
|
415
417
|
const url = `${graphqlUrl}&variables=${JSON.stringify(graphqlVariables)}`;
|
|
416
418
|
// logger.log(url);
|
|
417
419
|
await page.goto(url);
|
|
@@ -421,44 +423,47 @@ const Instauto = async (db, browser, options) => {
|
|
|
421
423
|
const pageInfo = subProp.page_info;
|
|
422
424
|
const { edges } = subProp;
|
|
423
425
|
|
|
424
|
-
|
|
426
|
+
const ret = [];
|
|
427
|
+
edges.forEach(e => ret.push(e.node.username));
|
|
425
428
|
|
|
426
429
|
graphqlVariables.after = pageInfo.end_cursor;
|
|
427
430
|
hasNextPage = pageInfo.has_next_page;
|
|
428
431
|
i += 1;
|
|
429
432
|
|
|
430
|
-
if (
|
|
433
|
+
if (hasNextPage) {
|
|
431
434
|
logger.log(`Has more pages (current ${i})`);
|
|
432
435
|
// await sleep(300);
|
|
433
436
|
}
|
|
437
|
+
|
|
438
|
+
yield ret;
|
|
434
439
|
}
|
|
435
440
|
|
|
436
441
|
return outUsers;
|
|
437
442
|
}
|
|
438
443
|
|
|
439
|
-
|
|
440
|
-
userId, getFollowers = false, maxPages, shouldProceed,
|
|
441
|
-
}) {
|
|
444
|
+
function getFollowersOrFollowingGenerator({ userId, getFollowers = false }) {
|
|
442
445
|
return graphqlQueryUsers({
|
|
443
446
|
getResponseProp: (json) => json.data.user[getFollowers ? 'edge_followed_by' : 'edge_follow'],
|
|
444
447
|
graphqlVariables: { id: userId },
|
|
445
|
-
shouldProceed,
|
|
446
|
-
maxPages,
|
|
447
448
|
queryHash: getFollowers ? '37479f2b8209594dde7facb0d904896a' : '58712303d941c6855d4e888c5f0cd22f',
|
|
448
449
|
});
|
|
449
450
|
}
|
|
450
451
|
|
|
451
|
-
async function
|
|
452
|
-
|
|
453
|
-
|
|
452
|
+
async function getFollowersOrFollowing({ userId, getFollowers = false }) {
|
|
453
|
+
let users = [];
|
|
454
|
+
for await (const usersBatch of getFollowersOrFollowingGenerator({ userId, getFollowers })) {
|
|
455
|
+
users = [...users, ...usersBatch];
|
|
456
|
+
}
|
|
457
|
+
return users;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
function getUsersWhoLikedContent({ contentId }) {
|
|
454
461
|
return graphqlQueryUsers({
|
|
455
462
|
getResponseProp: (json) => json.data.shortcode_media.edge_liked_by,
|
|
456
463
|
graphqlVariables: {
|
|
457
464
|
shortcode: contentId,
|
|
458
465
|
include_reel: true,
|
|
459
466
|
},
|
|
460
|
-
shouldProceed,
|
|
461
|
-
maxPages,
|
|
462
467
|
queryHash: 'd5d763b1e2acf209d62d22d184488e57',
|
|
463
468
|
});
|
|
464
469
|
}
|
|
@@ -549,9 +554,11 @@ const Instauto = async (db, browser, options) => {
|
|
|
549
554
|
/* eslint-enable no-undef */
|
|
550
555
|
|
|
551
556
|
|
|
552
|
-
async function
|
|
557
|
+
async function likeUserImages({ username, likeImagesMin, likeImagesMax } = {}) {
|
|
553
558
|
if (!likeImagesMin || !likeImagesMax || likeImagesMax < likeImagesMin || likeImagesMin < 1) throw new Error('Invalid arguments');
|
|
554
559
|
|
|
560
|
+
await navigateToUserWithCheck(username);
|
|
561
|
+
|
|
555
562
|
logger.log(`Liking ${likeImagesMin}-${likeImagesMax} user images`);
|
|
556
563
|
try {
|
|
557
564
|
await page.exposeFunction('instautoSleep', sleep);
|
|
@@ -575,73 +582,66 @@ const Instauto = async (db, browser, options) => {
|
|
|
575
582
|
|
|
576
583
|
const userData = await navigateToUserAndGetData(username);
|
|
577
584
|
|
|
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);
|
|
589
|
-
|
|
590
|
-
// Filter again
|
|
591
|
-
followers = followers.filter(f => !getPrevFollowedUser(f));
|
|
585
|
+
for await (const followersBatch of getFollowersOrFollowingGenerator({ userId: userData.id, getFollowers: true })) {
|
|
586
|
+
logger.log('User followers batch', followersBatch);
|
|
592
587
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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');
|
|
588
|
+
for (const follower of followersBatch) {
|
|
589
|
+
if (getPrevFollowedUser(follower)) {
|
|
590
|
+
logger.log('Skipping already followed user', follower);
|
|
624
591
|
} else {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
592
|
+
try {
|
|
593
|
+
if (numFollowedForThisUser >= maxFollowsPerUser) {
|
|
594
|
+
logger.log('Have reached followed limit for this user, stopping');
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
629
597
|
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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);
|
|
627
|
+
|
|
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
|
+
}
|
|
636
|
+
|
|
637
|
+
await sleep(20000);
|
|
638
|
+
await throttle();
|
|
636
639
|
}
|
|
640
|
+
} catch (err) {
|
|
641
|
+
logger.error(`Failed to process follower ${follower}`, err);
|
|
642
|
+
await sleep(20000);
|
|
637
643
|
}
|
|
638
|
-
|
|
639
|
-
await sleep(20000);
|
|
640
|
-
await throttle();
|
|
641
644
|
}
|
|
642
|
-
} catch (err) {
|
|
643
|
-
logger.error(`Failed to process follower ${follower}`, err);
|
|
644
|
-
await sleep(20000);
|
|
645
645
|
}
|
|
646
646
|
}
|
|
647
647
|
}
|
|
@@ -672,136 +672,57 @@ const Instauto = async (db, browser, options) => {
|
|
|
672
672
|
}
|
|
673
673
|
}
|
|
674
674
|
|
|
675
|
-
async function safelyUnfollowUserList(usersToUnfollow, limit) {
|
|
676
|
-
logger.log(
|
|
675
|
+
async function safelyUnfollowUserList(usersToUnfollow, limit, condition = () => true) {
|
|
676
|
+
logger.log('Unfollowing users, up to limit', limit);
|
|
677
677
|
|
|
678
678
|
let i = 0; // Number of people processed
|
|
679
679
|
let j = 0; // Number of people actually unfollowed (button pressed)
|
|
680
680
|
|
|
681
|
-
for (const
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
681
|
+
for await (const listOrUsername of usersToUnfollow) {
|
|
682
|
+
// backward compatible:
|
|
683
|
+
const list = Array.isArray(listOrUsername) ? listOrUsername : [listOrUsername];
|
|
684
|
+
|
|
685
|
+
for (const username of list) {
|
|
686
|
+
if (await condition(username)) {
|
|
687
|
+
try {
|
|
688
|
+
const userFound = await navigateToUser(username);
|
|
689
|
+
|
|
690
|
+
if (!userFound) {
|
|
691
|
+
await addPrevUnfollowedUser({ username, time: new Date().getTime(), noActionTaken: true });
|
|
692
|
+
await sleep(3000);
|
|
693
|
+
} else {
|
|
694
|
+
const { noActionTaken } = await unfollowUser(username);
|
|
695
|
+
|
|
696
|
+
if (noActionTaken) {
|
|
697
|
+
await sleep(3000);
|
|
698
|
+
} else {
|
|
699
|
+
await sleep(15000);
|
|
700
|
+
j += 1;
|
|
701
|
+
|
|
702
|
+
if (j % 10 === 0) {
|
|
703
|
+
logger.log('Have unfollowed 10 users since last break. Taking a break');
|
|
704
|
+
await sleep(10 * 60 * 1000, 0.1);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
}
|
|
690
708
|
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
} else {
|
|
694
|
-
await sleep(15000);
|
|
695
|
-
j += 1;
|
|
709
|
+
i += 1;
|
|
710
|
+
logger.log(`Have now unfollowed (or tried to unfollow) ${i} users`);
|
|
696
711
|
|
|
697
|
-
if (
|
|
698
|
-
logger.log(
|
|
699
|
-
|
|
712
|
+
if (limit && j >= limit) {
|
|
713
|
+
logger.log(`Have unfollowed limit of ${limit}, stopping`);
|
|
714
|
+
return j;
|
|
700
715
|
}
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
716
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
logger.log(`Have unfollowed limit of ${limit}, stopping`);
|
|
709
|
-
return;
|
|
717
|
+
await throttle();
|
|
718
|
+
} catch (err) {
|
|
719
|
+
logger.error('Failed to unfollow, continuing with next', err);
|
|
720
|
+
}
|
|
710
721
|
}
|
|
711
|
-
|
|
712
|
-
await throttle();
|
|
713
|
-
} catch (err) {
|
|
714
|
-
logger.error('Failed to unfollow, continuing with next', err);
|
|
715
722
|
}
|
|
716
723
|
}
|
|
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
|
-
|
|
782
|
-
const usersToUnfollow = allFollowing.filter(u =>
|
|
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
724
|
|
|
792
|
-
return
|
|
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));
|
|
725
|
+
return j;
|
|
805
726
|
}
|
|
806
727
|
|
|
807
728
|
function getPage() {
|
|
@@ -978,13 +899,125 @@ const Instauto = async (db, browser, options) => {
|
|
|
978
899
|
logger.error('Failed to detect username', err);
|
|
979
900
|
}
|
|
980
901
|
|
|
902
|
+
if (!myUsername) {
|
|
903
|
+
throw new Error('Don\'t know what\'s my username');
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
const myUserData = await navigateToUserAndGetData(myUsername);
|
|
907
|
+
const myUserId = myUserData.id;
|
|
908
|
+
|
|
909
|
+
// --- END OF INITIALIZATION
|
|
910
|
+
|
|
911
|
+
async function doesUserFollowMe(username) {
|
|
912
|
+
try {
|
|
913
|
+
logger.info('Checking if user', username, 'follows us');
|
|
914
|
+
const userData = await navigateToUserAndGetData(username);
|
|
915
|
+
const userId = userData.id;
|
|
916
|
+
|
|
917
|
+
const elementHandles = await page.$x("//a[contains(.,' following')][contains(@href,'/following')]");
|
|
918
|
+
if (elementHandles.length === 0) throw new Error('Following button not found');
|
|
919
|
+
|
|
920
|
+
const [foundResponse] = await Promise.all([
|
|
921
|
+
page.waitForResponse((response) => {
|
|
922
|
+
const request = response.request();
|
|
923
|
+
return request.method() === 'GET' && new RegExp(`instagram.com/api/v1/friendships/${userId}/following/`).test(request.url());
|
|
924
|
+
}),
|
|
925
|
+
elementHandles[0].click(),
|
|
926
|
+
// page.waitForNavigation({ waitUntil: 'networkidle0' }),
|
|
927
|
+
]);
|
|
928
|
+
|
|
929
|
+
const { users } = JSON.parse(await foundResponse.text());
|
|
930
|
+
if (users.length < 2) throw new Error('Unable to find user follows list');
|
|
931
|
+
// console.log(users, myUserId);
|
|
932
|
+
return users.some((user) => String(user.pk) === String(myUserId) || user.username === myUsername); // If they follow us, we will show at the top of the list
|
|
933
|
+
} catch (err) {
|
|
934
|
+
logger.error('Failed to check if user follows us', err);
|
|
935
|
+
return undefined;
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
async function unfollowNonMutualFollowers({ limit } = {}) {
|
|
940
|
+
logger.log(`Unfollowing non-mutual followers (limit ${limit})...`);
|
|
941
|
+
|
|
942
|
+
/* const allFollowers = await getFollowersOrFollowing({
|
|
943
|
+
userId: myUserId,
|
|
944
|
+
getFollowers: true,
|
|
945
|
+
}); */
|
|
946
|
+
const allFollowingGenerator = getFollowersOrFollowingGenerator({
|
|
947
|
+
userId: myUserId,
|
|
948
|
+
getFollowers: false,
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
async function condition(username) {
|
|
952
|
+
// if (allFollowers.includes(u)) return false; // Follows us
|
|
953
|
+
if (excludeUsers.includes(username)) return false; // User is excluded by exclude list
|
|
954
|
+
if (haveRecentlyFollowedUser(username)) {
|
|
955
|
+
logger.log(`Have recently followed user ${username}, skipping`);
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
const followsMe = await doesUserFollowMe(username);
|
|
960
|
+
logger.info('User follows us?', followsMe);
|
|
961
|
+
return followsMe === false;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
await safelyUnfollowUserList(allFollowingGenerator, limit, condition);
|
|
965
|
+
}
|
|
966
|
+
|
|
967
|
+
async function unfollowAllUnknown({ limit } = {}) {
|
|
968
|
+
logger.log('Unfollowing all except excludes and auto followed');
|
|
969
|
+
|
|
970
|
+
const unfollowUsersGenerator = getFollowersOrFollowingGenerator({
|
|
971
|
+
userId: myUserId,
|
|
972
|
+
getFollowers: false,
|
|
973
|
+
});
|
|
974
|
+
|
|
975
|
+
function condition(username) {
|
|
976
|
+
if (getPrevFollowedUser(username)) return false; // we followed this user, so it's not unknown
|
|
977
|
+
if (excludeUsers.includes(username)) return false; // User is excluded by exclude list
|
|
978
|
+
return true;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
await safelyUnfollowUserList(unfollowUsersGenerator, limit, condition);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
async function unfollowOldFollowed({ ageInDays, limit } = {}) {
|
|
985
|
+
assert(ageInDays);
|
|
986
|
+
|
|
987
|
+
logger.log(`Unfollowing currently followed users who were auto-followed more than ${ageInDays} days ago (limit ${limit})...`);
|
|
988
|
+
|
|
989
|
+
const followingUsersGenerator = getFollowersOrFollowingGenerator({
|
|
990
|
+
userId: myUserId,
|
|
991
|
+
getFollowers: false,
|
|
992
|
+
});
|
|
993
|
+
|
|
994
|
+
function condition(username) {
|
|
995
|
+
return getPrevFollowedUser(username) &&
|
|
996
|
+
!excludeUsers.includes(username) &&
|
|
997
|
+
(new Date().getTime() - getPrevFollowedUser(username).time) / (1000 * 60 * 60 * 24) > ageInDays;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
return safelyUnfollowUserList(followingUsersGenerator, limit, condition);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
async function listManuallyFollowedUsers() {
|
|
1004
|
+
const allFollowing = await getFollowersOrFollowing({
|
|
1005
|
+
userId: myUserId,
|
|
1006
|
+
getFollowers: false,
|
|
1007
|
+
});
|
|
1008
|
+
|
|
1009
|
+
return allFollowing.filter(u =>
|
|
1010
|
+
!getPrevFollowedUser(u) && !excludeUsers.includes(u));
|
|
1011
|
+
}
|
|
1012
|
+
|
|
981
1013
|
return {
|
|
982
1014
|
followUserFollowers,
|
|
983
1015
|
unfollowNonMutualFollowers,
|
|
984
1016
|
unfollowAllUnknown,
|
|
985
1017
|
unfollowOldFollowed,
|
|
986
|
-
|
|
987
|
-
|
|
1018
|
+
followUser,
|
|
1019
|
+
unfollowUser,
|
|
1020
|
+
likeUserImages,
|
|
988
1021
|
sleep,
|
|
989
1022
|
listManuallyFollowedUsers,
|
|
990
1023
|
getFollowersOrFollowing,
|
|
@@ -992,6 +1025,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
992
1025
|
safelyUnfollowUserList,
|
|
993
1026
|
getPage,
|
|
994
1027
|
followUsersFollowers,
|
|
1028
|
+
doesUserFollowMe,
|
|
995
1029
|
};
|
|
996
1030
|
};
|
|
997
1031
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "instauto",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.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
|
}
|