instauto 9.1.9 → 9.1.10
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/README.md +2 -3
- package/package.json +1 -1
- package/src/index.js +94 -19
package/README.md
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://discord.gg/Rh3KT9zyhj) [](https://paypal.me/mifino/usd)
|
|
4
4
|
|
|
5
|
-
instauto is an Instagram automation/bot library written in modern, clean javascript using Google's Puppeteer. Goal is to be very easy to set up, use, and extend, and obey instagram's limits. Heavily inspired by [InstaPy](https://github.com/timgrossmann/InstaPy), but I thought it was way too heavy and hard to setup.
|
|
5
|
+
instauto is an Instagram automation/bot library (API) written in modern, clean javascript using Google's Puppeteer. Goal is to be very easy to set up, use, and extend, and obey instagram's limits. Heavily inspired by [InstaPy](https://github.com/timgrossmann/InstaPy), but I thought it was way too heavy and hard to setup.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
Now there is a GUI application for those who don't want to code: [SimpleInstaBot](https://mifi.github.io/SimpleInstaBot/)
|
|
7
|
+
There is also a GUI application for those who don't want to code: [SimpleInstaBot](https://mifi.github.io/SimpleInstaBot/)
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
## Setup
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -16,6 +16,15 @@ function shuffleArray(arrayIn) {
|
|
|
16
16
|
return array;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// https://stackoverflow.com/questions/14822153/escape-single-quote-in-xpath-with-nokogiri
|
|
20
|
+
// example str: "That's mine", he said.
|
|
21
|
+
function escapeXpathStr(str) {
|
|
22
|
+
const parts = str.split("'").map((token) => `'${token}'`);
|
|
23
|
+
if (parts.length === 1) return `${parts[0]}`;
|
|
24
|
+
const str2 = parts.join(', "\'", ');
|
|
25
|
+
return `concat(${str2})`;
|
|
26
|
+
}
|
|
27
|
+
|
|
19
28
|
const botWorkShiftHours = 16;
|
|
20
29
|
|
|
21
30
|
const dayMs = 24 * 60 * 60 * 1000;
|
|
@@ -122,7 +131,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
122
131
|
const sleep = (ms, deviation = 1) => {
|
|
123
132
|
let msWithDev = ((Math.random() * deviation) + 1) * ms;
|
|
124
133
|
if (dryRun) msWithDev = Math.min(3000, msWithDev); // for dryRun, no need to wait so long
|
|
125
|
-
logger.log('Waiting',
|
|
134
|
+
logger.log('Waiting', (msWithDev / 1000).toFixed(2), 'sec');
|
|
126
135
|
return new Promise(resolve => setTimeout(resolve, msWithDev));
|
|
127
136
|
};
|
|
128
137
|
|
|
@@ -178,8 +187,9 @@ const Instauto = async (db, browser, options) => {
|
|
|
178
187
|
for (let attempt = 0; ; attempt += 1) {
|
|
179
188
|
logger.log(`Goto ${url}`);
|
|
180
189
|
const response = await gotoUrl(url);
|
|
181
|
-
await sleep(2000);
|
|
182
190
|
const status = response.status();
|
|
191
|
+
logger.log('Page loaded');
|
|
192
|
+
await sleep(2000);
|
|
183
193
|
|
|
184
194
|
// https://www.reddit.com/r/Instagram/comments/kwrt0s/error_560/
|
|
185
195
|
// https://github.com/mifi/instauto/issues/60
|
|
@@ -217,12 +227,14 @@ const Instauto = async (db, browser, options) => {
|
|
|
217
227
|
}
|
|
218
228
|
|
|
219
229
|
if (status === 200) {
|
|
230
|
+
// logger.log('Page returned 200 ☑️');
|
|
220
231
|
// some pages return 200 but nothing there (I think deleted accounts)
|
|
221
232
|
// https://github.com/mifi/SimpleInstaBot/issues/48
|
|
222
233
|
// example: https://www.instagram.com/victorialarson__/
|
|
223
234
|
// so we check if the page has the user's name on it
|
|
224
|
-
const
|
|
225
|
-
|
|
235
|
+
const elementHandles = await page.$x(`//body//main//*[contains(text(),${escapeXpathStr(username)})]`);
|
|
236
|
+
const foundUsernameOnPage = elementHandles.length > 0;
|
|
237
|
+
if (!foundUsernameOnPage) logger.warn(`Cannot find text "${username}" on page`);
|
|
226
238
|
return foundUsernameOnPage;
|
|
227
239
|
}
|
|
228
240
|
|
|
@@ -247,22 +259,85 @@ const Instauto = async (db, browser, options) => {
|
|
|
247
259
|
return cachedUserData;
|
|
248
260
|
}
|
|
249
261
|
|
|
262
|
+
async function getUserDataFromPage() {
|
|
263
|
+
// https://github.com/mifi/instauto/issues/115#issuecomment-1199335650
|
|
264
|
+
// to test in browser: document.getElementsByTagName('html')[0].innerHTML.split('\n');
|
|
265
|
+
try {
|
|
266
|
+
const body = await page.content();
|
|
267
|
+
for (let q of body.split(/\r?\n/)) {
|
|
268
|
+
if (q.includes('edge_followed_by')) {
|
|
269
|
+
// eslint-disable-next-line prefer-destructuring
|
|
270
|
+
q = q.split(',[],[')[1];
|
|
271
|
+
// eslint-disable-next-line prefer-destructuring
|
|
272
|
+
q = q.split(']]]')[0];
|
|
273
|
+
q = JSON.parse(q);
|
|
274
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
275
|
+
q = q.data.__bbox.result.response;
|
|
276
|
+
q = q.replace(/\\/g, '');
|
|
277
|
+
q = JSON.parse(q);
|
|
278
|
+
return q.data.user;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
} catch (err) {
|
|
282
|
+
logger.warn('Failed to get user data from page', err);
|
|
283
|
+
}
|
|
284
|
+
return undefined;
|
|
285
|
+
}
|
|
286
|
+
|
|
250
287
|
// intercept special XHR network request that fetches user's data and store it in a cache
|
|
251
288
|
// TODO fallback to DOM to get user ID if this request fails?
|
|
252
289
|
// https://github.com/mifi/SimpleInstaBot/issues/125#issuecomment-1145354294
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
290
|
+
async function getUserDataFromInterceptedRequest() {
|
|
291
|
+
const t = setTimeout(async () => {
|
|
292
|
+
logger.log('Unable to intercept request, will send manually');
|
|
293
|
+
try {
|
|
294
|
+
await page.evaluate(async (username2) => {
|
|
295
|
+
const response = await window.fetch(`https://i.instagram.com/api/v1/users/web_profile_info/?username=${encodeURIComponent(username2.toLowerCase())}`, { mode: 'cors', credentials: 'include', headers: { 'x-ig-app-id': '936619743392459' } });
|
|
296
|
+
await response.json(); // else it will not finish the request
|
|
297
|
+
}, username);
|
|
298
|
+
// todo `https://i.instagram.com/api/v1/users/${userId}/info/`
|
|
299
|
+
// https://www.javafixing.com/2022/07/fixed-can-get-instagram-profile-picture.html?m=1
|
|
300
|
+
} catch (err) {
|
|
301
|
+
logger.error('Failed to manually send request', err);
|
|
302
|
+
}
|
|
303
|
+
}, 5000);
|
|
304
|
+
|
|
305
|
+
try {
|
|
306
|
+
const [foundResponse] = await Promise.all([
|
|
307
|
+
page.waitForResponse((response) => {
|
|
308
|
+
const request = response.request();
|
|
309
|
+
return request.method() === 'GET' && new RegExp(`https:\\/\\/i\\.instagram\\.com\\/api\\/v1\\/users\\/web_profile_info\\/\\?username=${encodeURIComponent(username.toLowerCase())}`).test(request.url());
|
|
310
|
+
}, { timeout: 30000 }),
|
|
311
|
+
navigateToUserWithCheck(username),
|
|
312
|
+
// page.waitForNavigation({ waitUntil: 'networkidle0' }),
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
const json = JSON.parse(await foundResponse.text());
|
|
316
|
+
return json.data.user;
|
|
317
|
+
} finally {
|
|
318
|
+
clearTimeout(t);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
logger.log('Trying to get user data from HTML');
|
|
323
|
+
|
|
324
|
+
await navigateToUserWithCheck(username);
|
|
325
|
+
let userData = await getUserDataFromPage();
|
|
326
|
+
if (userData) {
|
|
327
|
+
userDataCache[username] = userData;
|
|
328
|
+
return userData;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
logger.log('Need to intercept network request to get user data');
|
|
332
|
+
|
|
333
|
+
// works for old accounts only:
|
|
334
|
+
userData = await getUserDataFromInterceptedRequest();
|
|
335
|
+
if (userData) {
|
|
336
|
+
userDataCache[username] = userData;
|
|
337
|
+
return userData;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return undefined;
|
|
266
341
|
}
|
|
267
342
|
|
|
268
343
|
async function getPageJson() {
|
|
@@ -1031,7 +1106,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
1031
1106
|
return followsMe === false;
|
|
1032
1107
|
}
|
|
1033
1108
|
|
|
1034
|
-
|
|
1109
|
+
return safelyUnfollowUserList(allFollowingGenerator, limit, condition);
|
|
1035
1110
|
}
|
|
1036
1111
|
|
|
1037
1112
|
async function unfollowAllUnknown({ limit } = {}) {
|
|
@@ -1048,7 +1123,7 @@ const Instauto = async (db, browser, options) => {
|
|
|
1048
1123
|
return true;
|
|
1049
1124
|
}
|
|
1050
1125
|
|
|
1051
|
-
|
|
1126
|
+
return safelyUnfollowUserList(unfollowUsersGenerator, limit, condition);
|
|
1052
1127
|
}
|
|
1053
1128
|
|
|
1054
1129
|
async function unfollowOldFollowed({ ageInDays, limit } = {}) {
|