instauto 7.1.4 → 7.2.2

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 +82 -24
  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,7 +799,7 @@ const Instauto = async (db, browser, options) => {
758
799
 
759
800
  if (enableCookies) await tryLoadCookies();
760
801
 
761
- const goHome = async () => page.goto(`${instagramBaseUrl}/`);
802
+ const goHome = async () => page.goto(`${instagramBaseUrl}/?hl=en`);
762
803
 
763
804
  // https://github.com/mifi/SimpleInstaBot/issues/28
764
805
  async function setLang(short, long) {
@@ -788,15 +829,22 @@ const Instauto = async (db, browser, options) => {
788
829
  logger.log('Found language selector');
789
830
 
790
831
  // https://stackoverflow.com/questions/45864516/how-to-select-an-option-from-dropdown-select
791
- await page.evaluate((selectElem, short2) => {
832
+ const alreadyEnglish = await page.evaluate((selectElem, short2) => {
792
833
  const optionElem = selectElem.querySelector(`option[value='${short2}']`);
834
+ if (optionElem.selected) return true; // already selected?
793
835
  optionElem.selected = true;
794
836
  // eslint-disable-next-line no-undef
795
837
  const event = new Event('change', { bubbles: true });
796
838
  selectElem.dispatchEvent(event);
839
+ return false;
797
840
  }, elementHandles[0], short);
798
- logger.log('Selected language');
799
841
 
842
+ if (alreadyEnglish) {
843
+ logger.log('Already English language');
844
+ return;
845
+ }
846
+
847
+ logger.log('Selected language');
800
848
  await sleep(3000);
801
849
  await goHome();
802
850
  await sleep(1000);
@@ -809,12 +857,12 @@ const Instauto = async (db, browser, options) => {
809
857
  const setEnglishLang = async () => setLang('en', 'English');
810
858
  // const setEnglishLang = async () => setLang('de', 'Deutsch');
811
859
 
812
- async function tryPressButton(elementHandles, name) {
860
+ async function tryPressButton(elementHandles, name, sleepMs = 3000) {
813
861
  try {
814
862
  if (elementHandles.length === 1) {
815
863
  logger.log(`Pressing button: ${name}`);
816
864
  elementHandles[0].click();
817
- await sleep(3000);
865
+ await sleep(sleepMs);
818
866
  }
819
867
  } catch (err) {
820
868
  logger.warn(`Failed to press button: ${name}`);
@@ -824,6 +872,8 @@ const Instauto = async (db, browser, options) => {
824
872
  await setEnglishLang();
825
873
 
826
874
  await tryPressButton(await page.$x('//button[contains(text(), "Accept")]'), 'Accept cookies dialog');
875
+ await tryPressButton(await page.$x('//button[contains(text(), "Only allow essential cookies")]'), 'Accept cookies dialog 2 button 1', 10000);
876
+ await tryPressButton(await page.$x('//button[contains(text(), "Allow essential and optional cookies")]'), 'Accept cookies dialog 2 button 2', 10000);
827
877
 
828
878
  if (!(await isLoggedIn())) {
829
879
  if (!myUsername || !password) {
@@ -835,7 +885,7 @@ const Instauto = async (db, browser, options) => {
835
885
  await page.click('a[href="/accounts/login/?source=auth_switcher"]');
836
886
  await sleep(1000);
837
887
  } catch (err) {
838
- logger.warn('Login page button not found, assuming we have login form');
888
+ logger.info('No login page button, assuming we are on login form');
839
889
  }
840
890
 
841
891
  // Mobile version https://github.com/mifi/SimpleInstaBot/issues/7
@@ -846,8 +896,15 @@ const Instauto = async (db, browser, options) => {
846
896
  await page.type('input[name="password"]', password, { delay: 50 });
847
897
  await sleep(1000);
848
898
 
849
- const loginButton = (await page.$x("//button[.//text() = 'Log In']"))[0];
850
- await loginButton.click();
899
+ for (;;) {
900
+ const loginButton = (await page.$x("//button[.//text() = 'Log In']"))[0];
901
+ if (loginButton) {
902
+ await loginButton.click();
903
+ break;
904
+ }
905
+ logger.warn('Login button not found. Maybe you can help me click it? And also report an issue on github with a screenshot of what you\'re seeing :)');
906
+ await sleep(6000);
907
+ }
851
908
 
852
909
  await sleep(6000);
853
910
 
@@ -901,6 +958,7 @@ const Instauto = async (db, browser, options) => {
901
958
  sleep,
902
959
  listManuallyFollowedUsers,
903
960
  getFollowersOrFollowing,
961
+ getUsersWhoLikedContent,
904
962
  safelyUnfollowUserList,
905
963
  getPage,
906
964
  followUsersFollowers,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "instauto",
3
- "version": "7.1.4",
3
+ "version": "7.2.2",
4
4
  "description": "Instagram automation library written in Node.js",
5
5
  "main": "index.js",
6
6
  "scripts": {