Skip to content

Instantly share code, notes, and snippets.

@PLhery
Created August 20, 2020 22:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save PLhery/c59e3a4d1af5e38e9ac89fa1725cabda to your computer and use it in GitHub Desktop.
Save PLhery/c59e3a4d1af5e38e9ac89fa1725cabda to your computer and use it in GitHub Desktop.
import { IgApiClient } from 'instagram-private-api';
// @ts-ignore (dependency not typed)
import Instagram from 'instagram-web-api'
import * as dotenv from 'dotenv';
dotenv.config({ path: __dirname+'/.env' });
const username = process.env.IG_USERNAME || '';
const password = process.env.IG_PASSWORD || '';
const INTERVAL_BETWEEN_CHECKS = 10*60*1000; // pause in ms between each checks (a check lasts ~4sec/user)
interface INinjaUser {
followers: number[]; // todo following dates
}
interface IInstaUser {
id: string;
username: string;
full_name: string;
profile_pic_url: string;
is_verified: boolean;
followed_by_viewer: boolean;
requested_by_viewer: boolean;
}
// TODO put these in a database to work after a downtime
const ninjaUsers: {[key: number]: INinjaUser} = {};
const usernamesById: {[key: number]: string} = {};
const mobileClient = new IgApiClient();
mobileClient.state.generateDevice(String(username));
const webClient = new Instagram({ username, password });
async function getAllFollowers(userId: number): Promise<number[]> {
const followers: number[] = [];
let cursor;
while (true) {
const response: any = await webClient.getFollowers({userId, after: cursor});
followers.push(...response.data.map((user: IInstaUser) => Number(user.id)));
response.data.forEach((user: IInstaUser) => usernamesById[Number(user.id)] = user.username);
console.log(`--fetching followers ${followers.length}/${response.count}...`);
if (!response.page_info.has_next_page) {
break;
}
cursor = response.page_info.end_cursor;
}
return followers
}
async function checkUnfollowers(userId: number) {
const username = usernamesById[userId];
console.log(`\nChecking @${username}'s unfollowers...`);
const followers = await getAllFollowers(userId);
const followersSet = new Set(followers);
const ninjaUser = ninjaUsers[userId];
if (ninjaUser) {
const formerFollowers = ninjaUser.followers;
const unfollowers = formerFollowers.filter(id => !followersSet.has(id));
for (const unfollowerId of unfollowers) {
const unfollowerUsername = usernamesById[unfollowerId];
console.log(`Someone unfollowed @${username} : ${unfollowerId} ${unfollowerUsername}`);
await mobileClient.directThread.broadcast({
userIds: [userId],
item: 'text',
form: {
text: `@${unfollowerUsername} unfollowed you 👋`,
},
});
console.log(`notification sent.`);
}
}
ninjaUsers[userId] = {
followers,
}
}
async function loginAndRun() {
console.log('logging in...');
// await mobileClient.simulate.preLoginFlow();
await mobileClient.account.login(username, password);
// await mobileClient.simulate.postLoginFlow();
const user = await webClient.login();
console.log(user);
await checkAllUsers(Number(user.userId));
}
async function checkAllUsers(userId: number) {
console.log(new Date(), 'checking all unfollowers');
const users = await getAllFollowers(userId);
console.log(`${users.length} users found`);
const start = Date.now();
try {
for (const userId of users) {
await checkUnfollowers(userId);
}
console.log(`All accounts checked in ${Date.now() - start}ms`);
} catch (error) {
console.log(`An error occured after ${Date.now() - start}ms, skipping this check`);
console.error(error);
}
setTimeout(() => checkAllUsers(userId).catch(err => console.error(err.stack)), INTERVAL_BETWEEN_CHECKS);
}
loginAndRun().catch(err => console.error(err.stack));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment