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
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:
Now run the following commands in your computer:
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:
npm start
The program is pretty straightforward. It will ask you for your username and password and will log you in successfully:
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:
-----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):
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!
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:
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!
All the code for InstaLiker resides in the index.js file. Let’s go through it together now. The first part is the imports:
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:
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:
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.
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:
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.
We already mentioned how to launch a browser with Puppeteer, so here you go:
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.
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:
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.
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:
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!
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:
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]});
}
This function simply takes us to the target-user’s feed where all the posts are:
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.
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.
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:
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.
There is a REST endpoint to unlike a post, so we run a loop that goes through each post id and unlikes it:
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;
}
Similar to the previous function, we now like all the posts:
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;
}
At last we close the browser to release all the resources consumed by our program:
() => browser.close();
With that, everything is finished. Go through it again if you like or read the code more clearly on GitHub.
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