← All projects

InstaLiker – Like thousands of Instagram posts at once using JavaScript

Viktor Ahmeti.12 months ago

This is a project I did a year ago as a prank on my girlfriend. It is a script that uses your Instagram Login credentials to log into your account and like all the posts of a target user at once. It uses Puppeteer, a JavaScript library for launching and controlling a Chromium Browser all through code – we’ll get to what that means in a second.

⚠️ Warning: If you use this script, Instagram might (will) ban you from liking posts for a couple of days

How to use this script

If you’re not interested in knowing how the script works, you can just try it immediately. Before you do so, though, go through this checklist:

  1. Use a computer in which you have previously logged into Instagram before
  2. Disable Two-Factor Authentication on your account
  3. Make sure you are following (or aren’t blocked by) the target user
  4. Install a newer version of Node on your computer

Now run the following commands in your computer:

Clone the Repository
git clone https://github.com/viktorahmeti/InstaLiker.git
cd InstaLiker
rm -rf package-lock.json
npm install

This will clone the repository from the GitHub and configure everything correctly. Run the following command to start the program:

Start the Application
npm start

The program is pretty straightforward. It will ask you for your username and password and will log you in successfully:

Running App
8888888                   888             888      d8b 888                       
  888                     888             888      Y8P 888                       
  888                     888             888          888                       
  888   88888b.  .d8888b  888888  8888b.  888      888 888  888  .d88b.  888d888 
  888   888 "88b 88K      888        "88b 888      888 888 .88P d8P  Y8b 888P"   
  888   888  888 "Y8888b. 888    .d888888 888      888 888888K  88888888 888     
  888   888  888      X88 Y88b.  888  888 888      888 888 "88b Y8b.     888     
8888888 888  888  88888P'  "Y888 "Y888888 88888888 888 888  888  "Y8888  888    

-----Please log in using your Instagram credentials-----
Username: yourusername
Password: your-complicated-password 

Please be patient because it may take a few seconds for the headless browser to start in the background. Next, you will be asked to provide the username and Instagram Id of the user who’s pictures you want to like:

Running App
-----Please provide the data for the OTHER user-----
What is the other user's username: _we_love_you_
What is the user's Instagram id? (You can find it here https://commentpicker.com/instagram-user-id.php): 220953986

Notice that you have to go to an external website to figure out your Instagram Id, but it takes only a few seconds. After submitting these you will see some info telling you that everything went fine (hopefully):

Running App
Navigated to profile _we_love_you_

Successfully retrieved 1000 posts

Successfully UNLIKED 1000 posts
Successfully LIKED 1000 posts

The code first unlikes all of the page’s posts and then likes them again so that your friend is spammed with tons of notifications. That’s about it regarding how to run the app and if you run into any issues please report them on GitHub. Let’s see how it works!

What is Puppeteer

The main tool we’ll use in this program is a library called Puppeteer. The easiest way to understand what it does is to imagine yourself using a Web Browser. You can take a lot of actions while using a browser:

The above actions, and many more, are taken by you using devices such as a mouse and a keyboard. Puppeteer gives us a clean API to perform all of the above actions using only code! So, instead of typing a URL in the address bar and clicking enter, in Puppeteer you can do:

Puppeteer Example
const puppeteer = require('puppeteer');

browser = await puppeteer.launch();
page = await browser.newPage();

await page.goto('https://viktorahmeti.me');

The code is very readable because the function accurately represent the actions we take while using a browser. In the background, Puppeteer launches a Headless Chromium Browser which is basically a browser without the GUI for us to use. This browser resides in memory and is manipulated by us as we wish – we can even take screenshots!

Building InstaLiker

All the code for InstaLiker resides in the index.js file. Let’s go through it together now. The first part is the imports:

index.js
const puppeteer = require('puppeteer');
var readline = require('readline').promises;
const userAgentGenerator = require('random-useragent');

We are importing Puppeteer, obviously, to do the browser manipulation. We are importing Readline to read input from the console, for taking the user’s username and password. Readline has an API with callbacks and a new one with Promises, so we’ll use the new one. Lastly, Random UserAgent is a library to generate a random User Agent String so we can trick Instagram into thinking that we’re not using the same device – this has made the code more reliable in my experience.

This next part contains everything that will happen in the code – everything else will be just functions. Here it is:

index.js
greet()
.then(launchBrowserAndPage)
.then(getCredentials)
.then(login)
.then(getTargetUserData)
.then(navigateToUserProfile)
.then(getPostIds)
.then(unlikeAllPosts)
.then(likeAllPosts)
.then(() => browser.close())
.catch((e) => {
  console.log(`An error occurred, try to figure it out:\n\n${e}`);
});;

Everything is asynchronous in this program, each function returning a promise and triggering the next function in the chain once it finishes. The code reads like a 10 step cookbook recipe:

  1. Greet the user
  2. Launch the browser and open a new tab
  3. Ask the user for their login credentials
  4. Log into Instagram using those credentials
  5. Ask the user for the target-user information
  6. Navigate to the target-user’s profile
  7. Get the ids of all the posts belonging to that user
  8. Unlike all their posts
  9. Like all their posts
  10. Close the browser

