Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typescript Support #146

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions @types/db.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
declare function _exports({ followedDbPath, unfollowedDbPath, likedPhotosDbPath, logger, }: {
followedDbPath: string;
unfollowedDbPath: string;
likedPhotosDbPath: string;
logger?: any;
}): Promise<void>;
export = _exports;
65 changes: 65 additions & 0 deletions @types/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export = Instauto;
declare function Instauto(db: any, browser: any, options: any): Promise<{
followUserFollowers: (username: string, { maxFollowsPerUser, skipPrivate, enableLikeImages, likeImagesMin, likeImagesMax, }?: {
maxFollowsPerUser: string;
skipPrivate: boolean;
enableLikeImages: boolean;
likeImagesMin: number;
likeImagesMax: number;
}) => Promise<void>;
unfollowNonMutualFollowers: ({ limit }?: {
limit: number;
}) => Promise<number>;
unfollowAllUnknown: ({ limit }?: {
limit: number;
}) => Promise<number>;
unfollowOldFollowed: ({ ageInDays, limit }?: {
ageInDays: number;
limit: number;
}) => Promise<number>;
followUser: (username: string) => Promise<void>;
unfollowUser: (username: string) => Promise<{
string;
number;
}>;
likeUserImages: ({ username, likeImagesMin, likeImagesMax, }?: {
username: string;
likeImagesMin: number;
likeImagesMax: number;
}) => Promise<void>;
sleep: (ms: number, deviation?: number) => any;
listManuallyFollowedUsers: () => Promise<any[]>;
getFollowersOrFollowing: ({ userId, getFollowers }: {
userId: any;
getFollowers: boolean;
}) => Promise<any>;
getUsersWhoLikedContent: ({ contentId }: {
contentId: any;
}) => Promise<{
queryHash: any;
getResponseProp: any;
graphqlVariables: any;
}>;
safelyUnfollowUserList: (usersToUnfollow: any[], limit: number, condition?: () => boolean) => Promise<number>;
safelyFollowUserList: ({ users, skipPrivate, limit }: {
users: any[];
skipPrivate: boolean;
limit: number;
}) => Promise<void>;
getPage: () => any;
followUsersFollowers: ({ usersToFollowFollowersOf, maxFollowsTotal, skipPrivate, enableFollow, enableLikeImages, likeImagesMin, likeImagesMax, }: {
usersToFollowFollowersOf: string[];
maxFollowsTotal?: number;
skipPrivate: boolean;
enableFollow?: boolean;
enableLikeImages?: boolean;
likeImagesMin?: number;
likeImagesMax?: number;
}) => Promise<void>;
doesUserFollowMe: (username: string) => Promise<boolean | undefined>;
navigateToUserAndGetData: (username: string) => any;
}>;
declare namespace Instauto {
export { JSONDB };
}
import JSONDB = require("./db");
162 changes: 162 additions & 0 deletions example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import puppeteer, { Browser } from 'puppeteer';
import Instauto from '.';

// Optional: Custom logger with timestamps
const log = (fn, ...args) => console[fn](new Date().toISOString(), ...args);
const logger = Object.fromEntries(
['log', 'info', 'debug', 'error', 'trace', 'warn'].map((fn) => [
fn,
(...args) => log(fn, ...args),
])
);

const options = {
cookiesPath: './cookies.json',

username: process.env.INSTAGRAM_USERNAME,
password: process.env.INSTAGRAM_PASSWORD,

// Global limit that prevents follow or unfollows (total) to exceed this number over a sliding window of one hour:
maxFollowsPerHour:
process.env.MAX_FOLLOWS_PER_HOUR != null
? parseInt(process.env.MAX_FOLLOWS_PER_HOUR, 10)
: 20,
// Global limit that prevents follow or unfollows (total) to exceed this number over a sliding window of one day:
maxFollowsPerDay:
process.env.MAX_FOLLOWS_PER_DAY != null
? parseInt(process.env.MAX_FOLLOWS_PER_DAY, 10)
: 150,
// (NOTE setting the above parameters too high will cause temp ban/throttle)

maxLikesPerDay:
process.env.MAX_LIKES_PER_DAY != null
? parseInt(process.env.MAX_LIKES_PER_DAY, 10)
: 30,

// Don't follow users that have a followers / following ratio less than this:
followUserRatioMin:
process.env.FOLLOW_USER_RATIO_MIN != null
? parseFloat(process.env.FOLLOW_USER_RATIO_MIN)
: 0.2,
// Don't follow users that have a followers / following ratio higher than this:
followUserRatioMax:
process.env.FOLLOW_USER_RATIO_MAX != null
? parseFloat(process.env.FOLLOW_USER_RATIO_MAX)
: 4.0,
// Don't follow users who have more followers than this:
followUserMaxFollowers: null,
// Don't follow users who have more people following them than this:
followUserMaxFollowing: null,
// Don't follow users who have less followers than this:
followUserMinFollowers: null,
// Don't follow users who have more people following them than this:
followUserMinFollowing: null,

// Custom logic filter for user follow
shouldFollowUser: null,
/* Example to skip bussiness accounts
shouldFollowUser: function (data) {
console.log('isBusinessAccount:', data.isBusinessAccount);
return !data.isBusinessAccount;
}, */
/* Example to skip accounts with 'crypto' & 'bitcoin' in their bio or username
shouldFollowUser: function (data) {
console.log('username:', data.username, 'biography:', data.biography);
var keywords = ['crypto', 'bitcoin'];
if (keywords.find(v => data.username.includes(v)) !== undefined || keywords.find(v => data.biography.includes(v)) !== undefined) {
return false;
}
return true;
}, */

// Custom logic filter for liking media
shouldLikeMedia: null,

// NOTE: The dontUnfollowUntilTimeElapsed option is ONLY for the unfollowNonMutualFollowers function
// This specifies the time during which the bot should not touch users that it has previously followed (in milliseconds)
// After this time has passed, it will be able to unfollow them again.
// TODO should remove this option from here
dontUnfollowUntilTimeElapsed: 3 * 24 * 60 * 60 * 1000,

// Usernames that we should not touch, e.g. your friends and actual followings
excludeUsers: [],

// If true, will not do any actions (defaults to true)
dryRun: false,

logger,
};

