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.
- package/index.js +115 -31
- 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
|
-
|
|
246
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
|
358
|
-
|
|
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 = `${
|
|
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
|
|
388
|
-
|
|
389
|
-
const
|
|
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
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
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")]'), '
|
|
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,
|