Any errors that may occur anywhere in this process are caught in the catch function and logged to the console. Let’s go through all these functions step by step.

Step 1: Greet the user

Greeting the user is nothing more than logging a message that says “Hi” to the console. To make things more fun, you can use a Text to ASCII Art Generator to generate a cool logo of your application. We have to use ASCII since we’re doing a console application in which we can’t insert pictures. Here’s the code:

The greet() function
async function greet(){
  let greetText = 
` 8888888                   888             888      d8b 888                       
    888                     888             888      Y8P 888                       
    888                     888             888          888                       
    888   88888b.  .d8888b  888888  8888b.  888      888 888  888  .d88b.  888d888 
    888   888 "88b 88K      888        "88b 888      888 888 .88P d8P  Y8b 888P"   
    888   888  888 "Y8888b. 888    .d888888 888      888 888888K  88888888 888     
    888   888  888      X88 Y88b.  888  888 888      888 888 "88b Y8b.     888     
  8888888 888  888  88888P'  "Y888 "Y888888 88888888 888 888  888  "Y8888  888     
`;
  console.log(greetText);
}

Notice that we define the function as async so it returns a promise at the end. Let’s now open the browser.

Step 2: Launch the Browser

We already mentioned how to launch a browser with Puppeteer, so here you go:

The launchBrowserAndPage() function
async function launchBrowserAndPage(){
  const randomUserAgent = userAgentGenerator.getRandom(function (ua) {
    return parseFloat(ua.browserVersion) >= 20 && ua.browserName === 'Firefox';
  });

  browser = await puppeteer.launch({args: [`--user-agent=${randomUserAgent}`]});
  page = await browser.newPage();
}

The first part is generating a random user agent and constraining it to Firefox with a version higher than 20. This is done because sometimes Instagram doesn’t allow old browsers from using their services. Next we launch the browser and create a new tab, both of which are stored in global variables.

Step 3: Get the user credentials

With the browser all set and ready to go, we now need the user credentials. We take these directly from the console by using the Readline library:

The getCredentials() function
function getCredentials(){
  console.log('-----Please log in using your Instagram credentials-----');

  let username, password;

  var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });

  return rl.question('Username: ')
  .then(usr => {username = usr; return rl.question('Password: ')})
  .then(pass => {password = pass; rl.close(); return [username, password]});
}

Things to note here is that we read from the standard input and output streams, and that when the function finishes it returns a promise with 2 values: the username and password. These values will be passed on to the next function in the chain.

Step 4: Log into the user’s account

To log in the users account we need to navigate to Instagram’s Log In page, type the credentials, and click the Log In button. Here is what the login page of Instagram looks like:

Puppeteer needs to know how to find all the input fields on the page so we provide it the correct CSS Selectors to find them. Since the page is very simple the selectors will be obvious. Read the code:

The login() function
async function login([username, password]){
  //the CSS selectors
  let usernameSelector = "input[type=\"text\"]";
  let passwordSelector = "input[type=\"password\"]";
  let submitButtonSelector = "button[type=\"submit\"]";

  // Navigate to Instagram's login page
  await page.goto('https://www.instagram.com/accounts/login/', {waitUntil: 'networkidle2'});
  
  //type the text inside the inputs
  await page.type(usernameSelector, username);
  await page.type(passwordSelector, password);
  
  //Remove the "disabled" attribute from the Submit button
  let submitButton = await page.$(submitButtonSelector);
  await page.evaluate((sB) => {
    sB.removeAttribute("disabled");
  }, submitButton);
  
  //log in
  await submitButton.click();
  await page.waitForNavigation();

  console.log(`Logged into account ${username}\n\n`);
}

One thing to note here is that we run some JavaScript inside the browser by using the evaluate() function. This is done to remove the disabled attribute from the submit button because sometimes it remains disabled even though something is typed. Cool!

Step 5: Getting the Target-User data

This is similar to what we did when we got the login credentials, but this time we return the username and id of that user:

The getTargetUserData() function
async function getTargetUserData(){
  console.log('-----Please provide the data for the TARGET user-----');

  let targetUsername, targetUserId;

  var rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
  });

  return rl.question('What is the other user\'s username: ')
  .then((username) => {targetUsername = username; return rl.question('What is the user\'s Instagram id? (You can find it here https://commentpicker.com/instagram-user-id.php): ')})
  .then(targetId => {targetUserId = targetId; rl.close(); return [targetUsername, targetUserId]});
}

Step 6: Navigate to the Target-User’s Profile

This function simply takes us to the target-user’s feed where all the posts are:

The navigateToUserProfile() function
async function navigateToUserProfile([targetUsername, targetUserId]){
  configureRequestInterception();

  await page.goto(`https://www.instagram.com/${targetUsername}/`, {waitUntil: 'networkidle0'});

  console.log(`Navigated to profile ${targetUsername} \n\n`);

  return targetUserId;
}

Yes, this function takes us to the user profile but what is that first function call? Configure Request Interception? Well, that’s a function I created to steal some authentication headers. Let me explain.