(async () => {
let browser: Browser;

try {
browser = await puppeteer.launch({
// set headless: false first if you need to debug and see how it works
headless: true, // true is deprecated use "new" instead

args: [
// Needed for docker
'--no-sandbox',
'--disable-setuid-sandbox',

// If you need to proxy: (see also https://www.chromium.org/developers/design-documents/network-settings)
// '--proxy-server=127.0.0.1:9876',
],
});

// Create a database where state will be loaded/saved to
const instautoDb = await Instauto.JSONDB({
// Will store a list of all users that have been followed before, to prevent future re-following.
followedDbPath: './followed.json',
// Will store all unfollowed users here
unfollowedDbPath: './unfollowed.json',
// Will store all likes here
likedPhotosDbPath: './liked-photos.json',
});

const instauto = await Instauto(instautoDb, browser, options);

// This can be used to unfollow people:
// Will unfollow auto-followed AND manually followed accounts who are not following us back, after some time has passed
// The time is specified by config option dontUnfollowUntilTimeElapsed
// await instauto.unfollowNonMutualFollowers();
// await instauto.sleep(10 * 60 * 1000);

// Unfollow previously auto-followed users (regardless of whether or not they are following us back)
// after a certain amount of days (2 weeks)
// Leave room to do following after this too (unfollow 2/3 of maxFollowsPerDay)
const unfollowedCount = await instauto.unfollowOldFollowed({
ageInDays: 14,
limit: options.maxFollowsPerDay * (2 / 3),
});

if (unfollowedCount > 0) await instauto.sleep(10 * 60 * 1000);

// List of usernames that we should follow the followers of, can be celebrities etc.
const usersToFollowFollowersOf =
process.env.USERS_TO_FOLLOW != null
? process.env.USERS_TO_FOLLOW.split(',')
: [];

// Now go through each of these and follow a certain amount of their followers
await instauto.followUsersFollowers({
usersToFollowFollowersOf,
maxFollowsTotal: options.maxFollowsPerDay - unfollowedCount,
skipPrivate: true,
enableLikeImages: true,
likeImagesMax: 3,
});

await instauto.sleep(10 * 60 * 1000);

console.log('Done running');

await instauto.sleep(30000);
} catch (err) {
console.error(err);
} finally {
console.log('Closing browser');
if (browser!) await browser.close();
}
})();
38 changes: 30 additions & 8 deletions src/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,17 @@
const fs = require('fs-extra');
const keyBy = require('lodash/keyBy');

/**
* Creates a module that provides functions to manage the bot's database.
* @async
* @function
* @param {Object} options - An object with the following properties:
* @param {string} options.followedDbPath - The path to the followed database.
* @param {string} options.unfollowedDbPath - The path to the unfollowed database.
* @param {string} options.likedPhotosDbPath - The path to the liked photos database.
* @param {Object} [options.logger=console] - An optional logger object.
* @returns {Promise<void>}
*/
module.exports = async ({
followedDbPath,
unfollowedDbPath,
Expand All @@ -16,8 +27,14 @@ module.exports = async ({

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(
followedDbPath,
JSON.stringify(Object.values(prevFollowedUsers))
);
await fs.writeFile(
unfollowedDbPath,
JSON.stringify(Object.values(prevUnfollowedUsers))
);
await fs.writeFile(likedPhotosDbPath, JSON.stringify(prevLikedPhotos));
} catch (err) {
logger.error('Failed to save database');
Expand All @@ -26,12 +43,18 @@ module.exports = async ({

async function tryLoadDb() {
try {
prevFollowedUsers = keyBy(JSON.parse(await fs.readFile(followedDbPath)), 'username');
prevFollowedUsers = keyBy(
JSON.parse(await fs.readFile(followedDbPath)),
'username'
);
} catch (err) {
logger.warn('No followed database found');
}
try {
prevUnfollowedUsers = keyBy(JSON.parse(await fs.readFile(unfollowedDbPath)), 'username');
prevUnfollowedUsers = keyBy(
JSON.parse(await fs.readFile(unfollowedDbPath)),
'username'
);
} catch (err) {
logger.warn('No unfollowed database found');
}
Expand All @@ -52,7 +75,7 @@ module.exports = async ({

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

async function addLikedPhoto({ username, href, time }) {
Expand All @@ -68,10 +91,9 @@ module.exports = async ({
return getPrevFollowedUsers().length; // TODO performance
}


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

function getPrevFollowedUser(username) {
Expand All @@ -93,7 +115,7 @@ module.exports = async ({

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

async function addPrevUnfollowedUser(user) {
Expand Down
Loading