instauto 7.1.2 → 7.2.1

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 (2) hide show
  1. package/index.js +115 -31
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -242,12 +242,31 @@ const Instauto = async (db, browser, options) => {
242
242
  }
243
243
  }
244
244
 
245
- async function findFollowButton() {
246
- const elementHandles = await page.$x("//header//button[text()='Follow']");
245
+ // How to test xpaths in the browser:
246
+ // document.evaluate("your xpath", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null ).singleNodeValue
247
+ async function findButtonWithText(text) {
248
+ // todo escape text?
249
+
250
+ // button seems to look like this now:
251
+ // <button class="..."><div class="...">Follow</div></button>
252
+ // https://sqa.stackexchange.com/questions/36918/xpath-text-buy-now-is-working-but-not-containstext-buy-now
253
+ // https://github.com/mifi/SimpleInstaBot/issues/106
254
+ let elementHandles = await page.$x(`//header//button[contains(.,'${text}')]`);
247
255
  if (elementHandles.length > 0) return elementHandles[0];
248
256
 
249
- const elementHandles2 = await page.$x("//header//button[text()='Follow Back']");
250
- if (elementHandles2.length > 0) return elementHandles2[0];
257
+ // old button:
258
+ elementHandles = await page.$x(`//header//button[text()='${text}']`);
259
+ if (elementHandles.length > 0) return elementHandles[0];
260
+
261
+ return undefined;
262
+ }
263
+
264
+ async function findFollowButton() {
265
+ let button = await findButtonWithText('Follow');
266
+ if (button) return button;
267
+
268
+ button = await findButtonWithText('Follow Back');
269
+ if (button) return button;
251
270
 
252
271
  return undefined;
253
272
  }
@@ -354,16 +373,12 @@ const Instauto = async (db, browser, options) => {
354
373
 
355
374
  const isLoggedIn = async () => (await page.$x('//*[@aria-label="Home"]')).length === 1;
356
375
 
357
- async function getFollowersOrFollowing({
358
- userId, getFollowers = false, maxPages, shouldProceed: shouldProceedArg,
359
- }) {
360
- const graphqlUrl = `${instagramBaseUrl}/graphql/query`;
361
- const followersUrl = `${graphqlUrl}/?query_hash=37479f2b8209594dde7facb0d904896a`;
362
- const followingUrl = `${graphqlUrl}/?query_hash=58712303d941c6855d4e888c5f0cd22f`;
376
+ async function graphqlQueryUsers({ queryHash, getResponseProp, maxPages, shouldProceed: shouldProceedArg, graphqlVariables: graphqlVariablesIn }) {
377
+ const graphqlUrl = `${instagramBaseUrl}/graphql/query/?query_hash=${queryHash}`;
363
378
 
364
379
  const graphqlVariables = {
365
- id: userId,
366
380
  first: 50,
381
+ ...graphqlVariablesIn,
367
382
  };
368
383
 
369
384
  const outUsers = [];
@@ -379,15 +394,14 @@ const Instauto = async (db, browser, options) => {
379
394
  };
380
395
 
381
396
  while (shouldProceed()) {
382
- const url = `${getFollowers ? followersUrl : followingUrl}&variables=${JSON.stringify(graphqlVariables)}`;
397
+ const url = `${graphqlUrl}&variables=${JSON.stringify(graphqlVariables)}`;
383
398
  // logger.log(url);
384
399
  await page.goto(url);
385
400
  const json = await getPageJson();
386
401
 
387
- const subPropName = getFollowers ? 'edge_followed_by' : 'edge_follow';
388
-
389
- const pageInfo = json.data.user[subPropName].page_info;
390
- const { edges } = json.data.user[subPropName];
402
+ const subProp = getResponseProp(json);
403
+ const pageInfo = subProp.page_info;
404
+ const { edges } = subProp;
391
405
 
392
406
  edges.forEach(e => outUsers.push(e.node.username));
393
407
 
@@ -404,6 +418,33 @@ const Instauto = async (db, browser, options) => {
404
418
  return outUsers;
405
419
  }
406
420
 
421
+ async function getFollowersOrFollowing({
422
+ userId, getFollowers = false, maxPages, shouldProceed,
423
+ }) {
424
+ return graphqlQueryUsers({
425
+ getResponseProp: (json) => json.data.user[getFollowers ? 'edge_followed_by' : 'edge_follow'],
426
+ graphqlVariables: { id: userId },
427
+ shouldProceed,
428
+ maxPages,
429
+ queryHash: getFollowers ? '37479f2b8209594dde7facb0d904896a' : '58712303d941c6855d4e888c5f0cd22f',
430
+ });
431
+ }
432
+
433
+ async function getUsersWhoLikedContent({
434
+ contentId, maxPages, shouldProceed,
435
+ }) {
436
+ return graphqlQueryUsers({
437
+ getResponseProp: (json) => json.data.shortcode_media.edge_liked_by,
438
+ graphqlVariables: {
439
+ shortcode: contentId,
440
+ include_reel: true,
441
+ },
442
+ shouldProceed,
443
+ maxPages,
444
+ queryHash: 'd5d763b1e2acf209d62d22d184488e57',
445
+ });
446
+ }
447
+
407
448
  /* eslint-disable no-undef */
408
449
  async function likeCurrentUserImagesPageCode({ dryRun: dryRunIn, likeImagesMin, likeImagesMax }) {
409
450
  const allImages = Array.from(document.getElementsByTagName('a')).filter(el => /instagram.com\/p\//.test(el.href));
@@ -758,19 +799,56 @@ const Instauto = async (db, browser, options) => {
758
799
 
759
800
  if (enableCookies) await tryLoadCookies();
760
801
 
802
+ const goHome = async () => page.goto(`${instagramBaseUrl}/`);
803
+
804
+ // https://github.com/mifi/SimpleInstaBot/issues/28
805
+ async function setLang(short, long) {
806
+ logger.log(`Setting language to ${long} (${short})`);
761
807
 
762
- // Not sure if we can set cookies before having gone to a page
763
- await page.goto(`${instagramBaseUrl}/`);
764
- await sleep(1000);
765
- logger.log('Setting language to english');
766
- await page.setCookie({
767
- name: 'ig_lang',
768
- value: 'en',
769
- path: '/',
770
- });
771
- await sleep(1000);
772
- await page.goto(`${instagramBaseUrl}/`);
773
- await sleep(3000);
808
+ // This doesn't seem to always work, hence why it's just a fallback now
809
+ async function fallbackSetLang() {
810
+ await goHome();
811
+ await sleep(1000);
812
+
813
+ await page.setCookie({
814
+ name: 'ig_lang',
815
+ value: short,
816
+ path: '/',
817
+ });
818
+ await sleep(1000);
819
+ await goHome();
820
+ await sleep(3000);
821
+ }
822
+
823
+ try {
824
+ await sleep(1000);
825
+ await goHome();
826
+ await sleep(3000);
827
+ const elementHandles = await page.$x(`//select[//option[@value='${short}' and text()='${long}']]`);
828
+ if (elementHandles.length < 1) throw new Error('Language selector not found');
829
+ logger.log('Found language selector');
830
+
831
+ // https://stackoverflow.com/questions/45864516/how-to-select-an-option-from-dropdown-select
832
+ await page.evaluate((selectElem, short2) => {
833
+ const optionElem = selectElem.querySelector(`option[value='${short2}']`);
834
+ optionElem.selected = true;
835
+ // eslint-disable-next-line no-undef
836
+ const event = new Event('change', { bubbles: true });
837
+ selectElem.dispatchEvent(event);
838
+ }, elementHandles[0], short);
839
+ logger.log('Selected language');
840
+
841
+ await sleep(3000);
842
+ await goHome();
843
+ await sleep(1000);
844
+ } catch (err) {
845
+ logger.error('Failed to set language, trying fallback (cookie)', err);
846
+ await fallbackSetLang();
847
+ }
848
+ }
849
+
850
+ const setEnglishLang = async () => setLang('en', 'English');
851
+ // const setEnglishLang = async () => setLang('de', 'Deutsch');
774
852
 
775
853
  async function tryPressButton(elementHandles, name) {
776
854
  try {
@@ -784,6 +862,7 @@ const Instauto = async (db, browser, options) => {
784
862
  }
785
863
  }
786
864
 
865
+ await setEnglishLang();
787
866
 
788
867
  await tryPressButton(await page.$x('//button[contains(text(), "Accept")]'), 'Accept cookies dialog');
789
868
 
@@ -816,7 +895,6 @@ const Instauto = async (db, browser, options) => {
816
895
  // Sometimes login button gets stuck with a spinner
817
896
  // https://github.com/mifi/SimpleInstaBot/issues/25
818
897
  if (!(await isLoggedIn())) {
819
- await sleep(5000);
820
898
  logger.log('Still not logged in, trying to reload loading page');
821
899
  await page.reload();
822
900
  await sleep(5000);
@@ -824,13 +902,18 @@ const Instauto = async (db, browser, options) => {
824
902
 
825
903
  let warnedAboutLoginFail = false;
826
904
  while (!(await isLoggedIn())) {
827
- if (!warnedAboutLoginFail) logger.warn('WARNING: Login has not succeeded. This could be because of an incorrect username/password, or a "suspicious login attempt"-message. You need to manually complete the process.');
905
+ if (!warnedAboutLoginFail) logger.warn('WARNING: Login has not succeeded. This could be because of an incorrect username/password, or a "suspicious login attempt"-message. You need to manually complete the process, or if really logged in, click the Instagram logo in the top left to go to the Home page.');
828
906
  warnedAboutLoginFail = true;
829
907
  await sleep(5000);
830
908
  }
831
909
 
910
+ // In case language gets reset after logging in
911
+ await setEnglishLang();
912
+
832
913
  // Mobile version https://github.com/mifi/SimpleInstaBot/issues/7
833
- await tryPressButton(await page.$x('//button[contains(text(), "Save Info")]'), 'Save login info dialog');
914
+ await tryPressButton(await page.$x('//button[contains(text(), "Save Info")]'), 'Login info dialog: Save Info');
915
+ // May sometimes be "Save info" too? https://github.com/mifi/instauto/pull/70
916
+ await tryPressButton(await page.$x('//button[contains(text(), "Save info")]'), 'Login info dialog: Save info');
834
917
  }
835
918
 
836
919
  await tryPressButton(await page.$x('//button[contains(text(), "Not Now")]'), 'Turn on Notifications dialog');
@@ -859,6 +942,7 @@ const Instauto = async (db, browser, options) => {
859
942
  sleep,
860
943
  listManuallyFollowedUsers,
861
944
  getFollowersOrFollowing,
945
+ getUsersWhoLikedContent,
862
946
  safelyUnfollowUserList,
863
947
  getPage,
864
948
  followUsersFollowers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instauto",
3
- "version": "7.1.2",
3
+ "version": "7.2.1",
4
4
  "description": "Instagram automation library written in Node.js",
5
5
  "main": "index.js",
6
6
  "scripts": {