By now, you might think that we will go through the user’s profile and click on each post, like it, scroll down, like some more, and so on until we reach the end. That was my idea in the beginning also but it’s very tedious, takes a lot of time, and loads a lot of unnecessary resources. That’s when I came up with another solution.

How does Instagram itself load the posts, I asked? How do they tell their servers that a post has been liked? Of course, they do this using their REST API. For me to use Instagram’s REST API I would need to get an API Key from the user using my program and then again I wouldn’t be able to do what I want to do because of restrictions. So, how do we do it?

The only problem is Authentication. To authenticate to a REST API, some headers with the authentication details need to be sent along with each HTTP request. I don’t know what headers to use so what do I do? Weeeell I simply intercept a request that Instagram’s JavaScript makes, inspect the headers, save the headers for myself, and attach those headers in the requests I’ll make further down the road. Easy.

Stealing the Headers
function configureRequestInterception(){
  page.setRequestInterception(true);
  
  page.on('request', interceptedRequest => {
    if (interceptedRequest.isInterceptResolutionHandled()) return;

    if(!gotHeaders && (interceptedRequest.url().endsWith('ig_sso_users/'))){
        customHeaders['x-asbd-id'] = interceptedRequest.headers()['x-asbd-id'];
        customHeaders['x-csrftoken'] = interceptedRequest.headers()['x-csrftoken'];
        customHeaders['x-ig-app-id'] = interceptedRequest.headers()['x-ig-app-id'];
        customHeaders['x-ig-www-claim'] = interceptedRequest.headers()['x-ig-www-claim'];
        customHeaders['x-instagram-ajax'] = interceptedRequest.headers()['x-instagram-ajax'];
        customHeaders['x-requested-with'] = interceptedRequest.headers()['x-requested-with'];
        gotHeaders = true;
    }

    interceptedRequest.continue({}, 0);
  });
}

I’m intercepting every request until I find one that ends with ig_sso_users/. I noticed that this request was always issued by Instagram and it contained everything I needed. I then steal the headers and save them to an array for later use. Perfect.

Step 7: Get the Ids of all the Posts

There is a REST endpoint to get the latest posts of a user so we will of course use that. However, that endpoint is paginated and returns a maximum of 12 posts at a time. No worries, we will call the endpoint a few times so we get all of them:

The getPostIds() function
async function getPostIds(targetUserId){
  let mediaIds = await page.evaluate(async (hd, usrId) => {
    let mediaIds = [];
    let nextMaxId;
    let moreAvailable = true;
    let requestUrl = `https://www.instagram.com/api/v1/feed/user/${usrId}?count=12`;

    while(moreAvailable){
        if(nextMaxId)
            requestUrl += `&max_id=${nextMaxId}`;

        let raw = await fetch(requestUrl, {method: 'GET', headers: hd})
        let data = await raw.json();
    
        nextMaxId = data.next_max_id;
        moreAvailable = data.more_available;

        data.items.forEach((item) => mediaIds.push(item.id));
    }

    return mediaIds;
  }, customHeaders, targetUserId);

  console.log(`Successfully retrieved ${mediaIds.length} posts`);

  return mediaIds;
}

Notice that we’re using evaluate() to run JavaScript in the browser. We’re requesting the data using fetch() and attaching the headers we got before. The while loop repeats as long as there are more posts available for the particular user and when it ends we pass the mediaIds (post ids) to the next function in the chain.

Step 8: Unlike all posts

There is a REST endpoint to unlike a post, so we run a loop that goes through each post id and unlikes it:

The unlikeAllPosts() function
async function unlikeAllPosts(mediaIds){
  await page.evaluate((ids, hd) => {
    return Promise.all(ids.map((id) => {
        return fetch(`https://www.instagram.com/api/v1/web/likes/${id.split('_')[0]}/unlike/`, {method: 'POST', headers: hd});
    }));
  }, mediaIds, customHeaders);

  console.log(`Successfully UNLIKED ${mediaIds.length} posts`);

  return mediaIds;
}

Step 9: Like all posts

Similar to the previous function, we now like all the posts:

The likeAllPosts() function
async function likeAllPosts(mediaIds){
  await page.evaluate((ids, hd) => {
    return Promise.all(ids.map((id) => {
        return fetch(`https://www.instagram.com/api/v1/web/likes/${id.split('_')[0]}/like/`, {method: 'POST', headers: hd});
    }));
  }, mediaIds, customHeaders);

  console.log(`Successfully LIKED ${mediaIds.length} posts`);

  return mediaIds;
}

Step 10: Close the browser

At last we close the browser to release all the resources consumed by our program:

Don’t forget to close the browser
() => browser.close();

With that, everything is finished. Go through it again if you like or read the code more clearly on GitHub.

Conclusion

That was a shallow dive into the code for InstaLiker. If you learned something new (or you think you learned something new) subscribe to my Newsletter below so you never miss a post again. If you liked this post, you might also like my previous one: How to make a Count-Up 📈 Animation Library with JavaScript.

← All projects