Skip to content

Commit

Permalink
implement liking images
Browse files Browse the repository at this point in the history
  • Loading branch information
mifi committed Apr 7, 2020
1 parent b8ea457 commit 5f4821a
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 9 deletions.
2 changes: 2 additions & 0 deletions example.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ const options = {
followedDbPath: './followed.json',
// Will store all unfollowed users here
unfollowedDbPath: './unfollowed.json',
// Will store all likes here
likesDbPath: './likes.json',

username: 'your-ig-username',
password: 'your-ig-password',
Expand Down
152 changes: 143 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,26 @@ const fs = require('fs-extra');
const keyBy = require('lodash/keyBy');
const UserAgent = require('user-agents');


function shuffleArray(array) {
// NOTE duplicated in page
function shuffleArray(arrayIn) {
const array = [...arrayIn];
for (let i = array.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]; // eslint-disable-line no-param-reassign
}
return array;
}

const dayMs = 24 * 60 * 60 * 1000;
const hourMs = 60 * 60 * 1000;

module.exports = async (browser, options) => {
const {
instagramBaseUrl = 'https://www.instagram.com',
cookiesPath,
followedDbPath,
unfollowedDbPath,
likesDbPath,

username: myUsernameIn,
password,
Expand All @@ -31,6 +36,8 @@ module.exports = async (browser, options) => {
maxFollowsPerHour = 20,
maxFollowsPerDay = 150,

maxLikesPerDay = 50,

followUserRatioMin = 0.2,
followUserRatioMax = 4.0,
followUserMaxFollowers = null,
Expand All @@ -52,11 +59,13 @@ module.exports = async (browser, options) => {
assert(cookiesPath);
assert(followedDbPath);
assert(unfollowedDbPath);
assert(likesDbPath);

// State
let page;
let prevFollowedUsers = {};
let prevUnfollowedUsers = {};
let prevLikes = [];


async function tryLoadDb() {
Expand All @@ -70,12 +79,18 @@ module.exports = async (browser, options) => {
} catch (err) {
logger.error('No unfollowed database found');
}
try {
prevLikes = JSON.parse(await fs.readFile(likesDbPath));
} catch (err) {
logger.error('No likes database found');
}
}

async function trySaveDb() {
try {
await fs.writeFile(followedDbPath, JSON.stringify(Object.values(prevFollowedUsers)));
await fs.writeFile(unfollowedDbPath, JSON.stringify(Object.values(prevUnfollowedUsers)));
await fs.writeFile(likesDbPath, JSON.stringify(prevLikes));
} catch (err) {
logger.error('Failed to save database');
}
Expand Down Expand Up @@ -129,6 +144,11 @@ module.exports = async (browser, options) => {
await trySaveDb();
}

async function onImageLiked() {
prevLikes.push({ time: new Date().getTime() });
await trySaveDb();
}

function getNumFollowedUsersThisTimeUnit(timeUnit) {
const now = new Date().getTime();

Expand All @@ -138,11 +158,20 @@ module.exports = async (browser, options) => {
}

function hasReachedFollowedUserDayLimit() {
return getNumFollowedUsersThisTimeUnit(24 * 60 * 60 * 1000) >= maxFollowsPerDay;
return getNumFollowedUsersThisTimeUnit(dayMs) >= maxFollowsPerDay;
}

function hasReachedFollowedUserHourLimit() {
return getNumFollowedUsersThisTimeUnit(60 * 60 * 1000) >= maxFollowsPerHour;
return getNumFollowedUsersThisTimeUnit(hourMs) >= maxFollowsPerHour;
}

function getNumLikesThisTimeUnit(timeUnit) {
const now = new Date().getTime();
return prevLikes.filter(u => now - u.time < timeUnit).length
}

function hasReachedDailyLikesLimit() {
return getNumLikesThisTimeUnit(dayMs) >= maxLikesPerDay;
}

function haveRecentlyFollowedUser(username) {
Expand Down Expand Up @@ -339,8 +368,100 @@ module.exports = async (browser, options) => {
return outUsers;
}

async function likeCurrentUserImagesPageCode({ likeImagesMin, likeImagesMax }) {
const allImages = Array.from(document.getElementsByTagName('a')).filter(el => /instagram.com\/p\//.test(el.href));

function shuffleArray(arrayIn) {
const array = [...arrayIn];
for (let i = array.length - 1; i > 0; i -= 1) {
const j = Math.floor(Math.random() * (i + 1));
[array[i], array[j]] = [array[j], array[i]]; // eslint-disable-line no-param-reassign
}
return array;
}

const imagesShuffled = shuffleArray(allImages);

const numImagesToLike = Math.floor(Math.random() * (likeImagesMax + 1 - likeImagesMin) + likeImagesMin);

instautoLog(`Liking ${numImagesToLike} image(s)`);

const images = imagesShuffled.slice(0, numImagesToLike);

if (images.length < 1) {
instautoLog('No images to like');
return;
}

for (const image of images) {
image.click();

await window.instautoSleep(3000);

const dialog = document.querySelector('*[role=dialog]');

if (!dialog) throw new Error('Dialog not found');

const section = Array.from(dialog.querySelectorAll('section')).find(s => s.querySelectorAll('*[aria-label="Like"]')[0] && s.querySelectorAll('*[aria-label="Comment"]')[0]);

if (!section) throw new Error('Like button section not found');

const likeButtonChild = section.querySelectorAll('*[aria-label="Like"]')[0];

if (!likeButtonChild) throw new Error('Like button not found (aria-label)');

function findClickableParent(el) {
let elAt = el;
while (elAt) {
if (elAt.click) {
return elAt;
}
elAt = elAt.parentElement;
}
}

const foundClickable = findClickableParent(likeButtonChild);

if (!foundClickable) throw new Error('Like button not found');

foundClickable.click();

window.instautoOnImageLiked();

await window.instautoSleep(3000);

const closeButtonChild = document.querySelector('button [aria-label=Close]');

if (!closeButtonChild) throw new Error('Close button not found (aria-label)');

const closeButton = findClickableParent(closeButtonChild);

if (!closeButton) throw new Error('Close button not found');

closeButton.click();

await window.instautoSleep(5000);
}

instautoLog('Done liking images');
}

async function likeCurrentUserImages({ likeImagesMin, likeImagesMax } = {}) {
if (!likeImagesMin || !likeImagesMax || likeImagesMax <= likeImagesMin || likeImagesMin < 1) throw new Error('Invalid arguments');

try {
await page.exposeFunction('instautoSleep', sleep);
await page.exposeFunction('instautoLog', (...args) => console.log(...args));
await page.exposeFunction('instautoOnImageLiked', onImageLiked);
} catch (err) {
// Ignore already exists error
}

await page.evaluate(likeCurrentUserImagesPageCode, { likeImagesMin, likeImagesMax });
}

async function followUserFollowers(username, {
maxFollowsPerUser = 5, skipPrivate = false,
maxFollowsPerUser = 5, skipPrivate = false, enableLikeImages, likeImagesMin, likeImagesMax,
} = {}) {
logger.log(`Following the followers of ${username}`);

Expand Down Expand Up @@ -408,6 +529,17 @@ module.exports = async (browser, options) => {
} else {
await followCurrentUser(follower);
numFollowedForThisUser += 1;

await sleep(10000);

if (!isPrivate && enableLikeImages && !hasReachedDailyLikesLimit()) {
try {
await likeCurrentUserImages({ likeImagesMin, likeImagesMax });
} catch (err) {
logger.error(`Failed to follow user's images ${follower}`, err);
}
}

await sleep(20000);
}
} catch (err) {
Expand All @@ -417,10 +549,10 @@ module.exports = async (browser, options) => {
}
}

async function followUsersFollowers({ usersToFollowFollowersOf, maxFollowsPerUser, skipPrivate }) {
async function followUsersFollowers({ usersToFollowFollowersOf, maxFollowsPerUser, skipPrivate, enableLikeImages = false, likeImagesMin = 1, likeImagesMax = 2 }) {
for (const username of shuffleArray(usersToFollowFollowersOf)) {
try {
await followUserFollowers(username, { maxFollowsPerUser, skipPrivate });
await followUserFollowers(username, { maxFollowsPerUser, skipPrivate, enableLikeImages, likeImagesMin, likeImagesMax });

if (hasReachedFollowedUserDayLimit()) {
logger.log('Have reached daily follow rate limit, exiting loop');
Expand Down Expand Up @@ -665,8 +797,9 @@ module.exports = async (browser, options) => {

await trySaveCookies();

logger.log(`Have followed/unfollowed ${getNumFollowedUsersThisTimeUnit(60 * 60 * 1000)} in the last hour`);
logger.log(`Have followed/unfollowed ${getNumFollowedUsersThisTimeUnit(24 * 60 * 60 * 1000)} in the last 24 hours`);
logger.log(`Have followed/unfollowed ${getNumFollowedUsersThisTimeUnit(hourMs)} in the last hour`);
logger.log(`Have followed/unfollowed ${getNumFollowedUsersThisTimeUnit(dayMs)} in the last 24 hours`);
logger.log(`Have liked ${getNumLikesThisTimeUnit(dayMs)} images in the last 24 hours`);

try {
const detectedUsername = await page.evaluate(() => window._sharedData.config.viewer.username);
Expand All @@ -688,5 +821,6 @@ module.exports = async (browser, options) => {
safelyUnfollowUserList,
getPage,
followUsersFollowers,
likeCurrentUserImages,
};
};

0 comments on commit 5f4821a

Please sign in to comment.