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.
- package/index.js +82 -24
- 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,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(
|
|
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.
|
|
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
|
-
|
|
850
|
-
|
|
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,
|