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.
Files changed (3) hide show
  1. package/.eslintrc +4 -3
  2. package/index.js +245 -211
  3. 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, dev = 1) => {
121
- const msWithDev = ((Math.random() * dev) + 1) * ms;
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(`${instagramBaseUrl}/${encodeURIComponent(username)}`, username);
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 navigateToUser(username);
233
+ await navigateToUserWithCheck(username);
226
234
  return user;
227
235
  }
228
236
 
229
- await navigateToUser(username);
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
- // NOTE: assumes we are on this page
311
- async function followCurrentUser(username) {
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 unfollowCurrentUser(username) {
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, maxPages, shouldProceed: shouldProceedArg, graphqlVariables: graphqlVariablesIn }) {
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
- const shouldProceed = () => {
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
- edges.forEach(e => outUsers.push(e.node.username));
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 (shouldProceed()) {
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
- async function getFollowersOrFollowing({
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 getUsersWhoLikedContent({
452
- contentId, maxPages, shouldProceed,
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 likeCurrentUserImages({ username, likeImagesMin, likeImagesMax } = {}) {
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
- // Check if we have more than enough users that are not previously followed
579
- const shouldProceed = usersSoFar => (
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
- 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');
588
+ for (const follower of followersBatch) {
589
+ if (getPrevFollowedUser(follower)) {
590
+ logger.log('Skipping already followed user', follower);
624
591
  } else {
625
- await followCurrentUser(follower);
626
- numFollowedForThisUser += 1;
627
-
628
- await sleep(10000);
592
+ try {
593
+ if (numFollowedForThisUser >= maxFollowsPerUser) {
594
+ logger.log('Have reached followed limit for this user, stopping');
595
+ return;
596
+ }
629
597
 
630
- if (!isPrivate && enableLikeImages && !hasReachedDailyLikesLimit()) {
631
- try {
632
- await likeCurrentUserImages({ username: follower, likeImagesMin, likeImagesMax });
633
- } catch (err) {
634
- logger.error(`Failed to follow user's images ${follower}`, err);
635
- await takeScreenshot();
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(`Unfollowing ${usersToUnfollow.length} users`);
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 username of usersToUnfollow) {
682
- try {
683
- const userFound = await navigateToUser(username);
684
-
685
- if (!userFound) {
686
- await addPrevUnfollowedUser({ username, time: new Date().getTime(), noActionTaken: true });
687
- await sleep(3000);
688
- } else {
689
- const { noActionTaken } = await unfollowCurrentUser(username);
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
- if (noActionTaken) {
692
- await sleep(3000);
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 (j % 10 === 0) {
698
- logger.log('Have unfollowed 10 users since last break. Taking a break');
699
- await sleep(10 * 60 * 1000, 0.1);
712
+ if (limit && j >= limit) {
713
+ logger.log(`Have unfollowed limit of ${limit}, stopping`);
714
+ return j;
700
715
  }
701
- }
702
- }
703
716
 
704
- i += 1;
705
- logger.log(`Have now unfollowed ${i} users of total ${usersToUnfollow.length}`);
706
-
707
- if (limit && j >= limit) {
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 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));
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
- followCurrentUser,
987
- unfollowCurrentUser,
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": "7.2.3",
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": "^4.19.1",
22
+ "eslint": "^7.32.0 || ^8.2.0",
23
23
  "eslint-config-airbnb": "^16.1.0",
24
- "eslint-plugin-import": "^2.9.0",
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
  }